随笔-119  评论-133  文章-4  trackbacks-0

3 语言基础

【变量】

1、使用var操作符定义的变量会成为包含它的函数的局部变量;但是在函数内定义变量时省略var操作符,可以创建一个全局变量(不推荐)

2、使用var关键字声明的变量会自动提升到函数作用域顶部(只是声明会提升,初始化赋值不会);但是let声明的变量不会在作用域中被提升

3let声明的范围是块作用域,而var声明的范围是函数作用域,块作用域是函数作用域的子集

4let不允许同一个块作用域中出现冗余声明(但是var可以),但是嵌套使用相同的标识符不会报错(这是因为同一个块中没有重复声明),不能混用letvar而对变量进行冗余声明

5、使用let在全局作用域中声明的变量不会成为window对象的属性(var声明的变量则会)

6、对于let这个声明关键字,不能依赖条件声明模式(因为它是块作用域),但用在for循环中刚好合适(使用let声明迭代变量时,JavaScript引擎在后台会为每个迭代循环声明一个新的迭代变量)

7const的行为与let基本相同,唯一区别是用const声明变量时必须同时初始化,且尝试修改const声明的变量会导致运行时错误(但是如果const变量引用的是一个对象,那么修改这个对象内部的属性并不违反const的限制)

8、有了letconst,大多数开发者会发现自己不再需要var了;很多开发者认为应该优先使用const来声明变量,只在提前知道未来会有修改时,再使用let

【数据类型】

1ECMAScript6种简单数据类型(也称为原始类型):UndefinedNullBooleanNumber

StringSymbol,还有一种复杂数据类型叫Object(也叫引用类型),包含普通对象、数组Array、函数Function、正则RegExp、日期Date

2、特殊值null被认为是一个对空对象的引用,所以typeof null返回的是"object";另外typeof函数返回”function”(为了方便识别,给函数单独标了function)

3、对未声明的变量,只能执行一个有用的操作,就是对它调用typeof。在对未初始化的变量调用typeof时,返回的结果是"undefined",但对未声明的变量调用它时,返回的结果还是"undefined"(所以建议在声明变量的同时进行初始化)

4、在定义将来要保存对象值的变量时,建议使用null来初始化,不要使用其他值

5undefined值是由null值派生而来的,所以==判断为true(所以推荐用===判断)undefinednull,都是假值

6Number类型使用双精度值表示整数和浮点值,浮点值的精确度最高可达17位小数,但在算术计算中远不如整数精确,因此永远不要测试某个特定的浮点值

7、任何涉及NaN的操作始终返回NaN,其次,NaN不等于包括NaN在内的任何值,任何不能转换为数值的值都会导致isNaN()函数返回true

8Number()是转型函数,可用于任何数据类型。后两个函数主要用于将字符串转换为数值,考虑到用Number()函数转换字符串时相对复杂且有点反常规,优先使用parseInt()parseFloat()函数。parseInt()也接收第二个参数,用于指定底数(进制数),parseFloat()只解析十进制值,因此不能指定底数

