ES6新特性(中篇)

ES6新特性(中篇)主要内容讲解

  1. Promise对象
  2. Symbol运算符
  3. Iterator遍历器
  4. Generator函数、async函数
  5. class关键字

Promise对象是什么?

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大。它由社区最早提出和实现,ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
Promise,简单说就是一个容器,里面保存着某个未来才会结束的事件(通常是一个异步操作)的结果。从语法上说,Promise 是一个对象,从它可以获取异步操作的消息。Promise 提供统一的 API,各种异步操作都可以用同样的方法进行处理。

Promise对象有以下两个特点:
①对象的状态不受外界影响。Promise对象代表一个异步操作,有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
②一旦状态改变,就不会再变,任何时候都可以得到这个结果。Promise对象的状态改变,只有两种可能:从pending变为fulfilled和从pending变为rejected。只要这两种情况发生,状态就凝固了,不会再变了,会一直保持这个结果,这时就称为resolved(已定型)。
Promise对象的用法:
es6 规定,Promise对象是一个构造函数,用来生成Promise实例。
例:

var promis = new Promise(function (resolve,reject) {
    if(2/*异步操作成功*/){
        resolve(value);
    }else {
        reject(value);
    }
    //resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”(即从 pending 变为 resolved),在异步操作成功时调用,并将异步操作的结果,作为参数传递出去;
    //reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”(即从 pending 变为 rejected),在异步操作失败时调用,并将异步操作报出的错误,作为参数传递出去。
})

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

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

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

function getJson(url) {
    var promise = new Promise(function (resolve,reject) {
        var ajax = new XMLHttpRequest();
        ajax.open("GET",url);
        ajax.send();
        ajax.onreadystatechange = function () {
            if(ajax.readyState != 4){
                return;
            }
            if(ajax.status == 200){
                resolve(ajax.response);
            }else {
                reject(new Error(ajax.statusText));
            }
        }
    });
    return promise;
}
//调用,then回调
getJson("file.json").then(function (json) {
console.log(json);
},function (text) {
console.log(text);
});

上面代码中,getJson是对XMLHttpRequest对象的封装,用于发出一个针对JSON数据的HTTP请求,并返回一个Priomis对象。
需要注意的是,resolve等函数都带有参数,这些参数会传递给回调函数,reject函数的参数通常是Error对象的实例,表示抛出的错误。

Promise.prototype.then()
Promise实例具体then方法,因此then方法是被定义在对象原型上的,他的作用是为Promise实例添加状态改变时的回调函数。
语法:Promise实例.then(function (){成功时调用},function(){失败时调用}可选)。
例:

getJson("file.json").then(function (json) {
console.log(json);
});

then方法也是会有返回值,返回是一个新的Promise对象,因此我们可以采用链式操作,即then方法后面调用另一个then方法。
例:

getJson("file.json").then(function (json) {
    return getJson("file.txt");
}).then(function () {
    console.log("Fd");
},function () {
    console.log("fd");
})

Symbol运算符是什么?

ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是 ES6 引入Symbol的原因。
ES6 引入了一种新的原始数据类型Symbol,表示独一无二的值。它是 JavaScript 语言的第七种数据类型,前六种是:undefined、null、布尔值(Boolean)、字符串(String)、数值(Number)、对象(Object)。
Symbol 值通过Symbol函数生成。这就是说,对象的属性名现在可以有两种类型,一种是原来就有的字符串,另一种就是新增的 Symbol 类型。凡是属性名属于 Symbol 类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。

Symbol运算符基本用法

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

上面代码中,变量s就是一个独一无二的值。typeof运算符的结果,表明变量s是 Symbol 数据类型,而不是字符串之类的其他类型。
注意,Symbol函数前不能使用new命令,否则会报错。这是因为生成的 Symbol 是一个原始类型的值,不是对象。也就是说,由于 Symbol 值不是对象,所以不能添加属性。基本上,它是一种类似于字符串的数据类型。
Symbol函数可以接受一个字符串作为参数,表示对 Symbol 实例的描述,主要是为了在控制台显示,或者转为字符串时,比较容易区分。

let s1 = Symbol('A');
let s2 = Symbol('B');

s1 // Symbol(foo)
s2 // Symbol(bar)

s1.toString() // "Symbol(A)"
s2.toString() // "Symbol(B)"

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

// 没有参数的情况
let s1 = Symbol();
let s2 = Symbol();

s1 === s2 // false

// 有参数的情况
let s1 = Symbol('A');
let s2 = Symbol('A');

s1 === s2 // false

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

let mySymbol = Symbol();

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

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

注意:for in, for of遍历时不会遍历symbol属性。

Iterator遍历器是什么?

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

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

Iterator 的遍历过程:
(1)创建一个指针对象,指向当前数据结构的起始位置。也就是说,遍历器对象本质上,就是一个指针对象。
(2)第一次调用指针对象的next方法,可以将指针指向数据结构的第一个成员。
(3)第二次调用指针对象的next方法,指针就指向数据结构的第二个成员。
(4)不断调用指针对象的next方法,直到它指向数据结构的结束位置。
下面模拟遍历过程代码例:

var it = makeIterator(['a', 'b']);

it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }

function makeIterator(array) {
  var nextIndex = 0;
  return {
    next: function() {
      return nextIndex < array.length ?
        {value: array[nextIndex++], done: false} :
        {value: undefined, done: true};
    }
  };
}

原生具备 Iterator 接口的数据结构如下。
Array、Map、Set、String、TypedArray、函数的 arguments 对象、NodeList 对象
下面的例子是数组的Symbol.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 }

