《ECMAScript 6 入门》读书笔记(二)
说实话,ES 6 新特性真的不少。感觉全部记下来都很难,更别谈消化了。继续啃《ECMAScript 6 入门》。希望能早日啃完,想去看 Vue.js 了…
此博文包括:函数的扩展、对象的扩展、Symbol、Set 和 Map 数据结构。
函数的扩展
函数参数的默认值
1.ES 6 允许为函数的参数设置默认值,即直接写在参数定义的后面。
1 | function log(x, y = 'World'){ |
参数变量是默认声明的,所以不能用let
或const
再次声明;使用参数默认值时,函数不能有同名参数。
定义了默认值的参数通常应该为函数的尾参数。
2.函数的length
属性:将返回没有指定默认值的参数个数。如果设置了默认值的参数不是尾参数,后面的参数也不会被计入。
3.作用域:一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域。等到初始化结束,作用域消失。
1 | // 函数foo内部声明的内部变量与参数不是同一个作用域 |
1 | var x = 1; |
4.函数参数默认值的应用:可以指定某一个参数不得省略,如果省略就抛出一个错误。
1 | function throwIfMissing(){ |
可以将参数默认值设为undefined,表明这个参数是可以省略的。
5.rest 参数:形式为”…变量名”,用于获取函数的多余参数。rest 参数中的变量代表一个数组。rest 参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
1 | const sortNumbers = (...numbers) => numbers.sort(); |
函数的length
属性,不包括 rest 参数。
1 | (function(a,...b){}).length // 1 |
函数参数的默认值
6.扩展运算符:...
,将一个数组转为用逗号分隔的参数序列。
1 | console.log(1, ...[2, 3, 4], 5) |
7.扩展运算符的应用:
- 合并数组:提供了新写法。
1 | var arr1 = ['a', 'b']; |
- 与解构赋值结合:用于生成数组(只能放在参数最后一位)。
1 | const [first, ...rest] = [1, 2, 3, 4, 5]; |
- 函数的返回值:为函数提供一种返回多个值的方法。
1 | // 从数据库取出一行数据,通过扩展运算符,直接传入构造函数Date |
- 字符串:扩展运算符还可以将字符串转为真正的数组。
1 | let str = 'x\uD83D\uDE80y'; |
- 实现了 Iterator 接口的对象:可以通过扩展运算符转为真正的数组。
1 | var nodeList = document.querySelectorAll('div'); |
- Map 和 Set 结构,Generator 函数:扩展运算符内部调用的是数据结构的 Iterator 接口,因此只要具有 Iterator 接口的对象,都可以使用扩展运算符。Generator 函数运行后,返回一个遍历器对象,因此也可以使用扩展运算符。
1 | // Map结构 |
8.函数内部严格模式:ES 5 时函数内部可以设定为严格模式;ES 6 规定只要函数参数使用了默认值、解构赋值或者扩展运算符,则函数内部不能显式设定为严格模式,否则报错。
原因:只有从函数体代码之中,才能知道参数代码是否应该以严格模式执行,但是参数代码却应该先于函数体代码执行。
两种规避方法:全局性严格模式,将函数包在一个无参数的立即执行函数里。
9.name 属性:返回该函数的函数名。
如果将一个匿名函数赋值给一个变量,ES5 的 name 属性,会返回空字符串,而 ES6 的 name 属性会返回实际的函数名。
如果将一个具名函数赋值给一个变量,则 ES5 和 ES6 的 name 属性都返回这个具名函数原本的名字。
1 | const bar = function baz(){}; |
Function
构造函数返回的函数实例,name 属性的值为 anonymous
。
1 | (new Function).name // "anonymous" |
bind
返回的函数,name属性值会加上bound
前缀。
1 | function foo() {}; |
箭头函数
10.如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。
1 | // 箭头函数与变量解构结合使用 |
11.箭头函数使用注意点:
- 函数体内的
this
对象就是定义时所在对象,而不是使用时所在对象。 - 不可当作构造函数(即不可使用
new
命令),否则抛出错误。 - 不可以使用
arguments
对象,该对象在函数体内不存在。如果要用,可以用 rest 参数代替。 - 不可以使用
yield
命令,因此箭头函数不能用作 Generator 函数。
this
指向的固定化,实际原因是箭头函数根本没有自己的this
,导致内部的this
就是外层代码块的this
。也正因如此,箭头函数不能用作构造函数,也不能用call()
、apply()
、bind()
这些方法去改变this
的指向。
除了this
,arguments
、super
、new.target
在箭头函数中也是不存在的,指向外层函数的对应变量。
12.嵌套的箭头函数:
1 | // ES 5 |
13.函数绑定运算符:::
,左边为对象,右边为函数。该运算符自动将左边的对象作为上下文环境(即 this 对象),绑定到右边的函数上。用来取代call
、apply
、bind
调用。
1 | foo::bar; |
如果双冒号左边为空,右边是一个对象的方法,则等于将该方法绑定在该对象上面。
1 | var method = obj::obj.foo; |
由于双冒号运算符返回的还是原对象,因此可以采用链式写法。
1 | let { find, html } = jake; |
该语法为已被 Babel 转码器支持的一个 ES 7 提案。
14.尾调用:某个函数的最后一步是调用另一个函数。
To be continued…
对象的扩展
1.属性的简洁表示法:ES 6 允许直接写入变量和函数,作为对象的属性和方法。这时,属性名为变量名,属性值为变量值。
1 | var foo = 'bar'; |
如果某个方法的值是一个 Generator 函数,前面需要加上星号。
1 | var obj = { |
2.属性名表达式:ES 6 允许字面量定义对象时,把表达式放在方括号内。表达式也可用于定义方法名。
1 | let propKey = 'foo'; |
但属性名表达式与简洁表达式不能同时使用。
1 | // 报错 |
属性名表达式如果是一个对象,默认情况下会自动将对象转为字符串[object Object]
(即不要这么用)。
3.方法的 name 属性:返回函数名。
如果对象的方法使用了取值函数(getter
)和存值函数(setter
),则name
属性不是在该方法上面,而是该方法的属性的描述对象的get
和set
属性上面,返回值是方法名前加上get
和set
。
有两种特殊情况:bind
方法创造的函数,name
属性返回bound
加上原函数的名字;Function
构造函数创造的函数,name
属性返回anonymous
。
如果对象的方法是一个 Symbol 值,那么name
属性返回的是这个 Symbol 值的描述。
4.Object.is()
:比较两个值是否严格相等,与严格比较运算符(===
)的行为基本一致(不同之处为+0
不等于-0
,以及NaN
等于自身)。
5.Object.assign()
:用于对象的合并,将源对象的所有可枚举属性复制到目标对象。第一个参数是目标对象,其他参数是源对象。
1 | var target = { a: 1 }; |
如果多个对象有同名属性,后面的属性会覆盖前面的。
如果只有一个参数将直接返回(参数不是对象会先转成对象)。无法转成对象的源对象将被跳过。undefined
和null
无法转成对象,所以不能作为目标对象(否则报错)。其他相关注意事项见文档。
实行浅拷贝,即源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。对于嵌套对象,一旦遇到同名属性会被替换。
1 | var target = { a: { b: 'c', d: 'e'} }; |
Object.assign()
有很多用处,其中包括为对象添加属性和方法。
1 | // 添加属性 |
属性的可枚举性与遍历
6.Object.getOwnPropertyDescriptor
:对象的每个属性都有一个描述对象,用来控制该属性的行为。Object.getOwnPropertyDescriptor
方法可以获得该属性的描述对象。
1 | let obj = { foo: 123 }; |
ES 7 引入Object.getOwnPropertyDescriptors
方法,返回指定对象所有自身属性(非继承属性)的描述对象。
1 | const obj = { |
7.属性的可枚举性:描述对象的enumerable
属性,成为“可枚举性”。ES 5 有三个操作会忽略enumerable
为false
的属性:
for...in
循环:只遍历对象自身的和继承的可枚举的属性。Object.keys()
:返回对象自身的所有可枚举的属性的键名。JSON.stringify()
:只串行化对象自身的可枚举的属性。
ES 6 新增Object.assign()
,会忽略enumerable
为false
的属性,只拷贝对象自身的可枚举的属性。
当只关心对象自身的属性时,尽量不要用for...in
循环,而用Object.keys()
代替。
8.Object.keys()
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键名。ES 2017 引入配套的Object.values
和Object.entries
作为遍历一个对象的补充手段。
Object.values()
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键值(不含 Symbol 属性)。参数为字符串时返回各个字符组成的一个数组,参数为数值或布尔值时返回空数组(与包装对象有关)。
Object.entries()
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性的键值对数组(不含 Symbol 属性)。
9.属性的遍历:ES 6 共有5种遍历对象的方法。
for...in
:循环遍历对象自身的和继承的可枚举属性(不含 Symbol 属性)。Object.keys(obj)
:返回一个数组,包括对象自身的(不含继承的)所有可枚举属性(不含 Symbol 属性)。Object.getOwnPropertyNames(obj)
:返回一个数组,包含对象自身的所有属性(不含 Symbol 属性,但包括不可枚举属性)。Object.getOwnPropertySymbols(obj)
:返回一个数组,包含对象自身的所有 Symbol 属性。Reflect.ownKeys(obj)
:返回一个数组,包含对象自身的所有属性(无论是否可枚举或者属性名是 Symbol 还是字符串)。
以上的5种方法遍历对象的属性,都遵守同样的属性遍历的次序规则(之前一些遍历的方法也遵循此规则)。
- 首先遍历所有属性名为数值的属性,按照数字排序。
- 其次遍历所有属性名为字符串的属性,按照生成时间排序。
- 最后遍历所有属性名为Symbol值的属性,按照生成时间排序。
__proto__
属性及相关方法
10.__proto__
属性:用于读取或设置当前对象的prototype
对象。内部属性,不是正式对外 API。建议使用Object.setPrototypeOf()
(写操作)、Object.getPrototype()
(读操作)、Object.create()
(生成操作)代替。
如果一个对象本身部署了__proto__
属性,则该属性的值就是对象的原型。
11.Object.setPrototypeOf()
:作用与__proto__
相同,用来设置一个对象的prototype
对象,返回参数对象本身。
1 | // 格式 |
如果第一个参数不是对象,会自动转为对象(由于返回的还是第一个参数,故无效果);是undefined
或null
则报错(无法转为对象)。
12.Object.getPrototypeOf()
:用于读取一个对象的原型对象。
1 | Object.getPrototypeOf(obj); |
如果参数不是对象,会被自动转为对象;是undefined
或null
则报错。
13.对象的扩展运算符:ES 2017 将...
引入对象。主要用途有:
- 解构赋值:
1 | let {x, y, ...z} = { x: 1, y: 2, a: 3, b: 4 }; |
注意解构赋值的拷贝是浅拷贝,且不会拷贝继承自原型对象的属性。
1 | // 扩展某个函数的参数,引入其他操作 |
- 扩展运算符:取出参数对象的所有可遍历属性,拷贝到当前对象之中。等同于
Object.assign
方法。
1 | let z = { a: 3, b: 4 }; |
如果用户自定义的属性放在扩展运算符后面,则扩展运算符内部的同名属性会被覆盖掉。因此可用于修改现有对象部分的部分属性。
1 | let newVersion = { |
如果把自定义属性放在扩展运算符前面,就变成了设置新对象的默认属性值。
1 | let aWithDefaults = { x: 1, y: 2, ...a }; |
扩展运算符的参数对象之中,如果有取值函数get
,这个函数是会执行的。
14.Null 传导运算符:?.
,用于判断对象及其内部属性是否存在。仅为提案,详见文档。
Symbol
1.概述:ES 6 引入的新的原始数据类型,表示独一无二的值。至此,JS 共有七种数据类型:
- Undefined
- Null
- 布尔值(Boolean)
- 字符串(String)
- 数值(Number)
- 对象(Object)
- Symbol
Symbol 值通过Symbol
函数生成。即对象的属性名有两种类型:原来就有的字符串,和新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就是独一无二的,不会与其他属性名产生冲突。
1 | let s = Symbol(); |
生成的 Symbol 是一个原始类型的值,不是对象。因此Symbol
函数前不能使用new
命令(否则报错),Symbol 值也不能添加属性。
Symbol
函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。但相同参数的Symbol
函数的返回值是不相等的。
1 | var s1 = Symbol('foo'); |
如果 Symbol 的参数是一个对象,就会调用该对象的toString
方法,将其转为字符串,然后生成一个 Symbol 值。
1 | const obj = { |
Symbol 值不能与其他类型的值进行运算(会报错),但可以显式转为字符串和布尔值。
2.作为属性名的Symbol:
1 | var mySymbol = Symbol(); |
Symbol 值作为对象属性名时,不能用点运算符。
1 | var mySymbol = Symbol(); |
Symbol 值作为属性名时,该属性是公开属性而非私有属性。
3.魔术字符串:在代码中多次出现、与代码形成强耦合的某一个具体的字符串或数值。应尽量消除而改用含义清晰的变量代替。
Symbol
可用于消除魔术字符串。
4.属性名的遍历:Symbol 作为属性名,该属性不会出现在for...in
、for...of
循环中,也不会被Object.keys()
、Object.getOwnPropertyNames()
、JSON.stringify()
返回。这个特性可被用于为对象定义一些非私有的、但又希望只用于内部的方法。
Object.getOwnPropertySymbols
方法返回一个数组,成员是当前对象的所有用作属性名的 Symbol 值。
Reflect.ownKeys(obj)
方法可以返回所有类型的键名,包括常规键名和 Symbol 键名。
1 | let obj = { |
5.Symbol.for()
、Symbol.keyFor()
:接受一个字符串作为参数,然后搜索有没有以该参数作为名称的 Symbol 值。如果有,就返回这个 Symbol 值,否则就新建并返回一个以该字符串为名称的 Symbol 值。
1 | var s1 = Symbol.for('foo'); |
Symbol.for()
与Symbol()
这两种写法,都会生成新的Symbol
。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。
1 | Symbol.for("bar") === Symbol.for("bar") |
Symbol.keyFor
方法返回一个已登记的 Symbol 类型值的key
。
1 | var s1 = Symbol.for("foo"); |
6.Singleton 模式:指调用一个类,任何时候返回的都是同一个实例。
Node 中,模块文件可以看作一个类。可以使用 Symbol,通过把实例放到顶层对象global
来实现 Singleton 模式。
内置的 Symbol 值(暂且略过)
7.Symbol.hasInstance
属性:指向一个内部方法。
8.Symbol.isConcatSpreadable
属性:
9.Symbol.species
10.Symbol.match
11.Symbol.replace
12.Symbol.search
13.Symbol.split
14.Symbol.iterator
15.Symbol.toPrimitive
16.Symbol.toStringTag
17.Symbol.unscopables
Set 和 Map 数据结构
Set
1.基本用法:Set 结构不会添加重复的值。
1 | const s = new Set(); |
Set 函数可以接受一个数组(或类似数组的对象)作为参数,用来初始化。因此有一种去除数组重复成员的方法:
1 | [...new Set(array)] |
Set 内部判断两个值是否不同,使用的算法叫做“Same-value equality”,类似精确相等运算符(===
),主要的区别是NaN
等于自身(===
认为NaN
不等于自身)。另外,两个对象总是不相等的。
2.Set 实例的属性和方法:
实例属性:
Set.prototype.constructor
:构造函数,默认就是Set
函数。Set.prototype.size
:返回Set
实例的成员总数。
操作方法:
add(value)
:添加某个值,返回 Set 结构本身。delete(value)
:删除某个值,返回一个表示删除是否成功的布尔值。has(value)
:返回一个布尔值。clear()
:清除所有成员,没有返回值。
遍历方法(Set 结构中,键名和键值是同一个值):
keys()
:返回键名的遍历器values()
:返回键值的遍历器entries()
:返回键值对的遍历器forEach()
:使用回调函数遍历每个成员
Array.from
方法可以将 Set 结构转为数组。
WeakSet
3.含义:与 Set 类似,但成员只能是对象,且都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用。如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。
ES 6 规定,WeakSet 不可遍历。
4.语法:有add
、delete
、has
方法;没有size
属性。
5.用处:是储存 DOM 节点,而不用担心这些节点从文档移除时,会引发内存泄漏。
Map
6.含义和基本用法:为了解决 Object 的只能用字符串当作键名的问题,ES 6 提供了 Map 数据结构。各种类型的值(包括对象)都可以当作键。
有set(key, value)
、get(key)
、has(key)
、delete(key)
、clear()
方法。
作为构造函数,Map 可以接受一个数组作为参数,该数组的成员是一个个表示键值对的数组。
如果对同一个键多次赋值,后面的值将覆盖前面的值;如果读取一个未知的键,则返回undefined
。
7.与其他数据结构的互相转换:
- Map 转为数组:使用扩展运算符(
...
)。 - 数组转为 Map:使用 Map 构造函数。
- Map 转为对象:如果所有 Map 的键都是字符串,可以转为对象。
1 | function strMapToObj(strMap){ |
- 对象转为 Map:
1 | function objToStrMap(obj){ |
- Map 转为 JSON:
1 | // 情况一:Map 的键名都是字符串 |
1 | // 情况二:Map 的键名有非字符串 |
- JSON 转为 Map:逆操作。
WeakMap
8.含义:与 Map 类似,用于生成键值对。但只接受对象作为键名(null
除外),且键名所指向的对象不计入垃圾回收机制。
专用场合:它的键所对应的对象可能会在将来消失。WeakMap
结构有助于防止内存泄漏。
9.语法:没有遍历操作,无法清空。只有四个方法可用:get()
、set()
、has()
、delete()
。
10.用处:DOM 节点作为键名。
1 | // 一个例子 |
注册监听事件的listener
对象,就很适合用 WeakMap 实现。
1 | const listener = new WeakMap(); |
WeakMap 的另一个用处是部署私有属性。
Proxy
暂时跳过。
Reflect
暂时跳过。