9String(字符串)数据类型表示零或多个16Unicode字符序列。字符串可以使用双引号(")、单引号(')或反引号(`)标示

10toString()方法可见于数值、布尔值、对象和字符串值,nullundefined值没有toString()方法;在对数值调用toString()方法时,可以接收一个底数参数,即以什么底数来输出数值的字符串表示

11、如果你不确定一个值是不是nullundefined,可以使用String()转型函数,它始终会返回表示相应类型值的字符串(typeof也可以判断)

12、模板字面量最常用的一个特性是支持字符串插值,字符串插值通过在${}中使用一个JavaScript表达式实现

13、即使采用相同的符号描述,在全局注册表中定义的符号跟使用Symbol()定义的符号也并不等同

【操作符】

1、  ECMAScript中的所有数值都以IEEE 754 64位格式存储,但位操作并不直接应用到64位表示,而是先把值转换为32位整数,再进行位操作,之后再把结果转换为64位,特殊值NaNInfinity在位操作中都会被当成0处理

2、  逻辑非操作,如果操作数是NaN,则返回true,如果操作数是undefined,则返回true

3、  +运算符倾向于得到字符串,-运算符-倾向于得到数值,关系运算符倾向于得到数值(任何关系操作符在涉及比较NaN时都返回false)

4、  相等操作符,第一组是等于和不等于,它们在比较之前执行转换。第二组是全等和不全等,它们在比较之前不执行转换。(null==undefined, null != 0, undefined != 0)。由于相等和不相等操作符存在类型转换问题,因此推荐使用全等和不全等操作符。

【语句】

1、  for-in语句用于枚举对象中的非符号键属性;for-of用于遍历可迭代对象的元素

2、  switch语句可以用于所有数据类型,因此可以使用字符串甚至对象,其次,条件的值不需要是常量,也可以是变量或表达式(switch语句在比较每个条件的值时会使用全等操作符,因此不会强制转换数据类型)

【函数】

1ECMAScript中的函数不需要指定是否返回值。任何函数在任何时间都可以使用return语句来返回函数的值,用法是后跟要返回的值


4章变量、作用域与内存

【原始值与引用值】

1、原始值大小固定,因此保存在栈内存上;引用值是对象,存储在堆内存上

2、  JavaScript 不允许直接访问内存位置,因此也就不能直接操作对象所在的内存空间。在操作对象时,实际上操作的是对该对象的引用(reference)而非实际的对象本身

3、  对于引用值而言,可以随时添加、修改和删除其属性和方法(原始值不能有属性)

4、  在通过变量把一个原始值赋值到另一个变量时,原始值会被复制到新变量的位置; 在把引用值从一个变量赋给另一个变量时, 这里复制的值实际上是一个指针,它指向存储在堆内存中的对象, 两个变量实际上指向同一个对象

5、  typeof 虽然对原始值很有用,但它对引用值的用处不大。为了解决这个问题,ECMAScript提供了instanceof操作符

【执行上下文与作用域】

1、  执行上下文分全局上下文、函数上下文和块级上下文。代码执行流每进入一个新上下文,都会创建一个作用域链,用于搜索变量和函数

2、每个上下文都有一个关联的变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。在浏览器中,全局上下文就是我们常说的window对象,因此所有通过var定义的全局变量和函数都会成为window对象的属性和方法

3、上下文在其所有代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数。每个函数调用都有自己的上下文。当代码执行流进入函数时,函数的上下文被推到一个上下文栈上。在函数执行完之后,上下文栈会弹出该函数上下文,将控制权返还给之前的执行上下文

4、上下文中的代码在执行的时候,会创建变量对象的一个作用域链(分清楚上下文<->作用域链<->变量对象的关系),内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西

5var声明会被拿到函数或全局作用域的顶部,位于作用域中所有代码之前。这个现象叫作“提升”

6、  Let的作用域是块级的,块级作用域由最近的一对包含花括号{}界定,if块、while块、function块,甚至连单独的块也是let声明变量的作用域

7、  使用const声明的变量必须同时初始化为某个值。一经声明,在其生命周期的任何时候都不能再重新赋予新值

【垃圾回收】

1、如果数据不再必要,那么把它设置为null,从而释放其引用。这也可以叫作解除引用。这个建议最适合全局变量和全局对象的属性。


5 基本引用类型

RegExp

1、对象被认为是某个特定引用类型的实例。新对象通过使用new操作符后跟一个构造函数(constructor)来创建

2、正则表达式可以使用字面量形式定义的,也可以使用RegExp构造函数来创建,它接收两个参数:模式字符串和(可选的)标记字符串

3、与其他语言中的正则表达式类似,所有元字符在模式中也必须转义,包括:( [ { \ ^ $ | ) ] } ? * + .

4、在全局匹配模式下,每次调用exec()都会更新lastIndex值,以反映上次匹配的最后一个字符的索引

【原始值包装类型】

1、  由于原始值包装类型的存在,JavaScript中的原始值可以被当成对象来使用。有3种原始值包装类型:BooleanNumberStringObject构造函数作为一个工厂方法,能够根据传入值的类型返回相应原始值包装类型的实例

2、  理解原始布尔值和Boolean对象之间的区别非常重要,强烈建议永远不要使用后者

3、  当某个参数是负值时, slice()方法将所有负值参数都当成字符串长度加上负参数值。

substr()方法将第一个负参数值当成字符串长度加上该值,将第二个负参数值转换为0

substring()方法会将所有负参数值都转换为0

4、  indexOflastIndexOf都可以接收可选的第二个参数,表示开始搜索的位置。这意味着,indexOf()会从这个参数指定的位置开始向字符串末尾搜索,忽略该位置之前的字符;lastIndexOf()则会从这个参数指定的位置开始向字符串开头搜索,忽略该位置之后直到字符串末尾的字符

5、  startsWith()includes()方法接收可选的第二个参数,表示开始搜索的位置;endsWith()方法接收可选的第二个参数,表示应该当作字符串末尾的位置(抹掉那以后的字符)

6、  字符串模式匹配方法有match()search()replace()split()


6 集合引用类型

Object

1、显式地创建Object的实例有两种方式。第一种是使用new操作符和Object构造函数,另一种方式是使用对象字面量(object literal)表示法(对象字面量是对象定义的简写形式) 在对象字面量表示法中,属性名可以是字符串或数值(数值属性会自动转换为字符串),对象字面量已经成为给函数传递大量可选参数的主要方式

2、虽然属性一般是通过点语法来存取的,但也可以使用中括号来存取属性。在使用中括号时,要在括号内使用属性名的字符串形式(””),使用中括号的主要优势就是可以通过变量访问属性(不需要””)。另外,如果属性名中包含可能会导致语法错误的字符,或者包含关键字/保留字时,也可以使用中括号语法

Array

1、  数组中每个槽位可以存储任意类型的数据,数组也是动态大小的,会随着数据添加而自动增长(不会动态缩小)

2、  创建数组的方法,可以使用Array构造函数(可以给构造函数传入一个数值表示length属性,也可以给Array构造函数传入要保存的元素);另一种创建数组的方式是使用数组字面量(array literal)表示法。Array构造函数还有两个ES6新增的用于创建数组的静态方法:from()of()from()用于将类数组结构转换为数组实例,而of()用于将一组参数转换为数组实例。Array.from()还接收第二个可选的映射函数参数。这个函数可以直接增强新数组的值,还可以接收第三个可选参数,用于指定映射函数中this的值

3、  实践中要避免使用数组空位。如果确实需要空位,则可以显式地用undefined值代替

4、  数组length属性不是只读的,通过修改length属性,可以从数组末尾删除或添加元素(使用length属性可以方便地向数组末尾添加元素)

5、  ES6中,Array 的原型上暴露了3个用于检索数组内容的方法:keys()values()entries()keys()返回数组索引的迭代器,values()返回数组元素的迭代器,而entries()返回索引/值对的迭代器(使用ES6的解构可以非常容易地在循环中拆分键/值对)

6、  ES6新增了两个方法:批量复制方法copyWithin(),以及填充数组方法fill()

7、  join()方法接收一个参数,即字符串分隔符,返回包含所有项的字符串

8、  栈是一种后进先出(LIFOLast-In-First-Out)的结构,也就是最近添加的项先被删除。数据项的插入(称为推入,push)和删除(称为弹出,pop)只在栈的一个地方发生,即栈顶(ECMAScript数组提供了push()pop()方法,以实现类似栈的行为)

9、  队列以先进先出(FIFOFirst-In-First-Out)形式限制访问。队列在列表末尾添加数据,但从列表开头获取数据。使用shift()push(),可以把数组当成队列来使用;通过使用unshift()pop(),可以在相反方向上模拟队列,即在数组开头添加新数据,在数组末尾取得数据

10、数组有两个方法可以用来对元素重新排序:reverse()sort(),调用sort()会按照这些数值的字符串形式重新排序,sort()方法可以接收一个比较函数,用于判断哪个值应该排在前面

11、如果传入一个或多个数组,则concat()会把这些数组的每一项都添加到结果数组(打平)

12、slice()用于创建一个包含原有数组中一个或多个元素的新数组。

13、splice()的主要目的是在数组中间插入元素(参数20表明插入而非删除;参数2>0代表要删除的个数)

14、ECMAScript提供两类搜索数组的方法:按严格相等搜索(indexOflastIndexOfincludes)和按断言函数搜索(findfindIndex方法使用了断言函数, 这两个方法也都接收第二个可选的参数,用于指定断言函数内部this的值。)

15、数组定义了5个迭代方法(everyfilterforEachmapsome)

16、ECMAScript为数组提供了两个归并方法:reduce()reduceRight()

【定型数组】

1、  定型数组(typed array)指的其实是一种特殊的包含数值类型的数组,ArrayBuffer是所有定型数组及视图引用的基本单位(要读取或写入ArrayBuffer,就必须通过视图)

2、  DataView。这个视图专为文件I/O和网络I/O设计,其API支持对缓冲数据的高度控制,但相比于其他类型的视图性能也差一些(其内存中值的字节序,默认为大端字节序)

3、  定型数组是另一种形式的ArrayBuffer视图,它特定于一种ElementType且遵循系统原生的字节序,设计定型数组的目的就是提高与WebGL等原生库交换二进制数据的效率(定型数组的构造函数和实例都有一个BYTES_PER_ELEMENT属性,返回该类型数组中每个元素的大小)

Map

1、  作为ECMAScript 6的新增特性,Map是一种新的集合类型。与Object只能使用数值、字符串或符号作为键不同,Map可以使用任何JavaScript数据类型作为键(Map实例会维护键值对的插入顺序,因此可以根据插入顺序执行迭代操作)

Set

1、  Set会维护值插入时的顺序,因此支持按顺序迭代

【迭代与扩展操作】

1、有4种原生集合类型定义了默认迭代器(Array、定型数组、MapSet),扩展操作符在对可迭代对象执行浅复制时特别有用,只需简单的语法就可以复制整个对象(浅复制意味着只会复制对象引用)


7章迭代器与生成器

【迭代】

1、可迭代对象不一定是集合对象,也可以是仅仅具有类似数组行为的其他数据结构。迭代器(iterator)是按需创建的一次性对象。每个迭代器都会关联一个可迭代对象,迭代器无须了解与其关联的可迭代对象的结构,只需要知道如何取得连续的值

2、在ECMAScript中,这意味着必须暴露一个属性作为“默认迭代器”,而且这个属性必须使用特殊的Symbol.iterator作为键。这个默认迭代器属性必须引用一个迭代器工厂函数,调用这个工厂函数必须返回一个新迭代器。接收可迭代对象的原生语言特性包括:for-of、数组解构、扩展操作符、Array.from()、创建集合、创建映射,Promise.all()Promise.race()yield*操作符(这些原生语言结构会在后台调用提供的可迭代对象的这个工厂函数,从而创建一个迭代器)

3、迭代器API使用next()方法在可迭代对象中遍历数据。每次成功调用next(),都会返回一个IteratorResult对象,包含两个属性:donevalue

4Iterator(迭代器):干活的人,有 next()方法;Iterable(可迭代对象):能被遍历的容器,有 Symbol.iterator属性,调用返回interator(迭代器)

【生成器】

1、生成器的形式是一个函数,函数名称前面加一个星号(*)表示它是一个生成器,调用生成器函数会产生一个生成器对象,生成器对象一开始处于暂停执行(suspended)的状态,调用next()方法会让生成器开始或恢复执行。

2yield关键字可以让生成器停止和开始执行(yield关键字有点像函数的中间返回语句,它生成的值会出现在next()方法返回的对象里。通过yield关键字退出的生成器函数会处在done: false状态;通过return关键字退出的生成器函数会处于done: true状态。) yield关键字只能在生成器函数内部使用,yield关键字必须直接位于生成器函数定义中。

3、调用生成器函数返回的就是一个标准 Iterator 迭代器,自带 .next(),每调用一次 it.next(),代码执行到下一个 yield 就暂停

4、和普通函数最大区别:普通函数,一口气跑完,不能暂停;生成器函数,可暂停、可分步执行,yield = 暂停 + 返回值

5、总结:生成器函数 function* 自动帮你快速生成迭代器,不用手写 nextdone,极简实现迭代协议


8章对象、类与面向对象编程

【理解对象】

1、  ECMA-262使用一些内部特性来描述属性的特征,开发者不能在JavaScript中直接访问这些特性。为了将某个特性标识为内部特性,规范会用两个中括号把特性的名称括起来,比如[[Enumerable]]。属性分两种:数据属性和访问器属性

1)       数据属性包括:[[Configurable]][[Enumerable]][[Writable]][[Value]],要修改属性的默认特性,就必须使用Object.defineProperty()方法

