ES6+【JS深入知识汇点12】

174 阅读7分钟

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.padStartString.prototype.padEnd,允许将字符串添加到原始字符串的开头或结尾。

String.padStart(targetLength,[padstring]):

  • targetLength: 字符串需要填充到的目标长度
  • padString:填充字符串,如果字符串太长,则只保留最左侧的部分,其他会被截断。此参数的缺省值为 " "(空格)

Object.getOwnPropertyDescriptors()

用来获取一个对象的所有自身属性的描述符,如果没有任何自身属性,返回空对象

ES2019

trimStart() 和 trimEnd()

可以用来消除字符串头部/尾部的空格,并且返回新字符串

Object.fromEntries()

把键值对列表(数组、man或者可迭代对象)转换成一个对象,是 Object.entries() 的逆操作

falt() 和 faltMap()

falt():按照一个可指定的深度递归遍历数组,然后将所有元素与遍历到的子数组中的元素合并成一个新数组返回

faltMap():使用映射函数映射每个元素,然后将结果压成一个新的数组,深度只有1