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

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值