2)       访问器属性包括:一个获取(getter)函数和一个设置(setter)函数,不过这两个函数不是必需的。访问器属性有4个特性描述它们的行为:[[Configurable]][[Enumerable]][[Get]][[Set]],访问器属性是不能直接定义的,必须使用Object.defineProperty()。获取函数和设置函数不一定都要定义。只定义获取函数意味着属性是只读的

2ECMAScript 6专门为合并对象提供了Object.assign()方法。这个方法接收一个目标对象和一个或多个源对象作为参数,然后将每个源对象中可枚举(Object.propertyIsEnumerable()返回true)和自有(Object.hasOwnProperty()返回true)属性复制到目标对象。这个方法会使用源对象上的[[Get]]取得属性的值,然后使用目标对象上的[[Set]]设置属性的值(Object.assign()实际上对每个源对象执行的是浅复制)

3、属性值简写: 简写属性名只要使用变量名(不用再写冒号)就会自动被解释为同名的属性键; 可计算属性: 中括号包围的对象属性键告诉运行时将其作为JavaScript表达式而不是字符串来求值; 简写方法名

1、  ECMAScript 6新增了对象解构语法,可以在一条语句中使用嵌套数据实现一个或多个赋值操作(也可以在解构赋值的同时定义默认值),解构赋值可以使用嵌套结构,以匹配嵌套的属性,在函数参数列表中也可以进行解构赋值

【创建对象】

1、  ECMAScript 6开始正式支持类和继承,构造函数名称的首字母都是要大写的,构造函数内部属性和方法直接赋值给了this。使用new操作符调用构造函数(构造函数与普通函数唯一的区别就是调用方式不同),赋值给变量的函数表达式也可以表示构造函数

2、  构造函数的主要问题在于,其定义的方法会在每个实例上都创建一遍

3、  每个函数都会创建一个prototype属性,这个属性是一个对象,包含应该由特定引用类型的实例共享的属性和方法

4、  理解原型:无论何时,只要创建一个函数,就会按照特定的规则为这个函数创建一个prototype属性(指向原型对象),默认情况下,所有原型对象自动获得一个名为constructor的属性,指回与之关联的构造函数。每次调用构造函数创建一个新实例,这个实例的内部[[Prototype]]指针就会被赋值为构造函数的原型对象。(关键在于理解这一点:实例与构造函数原型之间有直接的联系,但实例与构造函数之间没有。)

5、  构造函数有一个prototype属性,引用其原型对象,而这个原型对象也有一个constructor属性,引用这个构造函数,同一个构造函数创建的两个实例共享同一个原型对象。正常的原型链都会终止于Object的原型对,Object原型的原型是null。实例通过__proto__链接到原型对象,它实际上指向隐藏特性[[Prototype]] Prototype在构造函数、实例的中间起到桥接作用,原型存在原型链的概念。


isPrototypeOf()会在传入参数的[[Prototype]]指向调用它的对象时返回trueECMAScriptObject 类型有一个方法叫Object.getPrototypeOf(),返回参数的内部特性[[Prototype]]的值,Object类型还有一个setPrototypeOf()方法,可以向实例的私有特性[[Prototype]]写入一个新值。为避免使用Object.setPrototypeOf()可能造成的性能下降,可以通过Object.create()来创建一个新对象,同时为其指定原型

6、  在通过对象访问属性时,搜索开始于对象实例本身。如果在这个实例上发现了给定的名称,则返回该名称对应的值;如果没有找到这个属性,则搜索会沿着指针进入原型对象,然后在原型对象上找到属性后,再返回对应的值。这就是原型用于在多个对象实例间共享属性和方法的原理。

虽然可以通过实例读取原型对象上的值,但不可能通过实例重写这些值。如果在实例上添加了一个与原型对象中同名的属性,那就会在实例上创建这个属性,这个属性会遮住原型对象上的属性。hasOwnProperty()方法用于确定某个属性是在实例上还是在原型对象上。

in操作符会在可以通过对象访问指定属性时返回true,无论该属性是在实例上还是在原型上。在for-in循环中使用in操作符时,可以通过对象访问且可以被枚举的属性都会返回,包括实例属性和原型属性。

要获得对象上所有可枚举的实例属性,可以使用Object.keys()方法;如果想列出所有实例属性,无论是否可以枚举,都可以使用Object.getOwnPropertyNames()Object.getOwnPropertySymbols()方法与Object.getOwnPropertyNames()类似,只是针对符号而已。Object.values()Object.entries()接收一个对象,返回它们内容的数组。Object.values()

返回对象值的数组,Object.entries()返回键/值对的数组。(注意,非字符串属性会被转换为字符串输出。另外,这两个方法执行对象的浅复制,符号属性会被忽略)

7、  可以通过一个包含所有属性和方法的对象字面量来重写原型,但有一个问题:这样重写之后,Person.prototypeconstructor属性就不指向Person(上面的写法完全重写了默认的prototype对象,因此其constructor属性也指向了完全不同的新对象(Object构造函数),不再指向原来的构造函数)

8、  因为从原型上搜索值的过程是动态的,所以即使实例在修改原型之前已经存在,任何时候对原型对象所做的修改也会在实例上反映出来(因为实例和原型之间的链接就是简单的指针,而且修改原型对象不会改变原型的地址,也就不会改变实例和原型的联系)。但是重写整个原型是另外一种情况,重写整个原型会切断最初原型与构造函数的联系,但实例引用的仍然是最初的原型(实例的原型指针,是用构造函数创建实例的时候赋值的。重写构造函数上的原型之后再创建的实例才会引用新的原型。而在此之前创建的实例仍然会引用最初的原型)

【继承】

1、  每个构造函数都有一个原型对象,原型有一个属性指回构造函数,而实例有一个内部指针指向原型。如果原型是另一个类型的实例,那就意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。这样就在实例和原型之间构造了一条原型链。


原型链扩展了前面描述的原型搜索机制。我们知道,在读取实例上的属性时,首先会在实例上搜索这个属性。如果没找到,则会继承搜索实例的原型。在通过原型链实现继承之后,搜索就可以继承向上,搜索原型的原型,对属性和方法的搜索会一直持续到原型链的末端。

任何函数的默认原型都是一个Object 的实例,这意味着这个实例有一个内部指针指向Object.prototype

2、  如果一个实例的原型链中出现过相应的构造函数,则instanceof返回true。确定这种关系的第二种方式是使用isPrototypeOf()方法

3、  子类有时候需要覆盖父类的方法,或者增加父类没有的方法。为此,这些方法必须在原型赋值之后再添加到原型上。另外,以对象字面量方式创建原型方法会破坏之前的原型链,因为这相当于重写了原型链(覆盖后的原型是一个Object的实例,而不再是SuperType的实例)

4、  原型链的问题:

1)  原型中包含的引用值会在所有实例间共享(SubType通过原型继承SuperType后,SubType.prototype变成了SuperType的一个实例,因而也获得了SuperType的引用属性)

