关于 ES6 / ES7

 写在前面

0.1 JavaScript 语句的自动分号插入

一条语句以“(”、“[”、“/”、“+”或“-”开始,那么它极有可能和前一条语句合在一起解析。保守起见,可以在语句前加上一个分号以保证程序的正确执行;

var a = 0   // 这里省略了分号

;[a, a+1, a+2].forEach(console.log) // 前面的分号保证了正确地语句解析

0.2 闭包( closure ) 

函数内部形成一个局部作用域,定义在这个局部作用域上的内部函数,会持有对这个原始作用域的引用,只要在外部作用域上执行这个内部函数并访问原始作用域,就会形成闭包。

判断是否是闭包,需满足两个条件:

(1) 一个词法环境A(可以简单理解为函数)中创建另一个新的词法环境B。

(2) B执行的过程中,调用了A中的变量。

0.3 this 判断优先级

(1) 函数在 new 中调用,this 绑定的是新创建的实例化对象。

var bar = new foo()          // this 指向 bar

(2) 函数通过 call、apply 绑定调用(显式绑定),this 绑定的是指定的对象

var bar = foo.call(obj_other)      // this 指向obj

(3)函数在某个上下文对象中调用(隐式绑定),this 绑定的是上下文对象

obj.foo()            // this 指向 obj

(4)否则,属于默认绑定,this 绑定的是全局对象(严格模式下为undefined)

var bar = obj.foo;
bar()                         // this 指向全局对象

 0.4 new 运算符

  假设有一个构造函数 Base,对其实例化一个对象 obj : 

var obj = new Base()

  new 关键字会进行如下的操作:

//  1. 创建一个对象
var obj={};

//  2. 将这个空对象 obj 的 __proto__ 指向了 Base 函数对象 prototype 成员对象
obj.__proto__ = Base.prototype;

//  3. 将 Base 函数对象的 this 指针替换成 obj,然后再调用Base函数。即:改变构造函数的 this 指向,来给 obj 添加属性和方法
Base.call(obj);

 

 

1. async / await

async 确保了函数的返回值是一个 promise,也会包装非 promise 的值。

// 常规函数
function foo(){
  return 123
}
foo()       // 123

// 加限定词 async
async function foo(){
  return 123
}
foo()       // Promise {<resolved>: 123}

// 显式返回一个 promise
async function foo(){
  return Promise.resolve(123);
}
foo()       // Promise {<resolved>: 123}

 

await 让 JavaScript 引擎等待直到 promise 完成 ( * ) 并返回结果 

async function ff() {

  let promise = new Promise( resolve=> {
    setTimeout(() => resolve(456), 1000)
  });

  let result = await promise;   // 等待直到 promise 决议 并返回 resolve 的值 (*)

  console.log(result);          // 456
}

ff();

相比 promise.then 来获取 promise 结果,这只是一个更优雅的语法,同时也更可读和更易书写。

如果 promise 被拒绝(rejected),就会抛出一个错误,就像在那一行有个  throw  语句那样。可以用 try..catch 来捕获上面的错误,或者我们也可以在函数调用后添加 .catch 来处理错误:

// try... catch...
async function f() {
  try {
    let response = await fetch('http://url');
  } catch(err) {
    console.log(err);                // TypeError: failed to fetch
  }
}
f();

// .catch
async function f() {
  let response = await fetch('http://url');
}
f().catch(console.log);            // TypeError: failed to fetch 

 

2. 箭头函数

  函数体内的 this 对象,就是定义时所在的对象,而不是使用时所在的对象。这意味着 this 和 arguments 都是从它们的父函数继承而来的。如下:

const func = {
  name: 'example',
  anonFunction: function() {
    return function() {
      console.log('--------anon-------');
      console.log(this);
      console.log(this.name);
      console.log(arguments);
    };
  },
 
  arrowFunction: function() {
    return () => {
      console.log('-------arrow--------');
      console.log(this);
      console.log(this.name);
      console.log(arguments);
    };
  }
};

const anon = func.anonFunction('hello', 'world');
const arrow = func.arrowFunction('hello', 'world');

anon()
// Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
// 
// Arguments [callee: ƒ, Symbol(Symbol.iterator): ƒ]

anon('other')
// Window {parent: Window, postMessage: ƒ, blur: ƒ, focus: ƒ, close: ƒ, …}
// 
// Arguments ["other", callee: ƒ, Symbol(Symbol.iterator): ƒ]

arrow()
arrow('other')
// {name: "example", anonFunction: ƒ, arrowFunction: ƒ}
// example
// Arguments(2) ["hello", "world", callee: ƒ, Symbol(Symbol.iterator): ƒ]

  与其他形式的函数不同,箭头函数没有自己的执行期上下文。this指向的固定化,并不是因为箭头函数内部有绑定this的机制,实际原因是箭头函数根本没有自己的this,导致内部的this就是外层代码块的this。正是因为它没有this,所以也就不能用作构造函数。

 

3. 解构赋值

   3.1 数组解构

let [a, b, c] = [1, 2, 3];        // 从数组中提取值,按照对应位置,对变量赋值

/** 不完全匹配 **/
let [head, ...tail] = [1, 2, 3, 4];
head        // 1
tail                // [2, 3, 4] 

let [x, , y] = [1, 2, 3];
x     // 1
y        // 3

/** 添加默认值 **/
let [i, j = 'b'] = ['a'];          // i='a', j='b' 

/** 交换值 **/
let [m, n] = [1, 2];
[m, n] = [n, m];
m    // 2
n      // 1

  3.2  对象解构

  对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。形式如下:

let { foo: foo, bar: bar } = { foo: 'aaa', bar: 'bbb' };

  对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

let { bar, foo } = { foo: 'aaa', bar: 'bbb' };
foo    // "aaa"
bar    // "bbb"

  若想重写变量名

let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
baz      // "aaa"

  可以用于多层嵌套结构的对象

let obj = {
  p: [
    'Hello',
    { y: 'World' }
  ]
};
let { p: [x, { y }] } = obj;

x // "Hello"
y // "World"

  可用于函数的默认值解构

function fetch(url, { body = '', method = 'GET', headers = {} } = {}) {
  console.log(method);
}

fetch('http://example.com')
// "GET"

 

 

4. 数组、对象 的扩展

  4.1 新增数组方法

  Array.of() 用于将一组值,转换为数组

Array.of(3, 11, 8)   // [3,11,8]
Array.of(3)          // [3]
Array.of(3).length   // 1

  entries(),keys() 和 values() 都返回一个遍历器对象,可用  for...of  循环进行遍历。keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。

for (let index of ['a', 'b'].keys()) {
  console.log(index);
}
// 0
// 1

for (let elem of ['a', 'b'].values()) {
  console.log(elem);
}
// 'a'
// 'b'

for (let [index, elem] of ['a', 'b'].entries()) {
  console.log(index, elem);
}
// 0 "a"
// 1 "b"

  4.2 对象新增方法

  Object.assign  用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target),并返回目标对象。第一个参数是目标对象,后面的参数都是源对象,后面的属性会覆盖前面的属性。实行的是浅拷贝

const target = { a: 1, b: 1 };

const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };

Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}

  Object.keys(),Object.values(),Object.entries()  同数组,keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

  4.3 扩展运算符

  结构为( ... ),可以将一个数组转为用逗号分隔的参数序列,

