ES6--JavaScript的第六个版本

 一、新的变量声明方式 let/cons

与var不同,新的变量声明方式带来了一些不一样的特性,其中最重要的两个特性就是提供了块级作用域与不再具备变量提升。

若是对变量提升不怎么了解的话可以去参考我的其他文章   javascript预编译的过程 。

什么是块级作用域腻?

写在 “{}” 内的内容   都是块级作用域。

在es6之前,我们想保护一个变量怎么办,将其放在一个立即执行函数里面,写在局部作用域中。

这样写是不是挺麻烦的捏,多谢几个单词不花时间么?!

在ES6中,我们想保护一个变量 只要 写在花括号中就好了。

{

  let   a=10;

}

下面有两个例子:

{
    let a = 20;
}

console.log(a);  // a is not defined
//写在块级作用域中的内容会被保护起来,所以会打印  a is not defined

而这个简单的例子,会被编译为:

{
let _a = 20;
}
console.log(a); // a is not defined


// ES5
console.log(a); // undefined
var a = 20;
//  在es5中var一个变量 会变量提升
//  如同
//  var a;
//  console.log(a); //从上向下顺序执行 当然会打印 undefined 的捏。
//  a=20


// ES6 
console.log(a); // a is not defined 
let a = 20;
//变量不会提升 打印时当然是 a is not defined

当然,你的代码编译成为了ES5之后,仍然会存在变量提升,因此这一点只需要我们记住即可。

在实际使用中,也需要尽量避免使用变量提升的特性带来的负面影响。只有在面试题中,才会对变量提升不停的滥用。使用ES6,我们需要全面使用let/const替换var,那么什么时候用let,什么时候用const就成为了一个大家要熟练区分的一个知识点。

我们常常使用let来声明一个值会被改变的变量,而使用const来声明一个值不会被改变的变量,也可以称之为常量。当值为基础数据类型时,那么这里的值,就是指值本身。而当值对应的为引用数据类型时,那么我这里说的值,则表示指向该对象的引用。

这里需要注意,正因为该值为一个引用,只需要保证引用不变就可以,我们仍然可以改变该引用所指向的对象。当我们试图改变const声明的变量时,则会报错。

写几个例子,大家可以仔细揣摩一下:

let a = null;
a = 20;
const obDev = {
    a: 20,
    b: 30
}

obDev.a = 30;

console.log(obDev); // Object {a: 30, b: 30}
const fn = function() {}
const a = obDev.a;
... ...

只要抓住上面我说的特性,那么在使用let/const时就会显得游刃有余。根据我自己的经验,使用const的场景要比使用let的场景多很多。

 

二,解构赋值

我们经常定义许多对象和数组,然后有组织地从中提取相关的信息片段。在ES6中添加了可以简化这种任务的新特性:解构。解构是一种打破数据结构,将其拆分为更小部分的过程。

在ES5中,开发者们为了从对象和数组中获取特定数据并赋值给变量,编写了许多看起来同质化的代码

let options = {
    repeat: true,
    save: false
};
// 从对象中提取数据
let repeat = options.repeat,
save = options.save;

  这段代码从options对象中提取repeat和save的值,并将其存储为同名局部变量,提取的过程极为相似

  如果要提取更多变量,则必须依次编写类似的代码来为变量赋值,如果其中还包含嵌套结构,只靠遍历是找不到真实信息的,必须要深入挖掘整个数据结构才能找到所需数据

  所以ES6添加了解构功能,将数据结构打散的过程变得更加简单,可以从打散后更小的部分中获取所需信息

 对象解构:

对象字面量的语法形式是在一个赋值操作符左边放置一个对象字面量。

let node = {
    type: "Identifier",
    name: "foo"
};
let { type, name } = node;
console.log(type); // "Identifier"
console.log(name); // "foo"
let node = {
    type: "Identifier",
    name: "foo"
},
type = "Literal",
name = 5;
// 使用解构来分配不同的值
({ type, name } = node);
console.log(type); // "Identifier"
console.log(name); // "foo"

 

在这个示例中,声明变量type和name时初始化了一个值,在后面几行中,通过解构赋值的方法,从node对象读取相应的值重新为这两个变量赋值

  [注意]一定要用一对小括号包裹解构赋值语句,JS引擎将一对开放的花括号视为一个代码块。语法规定,代码块语句不允许出现在赋值语句左侧,添加小括号后可以将块语句转化为一个表达式,从而实现整个解构赋值过程。