2)  子类型在实例化时不能给父类型的构造函数传参(我们无法在不影响所有对象实例的情况下把参数传进父类的构造函数)

5、  为了解决原型包含引用值导致的继承问题,一种叫作“盗用构造函数”(constructor stealing)的技术在开发社区流行起来

1)                相比于使用原型链,盗用构造函数的一个优点就是可以在子类构造函数中向父类构造函数传参

2)                盗用构造函数的主要缺点, 必须在构造函数中定义方法,因此函数不能重用。此外,子类也不能访问父类原型上定义的方法

 

6、  组合继承基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性


7、  ECMAScript 5通过增加Object.create()方法将原型式继承的概念规范化了。这个方法接收两个参数:作为新对象原型的对象,以及给新对象定义额外属性的对象(第二个可选)。原型式继承非常适合不需要单独创建构造函数,但仍然需要在对象间共享信息的场合。但要记住,属性中包含的引用值始终会在相关对象间共享,跟使用原型模式是一样的。

8、  寄生式继承背后的思路类似于寄生构造函数和工厂模式:创建一个实现继承的函数,以某种方式增强对象,然后返回这个对象。寄生式继承同样适合主要关注对象,而不在乎类型和构造函数的场景

9、  组合继承其实也存在效率问题,父类构造函数始终会被调用两次:一次在是创建子类原型时调用,另一次是在子类构造函数中调用(父类构造函数的属性会被创造两份:一组在实例上,另一组在SubType的原型上)。寄生式组合继承通过盗用构造函数继承属性,但使用混合式原型链继承方法。基本思路是不通过调用父类构造函数给子类原型赋值,而是取得父类原型的一个副本。说到底就是使用寄生式继承来继承父类原型,然后将返回的新对象赋值给子类原型


【类】

1ECMAScript 6新引入的class 关键字具有正式定义类的能力。类(class)是ECMAScript中新的基础性语法糖结构,实际上它背后使用的仍然是原型和构造函数的概念。与函数定义不同的是,虽然函数声明可以提升,但类定义不能。另一个跟函数声明不同的地方是,函数受函数作用域限制,而类受块作用域限制。ECMAScript类就是一种特殊函数

constructor关键字用于在类定义块内部创建类的构造函数。方法名constructor会告诉解释器在使用new操作符创建类的新实例时,应该调用这个函数。构造函数的定义不是必需的,不定义构造函数相当于将构造函数定义为空函数。类实例化时传入的参数会用作构造函数的参数。默认情况下,类构造函数会在执行之后返回this对象。类构造函数与构造函数的主要区别是,调用类构造函数必须使用new操作符

      2、  类的语法可以非常方便地定义应该存在于实例上的成员、应该存在于原型上的成员,以及应该存在于类本身的成员

1)       每次通过new调用类标识符时,都会执行类构造函数。在这个函数内部,可以为新创建的实例(this)添加“自有”属性。另外,在构造函数执行完毕后,仍然可以给实例继续添加新成员(构造函数上创建的成员不会共享)

2)       为了在实例间共享方法,类定义语法把在类块中定义的方法作为原型方法。可以把方法定义在类构造函数中或者类块中,但不能在类块中给原型添加原始值或对象作为成员数据。类方法等同于对象属性,因此可以使用字符串、符号或计算的值作为键。类定义也支持获取和设置访问器。语法与行为跟普通对象一样

3)       可以在类上定义静态方法,静态成员每个类上只能有一个,静态类成员在类定义中使用static关键字作为前缀(静态类方法非常适合作为实例工厂)

4)       虽然类定义并不显式支持在原型或类上添加成员数据,但在类定义外部,可以手动添加

5)       类定义语法支持在原型和类本身上定义生成器方法

【继承】

1、  虽然类继承使用的是新语法,但背后依旧使用的是原型链。ES6类支持单继承,使用extends关键字,就可以继承任何拥有[[Construct]]和原型的对象。派生类都会通过原型链访问到类和原型上定义的方法,this的值会反映调用相应方法的实例或者类

2、  派生类的方法可以通过super关键字引用它们的原型。这个关键字只能在派生类中使用,而且仅限于类构造函数、实例方法和静态方法内部(ES6给类构造函数和静态方法添加了内部特性[[HomeObject]],这个特性是一个指针,指向定义该方法的对象, super始终会定义为[[HomeObject]]的原型)

1)       super只能在派生类构造函数和静态方法中使用

2)       不能单独引用super关键字,要么用它调用构造函数,要么用它引用静态方法

3)       调用super()会调用父类构造函数,并将返回的实例赋值给thissuper()的行为如同调用构造函数,如果需要给父类构造函数传参,则需要手动传入

4)       如果没有定义类构造函数,在实例化派生类时会调用super(),而且会传入所有传给派生类的参数。

5)       在类构造函数中,不能在调用super()之前引用this

6)       如果在派生类中显式定义了构造函数,则要么必须在其中调用super(),要么必须在其中返回一个对象

3、  new.target保存通过new关键字调用的类或函数。通过在实例化时检测new.target是不是抽象基类,可以阻止对抽象基类的实例化

4、  ES6类为继承内置引用类型提供了顺畅的机制,开发者可以方便地扩展内置类型

5、  Object.assign()方法是为了混入对象行为而设计的,如果只是需要混入多个对象的属性,那么使用Object.assign()就可以了。

10 函数

函数是ECMAScript中最有意思的部分之一,这主要是因为函数实际上是对象。每个函数都是Function类型的实例,而Function也有属性和方法,跟其他引用类型一样。因为函数是对象,所以函数名就是指向函数对象的指针

【箭头函数】

1、  ECMAScript 6新增了使用胖箭头(=>)语法定义函数表达式的能力,任何可以使用函数表达式的地方,都可以使用箭头函数。

2、  如果只有一个参数,那也可以不用括号。只有没有参数,或者多个参数的情况下,才需要使用括号。