console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5

  也可作用于 Object

let z = { a: 3, b: 4 };
let n = { ...z };
n     // { a: 3, b: 4 }

 

5. Promise

  then方法返回的是一个新的Promise实例(注意,不是原来那个Promise实例)。因此可以采用链式写法,即then方法后面再调用另一个then方法。

new Promise((resolve,reject)=>{
  resolve(123)
}).then((res)=>{ 
  console.log(res);
  return res 
}).then((res)=>{
  console.log(res)
})

// 123
// 123

// 甚至可以这样
Promise.resolve()
.then(_=>{console.log('ok')}) .then(_=>{console.log('ok')}) .then(_=>{console.log('ok')}) .then(_=>{console.log('ok')}) .then(_=>{console.log('ok')}) .then(_=>{console.log('ok')})

  .catch 方法是 .then(null, rejection) .then(undefined, rejection) 的别名。Promise 对象后面的 catch 方法,可以处理 Promise 内部,以及 catch 前面的 then 方法内发生的错误。catch方法返回的还是一个 Promise 对象,因此后面还可以接着调用then方法。运行完catch方法指定的回调函数,会接着运行后面那个then方法指定的回调函数。如果没有报错,则会跳过catch方法。

Promise.resolve().catch((error)=> {
  console.log('catch:', error);
}).then(()=> {
  console.log('carry on');
});
// carry on

  finally 方法用于指定不管 Promise 对象最后状态如何,都会执行的操作。该方法是 ES2018 引入标准的。

