nodejs死亡笔记之溯本归源--node4.0+与ECMAScript6新特性(two)

前言:本文接上一篇文章继续讲解nodev4的新特性


Generator

基本概念

Generator函数有多种理解角度。它是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。Generator函数是一个状态机,封装了多个内部状态。

执行Generator函数会返回一个遍历器对象,也就是说,Generator函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历Generator函数内部的每一个状态。

形式上,Generator函数是一个普通函数,但是有两个特征。一是function命令与函数名之间有一个星号;二是函数体内部使用yield语句,定义不同的内部状态(yield在英语里的意思就是“产出”)。

    function* helloWorldGenerator() {
      yield 'hello';
      yield 'world';
      return 'ending';
    }
    var hw = helloWorldGenerator();

上面代码定义了一个Generator函数helloWorldGenerator,它内部有两个yield语句“hello”和“world”,即该函数有三个状态:hello,world和return语句(结束执行)。

然后,Generator函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,遍历器对象(Iterator Object)。

next方法

  调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield语句(或return语句)为止。换言之,Generator函数是分段执行的,yield语句是暂停执行的标记,而next方法可以恢复执行。

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

上面代码一共调用了四次next方法。

  第一次调用,Generator函数开始执行,直到遇到第一个yield语句为止。next方法返回一个对象,它的value属性就是当前yield语句的值hello,done属性的值false,表示遍历还没有结束。

  第二次调用,Generator函数从上次yield语句停下的地方,一直执行到下一个yield语句。next方法返回的对象的value属性就是当前yield语句的值world,done属性的值false,表示遍历还没有结束。

  第三次调用,Generator函数从上次yield语句停下的地方,一直执行到return语句(如果没有return语句,就执行到函数结束)。next方法返回的对象的value属性,就是紧跟在return语句后面的表达式的值(如果没有return语句,则value属性的值为undefined),done属性的值true,表示遍历已经结束。

  第四次调用,此时Generator函数已经运行完毕,next方法返回对象的value属性为undefined,done属性为true。以后再调用next方法,返回的都是这个值。

总结一下,调用Generator函数,返回一个遍历器对象,代表Generator函数的内部指针。以后,每次调用遍历器对象的next方法,就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值,是yield语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束。

yield语句*

用来在一个Generator函数里面执行另一个Generator函数,我们需要用yield*语句。

如果yield命令后面跟的是一个遍历器对象,需要在yield命令后面加上星号,表明它返回的是一个遍历器对象。这被称为yield*语句。

    function* anotherGenerator(i) {
      yield i + 1;
      yield i + 2;
      yield i + 3;
    }
    function* generator(i){
      yield i;
      yield* anotherGenerator(i);
      yield i + 10;
    }
    var gen = generator(10);
    console.log(gen.next().value); // 10
    console.log(gen.next().value); // 11
    console.log(gen.next().value); // 12
    console.log(gen.next().value); // 13
    console.log(gen.next().value); // 20

运行结果就是使用一个遍历器,遍历了多个Generator函数,有递归的效果。

另外,本文对 Generator 函数的介绍很简单,如果有需要,会单独写一篇博客介绍。


Promise

Promise简介

所谓Promise,就是一个对象,用来传递异步操作的消息。

Promise对象有以下两个特点。

(1)对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:Pending(进行中)、Resolved(已完成,又称Fulfilled)和Rejected(已失败)。只有异步操作的结果可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。这也是Promise这个名字的由来,它的英语意思就是“承诺”,表示其他手段无法改变。

(2)一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从Pending变为Resolved和从Pending变为Rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果。就算改变已经发生了,你再对Promise对象添加回调函数,也会立即得到这个结果。这与事件(Event)完全不同,事件的特点是,如果你错过了它,再去监听,是得不到结果的。

基本用法

ES6规定,Promise对象是一个构造函数,用来生成Promise实例。

下面代码创造了一个Promise实例。

    var promise = new Promise(function(resolve, reject){
      // ... some code

      if (/* 异步操作成功 */){
        resolve(value);
      } else {
        reject(error);
      }
    });

