ES6学习笔记

强大的for-of循环

ES6不会破坏你已经写好的JS代码。目前看来,成千上万的Web网站依赖for-in循环,其中一些网站甚至将其用于数组遍历。如果想通过修正for-in循环增加数组遍历支持会让这一切变得更加混乱,因此,标准委员会在ES6中增加了一种新的循环语法来解决目前的问题。 就像这样:

for (var value of myArray) { 
 console.log(value); 
}
  • 这是最简洁、最直接的遍历数组元素的语法

  • 这个方法避开了for-in循环的所有缺陷

  • 与forEach()不同的是,它可以正确响应break、continue和return语句

箭头函数

标识符=>表达式。你无需输入function和return
箭头函数没有它自己的this值,箭头函数内的this值继承自外围作用域。

   $("#confetti-btn").click(event => { 
     playTrumpet(); 
     fireConfettiCannon(); 
   }); 
    // ES5 
    var selected = allJobs.filter(function (job) { 
      return job.isSelected(); 
    }); 
    
    //ES6 
    var selected = allJobs.filter(job => job.isSelected()); 

Iterator 迭代器

for of 会首先调用Symbol.iterator方法,然后就会返回一个迭代器对象;
迭代器对象可以是任何有.next()方法的对象

下面根据书上的作者认为最简单的迭代器,我重写了一个更最简单的迭代器,哈哈

  let some = {
    [Symbol.iterator]() {
      return this;
    },
    next(){
      return{
        done:false,
        value:1
      }
    }
  };
// 在 for of 中会在next()返回 done:true 之后完成,所以这是个死循环,不要在意这些细节。

来看一个正常一点的


let obj = {
  data: [ 'hello', 'world' ],
  [Symbol.iterator]() {
    const self = this;
    let index = 0;
    return {
      next() {
        if (index < self.data.length) {
          return {
            value: self.data[index++],
            done: false
          };
        } else {
          return { value: undefined, done: true };
        }
      }
    };
  }
};

for(let a of obj){
  console.log(a)
}
//hello
//world

反撇号 `

支持字符换行与外部变量 ` 号

var a = 'awe'
console.log(`my name is: ${a}`); //my name is: awe
var b = `<div>
          <h1>${a}</h1>
        </div>`;
console.log(b);
//<div>
//  <h1>awe</h1>
//</div>

symbol

  • symbol是对象状态的最终的解决方案

  • symbol是程序创建并且可以用作属性键的值,并且它能避免命名冲突的风险。

  • symbol 被创建后就不可变更,你不能为它设置属性(在严格模式下尝试设置属性会得到TypeError的错误)。他们可以用作属性名称,性质与字符串类似。

  • 每一个symbol都独一无二,不与其它symbol等同,即使二者有相同的描述也不相等;

var mySymbol = Symbol(); 
//调用Symbol()创建一个新的symbol,它的值与其它任何值皆不相等。 字符串或数字可以作为属性的键,symbol 也可以,它不等同于任何字符串,因而这个以symbol为键的属性可以保证不与任何其它属性产生冲突。 
   obj[mySymbol] = "ok!";  // 保证不会冲突 
   console.log(obj[mySymbol]);  // ok! 
//想要在上述讨论的场景中使用symbol,你可以这样做: 
   // 创建一个独一无二的symbol 
   var isMoving = Symbol("isMoving"); 
   ... 
   if (element[isMoving]) { 
     smoothAnimations(element); 
   } 
   element[isMoving] = true; 
JavaScript的6种原始类型
  • Undefined 未定义
  • Null 空值
  • Boolean 布尔类型
  • Number 数字类型
  • String 字符串类型
  • Object 对象类型

symbol成为了JavaScript的第七种原始类型

Class

直接上代码先看看

class Circle{
    constructor(radius){
      this.radius = radius;
      Circle.circlesMade++;
    }

    draw(circle,canvas){
      console.log('draw something')
    }
    
    static get circleMade(){
      return !this._count ? 0 : this.count;
    }

