# Javascript ES6(二)

# 解构:更方便的数据访问

  • 当使用解构来配合 varletconst 来声明变量时,必须提供初始化器(即等号右边的值)。
  • 当解构赋值表达式的右侧( = 后面的表达式)的计算结果为 nullundefined 时,会抛出错误。因为任何读取 nullundefined 的企图都会导致“运行时”错误(runtime error )。

# 对象结构

# 解构赋值

对象解构语法在赋值语句的左侧使用了对象字面量.

let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"

非初始化结构

// 使用解构来分配不同的值
({ type, name } = node);

注意你必须用圆括号包裹解构赋值语句,这是因为暴露的花括号会被解析为代码块语句,而块语句不允许在赋值操作符(即等号)左侧出现。

传递值给函数的同时赋值

let node = {
        type: "Identifier",
        name: "foo"
    },
    type = "Literal",
    name = 5;
function outputInfo(value) {
    console.log(value === node); // true
}
outputInfo({ type, name } = node);
console.log(type); // "Identifier"
console.log(name); // "foo"

node 传递进了 outputInfo,且 typename 完成了赋值。

# 默认值

let { type, name, value = true } = node;

node.value 缺失、或者为 undefined 时,会使用默认值。

# 赋值给不同的本地变量名

// 相当于:let localType = node.type, localName = node.name;
let { type: localType, name: localName } = node;

# 赋值给不同的本地变量名 + 默认值

// 相当于:let localType = node.type, localName = node.name || "bar";
let { type: localType, name: localName = "bar" } = node;

# 嵌套的对象解构

// 提取 node.loc.start赋值到localStart
let { loc: { start: localStart }} = node;

# 数组解构

数组解构时,解构作用在数组内部的位置上,而不是作用在对象的具名属性上。

// 相当于 let firstColor = colors[0], secondColor = colors[1];
let [ firstColor, secondColor ] = colors;

你也可以在解构模式中忽略一些项,并且只给感兴趣的项提供变量名。

// 相当于 let thirdColor = colors[2];
let [ , , thirdColor ] = colors;

数组解构赋值有一个非常独特的用例,能轻易地互换两个变量的值。

[a, b] = [b, a];

# 剩余项

// let firstColor = colors[0], restColors = colors.slice(1);
let [ firstColor, ...restColors ] = colors;

# 克隆数组

ES5

// 在 ES5 中克隆数组
var colors = [ "red", "green", "blue" ];
var clonedColors = colors.concat();
console.log(clonedColors); //"[red,green,blue]"

ES6

// 在 ES6 中克隆数组
var colors = [ "red", "green", "blue" ];
var [...clonedColors] = colors;
console.log(clonedColors); //"[red,green,blue]"

# 参数结构

function setCookie(name, value, { secure, path, domain, expires }) {
    // 设置 cookie 的代码
}
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

# 解构的参数是必需的

如果设置了解构参数,且解构参数没有设置默认值,那么解构参数必传。

// 设置了解构参数
function setCookie(name, value, { secure, path, domain, expires } = {}) {
    // 设置 cookie 的代码
}
setCookie("type", "js", {
    secure: true,
    expires: 60000
});

设置了默认值可以不传。

# 参数解构的默认值

你可以为参数解构提供可解构的默认值,就像在解构赋值时所做的那样,只需在其中每个参数后面添加等号并指定默认值即可。

function setCookie(name, value,
    {
    secure = false,
    path = "/",
    domain = "example.com",
    expires = new Date(Date.now() + 360000000)
    } = {}
) {
    // ...
}

# 符号与符号属性

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。ES6 引入了一种新的基本类型:符号( Symbol )。表示独一无二的值。这样就从根本上防止属性名的冲突。

# 创建符号值

你可以使用全局 Symbol 函数来创建一个符号值, Symbol 函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述。

    let s = Symbol('foo');
    console.log(s); // Symbol(foo)

如果 Symbol 的参数是一个对象,就会调用该对象的toString方法,将其转为字符串,然后才生成一个 Symbol 值。

const obj = {
  toString() {
    return 'abc';
  }
};
const sym = Symbol(obj);
sym // Symbol(abc)

# 使用符号值

作为属性名的Symbol, 你可以在任意能使用“需计算属性名”的场合使用符号。

let mySymbol = Symbol();

// 第一种写法
let a = {};
a[mySymbol] = 'Hello!';