Promise构造函数接受一个函数作为参数,该函数的两个参数分别是resolve和reject。它们是两个函数,由JavaScript引擎提供,不用自己部署。

resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从Pending变为Resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从Pending变为Rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。

Promise实例生成以后,可以用then方法分别指定Resolved状态和Reject状态的回调函数。

    promise.then(function(value) {
      // success
    }, function(value) {
      // failure
    });

then方法可以接受两个回调函数作为参数。第一个回调函数是Promise对象的状态变为Resolved时调用,第二个回调函数是Promise对象的状态变为Reject时调用。其中,第二个函数是可选的,不一定要提供。这两个函数都接受Promise对象传出的值作为参数。

Promise.prototype.then()与catch()示意图:

这里写图片描述

因为Promise.prototype.then和 Promise.prototype.catch方法返回 promises, 所以它们可以被链式调用—一种被称为 composition 的操作.

Promise.prototype.then()

then()方法返回一个Promise。它有两个参数,分别为Promise在 success 和 failure 情况下的回调函数。

    p.then(onFulfilled, onRejected);
    p.then(function(value) {
       // 满足
      }, function(reason) {
      // 拒绝
    });

一个Function, 当 Promise 为 fulfilled 时调用. 该函数有一个参数, 为肯定结果 value;为 rejected 时调用. 该函数有一个参数, 为否定原因 reason。

因为then方法返回一个Promise,你可以轻易地链式调用then。

    var p2 = new Promise(function(resolve, reject) {
      resolve(1);
    });

    p2.then(function(value) {
      console.log(value); // 1
      return value + 1;
    }).then(function(value) {
      console.log(value); // 2
    });

Promise.prototype.catch()

catch()方法只处理Promise被拒绝的情况,并返回一个Promise。该方法的行为和调用Promise.prototype.then(undefined, onRejected)相同。
语法

    p.catch(onRejected);

    p.catch(function(reason) {
       // 拒绝
    });

onRejected当Promise被拒绝时调用的Function。该函数调用时会传入一个参数:拒绝原因。
示例:使用catch方法

    var p1 = new Promise(function(resolve, reject) {
      resolve("成功");
    });

    p1.then(function(value) {
      console.log(value); // "成功!"
      throw "哦,不!";
    }).catch(function(e) {
      console.log(e); // "哦,不!"
    });

catch方法主要作用于处理promise组合。

Promise.all()

Promise.all方法用于将多个Promise实例,包装成一个新的Promise实例。

var p = Promise.all([p1,p2,p3]);

  上面代码中,Promise.all方法接受一个数组作为参数,p1、p2、p3都是Promise对象的实例,如果不是,就会先调用下面讲到的Promise.resolve方法,将参数转为Promise实例,再进一步处理。(Promise.all方法的参数不一定是数组,但是必须具有iterator接口,且返回的每个成员都是Promise实例。)

p的状态由p1、p2、p3决定,分成两种情况。

(1)只有p1、p2、p3的状态都变成fulfilled,p的状态才会变成fulfilled,此时p1、p2、p3的返回值组成一个数组,传递给p的回调函数。

(2)只要p1、p2、p3之中有一个被rejected,p的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数。

下面是一个具体的例子。

    // 生成一个Promise对象的数组
    var promise = Promise.resolve(3);
    Promise.all([true, promise])
           .then(values => {
               console.log(values); // [true, 3]
            });

Promise.race()

race 函数返回一个Promise,这个Promise根据传入的Promise中的第一个确定状态–不管是接受还是拒绝–的状态而确定状态。

Promise.race方法同样是将多个Promise实例,包装成一个新的Promise实例。

    var p1 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 500, "one"); 
    });
    var p2 = new Promise(function(resolve, reject) { 
        setTimeout(resolve, 100, "two"); 
    });
    Promise.race([p1, p2]).then(function(value) {
      console.log(value); // "two"
      // Both resolve, but p2 is faster
    });

