ES6语法在面试中是频繁被提及的,相比于ES3和ES5它具备许多特性,接下来,主要通过对比将从多个方面逐个介绍它的新特性。
一、常量定义
在ES3中不存在常量的定义,ES5中可以实现,但并未明确的定义,通过对象添加属性prototype实现,Object.defineProperty,在ES6中,提出了let
和const
1. let
- let作用域只限于当前代码块
- let声明的变量作用域不会被提升
- 在相同的作用域下不能声明相同的变量
- for循环体现let父子作用域
2. const
- const作用域只限于当前的代码块
- const声明变量作用域不会被提升
- 在相同的作用域下不能重复声明
- 声明的同时必须赋值,声明之后的值无法改变
3. var、let、const三者对比
var | let | const |
---|---|---|
全局、整个函数块 | 块级变量 | 块级变量 |
存在变量的提升 | 允许重新赋值 | 不允许重新赋值 |
二、数据类型
首先回忆一下Js中的数据类型
基本数据类型:Number, Boolean,String,Null,Undefined (变量值存储于栈中)
引用数据类型:Array, Object,Function(对象存储于堆中,对象的地址存在栈中)
在函数调用的过程中,都是传递栈中的内容,基本数据类型传递的是变量的值,引用数据类型传递的是对象的地址
1.ES6新增的数据类型: Symbol
ES5对象中的属性名都是字符串,容易造成属性冲突;
ES6引入了新的原始数据类型Symbol,表示独一无二的值
因此设置Object对象的属性名有两种方式:一是字符串,二是Symbol
let str1 = Symbol()
let str2 = Symbol()
console.log(str1 === str2) // false,每个都独一无二
console.log(typeof(str1)) // Symbol
// Symbol主要用于对象属性的定义,且属性是唯一的
const obj = {}
obj[Symbol("name")] = "Mike"
obj[Symbol("name")] = "Casey"
console.log(obj) // {Symbol(name):"Mike",Symbol(name):"Casey"}
2.数据集合Set
使用示例:
let set = new Set(['Juidy','Juidy','Lisa','Davie'])
console.log(set) // Juidy, Lisa, Davie
console.log(set.size) // 3
set.add('Gier')
set.delete('Lisa')
set.has('Yumy') // false
set.clear()
3.数据集合Map
使用示例:
let obj1 = {height:'cm'}
let obj2 = {weight:'kg'}
const map = new Map([
['name', 'Twitter'],
['age', 20],
['sex','male'],
[obj1,183],
[onb2,68]
])
console.log(map)
// Map { 'name' => 'Twitter', 'age' => 20, 'sex' => 'male', { height: 'cm' } => 183, { weight: 'kg' } => 68 }
console.log(map.size) // 5, 即不重复的key的个数
// 设置值和获取值
console.log(map.set('age',21))
console.log(map.get('age'))
// Map自带的获取键,值以及键值对hash关系的方法
console.log(map.keys())
// [Map Iterator] { 'name', 'age', 'sex', { height: 'cm' }, { weight: 'kg' } }
console.log(map.values())
// [Map Iterator] { 'Twitter', 21, 'male', 183, 68 }
console.log(map.entries())
// [Map Iterator] { [ 'name', 'Twitter' ], [ 'age', 21 ], [ 'sex', 'male' ], [ { height: 'cm' }, 183 ], [ { weight: 'kg' }, 68 ] }
三、解构赋值
ES6允许按照一定的模式从数组和对象中提取值,对变量进行赋值,这被称为解构
1. 基本用法:
let [name,age,sex] = ["Casey",23,"female"]
// 等价于,传统定义
let name = "Casey"
let age = 23
let sex = "female"
2.支持解构赋值的数据类型
1) 对象
2) 嵌套数组
3) 字符串
4) 空缺变量 let [a,b,,e] = ['Addy','Bob',[1,2],4]
5) 多余变量 let [a,b,,e,f] = ['a','b',[3,4],5]
: 因为字符串有解构器,所以可以解析出来
四、块级作用域
JS中的两种作用域是:全局作用域和函数作用域
我们知道,在JS中所有通过var声明的变量都会提升到当前作用域的最前面,也就是上面提到的var所实现的变量提升。如下:
function funC(){
console.log(temp)
var temp
}
正因为变量提升的存在,使得JS本身并不像类C语言一样支持块状作用域,根据我们的编码习惯,我们并不喜欢变量提升机制,而是鞥希望代码能按照顺序执行。因此神奇的ES6引入了块级作用域,并且采用了let、const
来支持块级作用域。
优势: 在循环中使用let来代替var可以在循环结束的时候销毁变量,避免无用的变量影响全局,安全明了,利用块级作用域可以实现累加器
Tips: 在ES6中最好使用函数表达式来表示一个函数,ES6中允许函数在块级作用域中可以声明的条件必须是在大括号里面,否则会报错
//函数声明报错
{
if(2 < 4){
function fn(){
console.log('I’m here!')
}
}
}
fn() // Uncaught TypeError: fn is not a function
//正确的函数声明
{
let fa = '111'
let fn = function(){
console.log('I’m here!')
}
console.log(fa, fn) // 111 f(){ console.log('I’m here!')}
}
使用块级绑定的最佳实践是:默认使用
const
,只在确定需要改变变量的值时,使用let
,可以确保基本层次的不可变性,以最大化地避免错误的产生。
五、箭头函数 =>
在ES6中引入了箭头函数 (MDN 箭头函数),let func = value => value+1
1.箭头函数与普通函数的区别:
- 箭头函数没有
this
,需要查找作用域链来确定this的值?
(如果它被非箭头函数包含,则this绑定的就是最近一层的非箭头函数的this), 箭头函数this是父作用域的this
,不是调用时的this
- 箭头函数没有自己的
arguments
对象,但是可以访问外围函数的arguments对象,也没有caller,calee
- 箭头函数不能作为构造函数,不能通过
new
关键词调用,同样也没有new.target值和原型属性 - 箭头函数通过
call
和apply
来调用,不会改变this
的指向,只会传入参数 - 箭头函数不能作为
Generator
函数,不能使用yield
关键字
此外还有一些特性:
- 箭头函数返回对象时,要加一个小括号
var func = () =>({ foo: 1 })
- 箭头函数在ES6 class中声明的方法为实例方法,不是原型方法
- 多重箭头函数就是高阶函数,相当于内嵌函数
提到箭头函数和this
,还有一个非常重要的问题必须有姓名 this的绑定方式
2.this的四种绑定方式:
- 默认绑定:全局环境中,this默认绑定到
window
对象上 - 隐式绑定:被直接对象所包含的函数调用(方法调用)时,this隐式绑定到该直接对象上(隐式丢失是指被隐式绑定的函数丢失绑定对象,默认绑定到window)
- 显式绑定:通过
call(),apply(),bind()
方法将对象绑定到this上 - new绑定:如果函数或方法调用之前带有关键字
new
,就构成构造函数调用。
关于this绑定,这一篇就不再过多赘述,想看详细的this绑定可以读一下this的绑定方式总结
六、扩展功能
ES6语法可以更好地支持扩展功能,包含数组、对象以及函数的扩展,此外ES6中出现了延展符…,既可以用来定义数组,还能扩展数组、对象,函数
一、数组的扩展
1.Array.from——实现非数组list的转化
let list = document.querySelectorAll("li")
console.log(Array.isArray(list)) // false
let newList = Array.from(list)
console.log(Array.isArray(newList)) // true
2.Array.of——零散元素转化为数组
转化方式:Array.of( 1, 3, 7, 9)
3. 延展符…
// 用延展符来定义数组
let str = "Kole";
let strArr =[...str];
console.log(Array.isArray(strArr)); //true
二、对象的扩展
1.ES6中key和value一样时,定义对象时只需要写一个就可以
let name = 'Peter'
let age = 19
let obj = {
name,
age
}
console.log(obj) // {name:'Peter', age:19}
2.assign的用法
let obj1 = {name: "Lisa"}
let obj2 = {age: 37}
let obj = {}
Object.assign(obj, obj1, obj2) // 将obj1,obj2合并到obj上
console.log(obj) // {name:"Lisa", age:37}
3. 将Set对象转为Array
主要用于数据传递和数组去重,因为Set具有唯一性
let myArr = [1, 2, 10, "ZS", 20, 2, 1]
console.log(new Set(myArr)) // 根据set唯一性去重复
let newArr = [...new Set(myArr)] // 将set转化为array
console.log(Array.isArray(newArr)) // true
三、函数的扩展
1. 在ES6中,形参可以设置默认值
let func = function(a = 1, b = 2){ return a+b }
2. 参数形式:可以用延展操作符…代替
可变参数…a, 在不清楚元素个数的时候可以表示如此
可以使用扩展运算符…来合并数组,如b = [10,19], a = [1,2...b]
// 在不清楚元素个数时,可以使用延展操作符代替
function sum (name, ...nums){
console.log(name)
let res = 0
for(let value of nums){
res += value
}
return res
}
console.log(sum("Juilian",2,34,6,19)) // Juilian 61
七、对象代理——Proxy
ES6中新添加了对象代理Proxy
Proxy是什么 MDN Proxy
代理Proxy是一个构造函数(target, handler),返回一个代理对象Proxy,主要用于从外部控制对对象内部的访问。
本质: 元编程非破坏性数据劫持。在原对象的基础上进行功能的衍生而且又不影响原对象,符合高内聚低耦合的设计理念
target
接收Proxy()转发的所有内部方法handler
可以覆写(overwrite)任意代理的内部方法。
总之,调用Proxy的方法就会调用target上对应的方法,外界每次通过Proxy访问target对象的属性时,都会经过handler对象
总之,Proxy就是在数据外层套了一个壳,然后通过这个壳访问内部数据
1. Proxy的作用:
对对象进行拦截、实现对数据读取、修改的过滤保护
语法 | 对象操作 |
---|---|
ES3 | 设局部变量,采用闭包方式的set和get方法 |
ES5 | 采用对象下面的Object.defineProperty() 对属性描述符进行操作 |
ES6 | 使用Proxy代理对象,设计与实现具备set和get方法的新对象 |
2. Proxy使用方法:
第一个参数:要拦截的对象,第二个参数:对象(内部有set和get)
get()——对象读数据的时候调用,两个参数:target(目标数据)和key(所读数据的key)
set()——对象设置数据时触发,内部三个形参:target, key 和 value
用法示例:
var Person = {
name:'Lily',
age: 17,
sex: 'female'
}
var p = new Proxy(Person,{
get(target, key){
return target[key]
}
set(target, key, value){
if(key !== 'sex'){ // 设置Person对象的sex属性不可修改
target[key] = value
}
}
})
console.log(p.name, p.age, p.sex) // Lily,17,female
// 重新设置对象属性
p.name = "Lisa"
p.age = 25
p.sex = 'male' // Person对象中的sex属性不可以设置
console.log(p.name, p.age, p.sex) // Lisa,25,female
不仅是set和get, Proxy有13种数据劫持的操作:
3.Proxy的应用
- 封装高阶函数,为函数添加特定的功能
- 代理对象的访问
- 作为胶水桥接不同结构的对象
- 监视对象的变化
- Proxy对象可以拦截目标对象的任意属性,这使得它很适合来编写web服务的客户端
- Proxy可以用来实现数据层的ORM(Object-Relationship-Model)层
- 在Vue 3.0中使用了Proxy去实现部分核心代码
- 在业务开发时应该注意Proxy使用场景,当对象的功能变得复杂或者我们需要进行一定的访问限制时,便可以考虑使用代理。
4. Proxy的优劣势
在ES5中,虽然使用Object.defineProperty()
实现属性已经非常的简单,但是在设置对象属性为不可写属性的时候,修改并不成功。相对于ES3和ES5,ES6中使用Proxy是非常便利的,因为可以在set和get里面做出更多的逻辑处理
优势:
- Proxy让开发者很方便的使用代理模式,使函数更加强大,业务逻辑更加清楚
- 不但可以取代
Object.defineProperty()
,并且还扩增了非常多的功能 - Proxy技术支持检测数组的push等方法操作,支持对象属性的动态添加和删除,极大地简化了响应化的代码量
缺点:
- 运行环境必须支持ES6
- 兼容性一般,(不过谷歌开发的proxy-polyfill目前已经支持get、set、apply、construct 到ie9了)
- 不能直接代理一些需要this的对象,代理之后行为可能发生变化,可能需要手动绑定this
- 性能比较差,但是因浏览器而异,proxy绝对不适合用在性能关键点的代码上,当然,也可以衡量proxy带来的遍历和可能损耗的性能,进行合理的中和,来达到最佳的开发体验和用户体验
目前浏览器没有办法判断对象是否被代理,但是在node版本10以上,可以使用
util.types.isProxy
来判断
八、其他
- 除此之外,ES6中还新增了
class
语法糖
作用: 让对象原型的写法更加清晰,更像面向对象的编程方式,也是构造函数的另一种写法。
//ES6之前的构造函数写法
function Person(name, age){
this.name = name
this.age = age
}
Person.prototype = {
constructor: Person,
print(){
console.log('Hi! My name is '+this.name,',and I\'m ' + this.age + ' years old.')
}
}
// class面向对象写法
class Person{
constructor(name,age){
this.name = name
this.age = age
}
print(){
console.log('Hi! My name is '+this.name,',and I\'m ' + this.age + ' years old.')
}
}
// 定义和调用类方法
let person = new Person("Rose",24)
person.print() // Hi! My name is Rose,and I'm 24 years old
- ES6中也支持模板字符串
作用: 使用 `` 符号定义模板,在里面使用${}
的形式调用变量,就可以实现代码的格式化,类似render()函数, 在 Vue 中常用
let name = "dance";
let content= "Coincidance"
let html = `
You can really Dance!
${content}
`
//这里使用${}的形式调用变量
console.log(html);
ES6语法带来了许多新的特性,目前也已经有非常成熟的使用,学习ES6语法可以帮助我们更好地了解JS
最后,附上一个ES6的特性小字典,来源于IQ前端的原创文章配图: