2015 年发布了 ES6,也就是 ES2015,这篇文章主要是总结部分 ES6 ,以及 ES6 以后新语法的知识点
ES6
箭头函数
() => {},与使用 function 关键字创建的函数的区别是:
- 箭头函数没有
arguments - 箭头函数没有 prototype 属性,不能用作构造函数(不能用 new)
- 箭头函数的 this 是词法作用域,始终等于它上层上下文的 this,call、apply、bind 也没办法改变箭头函数的 this
⚠️注意:不要在可能改变 this 指向的函数中使用箭头函数,如果使用了,会强行改变 this。
iterator 迭代器
iterator 迭代器,就是一个具有 next()方法的对象,它是 ES6 常用特性(解构赋值、剩余/扩展运算符、生成器、for...of 循环)的实现基础。
- 可迭代的数据结构会有一个
[Symbol.iterator]方法 [Symbol.iterator]执行后返回一个iterable对象iterable对象有一个 next 方法, 执行一个 next 方法会返回一个有 value、done 属性的对象
解构赋值
解构赋值可以直接使用对象的某个属性,而不需要通过属性访问的形式使用。
- 对象解构:通过寻找相同的属性名,然后原对象的这个属性名的值赋值给新对象对应的属性
- 数组解构:消耗数组的迭代器,把生成对象的 value 属性的值赋值给对应的变量,可以用来交换变量。
剩余/扩展运算符
使用扩展运算符可以把容器拆开,这样就只剩下元素集合。最重要的一个特点就是代替了以前的 arguments, 但是必须放在最后一个
function hh(...rest) {
console.log(rest) // [1,2,3]
}
hh(1,2,3)
剩余运算符和扩展运算符的区别就是:剩余运算符会收集集合,放到右边的数组中。扩展运算符是将右边的数组拆成元素的集合,它们是相反的。
for...of 循环
遍历一个含有 interator 接口的数据结构并且返回各项的值。
和 ES3 中的 for...in 的区别如下:
- for...of 只可以用在可迭代对象上,for...in 可以获取所有对象的键名
- for...of 只遍历当前对象,for...in 会遍历对象的整个原型链
- 对于数组的遍历,for...of 只返回数组的下标对应的属性值,for...in会返回数组中所有可枚举属性
Module
ES6 Module 默认还没有被浏览器支持,需要使用 babel,可以在 script 标签里使用 type="module"就可在 同域的情况下解决。
ES6 Module 使用 import 关键字导入模块,使用 export 关键字导出模块,它还有以下特点:
- Module 是静态的,也就是在编译阶段运行,和 var 和 function 一样有提升的效果
- 自动采用严格模式
- export {变量} 导出具名的接口,或者 export default 导出匿名的接口
和主流的 CommonJS 的区别:
- CommonJS 输出的是一个值的拷贝,Module
export {<变量>}输出的是一个变量的引用,export default 输出的是一个值 - CommonJS 运行在服务器上,运行时加载,即代码执行到那一行才回去加载模块,而 ES6 Module 是静态的输出一个接口,发生在编译阶段
- CommonJS 在第一次加载时候运行一次并且会生成一个缓存,之后加载返回的都是缓存中的内容
函数默认值
如果使用了函数默认参数,在函数的参数区域,它会作为一个单独的块级作用域,并且有 let/const 的一些特性,比如暂时性死区
function test(a = 1) {
console.log(a)
}
test() //1
test(2) //2
函数默认参数是在传入的参数为 undefined 时才使用函数的默认值。
function func({x = 10} = {}, {y} = {y:10}) {console.log(x,y)}
func({}, {}) // 10, undefined; 均未使用函数默认参数,x 有默认值10
func(undefined, {}) // 10, undefined;第一个用了默认参数
func() // 10 10;都用了函数默认参数
func({x:1}, {y:2}) //1,2
对象属性简写
当属性名和变量名一样,ES6 允许我们在设置一个对象的属性时不指定属性名
const name = 'Ming',age = 18, city = 'Shanghai'
const student = {
name,
age,
city
};
console.log(student) // {name: 'Ming', age: 18, city: 'Shanghai'}
Proxy
Proxy 作为一个 "拦截器",在目标对象前架设一个拦截器,他人访问对象,必须先经过这层拦截器。
let obj = {};
obj = new Proxy(obj, {
set(target, key, val) {
console.log('oops')
return Reflect.set(target, key, val)
}
})
Object.defineProperty
ES5 中的 Object.defineProperty 可以给一个对象添加属性以及这个属性的描述符/访问器,Proxy 像是 Object.defineProperty 的增强版,可以定义13种属性。
let obj = {}
Object.defineProperty(obj, {
configurable: true,
enumerable: true,
get() {
return 1;
}
})
obj.b = 1;
obj.b = 2; //Uncaught TypeError: Cannot set property b of #<object> which has only a getter
handler.apply
apply 让我们拦截一个函数的执行
const proxy = (func, time) => {
let previous = new Date(0).getTime();
let handler = {
apply(target, context, args) {
let now = new date().getTime();
if (now - pervious > time) {
pervious = now;
Reflect.apply(func, context, args)
}
}
}
return new Proxy(func, handler)
}
handler.contruct
contruct 可以拦截通过 new 关键字调用这个函数的操作,可以用在单例模式中。
function proxy(func) {
let instance;
let handler = {
constructor(target, args) {
if(!instance) {
instance = Reflect.construct(func, args)
}
return instance
}
}
return new Proxy(func, handler)
}
function Person(name, age) {
this.name = name;
this.age = age;
}
const SingletonPerson = proxy(Person);
let person1 = new SingletonPerson('hhh', 11)
let person2 = new SingletonPerson('test', 22) // 这个实例不会成功,会返回 person1
console.log(person1 === person2) //true
Object.assign
Object.assign 遍历需要合并给 target 的对象的属性,用 等号 进行赋值。 有以下绩点需要注意:
- Object.assign 是浅拷贝,对于值是引用类型的属性,拷贝的仍然是它的引用
- 可以拷贝 Symbol 属性
- 不能拷贝不可枚举的属性
- target 是一个对象,如果传入一个基本类型,会转为基本包装类型,null/undefined 没有基本包装类型,所以传入会报错。
- ⚠️注意:如果传入一个字符串,字符串的基本包装类型的属性是只读的,所以不可以再次赋值
- source 如果是不可枚举的数据类型会忽略合并
- 因为是 等号赋值,所以如果被赋值的对象的属性有 setter 函数会触发 setter函数,如果赋值的对象有getter函数,也会调用对象的属性的 getter 函数
ES9 支持在对象上使用扩展运算符,实现的功能和 Object.assign 类似,唯一区别就是在含有 getter/setter 函数的对象属性上有区别:
- 扩展运算符会合并两个对象,并且只触发2个对象对应的 getter 属性
- Object.assign 会合并两个对象,后面的参数调用 getter,前面的参数调用 setter,并且会用前面参数的 setter 函数代替默认的赋值行为
类(class)
ES6 引入了语法糖 class(类)
class Animal {
// 构造函数,实例化时被调用,如果不指定,会有一个不带参数的默认构造函数
constructor(name, color) {
this.name = name;
this.color = color;
}
toString() {
console.log(`name: ${this.name}, color: ${this.color}`)
}
}
var animal = new Animal('dog', 'white')
console.log(animal.hasOwnProperty('name')) //true
console.log(animal.hasOwnProperty('toString')) //false
console.log(animal.__proto__.hasOwnProperty('toDtring')) // true
class Cat extends Animal {
constructor(action) {
// 子类必须在 constructor 中指定 super 函数,否则新建实例时会报错
super('cat', 'yellow')
this.action = action
}
toString() {
console.log(super.toString());
}
}
var cat = new Cat('catch')
cat.toString(); // name: cat,color: white
ES7
Array.prototype.includes
includes 函数用来判断一个数组是否包含一个指定的值,如果包含返回 true,否则返回 false
指数
指数运算符**:计算基数的指数次幂,具有和 Math.pow(...) 等效的计算结果
ES8
Object.values
Object.values 返回的是 Object 自身属性的所有值组成的数组,不包括继承的值
Object.entries
Object.entries 返回一个对象自身可枚举属性的键值对的数组
String padding
String 新增了两个实例函数 String.prototype.padStart 和 String.prototype.padEnd,允许将字符串添加到原始字符串的开头或结尾。
String.padStart(targetLength,[padstring]):
- targetLength: 字符串需要填充到的目标长度
- padString:填充字符串,如果字符串太长,则只保留最左侧的部分,其他会被截断。此参数的缺省值为
" "(空格)
Object.getOwnPropertyDescriptors()
用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,返回空对象
ES2019
trimStart() 和 trimEnd()
可以用来消除字符串头部/尾部的空格,并且返回新字符串
Object.fromEntries()
把键值对列表(数组、man或者可迭代对象)转换成一个对象,是 Object.entries() 的逆操作
falt() 和 faltMap()
falt():按照一个可指定的深度递归遍历数组,然后将所有元素与遍历到的子数组中的元素合并成一个新数组返回
faltMap():使用映射函数映射每个元素,然后将结果压成一个新的数组,深度只有1