ES6

JavaScript ES6基础学习总结

# let/const命令

# let

let的作用类似var,是用来声明变量的,但是其所声明的变量只在let命令所在的代码块中有效。

let声明的变量不存在变量提升,所有变量的使用必须在声明之后。在let声明变量的代码块中存在暂时性死区(TDZ),在同一作用域内不能重复声明,let命令会形成块级作用域。

# const

用let类似,但是其声明的变量不可修改,并且声明之后必须马上初始化(即赋值)。同样也不存在变量提升,存在暂时性死区,以及只在其代码块中有效。其本质是地址不能改变。

# 箭头函数

  • TODO

# Iterator遍历器

iterator遍历器(迭代器)对ES6来说是很重要的一个概念,它是实现解构赋值、rest参数、扩展运算符、Generator生成器、for of循环的基础。ES6中新增的数据结构Set、Map也有使用到iterator迭代器。

对于可以迭代的数据结构,ES6为其在内部部署了一个Symbol.iterator的属性,它是一个函数,执行该函数后会生成一个iterator对象,我们把这个对象也叫做iterator接口,我们可以通过这个接口来访问数据内部。

遍历器(Iterator)就是这样一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署Iterator接口,就可以完成遍历操作(即依次处理该数据结构的所有成员)。

Iterator的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是ES6创造了一种新的遍历命令for...of循环,Iterator接口主要供for...of`消费。

默认部署Iterator接口的数据结构有:

1.Array

2.Map

3.Set

4.String

5.TypedArray

6.函数的 arguments 对象

7.NodeList 对象

一个例子(数组的iterator接口)

let arr = ['a', 'b', 'c'];
let iter = arr[Symbol.iterator]();
iter.next() // { value: 'a', done: false }
iter.next() // { value: 'b', done: false }
iter.next() // { value: 'c', done: false }
iter.next() // { value: undefined, done: true }

# 调用Iterator的场景

以下场景都是借着内置的Symbol.iterator方法实现的。

# 解构赋值

对数组和Set结构进行解构赋值时会默认调用。

let set = new Set().add('x').add('y').add('z');
let [a,b] = set;  // a = 'x' ; b = 'y';
let [first,...rest] = set;  // first = 'x' ; rest = [ 'y', 'z' ]

# 扩展运算

扩展运算符...也会默认的调用Symbol.iterator方法。

var str = 'hello';
[...str] //  ['h','e','l','l','o']
// 也就是说只要内部部署了Symbol.iterator()的结构,都可以通过调用它拉实现数组化。

let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']

# yield*

yield后面跟的是一个具体Iterator接口的数据结构。

let generator = function* () {
  yield 1;
  yield* [2,3,4];
  yield 5;
};

var iterator = generator();

iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }

# 其他

  • for...of
  • Array.from()
  • Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
  • Promise.all()
  • Promise.race()

# 变量的解构赋值

# 数组的解构赋值

数组解构的原理其实是消耗数组的迭代器,把生成对象的value属性的值赋值给对应的变量

let [a, b, c] = [1, 2, 3];
console.log(b) // 2

等号两边进行模式匹配,只要模式相同,右边的值就会对应的赋给左边,如果解构不成功,就赋'undefined',如果等号右边不是具有interator接口的数据,则会报错。

解构赋值允许存在默认值

let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a']; // x='a', y='b'
let [x, y = 'b'] = ['a', undefined]; // x='a', y='b'

当数组成员严格等于(===)undefined时,默认值才会生效。默认值可以引用解构赋值的其他变量,但该变量必须已经声明。

let [x = 1, y = x] = [];     // x=1; y=1
let [x = 1, y = x] = [2];    // x=2; y=2
let [x = 1, y = x] = [1, 2]; // x=1; y=2
let [x = y, y = 1] = [];     // ReferenceError: y is not defined

# 对象的解构赋值

对象的解构赋值是根据对象的键名进行赋值的。如键名与值相同,则可省略属性名。同样也可以进行嵌套解构赋值,也存在默认值。

# 字符串的解构赋值

const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

类似数组也有length属性

# 函数参数的解构赋值

function add([x, y]){
  return x + y;
}
add([1, 2]); // 3

函数参数的解构也可以使用默认值。

function move({x = 0, y = 0} = {}) {
  return [x, y];
}
move({x: 3, y: 8}); // [3, 8]
move({x: 3}); // [3, 0]
move({}); // [0, 0]
move(); // [0, 0]

上面代码中,函数move的参数是一个对象,通过对这个对象进行解构,得到变量x和y的值。如果解构失败,x和y等于默认值。

# rest参数与扩展运算符

剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(...),后面跟着一个含有iterator接口的数据结构

# 剩余运算符

ES6中剩余运算符代替的之前的arguments参数(箭头函数没有arguments参数),访问函数的arguments是一个非常昂贵的操作,建议在ES6环境下均使用剩余运算符来代替。

// 我们来对比一下arguments和rest参数
function F (a,b,c){
  console.log(arguments[0],arguments[1],arguments[2])
}; 
F(1,2,3) // 1,2,3

// rest参数
function F(...rest){
  console.log(rest)
}

F(1,2,3) //  [1,2,3]

剩余运算符除了可以在函数的参数中使用外,还能结合解构赋值一同在数组中使用。但是剩余运算符(...)必须在数组的最后一位,因为剩余运算符的原理其实是利用了数组的迭代器,它会消耗3个点后面的数组的所有迭代器,读取所有迭代器生成对象的value属性,而我们的解构也是消耗迭代器,如果剩余运算符在前会将数组的所以迭代器消耗,导致无法进行解构赋值。

let [first,...other] = [1,2,3,4]
first // 1
other // [2,3,4]

# 扩展运算符

扩展运算符(spread)是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列。

let arr1 = [1,2,3];
let arr2 = [...arr1,4,5]  // [1,2,3,4,5]

// 也能使用多个 类似数组的conect方法
let arr1 = [1,2,3]
let arr2 = [4,5,6]
let arr3 = [..arr1,...arr2]  // [1,2,3,4,5,6]

在ES2018中,对象也是可以使用扩展运算符,之前我们说过数组的扩展运算符原理是消耗所有迭代器,但对象中并没有迭代器,我个人认为可能是实现原理不同,但是仍可以理解为将键值对从对象中拆开,它可以放到另外一个普通对象中,类似Object.assign( )。

# 对象的简写

# 对象属性的简写

在ES6中,当对象(Object)中的属性与值相同时,则可省略属性名。但是这个值必须是一个变量。如:

let value = 0;
let object = {
  value: value;
}

let objectES6 = {
  value; //这里省略的是属性名value
}

在实际中对象属性名的简写一般与解构赋值一起使用。

# 方法的简写

ES6中允许当对象中存在方法(函数)时,我们可以简写为:

let objectES5 = {
  func: function(){
  }
}

let objectES6 = {
  func() {
  },
  func2:() => {
  }
}

# for...of...

for...of循环是ES6新增的遍历方法,允许我们遍历一个存在iterator接口的数据,并返回所有项的值。

简单对比for...in循环:

for ... of遍历获取的是对象的键值( for...of并不能遍历对象 ),for ... in 获取的是对象的键名 如:

let arr = [1,2,3,4,5]
for(let i in arr ) {
    console.log(i)            //0,1,2,3,4  输出的是下标,即键名
 }
        
let arr = [1,2,3,4,5]
 for(let i of arr ) {
  console.log(i)              //1,2,3,4,5  输出值 相当于键值
 }

for ... in会遍历对象的整个原型链,性能非常差不推荐使用,而for ... of只遍历当前对象不会遍历原型链 对于数组的遍历,for ... in会返回数组中所有可枚举的属性(包括原型链上可枚举的属性),for ... of只返回数组的下标对应的属性值

1 TODO: JavaScript的数据遍历

# 数据遍历

详见

# 对象新方法

  • TODO

# ES6 Module

1 TODO: Common.js规范和ES6规范

this

# what

什么是this?this是一个指针,指向调用函数的对象。为了搞清this指向问题,我们先要知道this绑定的规则

  • 默认绑定

  • 隐式绑定

  • 显式绑定

  • new绑定

JavaScript的运行机制以及Event Loop

了解JS运行机制,是了解学习JS的异步事件的基础,之后会着重学习JS的异步事件。