Generator函数是什么?

Generator 函数是 ES6 提供的一种异步编程解决方案,语法行为与传统函数完全不同。Generator 函数有多种理解角度。语法上,首先可以把它理解成,Generator 函数是一个状态机,封装了多个内部状态。
执行 Generator 函数会返回一个遍历器对象,也就是说,Generator 函数除了状态机,还是一个遍历器对象生成函数。返回的遍历器对象,可以依次遍历 Generator 函数内部的每一个状态。

形式上,Generator 函数是一个普通函数,但是有两个特征:
一是:function关键字与函数名之间有一个星号;
二是:函数体内部使用yield表达式,定义不同的内部状态。
例:

function* helloGenerator() {
  yield 'hello';
  yield 'Generator';
  return 'ending';
}

var hw = helloGenerator();

上面代码定义了一个 Generator 函数helloGenerator,它内部有两个yield表达式(hello和Generator),即该函数有三个状态:hello,Generator和 return 语句(结束执行)。
然后,Generator 函数的调用方法与普通函数一样,也是在函数名后面加上一对圆括号。不同的是,调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一节介绍的遍历器对象(Iterator Object)。
下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
例:

hw.next()
// { value: 'hello', done: false }

hw.next()
// { value: 'Generator ', 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表达式的值Generator,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属性是一个布尔值,表示是否遍历结束。

Generator yield表达式

由于 Generator 函数返回的遍历器对象,只有调用next方法才会遍历下一个内部状态,所以其实提供了一种可以暂停执行的函数。yield表达式就是暂停标志。
遍历器对象的next方法的运行逻辑如下。
(1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。
(2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。
(3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。
(4)如果该函数没有return语句,则返回的对象的value属性值为undefined。
需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。

function* gen() {
  yield  12 + 45
}

上面代码中,yield后面的表达式12+ 45,不会立即求值,只会在next方法将指针移到这一句时,才会求值。
yield表达式与return语句既有相似之处,也有区别。相似之处在于,都能返回紧跟在语句后面的那个表达式的值。区别在于每次遇到yield,函数暂停执行,下一次再从该位置继续向后执行,而return语句不具备位置记忆的功能。一个函数里面,只能执行一次(或者说一个)return语句,但是可以执行多次(或者说多个)yield表达式。正常函数只能返回一个值,因为只能执行一次return;Generator 函数可以返回一系列的值,因为可以有任意多个yield。从另一个角度看,也可以说 Generator 生成了一系列的值,这也就是它的名称的来历(英语中,generator 这个词是“生成器”的意思)。

Generator next 方法的参数

yield表达式本身没有返回值,或者说总是返回undefined。next方法可以带一个参数,该参数就会被当作上一个yield表达式的返回值。

function* main(){
    var r1 = yield 'hello'
    console.log(r1)
    var r2 = yield 'ujiuye'
    console.log(r2)
}

var ge = main()
ge.next()
ge.next('HELLO')
ge.next('UIJUYE')

则上述代码依次输出 HELLO和UJIUYE

async函数是什么?

ES2017 标准引入了 async 函数,使得异步操作变得更加方便。async 函数其实就是 Generator 函数的语法糖。async函数就是将 Generator 函数的星号 * 替换成async,将yield替换成await
async函数返回一个 Promise 对象,可以使用then方法添加回调函数。当函数执行的时候,一旦遇到await就会先返回,等到异步操作完成,再接着执行函数体内后面的语句。

例:

function timeout(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
}

asyncPrint('hello world', 1000);

上面代码指定 1000 毫秒以后,输出hello world。

class关键字是什么?

*ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
基本上,ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
JavaScript 语言中,生成实例对象的传统方法是通过构造函数。如下示例:

function Person( name ){
    this.name = name
}

Person.prototype.say= function () {
    console.log(this.name+'小明')
}
 var p1 = new Person('小明')
 p1.say()

上面的代码用 ES6 的class改写,就是下面这样。

class Person {
        //调用类的构造方法
        constructor(name, age){
            this.name = name;
            this.age = age;

        }
        //定义一般的方法
        showName(){
            console.log(this.name, this.age);
        }
    }
    let person = new Person('小明', 10);
    console.log(person, person.showName());

上面代码定义了一个“类”,可以看到里面有一个constructor方法,这就是构造方法,而this关键字则代表实例对象。也就是说,ES5 的构造函数Person,对应 ES6 的Person类的构造方法。
继承
Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。
语法:

class Father{
}

class Son extends Father {
}

上面代码定义了一个Son 类,该类通过extends关键字,继承了Father类的所有属性和方法。但是由于没有部署任何代码,所以这两个类完全一样,等于复制了一个Father类。下面,我们是具体实例。

 class Students extends Person{
        constructor(name, age, city){
            super(name, age);//调用父类的构造方法
            this.city = city;
        }
        showName(){//在子类自身定义方法
            console.log(this.name, this.age, this.city);
        }
    }
    let str = new Students ('小明', 20, '北京');
    console.log(str);
    str.showName();

子类必须在constructor方法中调用super方法,否则新建实例时会报错。这是因为子类自己的this对象,必须先通过父类的构造函数完成塑造,得到与父类同样的实例属性和方法,然后再对其进行加工,加上子类自己的实例属性和方法。如果不调用super方法,子类就得不到this对象。

以上是我对ES6新特性中部分的理解,下部分将在后续两篇文章中详细讲解,欢迎大家互相交流!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值