ES6你必须掌握的新特性

一:Class基本用法与继承

class Surname {  
    constructor(surname) { // 构造函数  
        this.surname = surname;  
    }  
    sayName() {  
        return this.surname  
    }  
} 

上面定义了一个Surname类,它有一个属性surname和一个方法sayName()。

class FirstName extends Surname {  
    constructor(surname, firstName) { // 构造函数  
        super(surname);    // 调用父类构造函数  
        this.firstName = firstName;  
    }  
    parent(){  
        return this.sayName()  
    }  
}  

接着定义一个FirstName类,使用extends来继承于Surname。注意,必须使用super关键字来引用父类的属性和方法。

var cp = new FirstName("B","everly__");  
console.log(cp.surname+cp.firstName);  
console.log(cp.parent());  
console.log(cp instanceof Surname);  
console.log(cp instanceof FirstName);  

仔细看上面的四行console.log会输出什么!

二:箭头函数

() => 1;  
// 其实这是一个自执行的匿名函数
(function(){
    return 1;
})

当然我们也可以给个参数(注意:参数就参数 ,别给个数字摆在那边~那是参数吗??)

(a) => 1 + a
// 相当于
(function(a){
    return 1 + a
})

如果你不想要( )也行,但是只能有一个参数

v => v + 1
// 相当于
(function(v){
    return v + 1
})

如果你说我不想要参数,又不想要给它( ),可以吗???扯犊子吧你!

(a,b) => a + b
// 相当于
(function(a, b){
    return a + b
})

注意:相当于不是完全等于,箭头函数没有constructor,没有prototype,没有this,所以不支持new操作符,不可以当做构造函数。但是箭头函数对于this的处理和其他函数不一样。箭头函数中的this始终指向函数定义时的this,而非执行的时候。也就是说箭头函数执行时,会向外层函数查找this,直到找到this就拿来为己用!

三:块级作用域let和const

——let——

你可能早就听说过ES6中let那些奇怪的传说,那么什么是块级:块级就是一个{},用let和const定义的变量的作用域会被限制在当前块内。

在ES5中存在一个 很经典的循环事件绑定的问题,我们可以使用数组模拟dom集合来还原这个问题:

var arr = [];

for(var i = 0; i <6; i++){
    arr.push(function () {
        console.log(i);
    });
}

arr[0]();
arr[1]();
arr[2]();

不难理解,arr i 输出的都会是5,因为在ES5中不存在块级作用域的概念,在for循环的括号中声明的变量就像在外面声明的变量那样,每执行一次循环,新的i值就会覆盖旧的i值,导致最后输出的是以后一轮循环的i值。

而在ES6中,我们可以使用let声明变量来处理这个问题。let的用法与var类似,但是其声明的变量只在let命令所在的代码块中有效。

let arr = [];

for(let i = 0; i <6; i++){
        arr.push(function () {
            console.log(i);
            i++;
        });
}

arr[0]();
arr[0](); // 注意这里输出1
arr[1]();
arr[2]();

上面的代码中,i只在本轮循环中有效,每一次循环的i其实都是一个新的变量,于是最后输出0,1,1,2。

块级作用域的出现也使得广泛使用的匿名立即执行函数不再必要了。

(function(){
    var a = 10;
    ... ...
}())

// 等价于

{
    let a = 10;
}

let的死区:

使用let声明的变量不会出现像var那样存在“变量提升”现象。但本质上,二者是相同的,它们都会在初始化时先初始化属性,再初始化逻辑,然而二者的区别在于使用let声明的变量虽然一开始就存在,但是不能使用,而使用var声明的变量则可以。一定要在声明后使用,否则将会报错。

let foo = 'foo';
if(true){
    console.log(foo);
    let foo = 'foo bar';
    console.log(foo);
}

下面的代码在第一次输出foo的时候会报错,提示foo没有定义,这就是死区效应。

只要块级作用域内存在let命令,它所声明的变量就绑定在这个区域,不再受外部影响。ES6明确规定,只要块级作用域中存在let命令,则这个块区对这些命令声明的变量从一开始就形成封闭的作用域。只要声明之前,使用这些变量,就会报错。这样说有些抽象,你只需要记住:在块级作用域内如果使用let声明了某个变量,那么这个变量名必须在声明它的语句后使用,即使块外部的变量有与之重名的也不行。从块开头到声明变量的语句中间的部分,称为这个变量的“暂时性死区”。

不允许重复声明

let不允许在相同作用域内重复声明同一个变量,即同一个作用域内不允许出现名称相同的变量。

——const——

const与let的特性基本相同,但顾名思义,const用于声明常量,一旦声明,必须立即赋值,且以后不可更改。

cosnt foo // 报错,没有立即赋值
const foo = {name:'Zheng Aojin'}
foo.name = 'Jane'; 
foo.age = 25; 
foo = {name:'Kyle Hu'} // 报错,因为改变了引用关系

