ES6 既是一个历史名词,也是一个泛指,含义是5.1版以后的JavaScript的下一代标准,涵盖了ES2015、ES2016、ES2017等等,而ES2015则是正式名称,特指该年发布的正式版本的语言标准。
一、变量扩展
1. let和const命令
let用来声明变量,它的用法类似于var。const声明一个只读的常量。一旦声明,常量的值就不能改(当const声明的常量为引用类型时只保证其指向地址不变,该地址上属性或数据可变)
下面主要学习它们和var关键字的区别。
(1)块级作用域
var不存在块级作用域,而let和const均存在块级作用域。
{
let a = 1;
var b = 10;
const c =100
}
console.log(a)// ReferenceError: a is not defined.
console.log(b)// 10
console.log(c)// ReferenceError: a is not defined.
(2)不存在变量提升,导致暂时性死区
变量提升:js引擎运行js时分为两步进行,预解析和代码执行。预解析阶段会把所有的var声明(只提升声明,赋值操作没有提升)和function(所有函数声明)提升到当前作用域的最前面
暂时性死区:使用let(const)命令声明变量(常量)之前,该变量(常量)都是不可用的。这在语法上,称为“暂时性死区”
变量提升:
console.log(foo); // 输出undefined
console.log(bar); // 报错ReferenceError
var foo = 2;
let bar = 2;
//提升后的实际代码如下
var foo ;
console.log(foo); // 未赋值,输出undefined
console.log(bar); // 报错ReferenceError
let bar;
bar = 2 ;
暂时性死区:
if (true) {
tmp = 'abc'; // ReferenceError,暂时性死区
console.log(tmp); // ReferenceError,暂时性死区
let tmp; //这里才声明
console.log(tmp); // undefined
tmp = 123;
console.log(tmp); // 123
}
(3)const一旦声明变量,就必须立即初始化
const声明的变量不得改变值,这意味着,const一旦声明变量,就必须立即初始化,不能留到以后赋值。
const foo;
// SyntaxError: Missing initializer in const declaration
(4)let 和 const 不能重复声明
var a = 1;
let b = 10;
const c = 100;
var a = 1000;
let b = 1000;
const c = 100;
console.log(a) //1000
console.log(b) // Uncaught SyntaxError: Identifier 'b' has already been declared
console.log(c) //Uncaught SyntaxError: Identifier 'c' has already been declared
2. 变量的解构赋值
ES6允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
数组解构赋值:
var a = 1;
var b = 2;
var c = 3;
var [a, b, c] = [1, 2, 3]; //结构赋值
对象解构赋值:
var { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
字符串解构赋值:
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
总结:非常好用,比如排序算法中不用借助第三个变量就可以交换两个数的位置:[arr[i] ,arr[j]] = [arr[j], arr[i]];
使用注意: 左右两边结构必须一样(相对应),声明和赋值不能分开。
二、函数扩展
1. 箭头函数
ES6允许使用“箭头”(=>)定义函数。(可理解为函数的语法糖)
匿名函数原写法:
function fn(v) { return v};
箭头函数写法:
(v)=> {return v};
按照以下规则使用:
(1)若只有一个参数时,原括号()可省略:v => { } ;
(2)若箭头函数的代码块部分只有一条语句时,则花括号和return可省略: v => v;
(3)由于大括号被解释为代码块,所以如果箭头函数直接返回一个对象,必须在对象外面加上括号: id => ({ id: id, name: "Temp" });
箭头函数有几个使用注意点(不熟悉慎用):
(1)函数体内的this对象,就是定义时所在的对象,而不是使用时所在的对象。(非常重要)
(2)不可以当作构造函数,也就是说,不可以使用new命令,否则会抛出一个错误。
(3)不可以使用arguments对象,该对象在函数体内不存在。如果要用,可以用rest参数(下面有介绍)代替。
(4)不可以使用yield命令,因此箭头函数不能用作Generator函数。
2. 函数参数的默认值
概括:有传参时用实参,无传参时用默认值
function Point(x = 0, y = 0) {
this.x = x;
this.y = y;
}
var p1 = new Point();
p1 // { x: 0, y: 0 }
var p2 = new Point(1);
p2 // { x: 1, y: 0 }
使用注意: 通常情况下,定义了默认值的参数,应该是函数的尾参数
3. rest参数
ES6引入rest参数(形式为“…变量名”),用于获取函数的多余参数,这样就不需要使用arguments对象了。rest参数搭配的变量是一个数组,该变量将多余的参数放入数组中。
function add(...values) {
let sum = 0;
for (var val of values) {
sum += val;
}
return sum;
}
add(2, 5, 3) // 10
上面代码的add函数是一个求和函数,利用rest参数,可以向该函数传入任意数目的参数。
注意: rest参数之后不能再有其他参数(即只能是最后一个参数),否则会报错。
4. 扩展运算符
扩展运算符(spread)是三个点(…)。它好比rest参数的逆运算,将一个数组转为用逗号分隔的参数序列。(数组扁平化中超好用)
var arr = [1,2,3];
console.log(arr); //[1,2,3]
console.log(...arr); //1,2,3
三、数组
主要扩展了一些新方法:
1.Array上的方法
(1)Array.from()
Array.from方法用于将两类对象转为真正的数组:类似数组的对象(array-like object)和可遍历(iterable)的对象(包括ES6新增的数据结构Set和Map)。
let arrayLike = {
'0': 'a',
'1': 'b',
'2': 'c',
length: 3
};
let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
Array.from('hello')
// ['h', 'e', 'l', 'l', 'o']
let namesSet = new Set(['a', 'b'])
Array.from(namesSet) // ['a', 'b']
(2)Array.of()
Array.of方法用于将一组值,转换为数组。
Array.of(3, 11, 8) // [3,11,8]
Array.of(3) // [3]
Array.of(3).length // 1
2.Array实例上的方法
(1)copyWithin()
在当前数组内部,将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组。也就是说,使用这个方法,会修改当前数组。
Array.prototype.copyWithin(target, start = 0, end = this.length)
它接受三个参数。
target(必需):从该位置开始替换数据。
start(可选):从该位置开始读取数据,默认为0。如果为负值,表示倒数。
end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示倒数。
这三个参数都应该是数值,如果不是,会自动转为数值。
[1, 2, 3, 4, 5].copyWithin(0, 3)
// [4, 5, 3, 4, 5]
(2)find()和findIndex()
用于找出第一个符合条件的数组成员。它的参数是一个回调函数,所有数组成员依次执行该回调函数,直到找出第一个返回值为true的成员,然后返回该成员。如果没有符合条件的成员,则返回undefined。
[1, 4, -5, 10].find((n) => n < 0)
// -5
数组实例的findIndex方法的用法与find方法非常类似,返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1
[1, 5, 10, 15].findIndex((n) => n > 9)
// 2
(3)fill()
fill方法使用给定值,填充一个数组。
['a', 'b', 'c'].fill(7)
// [7, 7, 7]
new Array(3).fill(7)
// [7, 7, 7]
上面代码表明,fill方法用于空数组的初始化非常方便。数组中已有的元素,会被全部抹去。
fill方法还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置。
['a', 'b', 'c'].fill(7, 1, 2)
// ['a', 7, 'c']
上面代码表示,fill方法从1号位开始,向原数组填充7,到2号位之前结束。
(4)entries(),keys()和values()
entries(),keys()和values()——用于遍历数组。它们都返回一个遍历器对象,可以用for…of循环进行遍历,唯一的区别是keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历。
for (let index of ['a', 'b'].keys()) {
console.log(index);
}
// 0
// 1
for (let elem of ['a', 'b'].values()) {
console.log(elem);
}
// 'a'
// 'b'
for (let [index, elem] of ['a', 'b'].entries()) {
console.log(index, elem);
}
// 0 "a"
// 1 "b"
(5)includes()
方法返回一个布尔值,表示某个数组是否包含给定的值。
[1, 2, 3].includes(2); // true
[1, 2, 3].includes(4); // false
[1, 2, NaN].includes(NaN); // true
该方法的第二个参数表示搜索的起始位置,默认为0。如果第二个参数为负数,则表示倒数的位置,如果这时它大于数组长度(比如第二个参数为-4,但数组长度为3),则会重置为从0开始。
[1, 2, 3].includes(3, 3); // false
[1, 2, 3].includes(3, -1); // true
附:
indexOf方法有两个缺点,一是不够语义化,它的含义是找到参数值的第一个出现位置,所以要去比较是否不等于-1,表达起来不够直观。二是,它内部使用严格相当运算符(===)进行判断,这会导致对NaN的误判。
[NaN].indexOf(NaN)
// -1
includes使用的是不一样的判断算法,就没有这个问题。
[NaN].includes(NaN)
// true
四、字符串
1. 实例上的新方法
(1)includes(), startsWith(), endsWith()
includes():返回布尔值,表示是否找到了参数字符串。
startsWith():返回布尔值,表示参数字符串是否在源字符串的头部。
endsWith():返回布尔值,表示参数字符串是否在源字符串的尾部。
var s = 'Hello world!';
s.startsWith('Hello') // true
s.endsWith('!') // true
s.includes('o') // true
这三个方法都支持第二个参数,表示开始搜索的位置。
var s = 'Hello world!';
s.startsWith('world', 6) // true
s.endsWith('Hello', 5) // true
s.includes('Hello', 6) // false
上面代码表示,使用第二个参数n时,endsWith的行为与其他两个方法有所不同。它针对前n个字符,而其他两个方法针对从第n个位置直到字符串结束。
(2)repeat()
repeat方法返回一个新字符串,表示将原字符串重复n次。
'x'.repeat(3) // "xxx"
'hello'.repeat(2) // "hellohello"
2. 模板字符串
用法:${变量}直接插入字符串中;反引号包裹
// 字符串中嵌入变量
var name = "Bob", time = "today";
var res = `Hello ${name}, how are you ${time}?`
console.log(res); //Hello Bob, how are you today?
五、对象
1. 字面量增强写法
ES6允许直接写入变量和函数,作为对象的属性和方法。这样的书写更加简洁。
var foo = 'bar';
var baz = {foo: foo};
// 等同于
var baz = {foo}; //字面量增强写法
var o = {
method: function() {
return "Hello!";
}
};
// 等同于
var o = {
method() { //字面量增强写法
return "Hello!";
}
};
2. Object的新方法
(1)Object.is()
ES5比较两个值是否相等,只有两个运算符:相等运算符==,和严格相等运算符===。它们都有缺点,前者会自动转换数据类型,后者的NaN不等于自身,以及+0等于-0。
Object.is新方法,用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
// false
不同之处只有两个:一是+0不等于-0,二是NaN等于自身。
+0 === -0 //true
NaN === NaN // false
Object.is(+0, -0) // false
Object.is(NaN, NaN) // true
(2)Object.assign()
Object.assign方法用于对象的合并,将源对象(source)的所有可枚举属性,复制到目标对象(target)。
var target = { a: 1 };
var source1 = { b: 2 };
var source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign方法的第一个参数是目标对象,后面的参数都是源对象。
注意:
(1)如果目标对象与源对象有同名属性,或多个源对象有同名属性,则后面的属性会覆盖前面的属性。
(2)Object.assign
方法实行的是浅拷贝,而不是深拷贝。也就是说,如果源对象某个属性的值是对象,那么目标对象拷贝得到的是这个对象的引用。
(3)Object.assign
拷贝的属性是有限制的,只拷贝源对象的自身属性(不拷贝继承属性),也不拷贝不可枚举的属性(enumerable: false)。
(3)Object.setPrototypeOf()和Object.getPrototypeOf()
对于原型对象的读写操作(对原型不熟悉欢迎阅读我的另一篇博客)
function Rectangle() {
}
var rec = new Rectangle();//rec的原型指向Rectangle.prototype
Object.getPrototypeOf(rec) === Rectangle.prototype; //获取rec的原型
// true
Object.setPrototypeOf(rec, Object.prototype); //设置rec的原型指向Object.prototype
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
六、Promise
Promise是javascript的一个内置对象,语法上是一个构造函数。
1. Promise的作用
(1)使回调函数更加灵活,不需提前指定回调函数。
(2)支持链式调用,封装异步操作,解决回调地狱。
2. 回调地狱的缺点:
(1)代码不便阅读和维护
(2)需要逐层处理异常,没有异常传透功能
3. Promise的特点:
(1)有三种状态
pending: 初始状态,既不是成功,也不是失败状态。
resolved(fulfilled): 意味着操作成功完成。
rejected: 意味着操作失败。
只有内部执行函数的执行结果能决定状态的变化。
(2)一旦状态改变就不可再变。
(面试高频题,详细介绍见:Promise的原理及实现)
七、Set和Map数据结构
Set
它类似于数组,但是成员的值都是唯一的,没有重复的值。(数组去重特别好用)
Set本身是一个构造函数,用来生成Set数据结构。
Map
JavaScript的对象(Object),本质上是键值对的集合(Hash结构),但是传统上只能用字符串当作键。这给它的使用带来了很大的限制。
为了解决这个问题,ES6提供了Map数据结构。它类似于对象,也是键值对的集合,但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。也就是说,Object结构提供了“字符串—值”的对应,Map结构提供了“值—值”的对应,是一种更完善的Hash结构实现。如果你需要“键值对”的数据结构,Map比Object更合适。
八、模块化
历史上,JavaScript 一直没有模块(module)体系,无法将一个大程序拆分成互相依赖的小文件,再用简单的方法拼装起来。其他语言都有这项功能,比如 Ruby 的require、Python 的import,甚至就连 CSS 都有@import,但是 JavaScript 任何这方面的支持都没有,这对开发大型的、复杂的项目形成了巨大障碍。
在 ES6 之前,社区制定了一些模块加载方案,最主要的有 CommonJS 和 AMD 两种。前者用于服务器,后者用于浏览器。ES6 在语言标准的层面上,实现了模块功能,而且实现得相当简单,完全可以取代现有的 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。
ES6 模块的设计思想,是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
核心: export 和 import 命令的使用(模块化开发项目)
以上内容只总结了ES6常用的新特性,下面的内容进行补充:
- Symbol: 新增的基本数据类型,表示独一无二的值。凡是属性名属于Symbol类型,就都是独一无二的,可以保证不会与其他属性名产生冲突。
- Generator: ES6提供的一种异步编程解决方案。
- Class关键字: 引入了Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。基本上,ES6的class可以看作只是一个语法糖,它的绝大部分功能,ES5都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
- async和await: 封装异步操作的终极方案。
- Proxy: Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。(vue3.0版本中用其取代Object.defineproperty进行数据劫持)
参考文档:阮一峰ES6入门文档