3、  箭头函数也可以不用大括号,那么箭头后面就只能有一行代码,比如一个赋值操作,或者一个表达式,省略大括号会隐式返回这行代码的值。

4、  箭头函数不能使用argumentssuper new.target,也不能用作构造函数。此外,箭头函数也没有prototype属性。

【函数名】

1、  函数名就是指向函数的指针,这意味着一个函数可以有多个名称

2、  使用不带括号的函数名会访问函数指针,而不会执行函数。

【理解参数】

1、  ECMAScript函数既不关心传入的参数个数,也不关心这些参数的数据类型。在使用function关键字定义(非箭头)函数时,可以在函数内部访问arguments对象,从中取得传进来的每个参数值,arguments对象是一个类数组对象(但不是Array的实例)ECMAScript函数的参数(命名参数)只是为了方便才写出来的,并不是必须写出来的(不写就通过arguments获取)

2、  arguments对象可以跟命名参数一起使用,arguments 对象的另一个有意思的地方就是,它的值始终会与对应的命名参数同步(双向绑定、自动同步)。但这并不意味着它们都访问同一个内存地址,它们在内存中还是分开的,只不过会保持同步而已。另外还要记住一点,arguments对象的长度是根据传入的参数个数,而非定义函数时给出的命名参数个数确定的。对于命名参数而言,如果调用函数时没有传这个参数,那么它的值就是undefined如果某个形参没有传入实参,就算非严格模式,也不会和 arguments 同步(只有传入了实参,形参和arguments才会同步)

3、  如果函数是使用箭头语法定义的,那么传给函数的参数将不能使用arguments关键字访问,而只能通过定义的命名参数访问

4、  注意  ECMAScript中的所有参数都按值传递的,不可能按引用传递参数。如果把对象作为参数传递,那么传递的值就是这个对象的引用

【没有重载】

1、  ECMAScript函数没有签名,因为参数是由包含零个或多个值的数组表示的。没有函数签名,自然也就没有重载。如果在ECMAScript中定义了两个同名函数,则后定义的会覆盖先定义的(把函数名当成指针也有助于理解为什么ECMAScript没有函数重载)

2、  可以通过检查参数的类型和数量,然后分别执行不同的逻辑来模拟函数重载

【默认参数值】

1、  ECMAScript 6支持显式定义默认参数,只要在函数定义中的参数后面用=就可以为参数赋一个默认值

2、  在使用默认参数时,arguments对象的值不反映参数的默认值,只反映传给函数的参数(arguments对象和形参本质是不同的,在内存中还是分开的,只不过会保持同步而已)。修改命名参数不会影响arguments对象,它始终以调用函数时传入的值为准(arguments对象反映的是调用函数时传入的实参)

3、  函数的默认参数只有在函数被调用时才会求值,不会在函数定义时求值

4、  箭头函数同样也可以这样使用默认参数,只不过在只有一个参数时,就必须使用括号而不能省略了

5、  给多个参数定义默认值实际上跟使用let关键字顺序声明变量一样,后定义默认值的参数可以引用先定义的参数(前面定义的参数不能引用后面定义的)。参数也存在于自己的作用域中,它们不能引用函数体的作用域。

【参数扩展与收集】

1、  ECMAScript 6新增了扩展操作符,使用它可以非常简洁地操作和组合集合数据(扩展操作符最有用的场景就是函数定义中的参数列表)。对可迭代对象应用扩展操作符,并将其作为一个参数传入,可以将可迭代对象拆分,并将迭代返回的每个值单独传入。在普通函数和箭头函数中,也可以将扩展操作符用于命名参数,当然同时也可以使用默认参数

2、  在构思函数定义时,可以使用扩展操作符把不同长度的独立参数组合为一个数组(这有点类似arguments对象的构造机制,只不过收集参数的结果会得到一个Array实例)。收集参数的前面如果还有命名参数,则只会收集其余的参数;如果没有则会得到空数组。因为收集参数的结果可变,所以只能把它作为最后一个参数(箭头函数虽然不支持arguments 对象,但支持收集参数的定义方式,因此也可以实现与使用arguments一样的逻辑)。另外,使用收集参数并不影响arguments对象,它仍然反映调用时传给函数的参数

【函数声明与函数表达式】

1JavaScript 引擎在任何代码执行之前,会先读取函数声明,并在执行上下文中

生成函数定义。而函数表达式必须等到代码执行到它那一行,才会在执行上下文中生成函数定义。即使函数定义出现在调用它们的代码之后,引擎也会把函数声明提升到顶部。

【函数作为值】

1、  因为函数名在ECMAScript中就是变量,所以函数可以用在任何可以使用变量的地方。这意味着不仅可以把函数作为参数传给另一个函数,而且还可以在一个函数中返回另一个函数

【函数内部】

ECMAScript 5中,函数内部存在两个特殊的对象:argumentsthisECMAScript 6又新增了new.target属性

1arguments对象其实还有一个callee属性,是一个指向arguments对象所在函数的

指针(使用arguments.callee就可以让函数逻辑与函数名解耦)

2、另一个特殊的对象是this,它在标准函数和箭头函数中有不同的行为。在标准函数中,this引用的是把函数当成方法调用的上下文对象(这个this到底引用哪个对象必须到

函数被调用时才能确定);在箭头函数中,this引用的是定义箭头函数的上下文(在事件回调或定时回调中调用某个函数时,this值指向的并非想要的对象。此时将回调函数写成箭头函数就可以解决问题。这是因为箭头函数中的this会保留定义该函数时的上下文)

注意:函数名只是保存指针的变量。因此全局定义的sayColor()函数和o.sayColor()

是同一个函数,只不过执行的上下文不同。

3、  ECMAScript 6新增了检测函数是否使用new关键字调用的new.target属性(如果函数

是正常调用的,则new.target的值是undefined;如果是使用new关键字调用的,则new.target将引用被调用的构造函数)

【函数属性与方法】