四:Promise对象

先来看看Promise长什么样子

var promise = new Promise(function (resolve, reject){
    if (success){
      return resolve(data);
    } else {
      return reject(data);
    }
});

promise.then(function(data){
    console.log(data);
}, function(err){
    // deal the err.
})

这里的promise变量是Promise的实例。

Promise对象在new的时候,接受一个回调函数作为参数,在Pomise对象创建的时候回执行回调函数里面的逻辑。

现在来认识两个Promise内的回调函数:resolve、reject,这两个函数什么时候调用由你自己来控制,如上代码,假如success为你的接口获取的数据,当它为真的时候调用resolve,else不用说了。也就是resolve是逻辑成功的回调,reject是捕获异常的回调。在实例化的promise.then(),then接受两个函数式参数,第一个即为resolve,同理反之。有的同学说,这也没什么用啊,我自己写回调也可以。那么请看下面的代码。

promise.then(function(data){
    console.log(data) .....................(1)
    return data + 'first'
}).then(function(data){
    console.log(data) .....................(2)
}).catch(function(err){
    console.log(err)  .....................(3)
})

此时的Promise就变成了另外一种链式调用的写法,相信聪明的同学已经看出来其中的奥妙了。

在Promise对象链式调用中,.then()即为resolve的回调。Promise的一个特殊性在于,Promise的then方法返回的依然是一个Promise对象。如果同一个resolve获取的数据,要经过处理,可以把处理过得数据return出来,传递给下一个.then()来用,这样上边的代码就不难理解了吧,第二个.then()其实是第一个.then()方法return的Promise对象,所以可以用.then来连接。在一个链式调用结构中,每一个then都返回一个Promise对象,那么每一个函数体内都有两个回调函数resolve和reject,如果是这样,那不又变成回调地狱了。

其实Promise还有另一个方法catch,用这个函数接受一个回调函数来统一处理错误。

现在假设上边的promise 获取的数据为一个字符串’get success’,即 data=’get success’,这样上边的三个console.log输出的结果为:

(1) 'get success'
(2) 'get success first'
(3) 'error'

注意: Promise对象是异步的,什么是异步的呢?就是一个.then()内的逻辑没执行完,不会执行下一个。

控制并发的Promise

Promise有一个”静态方法”——Promise.all(注意并非是promise.prototype), 这个方法接受一个元素是Promise对象的数组。

这个方法也返回一个Promise对象,如果数组中所有的Promise对象都resolve了,那么这些resolve的值将作为一个数组作为Promise.all这个方法的返回值的(Promise对象)的resolve值,之后可以被then方法处理。如果数组中任意的Promise被reject,那么该reject的值就是Promise.all方法的返回值的reject值.

很op的一点是:
then方法的第一个回调函数接收的resolve值(如上所述,是一个数组)的顺序和Promise.all中参数数组的顺序一致,而不是按时间顺序排序。

还有一个和Promise.all相类似的方法Promise.race,它同样接收一个数组,只不过它只接受第一个被resolve的值。

五:模板字符串

先来看一下以前我们对字符串的使用:

var name = 'CSDN博客';
var desc = 'CSDN深度IT技术博客,移动开发博客,Web前端博客,企业架构博客';
var html = function(name, desc){
    var tpl = '公司名:' + name + '\n'+
            '简介:'+ desc;
    return tpl;
}

而现在:

var html = `公司名:${name}
    简介:${desc}`;

在举个例子就明白了:

var x = 1;
var y = 2;
`${ x } + ${ y } = ${ x + y}`  // "1 + 2 = 3"

不同于普通字符串,模板字符串还可以多行书写,模板字符串中所有的空格,新行,缩进都会原样的输出在生成的字符串中。

而单纯的模板字符串还存在着很多的局限性。如:

  • 不能自动转义特殊的字符串。(这样很容易引起注入攻击) 不能很好的和国际化库
  • 配合(即不会格式化特定语言的数字,日期,文字等)
  • 没有内建循环语法,(甚至连条件语句都不支持, 只可以使用模板套构的方法)

六:ES Module – export import

export基本用法:

export function foo() {  
// ..  
}  
export var awesome = 42;  
var bar = [1,2,3];  
export { bar };  
function foo() {  
// ..  
}  
var awesome = 42;  
var bar = [1,2,3];  
export { foo, awesome, bar };  

导出的时候重命名:

function foo() { .. }  
export { foo as bar };  

默认导出,每个模块只能有一个默认导出:

function foo(..) {  
// ..  
}  
export default foo;  
export{ foo as default };  

混合默认导出和普通的导出:

function foo() { .. }  
function bar() { .. }  
function baz() { .. }  
export { foo as default, bar, baz, .. };  

从其他模块导出:

export { foo, bar } from "baz";  
export { foo as FOO, bar as BAR } from "baz";  
export * from "baz";  

import基本用法

import { foo } from "foo";  
foo();  
import { foo as theFooFunc } from "foo";  
theFooFunc(); 
import foo from "foo";  
// or:  
import { default as foo } from "foo";  
export default function foo() { .. }  
export function bar() { .. }  
export function baz() { .. }  

import FOOFN, { bar, baz as BAZ } from "foo";  
FOOFN();  
bar();  
BAZ();
export function bar() { .. }  
export var x = 42;  
export function baz() { .. }  

import * as foo from "foo";  
foo.bar();  
foo.x; // 42  
foo.baz(); 

import有一个hoisted的过程,和var变量提升一样:

foo();  
import { foo } from "foo";  

七:变量解构

ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构。

数组的解构赋值

ES6中对变量赋值可以写成下面的样式。

var [a,b,c] = [1,2,3];
//等同于下列三句
var a = 1;
var b = 2;
var c = 3;

本质上,这种写法属于‘模式匹配’,只要等号两边的模式相同,左边的变量就会被赋予对应的值。

let [foo,[[bar],baz]] = [1,[2],3]];
console.log(foo);//1
console.log(bar);//2
console.log(baz);//3

let [,,third] = ['foo','bar','baz'];
console.log(third)//'baz'

let [head,...tail] = [1,2,3,4];
console.log(head);//1
console.log(tail);//[2,3,4]

//当解构不成功的时候,变量的值为undefined
let [foo] = []; //foo值为undefined
let [bar,foo] = [1]; //foo值为undefined

不完全解构

当等号左边的模式只匹配等号右边数组的一部分时,解构依然可以成功。

let [x,y] = [1,2,3];//x值为1,y值为2
let [a,[b],d] = [1,[2,3],4];//a值为1,b值为2,d值为4

如果等号右边的不是可遍历的结构(数组、对象、Map和Set),那么将会报错。

let [foo] = 1;//报错

默认值
解构赋值允许指定默认值,当等号右边的值为undefined时,设置其值为默认值。这里需要主要,ES6内部使用严格相等运算符(===)判断一个位置是否有值。所以,如果一个数组成员不严格等于undefined,默认值是不会生效的。

var [foo = true] = [];//foo值为true
[x,y = 'b'] = ['a',undefined];//x = 'a',y = 'b'
var [x=1] = [null];//x=null

如果默认值是一个表达式,那么这个表达式是惰性求值的,即只有在用到的时候才会求值。

function f(){
    console.log('aaa');
}
let [x = f()] = [1];//函数f()不会执行

对象的解构赋值

解构不仅可以用于数组,还可以用于对象。但是对象的解构与数组的结构有一个重要的不同。数组的元素是按次序排列的,变量的取值是由它的位置决定的;而对象的属性没有次序,变量必须与属性同名,才能取到正确的值。

var {bar,foo} = {foo:'aaa',bar:'bbb'};//bar的值为‘bbb’ foo的值为'aaa'
var {baz} = {foo:'aaa',bar:'bbb'};//baz变量没有对应的同名属性 导致其值为undefined

对象的解构赋值的内部机制是先找到同名属性,然后在赋给对应的变量。真正被赋值的是后者,而不是前者。

var {foo:baz} = {foo:'aaa',bar:'bbb'};
console.log(baz);//'aaa'
console.log(foo);//error:foo is not defined

注意:采用解构赋值的写法时,变量的声明和赋值是一体的。对于let和const而言,变量不能重新声明,所以一旦赋值的变量以前声明过,就会报错。不过var命令允许重新声明,所以这个错误只会在使用let和const命令时出现。

let foo;
let {foo} = {foo:1};//SyntaxError: redeclaration of let foo

let baz;
let {bar:baz} = {bar :1};//SyntaxError: redeclaration of let baz

与数组解构赋值相同,对象的解构也可以指定默认值。默认值的生效条件是:对象的属性值严格等于undefined。

var {x = 3} = {}; //x = 3
var {x = 3} = {x : undefined};//x = 3
var {x = 3} = {x:null};//x = null

对象的解构赋值可以使用嵌套对象赋值。

var {foo:{bar}} = {foo:{bar:'aaa'}};//bar的值为‘aaa’

如果将一个已经声明的变量用于解构赋值,JavaScript引擎会将{}理解成一个代码块,从而发生语法错误。可通过外加小括号避免将其解释为代码块。

{x} = {x:1};//SyntaxError: expected expression, got '='
//正确写法
({x} = {x:1})//x的值为1

对象的解构赋值可以很方便地将现有对象的方法赋值到某个变量。

//将Math对象的取对数、正弦、余弦三个方法赋值到对应变量
let {log,sin,cos} = Math;
console.log(sin(1));

你可能感兴趣的:(ES6你必须掌握的新特性)