上面代码中,只要p1、p2之中有一个实例率先改变状态,p的状态就跟着改变。那个率先改变的Promise实例的返回值,就传递给p的回调函数。

Promise.reject()

Promise.reject(reason)方法也会返回一个新的Promise实例,该实例的状态为rejected。Promise.reject方法的参数reason,会被传递给实例的回调函数。

使用静态的Promise.reject()方法

    Promise.reject("Testing static reject").then(function(reason) {
      // 未被调用
    }, function(reason) {
      console.log(reason); // "测试静态拒绝"
    });

    Promise.reject(new Error("fail")).then(function(error) {
      // 未被调用
    }, function(error) {
      console.log(error); // 堆栈跟踪
    });

Promise.resolve()

Promise.resolve(value)方法返回一个以给定值resolve掉的Promise对象。但如果这个值是thenable的(就是说带有then方法),返回的promise会“追随”这个thenable的对象,接收它的最终状态(指resolved/rejected/pendding/settled);否则这个被返回的promise对象会以这个值被fulfilled。
语法

    Promise.resolve(value);
    Promise.resolve(promise);
    Promise.resolve(thenable);

value用来resolve待返回的promise对象的参数。既可以是一个Promise对象也可以是一个thenable。

静态方法 Promise.resolve返回一个Promise对象,这个Promise对象是被resolve的。
示例

使用静态方法Promise.resolve


    Promise.resolve("Success").then(function(value) {
      console.log(value); // "Success"
    }, function(value) {
      // not called
    });

以一个数组进行resolve

    var p = Promise.resolve([1,2,3]);
    p.then(function(v) {
      console.log(v[0]); // 1
    });

resolve另一个Promise对象

    var original = Promise.resolve(true);
    var cast = Promise.resolve(original);
    cast.then(function(v) {
      console.log(v); // true
    });

Symbol

Symbol简介

  ES6引入了一种新的原始数据类型Symbol,表示独一无二的值。它是JavaScript语言的第七种数据类型,前六种是:Undefined、Null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。

  Symbol值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的Symbol类型。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

    let s = Symbol();
    typeof s
    // "symbol"

  注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的Symbol是一个原始类型的值,不是对象。也就是说,由于Symbol值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。

Symbol函数可以接受一个字符串作为参数,表示对Symbol实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

Symbol函数的参数只是表示对当前Symbol值的描述,因此相同参数的Symbol函数的返回值是不相等的。

    var s1 = Symbol("foo");
    var s2 = Symbol("foo");
    console.log(s1 === s2); // false

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

但是,Symbol值可以显式转为字符串。

作为属性名的Symbol

  由于每一个Symbol值都是不相等的,这意味着Symbol值可以作为标识符,用于对象的属性名,就能保证不会出现同名的属性。这对于一个对象由多个模块构成的情况非常有用,能防止某一个键被不小心改写或覆盖。

    var mySymbol = Symbol();
    // 第一种写法
    var a = {};
    a[mySymbol] = 'Hello!';
    // 第二种写法
    var a = {
      [mySymbol]: 'Hello!'
    };
    // 第三种写法
    var a = {};
    Object.defineProperty(a, mySymbol, { value: 'Hello!' });
    // 以上写法都得到同样结果
    console.log(a[mySymbol]); // "Hello!"

上面代码通过方括号结构和Object.defineProperty,将对象的属性名指定为一个Symbol值。

Symbol值作为对象属性名时,不能用点运算符。

在对象的内部,使用Symbol值定义属性时,Symbol值必须放在方括号之中。

    let s = Symbol();
    let obj = {
      [s](arg) { ... }
    };

Symbol类型还可以用于定义一组常量,保证这组常量的值都是不相等的。

Symbol值作为属性名时,该属性还是公开属性,不是私有属性。

属性名遍历