// 第二种写法
let a = {
  [mySymbol]: 'Hello!'
};

// 第三种写法
let a = {};
Object.defineProperty(a, mySymbol, { value: 'Hello!' });

// 以上写法都得到同样结果
a[mySymbol] // "Hello!"

# 符号值的转换

Symbol 值不能与其他类型的值进行运算,会报错。

# 共享符号值

你或许想在不同的代码段中使用相同的符号值,例如:假设在应用中需要在两个不同的对象类型中使用同一个符号属性,用来表示一个唯一标识符。跨越文件或代码来追踪符号值是很困难并且易错的,为此, ES6 提供了“全局符号注册表”供你在任意时间点进行访问。

若你想创建共享符号值,应使用 Symbol.for() 方法而不是 Symbol() 方法。

let uid = Symbol.for("uid");
let object = {};
object[uid] = "12345";
console.log(object[uid]); // "12345"
console.log(uid); // "Symbol(uid)"

Symbol.for() 方法首先会搜索全局符号注册表,看是否存在一个键值为 "uid" 的符号值。 若是,该方法会返回这个已存在的符号值;否则,会创建一个新的符号值,并使用该键值将 其记录到全局符号注册表中,然后返回这个新的符号值。

# 检索符号属性

Object.keys()Object.getOwnPropertyNames() 方法可以检索对象的所有属性名称,前者返回所有的可枚举属性名称,而后者则返回所有属性名称而无视其是否可枚举。然而两者都不能返回符号类型的属性,以保持它们在 ES5 中的功能不发生变化。而 ES6 新增了Object.getOwnPropertySymbols() 方法,以便让你可以检索对象的符号类型属性。

Object.getOwnPropertySymbols() 方法会返回一个数组,包含了对象自有属性名中的符号值。

let uid = Symbol.for("uid");
let object = {
[uid]: "12345"
};
let symbols = Object.getOwnPropertySymbols(object);
console.log(symbols.length); // 1
console.log(symbols[0]); // "Symbol(uid)"
console.log(object[symbols[0]]); // "12345"

# 使用知名符号暴露内部方法

