ECMAScript 6.0(以下简称 ES6)是 JavaScript 语言的下一代标准,已经在 2015 年 6 月正式发布了。它的目标,是使得 JavaScript 语言可以用来编写复杂的大型应用程序,成为企业级开发语言。
ES6主要的新特性如下所示:
let关键字
let 关键字用来声明变量,使用 let 声明的变量有几个特点:
- 不允许重复声明
- 块级作用域
- 不存在变量提升
- 不影响作用域链
const关键字
const 关键字用来声明常量,const 声明有以下特点:
- 声明必须赋初始值
- 标识符一般为大写
- 不允许重复声明
- 值不允许修改
- 块儿级作用域
注意: 对象属性修改和数组元素变化不会触发 const 错误
应用场景:声明对象类型使用 const,非对象类型声明选择 let
const与let以及var分析
1.三者之间的区别
- 1.变量提升
- var声明 无论声明在何处,都会被视为声明在函数的最顶部
- let和const声明不会提升
- 2.作用域
- var是函数作用域, 在函数内部作用, 但是{}里是一样会提升的
- let和const是块级作用域, 在{}里就形成了一个作用域
- 3.重复声明
- var 可以重复定义
- let和const不可以重复定义,否则报错
- 4.const常量不可修改
- const 声明的变量都会被认为是常量,意思就是它的值被设置完成后就不能再修改了;
-
5. 如果const的是一个对象,对象所包含的值是可以被修改的。抽象一点儿说,就是对象所指向的地址没有变就行:
const student = { name: 'cc' } student.name = 'yy';// 不报错 student = { name: 'yy' };// 报错
2.暂时性死区
var tmp = 123;
if (true) {
tmp = 'abc'; // ReferenceError
let tmp;
}
- 上面代码中,存在全局变量
tmp
,但是块级作用域内let
又声明了一个局部变量tmp
,导致后者绑定这个块级作用域,所以在let
声明变量前,对tmp
赋值会报错。
3.使用建议
在日常开发中,建议是全面拥抱let/const,一般的变量声明使用let关键字,而当声明一些配置项(类似接口地址,npm依赖包,分页器默认页数等一些一旦声明后就不会改变的变量)的时候可以使用const,来显式的告诉项目其他开发者,这个变量是不能改变的(const声明的常量建议使用全大写字母标识,单词间用下划线),同时也建议了解var关键字的缺陷(变量提升,污染全局变量等),这样才能更好的使用新语法。
变量的解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构赋值。
解构赋值是对赋值运算符的扩展。它是一种针对数组或者对象进行模式匹配,然后对其中的变量进行赋值。
let [a,b,c] = [1,2,3];
console.log(a,b,c);//1,2,3
**************************
let [a,b,c] = [1,,3];
console.log(a,b,c);//1,undefined,3
**************************
let [a,,b] = [1,2,3];
console.log(a,b);//1,3
**************************
let [a,..b] = [1,2,3];//...是剩余运算符,表示赋值运算符右边除第一个值外剩余的都赋值给b
console.log(a,b);//1,[2,3]
对象的解构赋值和数组类似,不过左边的变量名需要使用对象的属性名,并且用大括号{}而非中括号[]:
let obj = {name:'ren',age:12,sex:'male'};
let {name,age,sex} = obj;
console.log(name,age,sex);//'ren' 12 'male'
let {name:myName,age:myAge,sex:mySex} = obj;//自定义变量名
console.log(myName,myAge,mySex);//'ren' 12 'male'
模板字符串
模板字符串(template string)是增强版的字符串,用反引号(`)标识,特点:
- 字符串中可以出现换行符
- 可以使用 ${xxx} 形式输出变量
- 需要拼接字符串的时候尽量改成使用模板字符串
// 定义字符串
let str = `<ul>
<li>沈腾</li>
<li>玛丽</li>
<li>魏翔</li>
<li>艾伦</li>
</ul>`;
// 变量拼接
let star = '王宁';
let result = `${star}在前几年离开了开心麻花`;
注意:当遇到字符串与变量拼接的情况使用模板字符串
箭头函数
ES6 允许使用「箭头」(=>)定义函数。
/**
* 1. 通用写法
*/
let fn = (arg1, arg2, arg3) => {
return arg1 + arg2 + arg3;
}
/**
* 2. 省略小括号的情况
*/
let fn2 = num => {
return num * 10;
};
/**
* 3. 省略花括号的情况
*/
let fn3 = score => score * 20;
/**
* 4. this 指向声明时所在作用域中 this 的值
*/
let fn4 = () => {
console.log(this);
}
箭头函数的注意点:
- 如果形参只有一个,则小括号可以省略
- 函数体如果只有一条语句,则花括号可以省略,函数的返回值为该条语句的执行结果
- 箭头函数 this 指向声明时所在作用域下 this 的值
- 箭头函数不能作为构造函数实例化
- 不能使用 arguments
注意:1. 箭头函数不会更改 this 指向,用来指定回调函数会非常合适
2.箭头函数和普通函数最大的区别在于其内部this永远指向其父级AO对象的this。
- 普通函数在预编译环节会在AO对象上添加this属性,保存一个对象。每个普通函数在执行时都有一个特定的this对象,而箭头函数执行时并不直接拥有this属性,如果你在箭头函数中使用this,将根据函数作用域链,直接引用父级AO对象上this绑定的对象。普通函数的AO对象只有在函数执行时才产生,换言之,普通函数的this是由函数执行时的环境决定。而箭头函数的特别之处在于,当函数被定义时,就引用了其父级AO对象的this,即箭头函数的this由定义时的环境决定。
函数的参数默认值
// ES6之前,当未传入参数时,text = 'default';
function printText(text) {
text = text || 'default';
console.log(text);
}
// ES6;
function printText(text = 'default') {
console.log(text);
}
printText('hello'); // hello
printText();// default
Promise
Promise作为ES6中推出的新的概念,改变了JS的异步编程,现代前端大部分的异步请求都是使用Promise实现,fetch这个web api也是基于Promise的,简述一下之前统治JS异步编程的回调函数,回调函数有什么缺点,Promise又是怎么改善这些缺点。
回调函数的缺点
- 多重嵌套,导致回调地狱
- 代码跳跃,并非人类习惯的思维模式
- 信任问题,你不能把你的回调完全寄托与第三方库,因为你不知道第三方库到底会怎么执行回调(多次执行)
- 第三方库可能没有提供错误处理
- 不清楚回调是否都是异步调用的(可以同步调用ajax,在收到响应前会阻塞整个线程,会陷入假死状态,非常不推荐)
Promise
针对回调函数这么多缺点,ES6中引入了一个新的概念Promise,Promise是一个构造函数,通过new关键字创建一个Promise的实例,来看看Promise是怎么解决回调函数的这些问题。
let promise = new Promise((resolve,reject)=>{
setTimeout(() => {
resolve('I have been resolved')
}, 2000);
})
promise.then(res=>{
console.log(res)//2秒后打印字符串
})
不清楚回调是否都是异步调用的(可以同步调用ajax,在收到响应前会阻塞整个线程,会陷入假死状态,非常不推荐)
Promise在设计的时候保证所有响应的处理回调都是异步调用的,不会阻塞代码的执行,Promise将then方法的回调放入一个叫微任务的队列中(MicroTask),确保这些回调任务在同步任务执行完以后再执行。
使用建议
- 在日常开发中,建议全面拥抱新的Promise语法,其实现在的异步编程基本也都使用的是Promise
- 建议使用ES7的async/await进一步的优化Promise的写法,async函数始终返回一个Promise,await可以实现一个"等待"的功能,async/await被成为异步编程的终极解决方案,即用同步的形式书写异步代码,并且能够更优雅的实现异步代码顺序执行以及在发生异步的错误时提供更精准的错误信息
对象
对象属性/方法简写
es6允许当对象的属性和值相同时,省略属性名
let x = 0
let obj = {
x //es6允许省略x:
}
- 省略的是属性名而不是值
- 值必须是一个变量
对象属性简写经常与解构赋值一起使用
let bar = () => ({ x: 4, y: 5, z: 6 })
let { x: x, y: y, z: z } = bar()
//简写为
let { x, y, z } = bar()
x //4
y //5
z //6
结合上文的解构赋值,这里的代码会其实是声明了x,y,z变量,因为bar函数会返回一个对象,这个对象有x,y,z这3个属性,解构赋值会寻找等号右边表达式的x,y,z属性,找到后赋值给声明的x,y,z变量
方法简写
es6允许当一个对象的属性的值是一个函数(即是一个方法),可以使用简写的形式
let obj = {
func: function () {
}
}
// es6方法的简写
let obj2 = {
func() {
}
}
//使用箭头函数进行简化
let obj3 = {
func: () => {
}
}
剩余运算符rest/扩展运算符
剩余/扩展运算符同样也是ES6一个非常重要的语法,使用3个点(…),后面跟着一个含有iterator接口的数据结构
扩展运算符
以数组为例,使用扩展运算符使得可以"展开"这个数组,可以这么理解,数组是存放元素集合的一个容器,而使用扩展运算符可以将这个容器拆开,这样就只剩下元素集合,你可以把这些元素集合放到另外一个数组里面, 代替ES3中数组原型的concat方法
let arr1 = [1, 2, 3]
let arr2 = [4, 5, 6]
console.log(arr1.concat(arr2)) //[1, 2, 3, 4, 5, 6]
console.log([...arr1, ...arr2]) //[1, 2, 3, 4, 5, 6]
剩余运算符
剩余运算符最重要的一个特点就是替代了以前的arguments;rest只是形参, 可以随意取名
- 访问函数的arguments对象是一个很昂贵的操作,以前的arguments.callee,arguments.caller都被废止了,建议在支持ES6语法的环境下不要在使用arguments对象,使用剩余运算符替代(箭头函数没有arguments,必须使用剩余运算符才能访问参数集合)
function func(a, b, c) {
console.log(arguments[0], arguments[1], arguments[2])
}
func(1, 2, 3)
// rest是形参,承载了所有的函数参数,可以随意取名
function func1(...rest){
console.log(rest) //[1, 2, 3]
}
func1(1,2,3)
-
剩余运算符可以和数组的解构赋值一起使用,但是必须放在最后一个,因为剩余运算符的原理其实是利用了数组的迭代器,它会消耗3个点后面的数组的所有迭代器,读取所有迭代器生成对象的value属性,剩运算符后不能在有解构赋值,因为剩余运算符已经消耗了所有迭代器,而数组的解构赋值也是消耗迭代器,但是这个时候已经没有迭代器了,所以会报错
let [first, ...arr] = [1, 2, 3, 4, 5]//success first //1 arr //[2,3,4,5] let [...arr, last] = [1, 2, 3, 4, 5] //Uncaught SyntaxError:Rest element must be last element
-
这里first会消耗右边数组的一个迭代器,…arr会消耗剩余所有的迭代器,而第二个例子…arr直接消耗了所有迭代器,导致last没有迭代器可供消耗了,所以会报错,因为这是毫无意义的操作
区别
剩余运算符和扩展运算符的区别就是,剩余运算符会收集这些集合,放到右边的数组中,扩展运算符是将右边的数组拆分成元素的集合,它们是相反的
类(class) (ES6)
ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过 class 关键字,可以定义类。基本上,ES6 的 class 可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的 class 写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
知识点:
- class 声明类
- constructor 定义构造函数初始化
- extends 继承父类
- super 调用父级构造方法
- static 定义静态方法和属性
- 父类方法可以重写
class Animal {
// 构造函数,实例化的时候将会被调用,如果不指定,那么会有一个不带参数的默认构造函数.
constructor(name,color) {
this.name = name;
this.color = color;
}
// toString 是原型对象上的属性
toString() {
console.log('name:' + this.name + ',color:' + this.color);
}
}
var animal = new Animal('dog','white');//实例化Animal
animal.toString();
console.log(animal.hasOwnProperty('name')); //true
console.log(animal.hasOwnProperty('toString')); // false
console.log(animal.__proto__.hasOwnProperty('toString')); // true
class Cat extends Animal {
constructor(action) {
// 子类必须要在constructor中指定super 函数,否则在新建实例的时候会报错.
// 如果没有置顶consructor,默认带super函数的constructor将会被添加、
super('cat','white');
this.action = action;
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString();
// 实例cat 是 Cat 和 Animal 的实例,和Es5完全一致。
console.log(cat instanceof Cat); // true
console.log(cat instanceof Animal); // true
迭代器
遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。
- ES6 创造了一种新的遍历命令 for…of 循环,Iterator 接口主要供 for…of 消费
- 原生具备 iterator 接口的数据(可用 for of 遍历)
- Array
- Arguments
- Set
- Map
- String
- TypedArray
- NodeList
- 工作原理
- 创建一个指针对象,指向当前数据结构的起始位置
- 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
- 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员;每调用 next 方法返回一个包含 value 和done 属性的对象
- 注: 需要自定义遍历数据的时候,要想到迭代器。
Set
ES6 提供了新的数据结构 Set(集合)。它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
-
size 返回集合的元素个数
-
add 增加一个新元素,返回当前集合
-
delete 删除元素,返回 boolean 值
-
has 检测集合中是否包含某个元素,返回 boolean 值
-
clear 清空集合,返回 undefined
//创建一个空集合 let s = new Set(); //创建一个非空集合 let s1 = new Set([1, 2, 3, 1, 2, 3]); //集合属性与方法 //返回集合的元素个数 console.log(s1.size); //添加新元素 console.log(s1.add(4)); //删除元素 console.log(s1.delete(1)); //检测是否存在某个值 console.log(s1.has(2)); //清空集合 console.log(s1.clear());
Map
ES6 提供了 Map 数据结构。它类似于对象,也是键值对的集合。但是“键” 的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法:
-
size 返回 Map 的元素个数
-
set 增加一个新元素,返回当前 Map
-
get 返回键名对象的键值
-
has 检测 Map 中是否包含某个元素,返回 boolean 值
-
clear 清空集合,返回 undefined
//创建一个空 map let m = new Map(); //创建一个非空 map let m2 = new Map( [ ['name', '张三'], ['slogon', '李四'] ]); //属性和方法 //获取映射元素的个数 console.log(m2.size); //添加映射值 console.log(m2.set('age', 6)); //获取映射值 console.log(m2.get('age')); //检测是否有该映射 console.log(m2.has('age')); //清除 console.log(m2.clear());
ES6中 forEach() map() some() every() filter() reduce() find() 的区别
foreach()
let arr = [1,2,3,4,5,6,7];
/*
* 没有返回值,只针对每个元素调用func。
* 优点:代码简介。
* 缺点:无法使用break,return等终止循环
*/
//ES5写法
arr.forEach(function(item,index){
console.log(item); // 1 2 3 4 5 6 7
})
//ES6写法
arr.forEach((item,index) => {
console.log(item); // 1 2 3 4 5 6 7
return item
})
map()
let arr = [1,2,3,4,5,6,7];
/*
* 有返回值,返回一个新的数组,每个元素为调用func的结果。
*/
let newArr = arr.map((item,index) => {
return item*2
})
console.log(newArr); // [2,4,6,8,10,12,14]
some()
let arr = [1,2,3,4,5,6,7];
/*
* 返回一个Boolean,判断是否有元素符合func,如果有一个符合条件,就会终止循环,返回true。
*/
arr.some((item,index) => {
return item > 5; // true
})
every()
let arr = [1,2,3,4,5,6,7];
/*
* 返回一个Boolean,判断每一个元素是否都符合func,如果有一个不符合,就会终止循环,返回false。
*/
arr.every((item,index) => {
return item < 10; //true
})
filter()
let arr = [1,2,3,4,5,6,7];
/*
* 有返回值,返回一个符合func条件的数组的集合
*/
let newArr = arr.filter((item,index) => {
return item > 3;
})
console.log(newArr); // [4,5,6,7]
reduce()
let arr = [1,2,3,4,5,6,7];
/*
* 让数组中的前项和后项做某种运算,并返回运算结果
*/
let res = arr.reduce((prev,next) => {
return prev + next;
})
console.log(res); // 28
find()
let arr = [1,2,3,4,5,6,7];
/*
* 不创建新数组,不改变元素组
* 在判断中一旦某个元素符合func,立马跳出循环,返回当前符合条件的元素
*/
let res = arr.find((item,index) => {
console.log( 'arr[' + index + '] = ' item ); // arr[3] = 4
return item > 3;
})
console.log(res); // 4
数值扩展
二进制和八进制
ES6提供了二进制和八进制的新的写法,分别用前缀0b和0o表示
Number.isFinite()与Number.isNaN()
Number.isFinite()
用来检查一个数值是否为有限的Number.isNaN()
用来检查一个值是否为NaN
Number.parseInt()与Number.parseFloat()
ES6 将全局方法 parseInt 和 parseFloat,移植到 Number 对象上面,使用不变
Math.trunc
用于去除一个数的小数部分,返回整数部分。
Number.isInteger
Number.isInteger()
用来判断一个数值是否为整数
对象扩展
ES6新增了一些Object
对象的方法
Object.is
比较两个值是否严格相等,与[===]行为基本一致Object.assign
对象的合并,将源对象的所有可枚举属性,复制到目标对象- __proto 、setPrototypeOf、 setPrototypeOf 可以直接设置对象的原型