Symbol作为属性名,该属性不会出现在for…in、for…of循环中,也不会被Object.keys()、Object.getOwnPropertyNames()返回。但是,它也不是私有属性,有一个Object.getOwnPropertySymbols方法,可以获取指定对象的所有Symbol属性名。

Object.getOwnPropertySymbols方法返回一个数组,成员是当前对象的所有用作属性名的Symbol值。

    var obj = {};
    var a = Symbol('a');
    var b = Symbol('b');

    obj[a] = 'Hello';
    obj[b] = 'World';
    var objectSymbols = Object.getOwnPropertySymbols(obj);
    console.log(objectSymbols);
    // [Symbol(a), Symbol(b)]

Symbol.for()

使用给定的key搜索现有符号,如果找到则返回符号。否则将得到一个新的使用给定的key在全局符号注册表中创建的符号。

  有时,我们希望重新使用同一个Symbol值,Symbol.for方法可以做到这一点。它接受一个字符串作为参数,然后搜索有没有以该参数作为名称的Symbol值。如果有,就返回这个Symbol值,否则就新建并返回一个以该字符串为名称的Symbol值。

    var s1 = Symbol.for('foo');
    var s2 = Symbol.for('foo');
    console.log(s1 === s2); // true

上面代码中,s1和s2都是Symbol值,但是它们都是同样参数的Symbol.for方法生成的,所以实际上是同一个值。

  Symbol.for()与Symbol()这两种写法,都会生成新的Symbol。它们的区别是,前者会被登记在全局环境中供搜索,后者不会。Symbol.for()不会每次调用就返回一个新的Symbol类型的值,而是会先检查给定的key是否已经存在,如果不存在才会新建一个值。比如,如果你调用Symbol.for(“cat”) 30次,每次都会返回同一个Symbol值,但是调用Symbol(“cat”) 30次,会返回30个不同的Symbol值。

    Symbol.for("bar") === Symbol.for("bar")
    // true
    Symbol("bar") === Symbol("bar")
    // false

上面代码中,由于Symbol()写法没有登记机制,所以每次调用都会返回一个不同的值。

Symbol.keyFor()

为给定符号从全局符号注册表中检索一个共享符号键。

Symbol.keyFor方法返回一个已登记的Symbol类型值的key。

    var s1 = Symbol.for("foo");
    Symbol.keyFor(s1) // "foo"

    var s2 = Symbol("foo");
    Symbol.keyFor(s2) // undefined

上面代码中,变量s2属于未登记的Symbol值,所以返回undefined。


箭头函数=>

基本用法

箭头函数就是个是简写形式的函数表达式,并且它拥有词法作用域的this值.箭头函数总是匿名的.

ES6允许使用“箭头”(=>)定义函数。

    var f = v => v;

    //等同于:

    var f = function(v) {
      return v;
    };

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分。

    var f = () => 5;
    // 等同于
    var f = function (){ return 5 };

    var sum = (num1, num2) => num1 + num2;
    // 等同于
    var sum = function(num1, num2) {
      return num1 + num2;
    };

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回。

    var sum = (num1, num2) => { return num1 + num2; }

由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号。

    var getTempItem = id => ({ id: id, name: "Temp" });

简化回调函数

箭头函数的一个用处是简化回调函数。

  // 正常函数写法
    [1,2,3].map(function (x) {
      return x * x;
    });

    // 箭头函数写法
    [1,2,3].map(x => x * x);

另一个例子是

  // 正常函数写法
    var result = values.sort(function(a, b) {
      return a - b;
    });

    // 箭头函数写法
    var result = values.sort((a, b) => a - b);

如果不用箭头函数,可能就要占用多行,而且还不如现在这样写醒目。

使用注意点

箭头函数有几个使用注意点。

(1)函数体内的this对象,绑定定义时所在的对象,而不是使用时所在的对象。

