国庆假期已过一半,来篇干货压压惊。javascript
ES6,并非一个新鲜的东西,ES七、ES8已经赶脚了。可是,东西不在于新,而在于总结。每一个学前端的人,身边也一定有本阮老师的《ES6标准入门》或者翻译的《深刻理解ECMAScript6》。本篇主要是对ES6的一些经常使用知识点进行一个总结。若是你喜欢个人文章,欢迎评论,欢迎Star~。欢迎关注个人github博客html
咱们会更具以前的罗列的内容进行一个深刻的分析。前端
在ES6没有被普及时,咱们会用的变量定义的方法是var。其实,var对于一个刚刚接触js的人说,或许并不以为怪异。可是,对于一个开发者而言,或许会在心里抨击它。由于它就是javascript的败笔之一,在其余语言看来的一个怪胎。那咱们就来看看怪在何处呢?java
能够重复定义。不知道你的代码里面会不会出现这样子的代码,举例:python
var a = 10;
var a = 11;复制代码
或许,你会看到这样子的写法以为没啥,那么你很厉(wei)害(xian)。其实,这样子的坏处不言而喻。在大型的工程化开发中,你定义一个a,我定义一个a,还有千千万万个你和我,这时,技术总监就该着急了。因此,这是var的第一点使人讨厌的地方,可是若是你会说不是有严格模式嘛。的确,严格模式作了必定的规范,可是咱们不加以讨论。毕竟,这时ES6的地盘(^-^)。ios
可随意修改。何为可随意修改?并非指变量,而是指常量。举例:git
var PI = 3.1415926
PI = 4.1415926复制代码
从例子中,咱们能够看到,PI是一个咱们常常会使用的常量,是公认的不可变更的。但在javascript中并非如此。那天,若是你的PI被大家公司新晋的实习生改了,可能你找错误都得找半天,但这可不是实习生的锅,也许,他并不知道这里是个常量。不过,这种状况也是玩笑话(^_^)。github
没有块级做用域。若是你连块级做用域都不知道的话,赶忙收藏一下^_^,回头再来看哈~,举例:web
if(true){
var i = 10;
}
console.log(i); //10复制代码
相信,这变量不存在块级做用域给咱们带来过很多麻烦吧。不知道啥时候,又得在循环中套一层闭包呢。并且,在非js开发者来讲,可能以为是个特(xiao)点(hua)。正则表达式
因此,let和const就来拯救var了,如何一个拯救法呢?
在同一个块级做用域中,不容许重复定义。那以前的例子来使用一下的话,你会发现浏览器报错了,如图:
const定义的变量不容许二次修改。还原一下以前的例子,如图:
是否是不再用担忧以前的实习生啦,呦!!!
let和const定义的变量会造成块级做用域:直接上图,看看:
它们定义的变量不存在变量提高,以及存在暂时性死区
这个问题,我想举个例子能够更加方便的说明。首先,咱们来看一题简单的笔试题:
var a = 10;
function hello(){
console.log(a);
var a = 11;
console.log(a);
}
hello();复制代码
我想这个答案不言而喻,是undefined和11。缘由:就是第一个console时,下面定义的变量a被提高了,因此a变成了undefined,第二个的话,就比较好理解。这个例子,我想会给初学者带来不小的麻烦,和当初的我同样哈。
使用let和const就会不同,它们并不存在变量提高,如图:
何为箭头函数,咱们先上例子:
export const addToCart = productId => (dispatch, getState) => {
if (getState().products.byId[productId].inventory > 0) {
dispatch(addToCartUnsafe(productId))
}
}复制代码
这是,我从redux例子中摘取的一个片断,第一感受就是『代码风格简洁』,总体代码规范很好,毕竟是示例代码么。可是会让人难以理解。所以,为了不之后看不懂的尴尬,仍是来好好聊聊这个神奇的东西吧。
其实,这个东西相似于python的lambda。可是,它的确特别适合js这门语言,就一个字「酷」。它的几个规则:
若是你一开始不会写,那就必须得多练习,这样才能在之后的工做中真正谋求便利。
固然咯,它有好处,可是在使用的时候,也得注意它的禁区。注意事项:
箭头函数不能做为构造函数。如图:
箭头函数没有它本身的this值,箭头函数内的this
值继承自外围做用域。如图:
箭头函数没有arguments。这个咱们直接测试一下就能够了,如图:
啥?没有arguments,那我若是正好要用到呢?这可咋办呢?下面再来讲个有意思的改动——剩余参数。
什么叫剩余参数?别着急,看个例子就懂了。
const restParam = function(a, ...args){
console.log(args);
};
restParam(1, 2, 3, 4); //[2, 3, 4]复制代码
这里你会发现这个args变量彷佛包含了以后输入的全部参数,除了a之外。因此,这就是所谓的剩余参数。其中,运用到了一个…
这个符号。其实这个符号的用处很是的多,ES6中能够将它称为扩展符。那么,咱们来看看在箭头函数中的运用。
固然,在使用剩余参数时,须要注意一个问题,就是剩余参数设置的位置。咱们先来看张图:
因此,在使用剩余参数时,须要注意的是,将这部分放在全部参数的最后位置。其实,ES6还带来了另外一个参数的变更——默认参数。或许,咱们能够先看一下默认参数这个问题,咱们以前是怎么处理的。场景:通常在设置延迟的时候,都会有一个时间的默认值,防止用户在没有设置的状况下使用,看看下面的例子:
function defaultParam(time){
let wait = time || 1000;
setTimeout(() => {
//...
}, wait);
}复制代码
这种写法应该很是的常见,使用的也比较普遍。可是,使用ES6的语法的话,就会变成这样子,例子:
function defaultParam(time = 1000){
setTimeout(() => {
//...
}, time);
}复制代码
看上去这样子的写法,会使得函数更加的简洁明了。
说到解构赋值呢?你们千万别误解为这是数组的特性。不是的,对象也可以知足。只是以为放在这边来写会比较好而已
解构赋值这个新特性,说实话是真的好用。咱们能够先来看一个复杂一点的例子:
let [a, b , {name, age}, ...args ] = [1, 2, {name: 'zimo', age: 24}, 3, 4];
console.log(a, b, name, age, args); //1, 2, 'zimo', 24, [3, 4]复制代码
你会发现例子中,有一个特色——对仗工整。
这是解构赋值时,必需要去知足的条件——想要解构的部分,内容保持一致。这样才能保证完美解构。对于解构而言,左右两边的内容长度不一致,不会出问题。好比,当你右边内容多一点的时候,其实没啥事,你只须要保证你左边的结构有一部分是你想要的,举例:
let [a, b, c] = [1, 2, 3, 4, 5];
console.log(a, b, c); //1, 2, 3复制代码
这种叫作部分解构,左边也是同样的,对于多处来的部分,会变成undefined。举例:
let [a,b,c] = [1, 2];
console.log(a, b, c); //1 2 undefined复制代码
解构赋值在使用过程当中,也是有须要注意的部分:
必须保证有赋值的过程。看个例子:
你能够看到图中的例子,单独先声明了a和b,可是没有赋值的过程,会报错。
左边内容部分的结构必须与右边保持一致。如图:
这里两边的结构没有一致,若是是foo,bar的话,是能够正常解构的。可是这个例子的意图多是想去解构foo中的值,可是写法上有必定的问题。
其实,解构也有没多种玩法:
默认值的使用。因为以前说过的部分解构的状况出现,因此咱们在解构时,可使用默认值的形式。
let [a, b = 10] = [1];
console.log(a, b); //1, 10复制代码
在这个例子中b原先是undefined,可是设置了默认值的状况下,undefined的变量会被赋上默认值
函数变量中使用解构。对于一个函数而言,它的参数也可能会是数组或对象,这是咱们就可使用解构赋值的方式
function destructuring({name, age}){
console.log(name, age);
}
destructuring({name: 'zimo', age: 21}); // zimo 21复制代码
解构赋值如今被使用的频率也是很是之大,好好掌握一下也是有必要的。
以后的话,咱们能够聊一下二进制数组的概念。
何为二进制数组?其实,咱们能够先来了解一下javascript的数组。熟悉js的人都知道,其实js的数组的性能并不高,它的本质是一个对象。之因此如今你看到数组在使用时速度还能够,是由于js的引擎在处理时,作了不一样的优化。拿v8引擎举例的话,对于内部元素类型相同的数组在编译运行的时候,会使用c编译器。若是对于内部元素类型不一样的时候,它会先将数组分离开来,而后再进行编译。具体能够查看深刻 JavaScript 数组:进化与性能
因此,咱们能够直接了解一下二进制数组的使用。二进制数组能够由Int8Array、Int16Array、Int32Array等形式组成,在整数方面,可用性较强。
const buffer = new Buffer(100000000);
const arr = new Int8Array(buffer);
console.time('test time');
for(let i = 0; i < arr.length; i++){
arr[i] = i;
}
console.timeEnd('test time');复制代码
其实,如今二进制数组使用的频率并很少,ES6也仅仅是提出,后续还会对数组这一块进行一个更加详细的完善。
在ES6中,对字符串也作了必定的改进。先来聊聊咱们的新朋友——模版字符串。其实,在语言中,字符串有多种表示方式:单引号、双引号和倒引号。在javascript中,双引号和单引号都是同样的,这点与一些静态语言不同。可是,每每有时候,对于字符串的拼接会使得开发者厌烦。如何解决呢?
ES6带来了解决方案——模版字符串。何为模版字符串呢?由倒引号包裹``,而后使用${}来包裹变量。咱们能够来实践一下
const name="zimo";
const str = `My name is ${name}`;
console.log(str); //My name is zimo复制代码
这样,咱们就能够很是方便的在其中添加变量了。或许,你会以为这样的拼接,使用普通的方式也能够很是好的完成。可是,在开发过程当中,咱们或许会碰到更佳复杂的状况,好比说,咱们如今要去建立一个DOM元素,以及它的内部元素。这种状况,一般还会带有表达式。
const width = 100;
const height = 200;
const src = "http://www.example.com/example.png";
const html = `<div class="block" width="${0.5 * width}px" height="${height}"><img src="${src}" /></div>`;复制代码
每每这样子的元素在手动拼接的过程当中,老是会出错,所以,使用模版字符串是一种既「高效」又「简洁」的方式。
有了模版字符串,你能够解决很是棘手的问题。那么,标题中带有的startsWith和endsWith又是起到什么做用呢?可能你会使用正则表达式,那么你就有可能不会使用到这两个API。
按照惯例,仍是须要来介绍一下这两个API的。
startsWith:返回值为boolean型,而后去匹配字符串开头的部分,举个例子:
const str = "start in the head";
console.log(str.startsWith('start')); //true
console.log(str.startsWith('head')); //false复制代码
其实,这也是可使用正则表达式来达到这一目的。还原上例:
const str = "start in the head";
console.log(/^start/.test(str)); //true
console.log(/^head/.test(str)); //false复制代码
其实,二者方式的区别基本上没有,可是正则表达式的功能更佳的完善。这个API仅仅在一些场景下起到必定的便捷。比方说,咱们须要去匹配一个URL的协议头是什么时,咱们每每须要用到这种方式。例子:
const url = "http://www.example.com";
if(url.startsWith('http')){
console.log('this is http');
}else if(url.startsWith('https')){
console.log('this is https');
}else if(url.startsWith('ws')){
console.log('this is websocket');
} //this is http复制代码
同理,endWith也是同样的效果。
endsWith:返回值是boolean类型,而后去匹配字符串的结尾。举个例子:
const str = "something in the end";
console.log(str.endsWith('end')); //true
console.log(str.endsWith('something')); //false复制代码
一样的,它也可使用正则表达式来实现:
const str = "something in the end";
console.log(/end$/.test(str)); //true
console.log(/something$/.test(str)); //false复制代码
这种状况的使用场景是,每每咱们须要为上传的文件准备图标,那么咱们就能够根据后缀来肯定图标。
const filename = "upload.jpg";
if(filename.endsWith('.jpg')){
console.log('this is jpg file');
}else if(filename.endsWith('.png')){
console.log('this is png file');
}else if(filename.endsWith('.webp')){
console.log('this is webp file');
} //this is jpg file复制代码
同时,字符串还增长了许许多多的东西,有兴趣的,能够本身去翻书本详细的了解
Iterator的概念是迭代器。在ES6中,终于正式的添加了这个属性。迭代器,主要是一个集合类元素的遍历机制。何为集合类元素?最多见的就是数组,还有对象。迭代器能够帮助开发者完成遍历集合的过程。最开始javascript并无设置接口,来自定义迭代器,可是从ES6开始,咱们能够自定义迭代器了。在自定义迭代器以前,咱们要清楚迭代器的做用有哪些:
迭代器,每每就是一个指针对象,不断调用,而后不断地指向下一个对象的过程,直到结束。ES6中,咱们能够建立一个指针对象,而后调用next的函数,使得指针对象向下移动。同时,next函数会返回value和done,肯定是否到达末尾。
同时,ES6还提供了Iterator接口——Symbol.iterator。首先,咱们来看一下具有原生接口的集合类——数组,类数组对象、Set和Map。这样咱们就能够直接调用它的接口来进行循环:
let arr = ['my', 'name', 'is', 'iterator'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); //{ value: 'my', done: false}
console.log(iter.next()); //{ value: 'name', done: false}
console.log(iter.next()); //{ value: 'is', done: false}复制代码
同时,定义iterator接口的数据结构能够轻松的使用for...of进行值的遍历
let arr = ['I', 'has', 'iterator'];
for(let item of arr){
console.log(item);
} //'I', 'has', 'iterator'复制代码
可是,若是没有定义iterator接口的数据结构就没有办法使用这种方式进行遍历,如图:
这时,咱们又该如何呢?其实,针对一些可迭代的数据结构,咱们是能够自定义迭代器的,例如:
let iteratorObj = {
0: 'a',
1: 'b',
2: 'c',
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
}
for(let item of iteratorObj){
console.log(item);
} // 'a', 'b', 'c'复制代码
迭代器是一个很是实用的东西,不妨你也能够试试,同时去改善你的代码。
其实,这两个是比较难以理解的东西。若是只是粗浅的了解一下,仍是有许多的新东西的。在ES6中,引入了generator和promise两个概念。可能在这以前,你已经使用过了,经过其余的类库实现的。那么,其实ES6中的新概念也是差很少的,只是标准化了而已。
generator,叫作生成器。能够说与iterator有点类似,一样是经过next函数,来一步步往下执行的。同时,它的定义时,所使用的是function*的标识符。还具有yield这个操做符,能够实现逐步逐步向下执行。咱们来看个例子:
function* generator(){
yield 1;
yield 2;
yield 3;
};
let generate = generator();
console.log(generate.next()); //{value: 1, done: false}
console.log(generate.next()); //{value: 2, done: false}
console.log(generate.next()); //{value: 3, done: true}
console.log(generate.next()); //{value: undefined, done: true}复制代码
这样子看起来,彷佛就是迭代器的步骤。其实,iterator的接口,能够定义成这样子的形式。可是,generator的做用不只仅如此。它就像一个状态机,能够在上一个状态到下一个状态之间进行切换。而一旦遇到yield部分,则能够表示当前是能够步骤的暂停。须要等到调用next方法才能进行下一步骤。同时,咱们还可使用上一步的结果值,进行下一步的运算。示例:
function* generator(){
yield 1;
let value = yield 2;
yield 3 + value;
};
let generate = generator();
let value1 = generate.next();
let value2 = generate.next();
let value3 = generate.next(value2.value);
console.log(value1); //{value: 1, done: false}
console.log(value2); //{value: 2, done: false}
console.log(value3); //{value: 5, done: true}复制代码
这样的话,就能够将value做为你第三步的参数值,进行使用。
以前说过,generator的next是须要本身调用的。可是,咱们如何使它本身自动调用呢。咱们可使用for...of来自动调用next,就像迭代器同样。示例:
function* generator(){
yield 1;
yield 2;
yield 3;
};
for(let value of generator()){
console.log(value);
} //1, 2, 3复制代码
其实,以前所讲的只是generator的基本使用。generator主要被使用在异步编程领域。由于咱们以前所讲的特性,很是适合在异步编程中使用。固然了,咱们也须要去提一下promise这个异步编程的功臣。
Promise,翻译过来叫作承诺。咱们能够理解为一种约定。你们都知道异步编程的时候,咱们通常会使用到回调函数这个东西。可是,回调函数会致使的问题,也很是的明显。示例:
callback1(function(data){
//...
callback2(function(data1){
const prevData = data;
//...
callback3(function(){
//...
callback4(function(){
//...
});
});
});
});复制代码
回调函数,写多了以后咱们会发现,这个倒金字塔会愈来愈深,而咱们会愈来愈难以管理。
这时,或许promise会起到必定的做用。试想一下,为何这几个回调函数都能在另外一个回调函数以外进行?主要缘由:
基于这两点,咱们就会发现,一旦你须要这样去编写代码,就必须保证你的上一个回调函数在下一个回调函数以前进行。咱们还能够发现,它们之间缺少一种约定,就是一旦上一个发生了,不管是正确仍是错误,都会通知对应的回调函数的约定。
Promise,或许就是起到了这样的一种做用。它具有三种状态:pending、resolved、rejected。它们之间分别对应:正在进行、已解决、已拒绝等三种结果。一个回调函数会开始从pending状态,它会向resolved和rejected的二者之一进行转换。并且这种转换是不可变的,即一旦你从pending状态转变到resolved状态,就不能够再变到rejected状态去了。
而后,promise会有一个then函数,能够向下传递以前回调函数返回的结果值。咱们能够写个promise示例:
new Promise((resolved, rejected) => {
resolved(1);
}).then(data => {
console.log(data);
}, err => {
console.log(err);
}).catch(err => {
console.log(err);
}); // 1复制代码
其实,只须要记住这样子的一种形式,就能够写好promise。Promise是一个比较容易书写的东西。由于它的形式比较单一,并且如今有许多封装的比较好的异步请求库,都带有Promise的属性,例如axios。
Promise,还带有其余的一些API,上面咱们也使用到了一个。
Promise能够和以前所讲的Generator一块儿使用,咱们能够看一下使用场景:
经过Generator函数来管理流程,遇到异步操做,就使用Promise进行处理。
function usePromise(){
return new Promise(resolve => {
resolve('my name is promise');
});
}
function* generator(){
try{
let item = yield usePromise();
console.log(item);
}catch(err){
console.log(err);
}
}
let generate = generator();
generate.next().value.then(data => {
console.log(data);
}, err => {
console.log(err);
}).catch(err => {
console.log(err);
}); //my name is promise复制代码
或许,你还能够写出更加复杂的程序。
最后要聊的一个主题就是class。相信抱怨javascript没有类的特性数不胜数。同时,还须要去了解js的类继承式概念。那么,ES6也带来了咱们最欢迎的class module部分。咱们就不介绍以前咱们是若是去构建对象的了(好像是构造函数)。
那么,咱们能够来看一下,ES6给我带来的新变化:
class Animal{
constructor(name){
this.name = name;
}
sayName(){
return this.name;
}
}
const animal = new Animal('dog');
console.log(animal.sayName()); // 'dog'复制代码
彷佛这样子的形式比以前的构造函数的方式强对了。咱们能够理解一下这个结构:
所以,上面那个其实能够写成原先的:
function Animal(name){
this.name = name;
}
Animal.prototype.sayName = function(){
return this.name;
}复制代码
其实,就是class在ES6中获得了封装,可使得如今的方式更加的优美。
以后,咱们简单了解一下继承这个概念吧。
任何的东西,都是须要继承的。由于咱们不可能都是从头去写这个类。每每是在原有类的基础之上,对它进行完善。在ES6以前,咱们可能对构造函数完成的是组合式继承。示例:
function Animal(name){
this.name = name;
}
Animal.prototype.sayName = function(){
return this.name;
}
function Dog(name, barking){
Animal.call(this, name);
this.barking = barking;
}
Dog.prototype = new Animal();
Dog.prototype.constructor = Dog;
Dog.prototype.makeBarking = function(){
return this.barking;
}
const dog = new Dog('zimo', '汪汪汪');
console.log(dog.makeBarking()); //汪汪汪
console.log(dog.sayName()); //zimo复制代码
这样子的组合式继承书写起来,比较麻烦,须要从新去对每一个元素设置,而后还要从新定义新类的原型。那么,咱们能够来看一下ES6对于继承的封装:
class Animal{
constructor(name){
this.name = name;
}
sayName(){
return this.name;
}
}
class Dog extends Animal{
constructor(name, barking){
super(name);
this.barking = barking;
}
makeBarking(){
return this.barking;
}
}复制代码
这样子,就能够轻松的完成以前的组合式继承步骤了。若是你对extends的封装感兴趣的话,不妨看一下这篇文章javascript之模拟类继承
在这里ES6的内容只是总结了部分,大体能够分为这么几个部分:
但愿,你能够从这些内容中对ES6多一些了解,同时,若是你还想深刻ES6进行了解的话,最直接的方式就是看书。但愿你的代码写的愈来愈优雅。
若是你对我写的有疑问,能够评论,如我写的有错误,欢迎指正。你喜欢个人博客,请给我关注Star~呦。你们一块儿总结一块儿进步。欢迎关注个人github博客