此文为ECMAScript 6 入门 - 阮一峰 - 读后笔记
2.let/const
let
- let对应块级作用域,且没有声明提升
暂时性死区
- 下面示例代码中存在全局变量tmp,但是块级作用域内let又声明了一个局部变量tmp,导致后者绑定这个块级作用域,所以在let声明变量前,对tmp赋值会报错
var tmp = 123; if (true) { tmp = 'abc'; // ReferenceError let tmp; }
- 不允许重复声明
- const
- 同样只作用于块级作用域
- 暂时性死区
- 不允许重复声明
- ES6为了保持兼容性,var命令和function命令声明的全局变量,依旧是全局对象的属性;而新增的let命令、const命令、class命令声明的全局变量,不属于全局对象的属性。示例如下
var a = 1;
window.a // 1
let b = 1;
window.b // undefined
3.变量的解构赋值
- 用于数组/对象
- 数组
[x, y] = [y, x];
- 对象
{bar, foo } = {foo: "aaa", bar: "bbb" }
- 字符串可以解构为数组
const [a, b, c, d, e] = 'hello'; /*'h', 'e', 'l', 'l', 'o'*/
- 数组
- 解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
//Number.prototype.toString和Boolean.prototype.toString均继承自Object
4.字符串的扩展
- 方法扩展
- includes():返回布尔值,表示是否找到了参数字符串
- startsWith():返回布尔值,表示参数字符串是否在源字符串的头部
- endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部
- 模板字符串
- 模板字符串(template string)是增强版的字符串,用反引号(`)标识。它可以当作普通字符串使用,也可以用来定义多行字符串,或者在字符串中嵌入变量
`Hello \${name}, how are you \${time}?`
5.正则的扩展
6.数值的扩展
- 二进制(0b)和八进制(0o)
- Math中的很多数学方法
7.数组的扩展
- Array.from() 将类数组对象转换为数组
// ES5的写法
var arr1 = [].slice.call(arrayLike); // ['a', 'b', 'c']
// ES6的写法
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
- Array.of 将一组值,转换为数组
- …更多的数组方法
8.函数的扩展
- 参数默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p = new Point();
p // { x: 0, y: 0 }
- rest参数用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中
// arguments变量的写法
function sortNumbers() {
return Array.prototype.slice.call(arguments).sort();
}
// rest参数的写法
const sortNumbers = (...numbers) => numbers.sort();
扩展运算符
该运算符主要用于函数调用,好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列
function push(array, ...items) { array.push(...items); } function add(x, y) { return x + y; } var numbers = [4, 38]; add(...numbers) // 42
任何Iterator接口的对象,都可以用扩展运算符转为真正的数组
var nodeList = document.querySelectorAll('div'); var array = [...nodeList];
- 箭头函数
- 函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象
- 不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替
- 不可以使用yield命令,因此箭头函数不能用作Generator函数
函数绑定(ES7提案)
- 函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面
foo::bar; // 等同于 bar.bind(foo); foo::bar(...arguments); // 等同于 bar.apply(foo, arguments);
- 函数绑定运算符是并排的两个双冒号(::),双冒号左边是一个对象,右边是一个函数。该运算符会自动将左边的对象,作为上下文环境(即this对象),绑定到右边的函数上面
- 尾调用优化
- 尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用帧,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用帧,取代外层函数的调用帧就可以了
9.对象的扩展
- 属性的简洁表示法
var foo = 'bar';
var baz = {foo};
baz // {foo: "bar"}
// 等同于
var baz = {foo: foo};
Object.assign(target, source1, source2);
浅拷贝
10.Symbol(不是很懂,存疑)
- 唯一对象
11.Proxy和Reflect(存疑)
- Proxy
- 拦截器
- Reflect
12.二进制数组(暂时不相关)
13.Set/Map
- Set
- 加入到Set内的值不会进行类型转换,因而
5
和'5'
是两个不同的值
- 加入到Set内的值不会进行类型转换,因而
Map
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制,故而引入了Map
由于对象只接受字符串作为键名,所以DOM节点对象会被自动转为字符串[object HTMLDivElement],所以在存在多个相同标签的情况下后者会覆盖掉前者导致数据丢失
var m = {}; var div = document.createElement('div'); m[div] = 123; var h1 = document.createElement('div'); m[h1] = 234; console.log(m); //{[object HTMLDivElement]: 234}
- set/get/has/delete/clear方法
- size属性,存储了成员数量
- 遍历方法
- keys():返回键名的遍历器。
- values():返回键值的遍历器。
- entries():返回所有成员的遍历器。
- forEach():遍历Map的所有成员。
- for…in…/forEach()遍历
- WeekSet/WeekMap
- 只接受对象作为元素/键名
- 弱引用,元素/键名不计入垃圾回收
- 以此为优势,可以方便的存储一些可能被移除的对象,不担心内存泄漏;例如操作DOM节点
14.Iterator/for…of循环
- Iterator
15.Generator(此处内容太多,详见原文)
- Generator函数是ES6提供的一种异步编程解决方案,语法行为与传统函数完全不同
- Generator函数有多种理解角度。从语法上,首先可以把它理解成,Generator函数是一个状态机,封装了多个内部状态
- 执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态
用于异步编程
回调形式
step1(function (value1) { step2(value1, function(value2) { step3(value2, function(value3) { step4(value3, function(value4) { // Do something with value4 }); }); }); });
Promise形式
Q.fcall(step1) .then(step2) .then(step3) .then(step4) .then(function (value4) { // Do something with value4 }, function (error) { // Handle any error from step1 through step4 }) .done();
Generator
function* longRunningTask() { try { var value1 = yield step1(); var value2 = yield step2(value1); var value3 = yield step3(value2); var value4 = yield step4(value3); // Do something with value4 } catch (e) { // Handle any error from step1 through step4 } }
然后,使用一个函数,按次序自动执行所有步骤
scheduler(longRunningTask()); function scheduler(task) { setTimeout(function() { var taskObj = task.next(task.value); // 如果Generator函数未结束,就继续调用 if (!taskObj.done) { task.value = taskObj.value scheduler(task); } }, 0); //此处延时0ms是为了异步执行,如果没有setTimeout则是同步的 }
16.Promise对象
- 三种状态
Pending
,Resolved
,Rejected
使用方法
var p = new Promise(function (resolve, reject) { console.log('p1'); setTimeout(function () { resolve(1); }, 1000); }); p.then( (val)=>{ console.log('p2', val); return Promise.reject(2); }, (err)=>{} ) .then((val)=>{}, (err)=>{ console.log('p3 - reject', err); return Promise.resolve(3); } ) .then((val)=>{ console.log('p4', val); }, (err)=>{} );
- Promise.resolve/Promise.reject 分别返回对应状态的Promise实例
17.异步操作和Async函数(ES7)(存疑)
- Async异步编程解决方案
18.Class
- constructor方法
- constructor方法是类的默认方法,通过new命令生成对象实例时,自动调用该方法。一个类必须有constructor方法,如果没有显式定义,一个空的constructor方法会被默认添加
- 不存在变量提升
- 继承 extends
- super
- 作为函数调用时(即super(…args)),super代表父类的构造函数。
- 作为对象调用时(即super.prop或super.method()),super代表父类。注意,此时super即可以引用父类实例的属性和方法,也可以引用父类的静态方法
- 静态属性/方法
- 静态属性/方法的是Class本身的属性/方法,即Class.propname/Class.funcname,而不是定义在实例对象(this)上
- super
18.Decorator(ES7)
19.Module
- ES6模块加载的实质
- ES6模块加载的机制,与CommonJS模块完全不同。CommonJS模块输出的是一个值的拷贝,而ES6模块输出的是值的引用
- export通过接口,输出的是同一个值。不同的脚本加载这个接口,得到的都是同样的实例
20.编程风格
- 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法
- 如果对象的属性名是动态的,可以在创造对象的时候,使用属性表达式定义
// bad
const obj = {
id: 5,
name: 'San Francisco',
};
obj[getKey('enabled')] = true;
// good
const obj = {
id: 5,
name: 'San Francisco',
[getKey('enabled')]: true,
};
- 对象的属性和方法,尽量采用简洁表达法,这样易于描述和书写
// good
var ref = 'some value';
// bad
const atom = {
ref: ref,
value: 1,
addValue: function (value) {
return atom.value + value;
},
};
// good
const atom = {
ref,
value: 1,
addValue(value) {
return atom.value + value;
},
};
- 那些需要使用函数表达式的场合,尽量用箭头函数代替。因为这样更简洁,而且绑定了this
- 注意区分Object和Map,只有模拟现实世界的实体对象时,才使用Object。如果只是需要key: value的数据结构,使用Map结构。因为Map有内建的遍历机制,且键值可以不限于字符串