6. Class

 ES6 的 class 只是一个语法糖,它的绝大部分功能,ES5 都可以做到。本质是使用 es5 的构造函数来模拟 class 类。

// class 声明
class Point {
  constructor(x, y) {
    this.x = x;
    this.y = y;
  }
  toString() {
    return '(' + this.x + ', ' + this.y + ')';
  }
}

/**
 *  等同于:
 * 
 * function Point(x, y) {
 *   this.x = x;
 *   this.y = y;
 * }
 * Point.prototype.toString = function () {
 *   return '(' + this.x + ', ' + this.y + ')';
 * };
 * 
 */ 

因此,以上代码中,可以直接通过原型链方式,给 Point 添加 类方法

class Point {
  // ...
}

Point.prototype.join = function () {
  console.log(this.x + '__' + this.y);
};

let p = new Point(1,2)
p.join()                // 1__2

constructor 方法是类的默认方法,通过 new 命令生成对象实例时,自动调用该方法。默认返回实例对象(即this),也可以指定返回另外一个对象。

在类内部的方法前面,加上 static 关键字,表示该方法为静态方法,不会被实例继承,而是直接通过类来调用。

class Foo {
  static func() {
    return 'hello';
  }
}

Foo.func()                // 'hello'

var foo = new Foo();
foo.func()                // TypeError: foo.funcis not a function

可以通过 extends 关键字实现 class类 的继承。父类的静态方法,也会被子类继承。

class ColorPoint extends Point {
  constructor(x, y, color) {
    super(x, y);         // 调用父类的constructor(x, y)
    this.color = color;
  }
  toString() {
    return this.color + ' ' + super.toString();     // 调用父类的toString()
  }
}

子类自己的 this 对象,必须先通过父类的构造函数来构造,然后才能加上子类自己的实例属性和方法。super 关键字,表示父类的构造函数,用来新建父类的this对象。因此,子类必须在 constructor 方法中调用super方法,不调用super方法,子类就得不到this对象,新建实例时会报错。

class Parent {}
class Child extends Parent {
  constructor(){}
}
new Child()
// ReferenceError: Must call super constructor in derived class before accessing 'this' or returning from derived constructor

需要注意的是,如果子类没有定义 constructor 方法,这个方法会被默认添加

class Parent {}
class Child extends Parent {
  // ...
}
new Child()

// 这段代码没有报错,因为以上操作等同于:

class Parent {}
class Child extends Parent {
  constructor(...args){
    super(...args);
  }
}
new Child()

同样,子类的构造函数中,只有调用super之后,才可以使用 this 关键字,否则会报错。这是因为子类实例的构建,基于父类实例,只有 super 方法才能调用父类实例。

super 可用作函数调用,也可作为对象使用

(1)super 作为函数调用时,代表父类的构造函数,只能用在子类的构造函数之中。super 虽然代表了父类的构造函数,但是返回的是子类的实例;

(2)super 作为对象时,在普通方法中,指向父类的原型对象,但是无法调用父类实例上的方法或属性( 例如 this.xx 方式定义);在静态方法中,指向父类。

 

 

7. Proxy

posted @ 2019-08-15 14:07  晨の风  阅读(284)  评论(0编辑  收藏  举报