除了定义自己使用的 Symbol 值以外,ES6 还提供了 11 个内置的 Symbol 值,指向语言内部使用的方法。

  • Symbol.hasInstance :供 instanceof 运算符使用的一个方法,用于判断对象继承关系。

    obj instanceof Array
    

    等价于:

    Array[Symbol.hasInstance](obj)
    

    当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。

    // 当其他对象使用instanceof运算符,判断是否为该对象的实例时,会调用这个方法。
     let test = {
        [Symbol.hasInstance]() {
           console.log('test-----hasInstance')
        }
      }
      1 instanceof test // test-----hasInstance
    
  • Symbol.isConcatSpreadable :一个布尔类型值,在集合对象作为参数传递给Array.prototype.concat() 方法时,指示是否要将该集合的元素扁平化。

    let colors1 = [ "red", "green" ],
    colors2 = colors1.concat([ "blue", "black" ]);
    console.log(colors2.length); // 4
    console.log(colors2); // ["red","green","blue","black"]
    

    数组默认是要展开的,类数组则相反。

    let obj = {length: 2, 0: 'c', 1: 'd'};
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', obj, 'e']
    obj[Symbol.isConcatSpreadable] = true;
    ['a', 'b'].concat(obj, 'e') // ['a', 'b', 'c', 'd', 'e']
    
  • Symbol.species :用于产生派生对象的构造器。 它主要的用途是,有些类库是在基类的基础上修改的,那么子类使用继承的方法时,作者可能希望返回基类的实例,而不是子类的实例。

    class MyArray extends Array {
        // Symbol.species改变了实例的指向 不设置就是MyArray
        static get [Symbol.species]() { return Array; }
    }
      
    const a = new MyArray();
    const b = a.map(x => x);
      
    b instanceof MyArray // false
    b instanceof Array // true
    
  • Symbol.match :供 String.prototype.match() 函数使用的一个方法,用于比较字符串。

  • Symbol.replace :供 String.prototype.replace() 函数使用的一个方法,用于替换子字符串。

  • Symbol.search :供 String.prototype.search() 函数使用的一个方法,用于定位子字符串。

  • Symbol.split :供 String.prototype.split() 函数使用的一个方法,用于分割字符串。

    let MyStringSymbol = {
        [Symbol.match](string) {
          console.log('Symbol.match', string);
        },
        [Symbol.replace](string) {
          console.log('Symbol.replace', string);
        },
        [Symbol.search](string) {
          console.log('Symbol.search', string);
        },
        [Symbol.split](string) {
          console.log('Symbol.split', string);
        },
    }
    'abc'.match(MyStringSymbol); // Symbol.match abc
    'abc'.replace(MyStringSymbol, 'a') // Symbol.replace abc
    'abc'.search(MyStringSymbol) // Symbol.search abc
    'abc'.split(MyStringSymbol) // Symbol.split abc
    
  • Symbol.iterator :返回迭代器的一个方法。 对象进行for...of循环时,会调用Symbol.iterator方法,返回该对象的默认遍历器

      class Collection {
        *[Symbol.iterator]() {
          let i = 0;
          while(this[i] !== undefined) {
            yield this[i];
            ++i;
          }
        }
      }
    
      let myCollection = new Collection();
      myCollection[0] = 1;
      myCollection[1] = 2;
      
      for(let value of myCollection) {
        console.log(value);
      }
      // 1
      // 2
    
  • Symbol.toPrimitive :返回对象所对应的基本类型值的一个方法。 该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。

      function Temperature(degrees) {
          this.degrees = degrees;
      }
      // 只要该对象进行试图基本类型转换就会触发该方法
      Temperature.prototype[Symbol.toPrimitive] = function(hint) {
          switch (hint) {
              case "string":
                  return this.degrees + "\u00b0"; // 温度符号
              case "number":
                  return this.degrees;
              case "default":
                  return this.degrees + " degrees";
          }
      };
      let freezing = new Temperature(32);
      console.log(freezing + "!"); // "32 degrees!"
      console.log(freezing / 2); // 16
      console.log(String(freezing)); // "32°"
    
  • Symbol.toStringTag :供 String.prototype.toString() 函数使用的一个方法,用于创建对象的描述信息。 这个属性可以用来定制[object Object][object Array]object后面的那个字符串。

    ({[Symbol.toStringTag]: 'foo'}).toString() // "[object foo]"
    
  • Symbol.unscopables :一个对象,该对象的属性指示了哪些属性名不允许被包含在 with 语句中。

    // 没有 unscopables 时
    class MyClass {
      foo() { return 1; }
    }
    
    var foo = function () { return 2; };
    
    with (MyClass.prototype) {
      foo(); // 1
    }
    
    // 有 unscopables 时
    class MyClass {
      foo() { return 1; }
      get [Symbol.unscopables]() {
        return { foo: true }; // 排除了MyClass.prototype的foo
      }
    }
    
    var foo = function () { return 2; };
    
    with (MyClass.prototype) {
      foo(); // 2 这里直接指向全局了
    }
    

    上面代码通过指定Symbol.unscopables属性,使得with语法块不会在当前作用域寻找foo属性,即foo将指向外层作用域的变量。

# Set与Map

Set的作用主要用于检查某个值在列表中是否存在,Map 多数被用来提取数据,而 不是仅检查键的存在性。

# Set

set是一个无重复值的列表

let set = new Set(); // ()里面无值()可省略 let set = new Set;
// Set 不会使用强制类型转换来判断值是否重复 所以1和‘1’是不同的两个值
set.add(1);
set.add('1');
set.size; // 2
// 使用数组初始化Set
let set = new Set([1, 2, 3, 4, 5, 5, 5, 5]);
console.log(set.size); // 5
// 判断有没有值
set.has(2) // true
// 删除值
set.delete(2); // 删除2
set.clear(); // 清空列表
// 循环
let set = new Set([1,2]);
set.forEach(value => console.log(value)); // 1, 2
// 将 Set 转换为数组
let set = new Set([1,2]) // 将数组转换为Set
let arr = [...set] // 将 Set 转换为数组

# WeakSet

WeakSet 的成员只能是对象,而不能是其他类型的值。 WeakSet 中的对象都是弱引用,即垃圾回收机制不考虑 WeakSet 对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于 WeakSet 之中。 WeakSet主要实现了如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存。

let first = {};
let wk = new WeakSet([first]);
wk.has(first); // true
first = null; // 移除对于键的引用 同时wk也移除了
wk.has(first); // false

wk没有size,不可循环遍历。

# Map

ES6Map 类型是键值对的有序列表,而键和值都可以是任意类型。

