《ES6标准入门——阮一峰》总结

这是笔者读完阮一峰的《ES6标准入门》的总结,可称为《ES6标准入门的入门》,总结的知识点都比较通俗易懂,可为想大概了解ES6但没有时间阅读全书的人做一个参考。

1.let 和 const

暂时性死区 (Temporal Dead Zone)

letconst命令声明的变量无变量提升,且都会被锁定在声明的代码块中,在letconst命令执行前,使用该变量都将报错,这一部分称为“暂时性死区”。

var tmp = 123;

if (true) {
  tmp = 'abc'; // ReferenceError
  let tmp;
}

let tmptmp变量绑定至{}代码块之内,外部的tmp声明无效,tmp = 'abc'就处在死区,所以报错。同理在以前没有letconst命令的时候,typeof是一个安全的运算符,即使变量没有被声明,也会正常返回undefined,但如果typeof处在死区中,处理了在后文被letconst的变量,将会报错。

typeof undeclared_variable; // undefined 未被let和const声明反而没事
if (true) {
  // TDZ开始 (Temporal Dead Zone: 暂时性死区)
  typeof tmp // ReferenceError
  let tmp; // TDZ结束
  console.log(tmp); // undefined
}
顶层对象

varfunction的全局声明会自动绑定到windowglobal对象,这是ES5全局变量的一个缺陷letconst不会。

var a = 1;
// 如果在 Node 的 REPL 环境,可以写成 global.a
// 或者采用通用方法,写成 this.a
window.a // 1

let b = 1;
window.b // undefined
const命令

const声明的变量只是引用无法修改,对象的内部结构可以改变,使用Object.freeze()可以彻底锁定某对象,需递归锁定多层级对象。

var constantize = (obj) => {
  Object.freeze(obj);
  Object.keys(obj).forEach( (key, i) => {
    if ( typeof obj[key] === 'object' ) {
      constantize( obj[key] );
    }
  });
};

2.变量的解构赋值

解构时分为匹配模式和被赋值的变量,若相同可简写,注意区分
// 被解构的对象的key,和后边被赋值的变量同名,可以简写。
let { matchValue } = { matchValue: 123 };
console.log(matchValue); //123

等价于

let { matchValue: matchValue } = { matchValue: 123 }
console.log(matchValue); //123

通过代码的高亮可以看出相互之间的对应关系。所以

let { matchValue: value } = { matchValue: 123 }
console.log(matchValue); //报错,未定义,这个只是匹配模式,不会被赋值
console.log(value); //123
函数参数

首先解构赋值允许指定默认值,这为函数参数设置默认值提供基础。

// 数组解构赋值的默认值
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x = 'a', y = 'b'] = ['aa', undefined]; // x='aa', y='b'

// 对象解构赋值的默认值
let {x, y = 5} = {x: 1};
x // 1
y // 5

这里只讨论一下参数为Object类型时,该如何设置默认值,比如一些options的设置,通过设置默认值,可有效避免var foo = options.foo || 'default foo';。有三种形式,注意这三种的区别:

const ajax1 = function (url, { type = 'GET', dataType = 'json'} = {}) {
  // TODO
}
const ajax2 = function (url, {} = { type = 'GET', dataType = 'json' }) {
  // TODO
}
const ajax3 = function (url, { type = 'GET', dataType = 'json'} ) {
  // TODO
}

ajax1的默认参数表示,如果没有传入options,则用一个没有键值对的对象{}作为默认值,但也正是因此,传入的options没有options.typeoptions.dataType这两个属性,则赋予默认值type = 'GET', dataType = 'json',这是针对键值对某一个key设默认值。

ajax2的默认参数表示,如果没有传入options对象,则用一个{ type = 'GET', dataType = 'json' }这样的options对象作为默认值,这是针对这一整个options设默认值。弊端就是如果开发者在使用时这样写

ajax2(url, {type = 'POST'})

那么dataType参数将要丢失,因为{type = 'POST'}代替了默认参数{ type = 'GET', dataType = 'json' },所以一般通过形如ajax1的方式定义默认参数。

ajax3的默认参数有一个问题,就是当没有传入options的时候,相当于从undefined中取值typedataType来解构,所以会报错,所以ajax1会通过= {}的方式,把不传入options的情况过滤掉。