(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。

(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用Rest参数代替。

(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。

this对象的指向是可变的,但是在箭头函数中,它是固定的。

    function Timer () {
      this.seconds = 0
      setInterval(() => this.seconds++, 1000)
    }
    var timer = new Timer()
    setTimeout(() => console.log(timer.seconds), 3100)
    // 3

上面代码中,Timer函数内部的setInterval-调用了_this.seconds-属性,通过箭头函数将_this绑定在Timer的实例对象。否则,输出结果是0,而不是3。


模板字符串

模板字符串简介

模板字符串允许嵌入表达式,并且支持多行字符串和字符串插补特性。

  模板字符串使用反引号 ( ) 来代替普通字符串中的用双引号和单引号。模板字符串可以包含特定语法(${expression})的占位符。占位符中的表达式和周围的文本会一起传递给一个默认函数,该函数负责将所有的部分连接起来,如果一个模板字符串由表达式开头,则该字符串被称为带标签的模板字符串,该表达式通常是一个函数,它会在模板字符串处理后被调用,在输出最终结果前,你都可以在通过该函数对模板字符串来进行操作处理。
语法

    'string text'

    'string text line 1'
    'string text line 2'

    'string text ${expression} string text'

    tag 'string text ${expression} string text'

多行字符串

在新行中插入的任何字符都是模板字符串中的一部分,使用普通字符串,你可以通过以下的方式获得多行字符串:

    console.log("string text line 1\n\
    string text line 2");
    // "string text line 1
    // string text line 2"

要获得同样效果的多行字符串,只需使用如下代码:

    console.log(`string text line 1
    string text line 2`);
    // "string text line 1
    // string text line 2"

表达式插补

在普通字符串中嵌入表达式,必须使用如下语法:

    var a = 5;
    var b = 10;
    console.log("Fifteen is " + (a + b) + " and\nnot " + (2 * a + b) + ".");
    // "Fifteen is 15 and
    // not 20."

现在通过模板字符串,我们可以使用一种更优雅的方式来表示:

    var a = 5;
    var b = 10;
    console.log(`Fifteen is ${a + b} and\nnot ${2 * a + b}.`);
    // "Fifteen is 15 and
    // not 20."

带标签的模板字符串

  模板字符串的一种更高级的形式称为带标签的模板字符串。它允许您通过标签函数修改模板字符串的输出。标签函数的第一个参数是一个包含了字符串字面值的数组(在本例中分别为“Hello”和“world”);第二个参数,在第一个参数后的每一个参数,都是已经被处理好的替换表达式(在这里分别为“15”和“50”)。 最后,标签函数返回处理好的字符串。在后面的示例中,标签函数的名称可以为任意的合法标示符。

    var a = 5;
    var b = 10;

    function tag(strings) {
      console.log(arguments);    // { '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }
      console.log(strings[0]);   // "Hello "
      console.log(strings[1]);   // " world "
      console.log(arguments[1]);  // 15
      console.log(arguments[2]);  // 50

      return "Hubwiz!";
    }

    console.log(tag`Hello ${ a + b } world ${ a * b}`);
    // "Hubwiz!"

我们打印出arguments可以看到 处理的参数为

    { '0': [ 'Hello ', ' world ', '' ], '1': 15, '2': 50 }

我们已有的Hello和world参数放到一个数组中,后续处理的参数依次生成。

原始字符串

在标签函数的第一个参数中,存在一个特殊的属性raw ,我们可以通过它来访问模板字符串的原始字符串。

    function tag(strings, values) {
      console.log(strings.raw[0]); 
      // "string text line 1 \\n string text line 2"
    }

    tag'string text line 1 \n string text line 2';

另外,使用String.raw() 方法创建原始字符串和使用默认模板函数和字符串连接创建是一样的。

    String.raw`Hi\n${2+3}!`;
    // "Hi\\n5!"

安全性

由于模板字符串能够访问变量和函数,因此不能由不受信任的用户来构造。

    "use strict"
    let a = 10;
    console.warn(`${a+=20}`); // "30"
    console.warn(a); // 30

新特性暂时就告一段落,有需要补充的欢迎大家联系我。

发布了46 篇原创文章 · 获赞 675 · 访问量 75万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章