数组解构:

与对象解构的语法相比,数组解构就简单多了,它使用的是数组字面量,且解构操作全部在数组内完成,而不是像对象字面量语法一样使用对象的命名属性

let colors = [ "red", "green", "blue" ];
let [ firstColor, secondColor ] = colors;
console.log(firstColor); // "red"
console.log(secondColor); // "green"

  在这段代码中,我们从colors数组中解构出了"red"和"green"这两个值,并分别存储在变量firstColor和变量secondColor中。在数组解构语法中,我们通过值在数组中的位置进行选取,且可以将其存储在任意变量中,未显式声明的元素都会直接被忽略

  在解构模式中,也可以直接省略元素,只为感兴趣的元素提供变量名

let colors = [ "red", "green", "blue" ];
let [ , , thirdColor ] = colors;
console.log(thirdColor); // "blue"

  这段代码使用解构赋值语法从colors中获取第3个元素,thirdColor前的逗号是前方元素的占位符,无论数组中的元素有多少个,都可以通过这种方法提取想要的元素,不需要为每一个元素都指定变量名

混合解构:

  可以混合使用对象解构和数组解构来创建更多复杂的表达式,如此一来,可以从任何混杂着对象和数组的数据解构中提取想要的信息

let node = {
    type: "Identifier",
    name: "foo",
    loc: {
        start: {
            line: 1,
            column: 1
        },
        end: {
            line: 1,
            column: 4
        }
    },
    range: [0, 3]
};
let {
    loc: { start },
    range: [ startIndex ]
} = node;
console.log(start.line); // 1
console.log(start.column); // 1
console.log(startIndex); // 0

  这段代码分别将node.loc.start和node.range[0]提取到变量start和startlndex中

  解构模式中的loc和range仅代表它们在node对象中所处的位置(也就是该对象的属性)。当使用混合解构的语法时,则可以从node提取任意想要的信息。这种方法极为有效,尤其是从JSON配置中提取信息时,不再需要遍历整个结构了

 

三、 箭头函数的使用

之前我说ES6颠覆了js的编码习惯,箭头函数的使用占了很大一部分。

首先是写法上的不同:

// es5
var fn = function(a, b) {
    return a + b;
}

// es6 箭头函数写法,当函数直接被return时,可以省略函数体的括号
const fn = (a, b) => a + b;

// es5
var foo = function() {
    var a = 20var b = 30;
    return a + b;
}

// es6
const foo = () => {
   const a = 20;
   const b = 30;
   return a + b;
}

箭头函数可以替换函数表达式,但是不能替换函数声明

其次还有一个至关重要的一点,那就是箭头函数中,没有this。如果你在箭头函数中使用了this,那么该this一定就是外层的this。

也正是因为箭头函数中没有this,因此我们也就无从谈起用call/apply/bind来改变this指向。记住这个特性,能让你在react组件之间传值时少走无数弯路。

var person = {
    name: 'tom',
    getName: function() {
        return this.name;
    }
}

// 我们试图用ES6的写法来重构上面的对象
const person = {
    name: 'tom',
    getName: () => this.name
}

// 但是编译结果却是
var person = {
    name: 'tom',
    getName: function getName() {
        return undefined.name;
    }
};

在ES6中,会默认采用严格模式,因此this也不会自动指向window对象了,而箭头函数本身并没有this,因此this就只能是undefined,这一点,在使用的时候,一定要慎重慎重再慎重,不然踩了坑你都不知道自己错在哪!这种情况,如果你还想用this,就不要用使用箭头函数的写法。

// 可以稍做改动
const person = {
    name: 'tom',
    getName: function() {
        return setTimeout(() => this.name, 1000);
    }
}

// 编译之后变成
var person = {
    name: 'tom',
    getName: function getName() {
        var _this = this;  // 使用了我们在es5时常用的方式保存this引用

        return setTimeout(function () {
            return _this.name;
        }, 1000);
    }
};

先记住箭头函数的写法,并留意箭头函数中关于this的特殊性,更过实践与注意事项我们在封装react组件时再慢慢来感受。

还有就是  原函数中  arguments (实参) 在箭头函数中是是用不了的。

 