    static set circleMade(val){
      return this._count  = val;
    }

    area(){
      console.log(Math.pow(this.radius,2) * Math.PI)
      return Math.pow(this.radius,2) * Math.PI;
    }

    get radius(){
      return this._radius;
    }

    set radius(radius){
      return this._radius = radius;
    }

  }

  var  circle = new  Circle(3);

  circle.draw() // draw something
  circle.area() // 28.274333882308138
  circle.radius = 99;
  circle.area() // 30790.74959783356


  • 在class中 ; 符号是可选的。
  • constructor也是可选的,如果不指定,会默认构造一个空的constructor(){}
  • 不可以用生成器来做构造函数

ES6的类,完全可以看作构造函数的另一种写法。

class Point{ // ...}
typeof Point // "function"
Point === Point.prototype.constructor // true

上面代码表明,类的数据类型就是函数,类本身就指向构造函数。

构造函数的prototype属性,在ES6的“类”上面继续存在。事实上,类的所有方法都定义在类的prototype属性上面。

class Point { 
  constructor(){ // ... }
  toString(){ // ... }
   toValue(){ // ... }
}
// 等同于
Point.prototype = { 
 constructor(){}, 
   toString(){}, 
   toValue(){}
}

在类的实例上面调用方法,其实就是调用原型上的方法。

class B {}
let b = new B();
b.constructor === B.prototype.constructor // true

上面代码中,b是B类的实例,它的constructor方法就是B类原型的constructor方法。
由于类的方法(除constructor以外)都定义在prototype对象上面,所以类的新方法可以添加在prototype对象上面。Object.assign方法可以很方便地一次向类添加多个方法。

class Point { 
  constructor(){ // ... }
}
Object.assign(Point.prototype, {
  toString(){}, 
  toValue(){}
})

prototype对象的constructor属性,直接指向“类”的本身,这与ES5的行为是一致的。

Point.prototype.constructor === Point // true
  • 类的内部所有定义的方法,都是不可枚举的(enumerable)。
//ES6
class Point { 
  constructor(x, y) { // ... }
  toString() { // ... }
}
Object.keys(Point.prototype)// []
Object.getOwnPropertyNames(Point.prototype)// ["constructor","toString"]

//ES5的写法,toString方法就是可枚举的。
var Point = function (x, y){ // ...}
Point.prototype.toString = function() { // ...}
Object.keys(Point.prototype)// ["toString"]
Object.getOwnPropertyNames(Point.prototype)// ["constructor","toString"]

上面代码中,toString方法是Point类内部定义的方法,它是不可枚举的。这一点与ES5的行为不一致。

类的属性名,可以采用表达式。

let methodName = "getArea";
class Square{ 
  constructor(length) { // ... } 
  [methodName]() { // ... }//这个[methodName ]方法是从表达式得到的。
}

未完,,,还在学习中。

Set & Map

ES6提供了新的数据结构Set。它类似于数组,但是成员的值都是唯一的,没有重复的值。

Set本身是一个构造函数,用来生成Set数据结构。

一个Set是一群值的集合。它是可变的,能够增删元素。

  • set 它和数组不同是不会包含相同元素。试图再次加入一个已有元素不会产生任何效果。
  • new Set:创建一个新的、空的Set。
  • new Set(iterable):从任何可遍历数据中提取元素,构造出一个新的集合。
  • set.size:获取集合的大小,即其中元素的个数。
  • set.has(value):判定集合中是否含有指定元素,返回一个布尔值。
  • set.add(value):添加元素。如果与已有重复,则不产生效果。
  • set.delete(value):删除元素。如果并不存在,则不产生效果。.add()和.delete()都会返回集合自身,所以我们可以用链式语法。
    _ setSymbol.iterator:返回一个新的遍历整个集合的迭代器。一般这个方法不会被直接调用,因为实际上就是它使集合能够被遍历,也就是说,我们可以直接写for (v of set) {...}等等。
  • set.forEach(f):类似于数组的.forEach()方法。 直接上代码。
 for (let value of set) { f(value, value, set); }
  • set.clear():清空集合。
  • set.keys()、set.values()和set.entries()返回各种迭代器,它们是为了兼容Map而提供的.
var s = new Set();

[2,3,5,4,5,2,2].map(x => s.add(x))

for (i of s) {console.log(i)}
// 2 3 5 4

上面代码通过add方法向Set结构加入成员,结果表明Set结构不会添加重复的值。

   > arrayOfWords[15000] 
       "anapanapa" 
   > setOfWords[15000]    
       undefined 

Set不支持索引

  
  arr.indexOf('a') !== -1 //慢
  //true
  setOfWords.has('a') //快 
  //true

Set的数据存储结构专门为一种操作作了速度优化:包含性检测。

Map

一个Map对象由若干键值对组成,支持:

  • new Map:返回一个新的、空的Map。
  • new Map(pairs):根据所含元素形如[key, value]的数组pairs来创建一个新的Map。这里提供的 pairs 可以是一个已有的 Map 对象,可以是一个由二元数组组成的数组,也可以是逐个生成二元数组的一个生成器,等等。
  • map.size:返回Map中项目的个数。
  • map.has(key):测试一个键名是否存在,类似key in obj。
  • map.get(key):返回一个键名对应的值,若键名不存在则返回undefined,类似obj[key]。
  • map.set(key, value):添加一对新的键值对,如果键名已存在就覆盖。
  • map.delete(key):按键名删除一项,类似delete obj[key]。
  • map.clear():清空Map。
  • mapSymbol.iterator:返回遍历所有项的迭代器,每项用一个键和值组成的二元数组表示。
  • map.forEach(f) 类似 for (let [key, value] of map) { f(value, key, map); } 。 这 里 诡 异 的 参 数 顺 序 , 和 Set 中 一 样 , 是 对 应 着数组的forEach()。
  • map.keys():返回遍历所有键的迭代器。
  • map.values():返回遍历所有值的迭代器。
  • map.entries():返回遍历所有项的迭代器,就像mapSymbol.iterator。实际上,它们就是同一个方法,不同名字。

先从书上把map的api记下来,

Map数据结构类似于对象,同样是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。

var m = new Map();
var o = {p: "Hello World"};
m.set(o, "content")
m.get(o) // "content"
m.has(o) // true
m.delete(o) // true
m.has(o) // false

作为构造函数,Map也可以接受一个数组作为参数。该数组的成员是一个个表示键值对的数组。(感觉真强大)

var map = new Map([["name", "张三"], ["title", "Author"]]);
map.size // 2
map.has("name") // true
map.get("name") // "张三"
map.has("title") // true
map.get("title") // "Author"

上面代码在新建Map实例时,就指定了两个键name
和title。

* 注意,只有对同一个对象的引用,Map结构才将其视为同一个键。这一点要非常小心。

var map = new Map();
map.set(['a'], 555);
map.get(['a']) // undefined

let b = ['b'];
map.set(b, 555);
map.get(b) // 555

上面代码的set和get方法,表面是针对同一个键,但实际上这是两个值,内存地址是不一样的,因此get方法无法读取该键,返回undefined。
这个也比较好理解,因为这两个['a']是两个不同的数组对象。

有一个坏处。
Map和Set都为内部的每个键或值保持了强引用,也就是说,如果一个 DOM 元素被移除了,回收机制无法取回它占用的内存,除非 movingSet中也删除了它。在最理想的情况下,库在善后工作上对使用者都有复杂的要求,所以,这很可能引发内存泄露。

所已有了 WeakSet

WeakSet与Set有两个区别:

  • WeakSet的成员只能是对象,而不能是其他类型的值。
  • WeakSet中的对象都是弱引用,即垃圾回收机制不考虑WeakSet对该对象的引用,也就是说,如果其他对象都不再引用该对象,那么垃圾回收机制会自动回收该对象所占用的内存,不考虑该对象还存在于WeakSet之中。这个特点意味着,无法引用WeakSet的成员,因此WeakSet是不可遍历的。
var ws = new WeakSet();
ws.add(1)
// TypeError: Invalid value used in weak set
ws.add(Symbol())
// TypeError: invalid value used in weak set

上面代码试图向WeakSet添加一个数值和Symbol值,结果报错。

WeakSet结构有以下三个方法。

  • WeakSet.prototype.add(value):向WeakSet实例添加一个新成员。
  • WeakSet.prototype.delete(value):清除WeakSet实例的指定成员。
  • WeakSet.prototype.has(value):返回一个布尔值,表示某个值是否在WeakSet实例之中。

下面是一个例子。

var ws = new WeakSet();
var obj = {};
var foo = {};

ws.add(window);
ws.add(obj);

ws.has(window); // true
ws.has(foo);    // false

ws.delete(window);
ws.has(window);    // false
WeakSet没有size属性,没有办法遍历它的成员。
ws.size // undefined
ws.forEach // undefined

ws.forEach(function(item){ console.log('WeakSet has ' + item)})
// TypeError: undefined is not a function

上面代码试图获取size和forEach属性,结果都不能成功。
WeakSet不能遍历,是因为成员都是弱引用,随时可能消失,遍历机制无法保证成员的存在,很可能刚刚遍历结束,成员就取不到了。WeakSet的一个用处,是储存DOM节点,而不用担心这些节点从文档移除时,会引发内存泄漏。

let

let 是用来解决的js没有块级作用域和循环内过度共享问题的。

  var arr = [1,2,3,4,5]
  for(let i = 0; i < arr.length; i++){
    console.log(arr[i]) //1  2  3...5
  }

  console.log(i) //ReferenceError: i is not defined


  var arr = [1,2,3,4,5]
  for (var i = 0; i < arr.length; i++) {
      setTimeout(function() {
          console.log('var'+arr[i])
      }, 1000)
  }

  //varundefined
  //varundefined
  //varundefined
  //varundefined
  //varundefined

  var arr = [1,2,3,4,5]
    for (let a = 0; a < arr.length; a++) {
      setTimeout(function() {
          console.log('let'+arr[a])
      }, 2000)
  }

  //let1
  //let2
  //let3
  //let4
  //let5


let 是更完美的var

  • let声明的变量拥有块级作用域
  • let声明的全局变量不是全局对象属性
  • 形如for(let x...),每次迭代都是为x创建新的绑定
  • let 声明是控制流到达定义时才会生效(babel编译到es5当然没有这个特性)
  function one(){
    console.log(a);
    let a;
  }
  //ReferenceError: a is not defined

  function two(){
    console.log(b);
  }
  //ReferenceError: b is not defined

  function thr(){
    console.log(c);
    var c;
  }
  //undefined

还有一点是关于let的性能细节:在大多数情况下,查看代码就可以区分声明是否已经执行,所以事实上,JavaScript引擎不需要在每次代码运行时都额外执行 一次变量可访问检查来确保变量已经被初始化。然而在闭包内部有时不是透明的,这时JavaScript引擎将会做一个运行时检查,也就意味着let相对var而言比较慢。

const

ES6引入的第三个声明类关键词与let类似:const。 const声明的变量与let声明的变量类似,它们的不同之处在于,const声明的变量只可以在声明时赋值,不可随意修改,否则会导致SyntaxError(语法错误)。


    const MAX_CAT_SIZE_KG = 3000; // 正确 
    MAX_CAT_SIZE_KG = 5000; // 语法错误(SyntaxError) 
    MAX_CAT_SIZE_KG++; // 虽然换了一种方式,但仍然会导致语法错误 
   const theFairest;  // 语法错误

用const声明变量后必须要赋值,否则也抛出语法错误。

class RangeIterator { 
 constructor(start, stop) { 
   this.value = start; 
   this.stop = stop; 
 } 
 [Symbol.iterator]() { return this; } 
 next() { 
   var value = this.value; 
   if (value < this.stop) { 
     this.value++; 
     return {done: false, value: value}; 
   } else { 
     return {done: true, value: undefined}; 
   } 
 } 
} 

Generator 生成器

这个被作者称为最具魔力的特性,貌似很强大的样子,继续看下去。

Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。


function* helloWorldGenerator(name) {
  yield 'hello';
  yield 'world';
  if(name){
    yield name;
  }
  return 'ending';
}
var a = helloWorldGenerator('awe');

a.next() //{ value: 'hello', done: false }
a.next() //{ value: 'world', done: false }
a.next() //{ value: 'awe', done: false }
a.next() //{ value: 'ending', done: true }


看完这个代码是不是感觉像迭代器?
还真是。“所有生成器都是内建了.next()和 Symbol.iterator的实现。

感觉生成器内容真是太多了,推荐上这里细看 https://likebeta.gitbooks.io/es6tutorial/content/docs/generator.html

Modules

ES6模块都是一个包含JS代码的文件,模块本质上就是一段脚本,而不是用module关键字定义一个模块,但是模块与脚本还是有两点区别: 在ES6模块中,无论你是否加入“use strict;”语句,默认情况下模块都是在严格模式下运行。 在模块中你可以使用import和export关键字。

一个独立文件中,我们可以导入detectCats()函数然后用它来做点儿什么:

  // a.js
  export function say(word){
    console.log(word)
  }

  var b = function(){
    console.log('world')
  }
  export b

  // demo.js
  import {say} from "a.js"; 
  function go(word) { 
      say(word)
  }
  go('hello') 
  // hello

如果想从一个模块中导入多个名称,你可以这样写:

  import {a, b} from "a.js"; 

当你运行的模块中包含一条import声明时,首先会加载被导入的模块;然后依赖图的深度优先遍历按顺序执行每一个模块的主体代码;为了避免形成回环,所有已执行的模块都会被忽略。 这些就是模块的基本知识了。


import name from "module-name";
import * as name from "module-name";
import { member } from "module-name";
import { member as alias } from "module-name";
import { member1 , member2 } from "module-name";
import { member1 , member2 as alias2 , [...] } from "module-name";
import defaultMember, { member [ , [...] ] } from "module-name";
import defaultMember, * as alias from "module-name";
import defaultMember from "module-name";
import "module-name";


export { name1, name2, …, nameN };
export { variable1 as name1, variable2 as name2, …, nameN };
export let name1, name2, …, nameN; // also var
export let name1 = …, name2 = …, …, nameN; // also var, const

export default expression;
export default function (…) { … } // also class, function*
export default function name1(…) { … } // also class, function*
export { name1 as default, … };

export * from …;
export { name1, name2, …, nameN } from …;
export { import1 as name1, import2 as name2, …, nameN } from …;

详细了解点这里->
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/export

子类 subclassing

我们再来回顾一下之前 class那一节的代码。

class Circle{
    constructor(radius){
      this.radius = radius;
      Circle.circlesMade++;
    }

    draw(circle,canvas){
      console.log('draw something')
    }
    
    static get circleMade(){
      return !this._count ? 0 : this.count;
    }

    static set circleMade(val){
      return this._count  = val;
    }

    area(){
      console.log(Math.pow(this.radius,2) * Math.PI)
      return Math.pow(this.radius,2) * Math.PI;
    }

    get radius(){
      return this._radius;
    }

    set radius(radius){
      return this._radius = radius;
    }

  }

  var  circle = new  Circle(3);

  circle.draw() // draw something
  circle.area() // 28.274333882308138
  circle.radius = 99;
  circle.area() // 30790.74959783356


javascriptの继承

 var proto = {
 value: 4,
 method() { return 14; }
 }
 var obj = Object.create(proto);
 obj.value; // 4
 obj.method(); // 14
//如果我们在创建 obj 时给它添加 proto 已有的属性,则新创建对象会覆盖原型对象中的同名属性。
 obj.value = 5;
 obj.value; // 5
 proto.value; // 4

我们在创建对象的时候可以为其添加各种属性,但在这个过程中,新创建的对象同
时也继承了原型对象的属性.

然后继续下一步阅读

假设我们有一个类 Shape,并且想要基于这个类生成一个子类

class Shape {
    get color() {
        return this._color;
    }
    set color(c) {
        this._color = parseColorAsRGB(c);
        this.markChanged(); // 稍后重绘 Canvas
    }
}

在尝试编写这些代码时,我们仍会面临以前遇到过的 static 属性的问题:我们不能在定义函数的同时改变它的原型,到目前为止我们所知的语法不能实现这种功能。只不过你可以通过 Object.setPrototypeOf 方法绕过这一限制,但随即带来的问题是,这
个方法性能很差,而 JS 引擎又很难对其进行优化。

 class Circle extends Shape {
   // 与上文中代码相同
 }

extends 后面可以接驳任意合法且拥有 prototype 属性的构造函数。

  • 一个普通的函数
  • 一个包含一个函数或类的变量
  • 一个对象上的属性访问
  • 一个函数调用
    如果不希望创建出来的实例继承自 Object.prototype,你甚至可以在 extends 后
    使用 null 来进行声明。

Super 属性

现在我们学会怎样创建子类了,子类可以继承父类的属性,有时我们会在子类中重
新定义同名方法,这样会覆盖掉我们继承的方法。但在某些情况下,如果你重新定义了
一个方法,但有时你又想绕开这个方法去使用父类中的同名方法,应该怎样做呢?
假设我们想基于 Circle 类生成一个子类,这个子类可以通过一些因子来控制圆的
缩放,为了实现这一目标,我们写下的这个类看起来有些不太自然:

 class ScalableCircle extends Circle {
     get radius() {
         return this.scalingFactor * super.radius;//super.radius就是引用自原始对象的get radius方法
     }
     set radius() {
         throw new Error("可伸缩圆的半径 radius 是常量。" +
             "请设置伸缩因子 scalingFactor 的值。");
     }
     // 处理 scalingFactor 的代码
 }

请注意 radius 的 getter 使用的是 super.radius。这里的 super 是一个全新的关键
字,它可以帮我们绕开我们在子类中定义的属性,直接从子类的原型开始查找属性,从
而绕过我们覆盖到父类上的同名方法。通过方法定义语法定义的函数,其原始对象方法的定义在初始化后就已完成,从而我们可以访问它的 super 属性(也可以访问 super[expr]),由于该访问依赖的是原始对象,所以即使我们将方法存到本地变量,再访问时也不会改变 super 的行为。

 var obj = {
     toString() {
         return "MyObject: " + super.toString();
     }
 }
 obj.toString(); // MyObject: [object Object]
 var a = obj.toString;
 a(); // MyObject: [object Object]

子类化内建方法

你想做的另一件事可能是扩展 JavaScript 的内建方法。现在你拥有极为强大的 JS
内建数据结构,它是子类化设计的基础之一,可被用来创建新的类型。假设你想编写一
个带版本号的数组,你可以改变数组内容然后提交,或者将数组回滚到之前的状态。我
们可以通过子类化 Array 来快速实现这一功能。

 class VersionedArray extends Array {
     constructor() {
         super();
         this.history = [
             []
         ];
     }
     commit() {
         // 将变更保存到 history。
         this.history.push(this.slice());
     }
     revert() {
         this.splice(0, this.length, this.history[this.history.length -
             1]);
     }
 }

VersionedArray 的实例保留了一些重要的属性,包括 map、filter 还有 sort,它
们是组成数组的核心方法;当然,Array.isArray()也会把 VersionedArray 视为数组;
当你向 VersionedArray 中添加元素时,它的 length 属性也会自动增长;说远一些,之前能够返回一个新数组的函数(例如 Array.prototype.slice())现在会返回一个
VersionedArray!

派生类构造函数


 class Shape {
     constructor(color) {
         this._color = color;
     }
 }
 class Box extends Shape {
     constructor(x, y,color) {
         super(color);
         this.x = x;
         this.y = y;
     }
     area(){
        console.log(`the box area is ${this.x*this.y}`)
     }

 }

 let b = new Box(1,2,'red');
 b.area()
//the box area is 2

当我们执行基类的构造函数前,this对象没有被分配,从而我们**无法得到一个确定的this值。因此,在子类的构造函数中,调用super构造函数之前访问this会触发一个引用错误(ReferenceError)。

Mixin

如 果你想继续使用混入类,你可能希望你能有这样一种类,它继承自几个不同的主体,所以你可以继承每一个混入(Mixin)然后获取它们的精华。不幸的是,如 果改变现有的继承模型可能会使整个系统非常混乱,所以JavaScript没有实现类的多继承。那也就是说,在一个基于类的框架内部有一种混合解决方案可 以支持混入类特性。请看下面的这段代码,这是一个基于我们熟知的extend混入习语打造的函数:


 function mix(...mixins) {
     class Mix {}
     // 以编程方式给Mix类添加
     // mixins的所有方法和访问器
     for (let mixin of mixins) {
         copyProperties(Mix, mixin);
         copyProperties(Mix.prototype, mixin.prototype);
     }
     return Mix;
 }

 function copyProperties(target, source) {
     for (let key of Reflect.ownKeys(source)) {
         if (key !== "constructor" && key !== "prototype" && key !== "name") {
             let desc = Object.getOwnPropertyDescriptor(source, key);
             Object.defineProperty(target, key, desc);
         }
     }
 }

现在,我们无须在各种各样的混入之间创建显示的继承关系,我们只须使用mix
函数就可以创建一个组合而成的超类。设想一下,如果你想编写一个协作编辑工具,在这个工具中的所有编辑动作都被记录下来,然后将内容序列化。你可以使用mix
函数写一个DistributedEdit类:

class DistributedEdit extends mix(Loggable, Serializable) {
   // 事件方法
 }

这真是两全其美啊。如果你想将构造本身有超类的混入类,也可以用这个模型来解决:我们将超类传递给mix函数后它就会返回扩展后的类。

不定参数

function containsAll(haystack, ...needles) {
  for (var needle of needles) {
    if (haystack.indexOf(needle) === -1) {
      return false;
    }
  }
  return true;
}

这一版containsAll函数与前者有相同的行为,但这一版中使用了一个特殊的...needles语法。我们来看一下调用containsAll("banana", "b", "nan")之后的函数调用过程,与之前一样,传递进来的第一个参数"banana"赋值给参数haystack,needles前的省略号表明它是一个不定参数,所有传递进来的其它参数都被放到一个数组中,赋值给变量needles。对于我们的调用示例而言,needles被赋值为["b", "nan"],后续的函数执行过程一如往常。(注意啦,我们已经使用过ES6中for-of循环。)

在所有函数参数中,只有最后一个才可以被标记为不定参数。函数被调用时,不定参数前的所有参数都正常填充,任何“额外的”参数都被放进一个数组中并赋值给不定参数。如果没有额外的参数,不定参数就是一个空数组,它永远不会是undefined。

默认参数

这个很好理解,上代码。


function log(x, y = 'Awe') {
  console.log(x, y);
}

log('Hello') // Hello Awe
log('Hello', 'China') // Hello China
log('Hello', '') // Hello

解构

解构赋值允许你使用类似数组或对象字面量的语法将数组和对象的属性赋给各种变量。这种赋值语法极度简洁,同时还比传统的属性访问方法更为清晰。 通常来说,你很可能这样访问数组中的前三个元素:

    var first = someArray[0]; 
    var second = someArray[1]; 
    var third = someArray[2]; 

如果使用解构赋值的特性,将会使等效的代码变得更加简洁并且可读性更高:

    var [first, second, third] = someArray; 
  • 可以对任意深度的数组进行结构
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
  • 可以留空来跳过被解构数组中的某些元素
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"

proxies

首先作者让我先看了看这代码。嗯,看代码。

 var obj = new Proxy({}, {
     get: function(target, key, receiver) {
         console.log(`getting ${key}!`);
         return Reflect.get(target, key, receiver);//
     },
     set: function(target, key, value, receiver) {
         console.log(`setting ${key}!`);
         return Reflect.set(target, key, value, receiver);
     }
 });

obj.count = 1;
//setting count!
++obj.count;
//getting count!
//setting count!
//2

结合这输出有没一点感觉?
我觉得利用Proxy拦截了这个对象的属性访问getter setter方法。

然后作者花了很大篇幅谈了谈虚拟化,接口,对象的定义,思维高度很高,还在理解中。

也推荐大家看看:http://www.infoq.com/cn/articles/es6-in-depth-proxies-and-reflect

总结了ES6标准列表5和6中找到全部的14种方法,这里讲解其中一部分。

双方括号[[ ]]代表内部方法,在一般的JS代码中不可见,你可以调用、删除或覆写普通方法,但是无法操作内部方法。

obj.[[Get]](key, receiver) – 获取属性值。
当JS代码执行以下方法时被调用:obj.prop
或obj[key]。

obj是当前被搜索的对象,receiver是我们首先开始搜索这个属性的对象。有时我们必须要搜索几个对象,obj可能是一个在receiver原型链上的对象。

obj.[[Set]](key, value, receiver) – 为对象的属性赋值。
当JS代码执行以下方法时被调用:obj.prop = value
或obj[key] = value。
执行类似obj.prop += 2
这样的赋值语句时,首先调用[[Get]]方法,然后调用[[Set]]方法。对于++和--操作符来说亦是如此。

obj.[HasProperty] – 检测对象中是否存在某属性。
当JS代码执行以下方法时被调用:key in obj。

obj.[Enumerate] – 列举对象的可枚举属性。
当JS代码执行以下方法时被调用:for (key in obj)

这个内部方法会返回一个可迭代对象,for-in
循环可通过这个方法得到对象属性的名称。

obj.[GetPrototypeOf] – 返回对象的原型。
当JS代码执行以下方法时被调用:obj.[proto]
Object.getPrototypeOf
(obj)。

functionObj.[[Call]](thisValue, arguments) – 调用一个函数。
当JS代码执行以下方法时被调用:functionObj()
或x.method()。
可选的。不是每一个对象都是函数。

constructorObj.[[Construct]](arguments, newTarget) – 调用一个构造函数。
当JS代码执行以下方法时被调用:举个例子,new Date(2890, 6, 2)。

代理 Proxy

ES6规范定义了一个全新的全局构造函数:代理Proxy)。它可以接受两个参数:目标对象(target)句柄对象(handler)。请看一个简单的示例:

var target = {}, handler = {}; var proxy = new Proxy(target, handler);

我们先来探讨代理目标对象之间的关系,然后再研究句柄对象的功用。
代理的行为很简单:将代理的所有内部方法转发至目标。简单来说,如果调用proxy.[[Enumerate]](),就会返回target.[[Enumerate]]()。
现在,让我们尝试执行一条能够触发调用proxy.[[Set]]()
方法的语句。

proxy.color = "pink";

好的,刚刚都发生了什么?proxy.[[Set]]()
应该调用target.[[Set]]()
方法,然后在目标上创建一个新的属性。实际的结果如何?

 console.log(target.color )
 //"pink"

是的,它做到了!对于所有其它内部方法而言同样可以做到。新创建的代理会尽可能与目标的行为一致。
当然,它们也不完全相同,你会发现proxy !== target
。有时也有目标能够通过类型检测而代理无法通过的情况发生,举个例子,如果代理的目标是一个DOM元素,相应的代理就不是,此时类似document.body.appendChild(proxy)的操作会触发类型错误(TypeError)。

推荐阅读更多精彩内容