3.各种数据结构的扩展

字符串

``表示模板字符串,就不多介绍了,功能强大好用。
--------------------
codePointAt可作为charCodeAt的替代品,必要时使用for...of遍历字符串,他们都是为了处理32 位的 UTF-16 字符。

var s = "𠮷a";
s.length // 3  无法正确识别字符串长度,会把‘𠮷’识别为2个字符
s.charAt(0) // '' charAt无法处理这个字
s.charAt(1) // ''
s.charCodeAt(0) // 55362 charCodeAt只能两字节两字节的分开返回
s.charCodeAt(1) // 57271

下面看一下ES6的codePointAtfor...of

let s = '𠮷a';
s.codePointAt(0) // 134071 可以识别一整个字
s.codePointAt(1) // 57271 第三,四字节会被返回
s.codePointAt(2) // 97 字符串长度仍有问题
// 使用for...of循环正确处理
for (let ch of s) {
  console.log(ch.codePointAt(0).toString(16));
}
// 20bb7 134071是10进制,20bb7为16进制表示
// 61 字符串长度也没问题,循环执行了2次

--------------------
还引入了includesstartWithendWithpadStartpadEnd等方法,具体使用方法可以通过API手册了解一下。

Number

parseInt等全局方法挂在到Number上,如Number.parseIntNumber.parseFloat等,增加了一些高阶计算函数。

函数

箭头函数,this的指向在函数生成时固定,说白了就是this指向与外部一致。
--------------------
函数参数设置默认值,前文已经说明。补充一点,设置某参数必须可以:

function throwNeedThisParamException(param) {
  throw new Error(`Missing parameter: ${param}`);
}
function demo (x = throwNeedThisParamException('x')) {

}

参数的默认值是在取不到值的情况下才会执行,所以正常情况不会抛出这个错误。
--------------------
参数的rest形式,例子如下:

function demo (...values) {
  console.log(values)
  console.log('-----------------------')
  console.log(arguments)
}
demo(1,2,3,4)   
//[1,2,3,4]  
-----------------------
//[1, 2, 3, 4, callee: (...), Symbol(Symbol.iterator): ƒ]

内置的arguments为类数组结构,可以看见有一个Symbol类型的字段Symbol.iterator,这是它的迭代器,使其可以向数组一样遍历。但如果展开看其__proto__,值为Object。而使用rest形式的参数,可以直接将参数转为数组。注意rest形式的参数只能用作最后一个参数。
--------------------
函数的length属性返回函数参数的个数,name属性返回声明的函数名称,ES6的变量式声明返回变量名。

function f1 () {}
f1.name // f1
const f2 = function (x,y) {}
f2.name // f2
f2.length // 2

--------------------
双冒号运算符,代替bindcallapply绑定this对象指向。foo::bar(arguments)相当于bar.apply(foo, arguments)
--------------------
尾调用,说白了就是最后返回值为执行另一个函数return anotherFunction(),而return anotherFunction() + 1不属于尾调用,因为在执行完anotherFunction后还需要+1。尾调用的优势就是在return后,可以释放当前函数执行所需要的一切资源空间。对比如下两个例子,是做斐波那契数列求值的:

function Fibonacci (n) {
  if ( n <= 1 ) {return 1};

  return Fibonacci(n - 1) + Fibonacci(n - 2);
}
Fibonacci(10) // 89
Fibonacci(100) // 堆栈溢出
Fibonacci(500) // 堆栈溢出

这是最简单的写法,清晰明了,第n项就是前两项的和。但是,为了计算加号两边的值,必须要保存函数执行的全部资源,递归后造成堆栈溢出。这不属于尾调用。

function Fibonacci2 (n , ac1 = 1 , ac2 = 1) {
  if( n <= 1 ) {return ac2};
  return Fibonacci2 (n - 1, ac2, ac1 + ac2);
}
Fibonacci2(100) // 573147844013817200000
Fibonacci2(1000) // 7.0330367711422765e+208
Fibonacci2(10000) // Infinity

这是优化过的递归调用,return之后无需保存函数所需要的资源,所以不会堆栈溢出,只是在逻辑上不好理解。这种写法Fibonacci2 (n - 1, ac2, ac1 + ac2)可以看成一个从前到后推导过程,n相当于一个计数器,每次值的增长是通过两个数求和ac1 + ac2作为第二个数,ac2作为第一个数。