1、  每个函数都有两个属性:lengthprototype。其中,length属性保存函数定义的命名参数的个数;prototype是保存引用类型所有实例方法的地方。

2、  函数还有两个方法:apply()call(),这两个方法都会以指定的this值来调用函数,即会设置调用函数时函数体内this对象的值。call()方法与apply()的作用一样,只是传参的形式不同。apply()call()真正强大的地方并不是给函数传参,而是控制函数调用上下文即函数体内this值的能力

3、  ECMAScript 5出于同样的目的定义了一个新方法:bind()bind()方法会创建一个新的函数实例,其this值会被绑定到传给bind()的对象

【函数表达式】

1、  定义函数有两种方式:函数声明和函数表达式,函数声明的关键特点是函数声明提升;函数表达式看起来就像一个普通的变量定义和赋值,即创建一个函数再把它赋值给一个变量,未赋值给其他变量的匿名函数的name属性是空字符串。理解函数声明与函数表达式之间的区别,关键是理解提升。任何时候,只要函数被当作值来使用,它就是一个函数表达式

【闭包】

闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的

1、  在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链(临时作用域链,存在于栈上的执行上下文中)

2、  函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。这意味着函数执行上下文的作用域链中有两个变量对象:局部变量对象和全局变量对象

3、  在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中

4、  执行上下文EC、变量对象VO/AO、临时/词法作用域链的关系:

      1)执行上下文 EC永远在调用栈(Stack, 每一个函数执行上下文 EC 内部有三个核心属性:this、变量对象 VO/AO[[Scope]] 临时作用域链

2)变量对象 VO / 活动对象 AO ,本身是对象,真身永远在堆(Heap

3)栈里的 EC 只存对堆里 AO 的引用地址

4)js里面函数即对象,存在于堆里,当函数对象不再被引用,那函数对象会被GC回收,函数对象上面的词义作用域链也跟着销毁

5) 词法链 [[Scope]]:定义时生成,堆里躺着,存外层作用域,无自己 AO,是静态模板; 临时作用域链:调用时现场拼接,栈里临时存在,头部插自己当前 AO,是运行时可用的完整链条。词法链负责锁闭包、锁外层引用;临时链负责真正运行时变量查找。


5、  普通函数的 this 是运行时动态绑定,看怎么调用,不看在哪定义。闭包里的内层普通函数,独立调用时没有绑定任何对象,导致非严格模式 this→window,严格模式 this→ undefined。箭头函数设计初衷之一就是解决闭包this丢失问题:箭头函数本身没有自己的 this,箭头函数的this继承自外层词法作用域的this,不是运行时绑定,是定义时就固定继承外层this

6、  函数可以在创建之后立即调用,执行其中代码之后却不留下对函数的引用。

7、  这是因为在ECMAScript 6中,如果对for循环使用块级作用域变量关键字,在这里就是let,那么循环就会为每个循环创建独立的变量,从而让每个单击处理程序都能引用特定的索引(注意,如果把变量声明拿到for循环外部,那就不行了)

8、  任何定义在函数或块中的变量,都可以认为是私有的,因为在这个函数或块的外部无法访问其中的变量。私有变量包括函数参数、局部变量,以及函数内部定义的其他函数。利用闭包,可以创建出能够访问私有变量的公有方法(可以访问私有变量的公共方法叫作特权方法)

9、  特权方法可以使用构造函数或原型模式通过自定义类型中实现,也可以使用模块模式或模块增强模式在单例对象上实现

10、定义在构造函数中的特权方法其实是一个闭包,它具有访问构造函数中定义的所有变量和函数的能力

11、单例对象(singleton)就是只有一个实例的对象,JavaScript是通过对象字面量来创建单例对象的。模块模式是在单例对象基础上加以扩展,使用了匿名函数返回一个对象,使其通过作用域链来关联私有变量和特权方法

11章期约与异步函数

【期约】

以前异步靠回调函数嵌套,嵌套多了就是回调地狱,代码向右无限缩进、难读难维护,Promise 就是把异步写法从嵌套改成链式平铺。

1、  ECMAScript 6新增的引用类型Promise,可以通过new操作符来实例化。期约是一个有状态的对象,可能处于如下3种状态之一:pendingfulfilled(resolved)rejected。期约的状态是私有的,不能直接通过JavaScript检测到,期约的状态也不能被外部JavaScript代码修改。只要从待定转换为兑现或拒绝,期约的状态就不再改变。

2、  期约主要有两大用途。首先是抽象地表示一个异步操作。每个期约只要状态切换为兑现,就会有一个私有的内部值(value)。类似地,每个期约只要状态切换为拒绝,就会有一个私有的内部理由(reason)。控制期约状态的转换是通过调用它的两个函数参数实现的。这两个函数参数通常都命名为resolve()reject()

3、  期约实例的方法是连接外部同步代码与内部异步代码之间的桥梁。

4、  Promise.prototype.then()方法接收最多两个参数:onResolved处理程序和onRejected处理程序(两个处理程序参数都是可选的)。因为期约只能转换为最终状态一次,所以这两个操作一定是互斥的。onResovled处理程序的返回值构建会通过Promise.resolve()包装来生成新期约,onRejected处理程序返回的值也会被Promise.resolve()包装,所以支持链式调用。

5、  Promise.prototype.catch()方法用于给期约添加拒绝处理程序。这个方法只接收一个参数:

onRejected处理程序。事实上,这个方法就是一个语法糖,调用它就相当于调用Promise.prototype. then(null, onRejected)

6Promise.prototype.finally()方法用于给期约添加onFinally处理程序,这个处理程序在期约转换为解决或拒绝状态时都会执行。但onFinally处理程序没有办法知道期约的状态是解决还是拒绝,所以这个方法主要用于添加清理代码

7、在执行函数中,解决的值和拒绝的理由是分别作为resolve()reject()的第一个参数往后传的。然后,这些值又会传给它们各自的处理程序,作为onResolvedonRejected处理程序的唯一参数。