四,promise  (承诺)

 他就是一个对象,主要是用来处理异步数据的。

在promise中,有三种状态:

pending(等待,处理中)   -->  1.resolve(完成)   2.rejected(失败,拒绝)。

又,这三种状态的变化只有两种模式,并且一旦状态改变,就不会再变:

  1、异步操作从pending到resolved;

  2、异步操作从pending到rejected;

好了,既然它是属于ES6规范,我们再通过chrome,直接打印出Promise,看看这玩意:

 

恩,一目了然,Promise为构造函数,欧克,这样通过它,我们就可以实例化自己的Promise对象了,并加以利用。 

Promise对象中的then方法

可以接收构造函数中处理的状态变化,并分别对应执行。then方法有2个参数,第一个函数接收resolved状态的执行,第二个参数接收reject状态的执行。

function fn(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    }).then(function() {
        console.log('参数是一个number值');
    }, function() {
        console.log('参数不是一个number值');
    })
}

fn('hahha');
fn(1234);

 

then方法的执行结果也会返回一个Promise对象。因此我们可以进行then的链式执行,这也是解决回调地狱的主要方式。

function fn(num) {
    return new Promise(function(resolve, reject) {
        if (typeof num == 'number') {
            resolve();
        } else {
            reject();
        }
    })
    .then(function() {
        console.log('参数是一个number值');
    })
    .then(null, function() {
        console.log('参数不是一个number值');
    })
}

fn('hahha');
fn(1234);

 

then(null, function() {}) 就等同于catch(function() {})

catch的用法

我们知道Promise对象除了then方法,还有一个catch方法,它是做什么用的呢?其实它和then的第二个参数一样,用来指定reject的回调,用法是这样:
getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});

效果和写在then的第二个参数里面一样。不过它还有另外一个作用:在执行resolve的回调(也就是上面then中的第一个参数)时,如果抛出异常了(代码出错了),那么并不会报错卡死js,而是会进到这个catch方法中。请看下面的代码:

getNumber()
.then(function(data){
    console.log('resolved');
    console.log(data);
    console.log(somedata); //此处的somedata未定义
})
.catch(function(reason){
    console.log('rejected');
    console.log(reason);
});
 
在resolve的回调中,我们console.log(somedata);而somedata这个变量是没有被定义的。如果我们不用Promise,代码运行到这里就直接在控制台报错了,不往下运行了。但是在这里,会得到这样的结果:
 
 
也就是说进到catch方法里面去了,而且把错误原因传到了reason参数中。即便是有错误的代码也不会报错了,这与我们的try/catch语句有相同的功能。

all的用法

Promise的all方法提供了并行执行异步操作的能力,并且在所有异步操作执行完后才执行回调。我们仍旧使用上面定义好的runAsync1、runAsync2、runAsync3这三个函数,看下面的例子:
 
Promise
.all([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
用Promise.all来执行,all接收一个数组参数,里面的值最终都算返回Promise对象。这样,三个异步操作的并行执行的,等到它们都执行完后才会进到then里面。那么,三个异步操作返回的数据哪里去了呢?都在then里面呢,all会把所有异步操作的结果放进一个数组中传给then,就是上面的results。所以上面代码的输出结果就是:
 
有了all,你就可以并行执行多个异步操作,并且在一个回调中处理所有的返回数据,是不是很酷?有一个场景是很适合用这个的,一些游戏类的素材比较多的应用,打开网页时,预先加载需要用到的各种资源如图片、flash以及各种静态文件。所有的都加载完后,我们再进行页面的初始化。

race的用法

all方法的效果实际上是「谁跑的慢,以谁为准执行回调」,那么相对的就有另一个方法「谁跑的快,以谁为准执行回调」,这就是race方法,这个词本来就是赛跑的意思。race的用法与all一样,我们把上面runAsync1的延时改为1秒来看一下:
Promise
.race([runAsync1(), runAsync2(), runAsync3()])
.then(function(results){
    console.log(results);
});
这三个异步操作同样是并行执行的。结果你应该可以猜到,1秒后runAsync1已经执行完了,此时then里面的就执行了。结果是这样的:

 

 


posted @ 2019-03-06 10:16  徐小禹_web  阅读(2079)  评论(0编辑  收藏  举报