数组

扩展运算符...,与上文的rest参数是相反的用法,rest参数是把一个个的参数总和到数组rest参数中,而扩展运算符是把数组中的元素一个个提取出来。
扩展运算符可以用来方便的复制一个数组。

let arr = [1,2,3]
console.log(...arr)  // 等价于console.log(1,2,3)
let arr2 = [...arr] // 等价于let arr2 = [1,2,3],新建一个数组
arr.push(4)
console.log(arr2) // [1,2,3]

--------------------
数组可以通过Array.fromArray.of生成,可以通过keys()values()entries()遍历。
Array.from可以从具有iterator的数据结构生成数组,比如arguments对象,document.querySelectorAll()获得的DOM对象,这些都是类数组,或者MapSet等新增的数据结构。
Array.of可以代替new Array(),因为new Array()的参数与行为不统一,当传入一个参数且为数字时,表示数组长度,Array.of不会有这个问题,会通过参数创建数组。
Array还新增了一些工具方法,如findfindIndexincludes等可以参考其他API手册。

对象

Object.assign是合并对象,把多个对象合并到第一个对象上。
Object.create是以某原型,生成一个新对象。可选第二个参数,为属性描述符,使用方式参见下方代码。
Object.getPrototypeOfObject.setPrototypeOf是获取和设置对象的原型属性__proto__,不应显式使用__proto__这个属性。
Object.getOwnPropertyDescriptors是获取对象的属性信息,包括valuewritableenumerableconfigurable

const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
-------------------
Object.setPrototypeOf(target, { myProto: 'PROTO'})
Object.getPrototypeOf(target) //{ myProto: 'PROTO', __proto__: Object}
let newObj = Object.create(Object.getPrototypeOf(target))
newObj // 无显式属性{ __proto__:{ myProto: 'PROTO', __proto__: Object} } 
-------------------
const descriptors = Object.getOwnPropertyDescriptors(target)
console.log(descriptors)
// {
//   a: {value: 1, writable: true, enumerable: true, configurable: true},
//   b: {value: 2, writable: true, enumerable: true, configurable: true},
//   c: {value: 3, writable: true, enumerable: true, configurable: true}
// }
newObj = Object.create(Object.getPrototypeOf(target), descriptors)
newObj // { a:1, b:2, c:3, __proto__:{ myProto: 'PROTO', __proto__: Object} } 

--------------------
ES6允许字面量定义对象时,用表达式作为属性名,把表达式放在方括号内。

const propKey = 'foo';

const obj = {
  [propKey]: true,
  ['a' + 'bc']: 123
};
obj // { foo: true, abc: 123 }

--------------------
Object.is优化了===运算符,处理了===的两个问题。

NaN === NaN // false
Object.is(NaN, NaN) // true
--------------
+0 === -0 // true 
Object.is(+0, -0) // false

4.Symbol

Symbol为不会重复的值,第七种基本数据类型,类似字符串,可以作为对象的key,但不会被for...offor...inObject.getOwnPropertyNames()Object.keys()返回,如需要遍历,需使用Object.getOwnPropertySymbols(),或者Reflect.ownKeys()返回全部key

let foo = Symbol('foo');
const obj = { [foo]: 'foobar' }
for (let i in obj) {
  console.log(i); // 无输出
}
Object.getOwnPropertyNames(obj)
// []
Object.getOwnPropertySymbols(obj)
// [Symbol(foo)]
Reflect.ownKeys(obj)
// [Symbol(foo)]

Symbol.for() 和 Symbol.keyFor()

Symbol可以去确保生成的值不同,但有时需要保存下来以便再次使用,类似于单例,如果存在就不会重新创建。这个时候就需要使用Symbol.for()

let s1 = Symbol('foo');
let s2 = Symbol.for('foo');
let s3 = Symbol.for('foo');
s1 === s2 // false
s2 === s3 // true

从上例可以看出,Symbol.for类似于将这个Symbol登记,所以s1这个未登记的Symbol不会等于其他Symbol
Symbol.keyFor会返回已登记的Symbolkey,一定是登记过的才会返回。接上例:

Symbol.keyFor(s1) // undefiend
Symbol.keyFor(s2) // "foo"

5.Proxy和Reflect

Proxy代理对象的各种内置方法,get set construct等,类似于拦截器。
Reflect则作为Object的替代者,Object上的一些静态方法被移植到了Reflect上。
Reflect对象一共有 13 个静态方法。

  • Reflect.apply(target, thisArg, args)
  • Reflect.construct(target, args)
  • Reflect.get(target, name, receiver)
  • Reflect.set(target, name, value, receiver)
  • Reflect.defineProperty(target, name, desc)
  • Reflect.deleteProperty(target, name)
  • Reflect.has(target, name)
  • Reflect.ownKeys(target)
  • Reflect.isExtensible(target)
  • Reflect.preventExtensions(target)
  • Reflect.getOwnPropertyDescriptor(target, name)
  • Reflect.getPrototypeOf(target)
  • Reflect.setPrototypeOf(target, prototype)
    通过ProxyReflect可以实现观察者模式,说白了就是:监听set方法,执行相应操作。
const person = { name: 'Li', age: 18}
const personObserved = observe(person)

function observe(obj) {
  return new Proxy(obj, {
    set: function (target, key, value, receiver) {
      console.log(`setting ${key} to ${value}!`);
      return Reflect.set(target, key, value, receiver);
    }
  })
}

personObserved.name = 'zhang'
// setting name to zhang!

6.Promise

Promise用来处理异步操作,是构造函数,参数为thencatch后需要执行的方法。下面是使用Promise封装的ajax

const getJSON = function(url) {
  const promise = new Promise((resolve, reject) => {
    const handler = function() {
      if (this.readyState !== 4) {
        return;
      }
      if (this.status === 200) {
        resolve(this.response);
      } else {
        reject(new Error(this.statusText));
      }
    };
    const client = new XMLHttpRequest();
    client.open("GET", url);
    client.onreadystatechange = handler;
    client.responseType = "json";
    client.setRequestHeader("Accept", "application/json");
    client.send();
  });
  return promise;
};

getJSON("/posts.json").then(function(json) {
  console.log('Contents: ' + json);
}, function(error) {
  console.error('出错了', error);
});

7. Iterator 和 for...of循环

Iterator被挂载在对象的Symbol.iterator属性下,Symbol.iterator不是一个Iterator,而是会返回Iterator的函数。

const arr = [1,2,3,4,5]
let iterator = arr[Symbol.iterator]();
iterator // Array Iterator {}
iterator.next() // {value: 1, done: false}
......
iterator.next() // {value: 5, done: false}
iterator.next() // {value: undefined, done: true}

8. Generator 和 yield

Generator会生成一个Iterator,每次iterator.next()返回yield的产出值,且中断程序执行。yield*表示产出的值是另外一个generator的结果。代码如下:

function* demo(){
  console.log(`${yield 1}`);
  console.log(`${yield 2}`);
  yield* demo2(); //返回另一个generator的结果
}
function* demo2(){
  yield 3;
}
let ite = demo();
ite.next() // 返回值:{value: 1, done: false}
ite.next() // console:undefined, 返回值:{value: 2, done: false}
ite.next(123456789) // console: 123456789, 返回值:{value: 3, done: false} 

解释一下运行结果:第一次ite.next()时,程序执行到yield 1被终止,故没有打印日志,再次执行ite.next()时,代码继续,开始执行console.log(`${yield 1}`);,但输出不是1而是undefiend,因为ite.next()的参数值会被当做上次yield语句的执行结果,所以下面的ite.next(123456789)会输出数字123456789

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,457评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,837评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,696评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,183评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,057评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,105评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,520评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,211评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,482评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,574评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,353评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,897评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,489评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,683评论 2 335

推荐阅读更多精彩内容

  • 听歌的时候,我都会去关注一下歌词,这里收集了一些自己特别喜欢并且觉得写入心里去的。缺少了歌词,或许再好的曲调也只会...
    挑兮达兮阅读 607评论 0 2
  • 年少时我们有足够多的理由去认认真真地喜欢另一个人,而长大后我们有同样多的理由去认认真真地辜负另一个人。所以最初...
    Silvia15阅读 734评论 3 2