8、在期约的执行函数或处理程序中抛出错误会导致拒绝,对应的错误对象会成为拒绝的理由。期约可以以任何理由拒绝,包括undefined,但最好统一使用错误对象。异步错误只能通过异步的onRejected处理程序捕获(不能通过try catch这种同步错误处理来捕获)。另外,onRejected处理程序在捕获异步错误之后会返回一个解决的期约(所以可以.catch.then.catch.finally链式处理)

9、每个期约实例的方法(then()catch()finally())都会返回一个新的期约对象,所以可以实现期约连锁。

链式写法永远按顺序:then → catch → finally

成功:走 then → finally

失败:走 catch → finally

resolve then,不走 catchreject catch,不走 then;不管成功失败,必走 finally

10Promise类提供两个将多个期约实例组合成一个期约的静态方法:Promise.all()Promise.race()

1)       Promise.all()静态方法创建的期约会在一组期约全部解决之后再解决。这个静态方法接收一个可迭代对象,返回一个新期约。如果至少有一个包含的期约待定,则合成的期约也会待定。如果有一个包含的期约拒绝,则合成的期约也会拒绝;如果所有期约都成功解决,则合成期约的解决值就是所有包含期约解决值的数组。

2)       Promise.race()静态方法返回一个包装期约,是一组集合中最先解决或拒绝的期约的镜像(不过,这并不影响所有包含期约正常的拒绝操作)

【异步函数】

异步函数,也称为“async/await”(语法关键字),是ES6期约模式在ECMAScript函数中的应用,让以同步方式写的代码能够异步执行。

1、  async关键字用于声明异步函数。这个关键字可以用在函数声明、函数表达式、箭头函数和方法上。使用async关键字可以让函数具有异步特征,异步函数如果使用return关键字返回了值(如果没有return则会返回undefined),这个值会被Promise.resolve()包装成一个期约对象(异步函数始终返回期约对象)。在异步函数中抛出错误会返回拒绝的期约,不过,拒绝期约的错误不会被异步函数捕获。

2、  使用await关键字可以暂停异步函数代码的执行,等待期约解决(await关键字会暂停执行异步函数后面的代码,让出JavaScript运行时的执行线程)await关键字必须在异步函数中使用,async/await中真正起作用的是awaitasync关键字,无论从哪方面来看,都不过是一个标识符。


21 错误处理与调试

【错误处理】

1、  任何可能出错的代码都应该放到try块中,而处理错误的代码则放在catch块中。如果try块中有代码发生错误,代码会立即退出执行,并跳到catch块中。错误对象中暴露的实际信息因浏览器而异,但至少包含保存错误消息的message属性。

2、  try/catch 语句中可选的finally 子句始终运行,只要代码中包含了finally子句,try块或catch块中的return语句就会被忽略

3、  ECMA-262定义了以下8种错误类型;ErrorInternalErrorEvalErrorRangeErrorReferenceErrorSyntaxErrorTypeErrorURIError

1)       浏览器很少会抛出Error类型的错误,该类型主要用于开发者抛出自定义错误

2)       ReferenceError 会在找不到对象时发生。(这就是著名的"object expected"浏览器错误的原因。)这种错误经常是由访问不存在的变量而导致的

3)       TypeErrorJavaScript中很常见,主要发生在变量不是预期类型,或者访问不存在的方法时。在给函数传参数之前没有验证其类型的情况下,类型错误频繁发生。

4)       try/catch语句的catch块中,可以使用instanceof操作符确定错误的类型。try/catch语句最好用在自己无法控制的错误上

4、  try/catch语句对应的一个机制是throw操作符,用于在任何时候抛出自定义错误,可以通过内置的错误类型来模拟浏览器错误,当然,使用特定的错误类型也是一样的。

5、  通过继承Error也可以创建自定义的错误类型,创建自定义错误类型时,需要提供name属性和message属性。自定义错误类型有助于在捕获错误时更准确地区分错误。

6、  任何没有被try/catch语句处理的错误都会在window对象上触发error事件。通过返回false,这个函数实际上就变成了整个文档的try/catch语句,可以捕获所有未处理的运行时错误。适当使用try/catch语句意味着不会有错误到达浏览器这个层次,因此也就不会触发error事件。

7、  针对数据类型错误问题,一般来说,原始类型的值应该使用typeof检测,而对象值应该使用instanceof检测。

8、  针对通信错误,比如第一种错误是URL格式或发送数据的格式不正确,对于查询字符串,应该都要通过encodeURIComponent()编码

 

23JSON

1、  JSONJavaScript的严格子集,利用JavaScript中的几种模式来表示结构化数据。理解JSON最关键的一点是要把它当成一种数据格式,而不是编程语言。JSON不属于JavaScript,它们只是拥有相同的语法而已,JSON是一种通用数据格式。

2、  SON语法支持表示3种类型的值:简单值、对象、数组

3、  JSON字符串必须使用双引号(单引号会导致语法错误),JSON中的对象必须使用双引号把属性名包围起来。手动编写JSON时漏掉这些双引号或使用单引号是常见错误。

4、  JSON可以直接被解析成可用的JavaScript对象,JSON 对象有两个方法:stringify()parse()

1)       在序列化JavaScript对象时,值为undefined的任何属性也会被跳过。如果第二个参数是一个数组,那么JSON.stringify()返回的结果只会包含该数组中列出的对象属性; 第二个参数也可以是一个函数过滤器;第三个参数控制缩进和空格。可以在要序列化的对象中添加toJSON()方法,序列化时会基于这个方法返回适当的JSON表示(注意,箭头函数不能用来定义toJSON()方法)

2)       如果给JSON.parse()传入的JSON字符串无效,则会导致抛出错误。JSON.parse()方法也可以接收一个额外的参数,这个函数会针对每个键/值对都调用一次


posted on 2026-04-25 15:39 lfc 阅读(35) 评论(0)  编辑 收藏 引用
只有注册用户登录后才能发表评论。