let map = new Map(); // ()里面无值()可省略 let map = new Map;
map.set(1, 1);
map.set('1', '1'); // 由于是通过Object.is()判断 1和‘1’是不同的键
console.log(map.size); // 2
map.has('1') // true
map.delete(1); // 删除1
map.clear(); // 清空map

// 初始化传入 以二维数组的形式
let map = new Map([['1', '1'], [1, 1]]);
// 循环
map.forEach(function(value, key, ownerMap) {
    console.log(key + " " + value);
    console.log(ownerMap === map);
});
// '1' '1'
// true
// 1 1
// true

# WeakMap

WeakMap只能用来存储键为对象的值

let first = {};
let wm = new WeakMap([[first, 1]])
wm.has(first); // true
first = null; // 移除对于键的引用 同时wm也移除了
wm.has(first); // false

# 迭代器与生成器

迭代器Iterator接口主要是用于for...of消费。还有扩展运算符... 原生具备 Iterator 接口的数据结构如下。

  • Array
  • Map
  • Set
  • String
  • TypedArray
  • 函数的 arguments 对象
  • NodeList 对象

这说明上面的数据结构天生就支持for...of。如果想要原生未具备 Iterator 接口的数据结构支持for...of遍历,就得自己实现迭代器了。

生成器是一个特殊的函数,可以在被调用时自动创建一个迭代器。生成器的定义用一个星号( * )来表示,使用 yield 关键字能指明在每次成功的 next() 方法调用时应当返回什么值。

下面是一个例子:

// let createIterator = function *() {} 生成器函数表达式
// 函数声明
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

// 生成器能像正规函数那样被调用,但会返回一个迭代器
let iterator = createIterator();
console.log(iterator.next().value); // 1
console.log(iterator.next().value); // 2
console.log(iterator.next().value); // 3

let obj = {};
// 直接挂载到obj[Symbol.iterator] 使得obj可以被for...of
obj[Symbol.iterator] = createIterator;
for(let item of obj){
    console.log(item);
}
// 1
// 2
// 3

不能将箭头函数创建为生成器。

# 访问默认迭代器

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"

# 创建可迭代对象

obj = {
    items: [1,2,3],
    *[Symbol.iterator]() {
        for(let item of this.items) {
            yield item;
        }
    }
}

for(let item of obj) {
    console.log(item);
}

// 1
// 2
// 3
[...obj] // [1,2,3]

# 集合的迭代器

ES6 具有三种集合对象类型:数组、 MapSet。这三种类型都拥有如下的迭代器,有助于探索它们的内容:

  • entries() :返回一个包含键值对的迭代器;
  • values() :返回一个包含集合中的值的迭代器;
  • keys() :返回一个包含集合中的键的迭代器。
let arr = [1,2,3];
let set = new Set([10, 20, 30]);
let map = new Map([['a', 'a'], ['b', 'b'], ['c', 'c']]);

// 与使用 arr.values() 相同
for (let value of arr) {
console.log(value);
}
// 与使用 set.values() 相同
for (let value of set) {
console.log(value);
}
// 与使用 map.entries() 相同
for (let [key, value] of map) { // 解构
console.log(key, value);
}

// 1 2 3
// 10 20 30
// a a
// b b
// c c

# 传递参数给迭代器

function *createIterator() {
let first = yield 1;
let second = yield first + 2; // 4 + 2
// return 9; 如果有return后面的将不会执行 9将会当成最后一个值被返回
yield second + 3; // 5 + 3
}
let iterator = createIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next(4)); // "{ value: 6, done: false }" first = 4;
console.log(iterator.next(5)); // "{ value: 8, done: false }" second = 5;
console.log(iterator.next()); // "{ value: undefined, done: true }"

# 生成器委托

function *createNumberIterator() {
    yield 1;
    yield 2;
    return 3;
}
function *createRepeatingIterator(count) {
    for (let i=0; i < count; i++) {
        yield "repeat";
    }
}
function *createCombinedIterator() {
    let result = yield *createNumberIterator(); // 这里不会输出return的值 不输出3
    yield result;
    yield *createRepeatingIterator(result); // result被传入了createRepeatingIterator
}
var iterator = createCombinedIterator();
console.log(iterator.next()); // "{ value: 1, done: false }"
console.log(iterator.next()); // "{ value: 2, done: false }"
console.log(iterator.next()); // "{ value: 3, done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: "repeat", done: false }"
console.log(iterator.next()); // "{ value: undefined, done: true }"