前段时间找工作面试,被问到平常使用哪些es6 的语法,这个问题还是比较常见的,而且会引出几个问题的后续,平时用的有一些但是没专门看着去总结,突然被问的时候脑回路转的不够快,只说了经常用的 let
const
之类的,事实上用的远不止这些,回来的时候就在思考这个问题和用过的一些,特写一篇记录总结一下,当然写的也是现阶段操作的,像那些不常用或者没用过的暂时不做记录。
let,const,var
let
和 const
对比之前的 var
都是一个定义变量的关键字,但是这肯定是有些区别的,不然也不会出这两个。
我们都知道在 es5 时有两个作用域,第一个是全局作用域,另一个是函数作用域,那么到了es6 有新增了一个作用域就是块级作用域。
var
其中使用 var 定义时会有变量提升的问题,导致一些奇怪的问题,比如说在函数内容没有写 var
定义变量时,会导致这个变量可以在函数外部访问,函数在运行完之后并不会被垃圾回收机制回收,这就可以在接下来继续使用,基本上了解这个就行了,其他的地方注意一下在开发上是没问题的。如果一定要去规范那就尝试用 es6
提供的 let
和 const
来定义一个变量。
let,const
let
定义变量不会造成变量的提升,上面也提到了一个块级作用域的概念,意思就是 {}
包括的一块地方就相当于一个作用域,使用 let
来定义变量就可以了,一般的 let
会用作定义一个 变量 而不是 常量 ,变量顾名思义就是经常需要变化的,常量就是固定不变的值,使用 const
来定义一个常量,比如一个数字或者一个节点对象等。
let
使用的最经典的场景是用在 for
循环当中,如果用的是 var
定义的话,在循环片段完了之后外面是还可以访问到循环的 i
值的,那么 let
就不会有;
for(var i = 0; i < 5; i++) {}
console.log(i) // 5
for(let i = 0; i < 5; i++) {}
console.log(i) // Uncaught ReferenceError: i is not defined
变量的结构和赋值
按字面的意思的解读就是,变量拆解出来然后再给与新的值。
以前的只能去用 var
单独的去定义一个或者多个变量 var a = 1, b = 2;
,但是现在 es6
可以准许我们换一个写法:
let [a, b] = [1, 2]
从形式上看,右边的数组数据 [1, 2]
赋值给左边的 a,b,这样的话就知道了 a 的值为 1,b 的值为 2,简洁明了。再多看几个不一样的例子,就知道规律了。
let [foo, [[bar], baz]] = [1, [[2], 3]];
foo // 1
bar // 2
baz // 3
let [ , , third] = ["foo", "bar", "baz"];
third // "baz"
let [x, , y] = [1, 2, 3];
x // 1
y // 3
匹配相等,差不多就是左右两边的形式和写法一致,然后去获取。但是如果左右两边不相等情况下是怎么样的呢,答案是会给一个 undefined
值。看下面例子;
let [foo] = [];
let [bar, foo] = [1];
foo // undefined
let [x, y] = [1, 2, 3];
x // 1
y // 2
let [a, [b], d] = [1, [2, 3], 4];
a // 1
b // 2
d // 4
很奇妙的现象,左边少于右边的时候如果形式一致,那就会找到对应的值,少了也没关系,但是右边少于左边的情况,拿到的值就是一个 undefined
上面说到的只是单纯的结构赋值状态,为什么说是单纯,因为没有在左边重新赋予新的值或者说是默认的值,看下面的例子理解;
let [foo = true] = [];
foo // true
let [x, y = 'b'] = ['a'];
x // 'a'
y // 'b'
在这个过程中,可以去给其一个默认的值,这就赋值的一个过程。
以上例子都是些数组的解构赋值处理,对象其实也有这样的操作,只不过对象的结构操作左边的括号内容是右边的对象的 key
值,还是要看例子说;
let { foo, bar } = { foo: 'aaa', bar: 'bbb' };
foo // "aaa"
bar // "bbb"
let { baz } = { foo: 'aaa', bar: 'bbb' };
baz // undefined
观察规律可得,没有顺序排列,不像数组是有顺序排列的,左边的结构的值是右边的健名,而且一定是一样的才可以,如果没有就是一个 undefined
。除了一下初始化复制之类的也跟数组的差不太多,具体用法可以查阅相关文档或者直接看 阮一峰 老师的 es6 教程,这里就不多赘述。
扩展运算符的使用 (...)
说扩展运算符其实最常用的还是针对与对象数组的扩展,针对数组的一般定义的是,将一个数组转为用逗号分隔的参数序列,对象的扩展就是相当于 Object.assign()
方法。看下面例子;
function add(x, y) {
return x + y;
}
const numbers = [4, 38];
add(...numbers) // 42
let aClone = { ...a };
// 等同于
let aClone = Object.assign({}, a);
...numbers
会被展开成 1, 2,正好当参数传入到 add
函数中再相加。这个一般没啥好说的,无非是几种用法,最常用的就是
- 做一个克隆拷贝
- 与解构赋值结合
- 合并数组操作
克隆拷贝
对于拷贝这个东西来说的话又是另外的事情,本章不多做介绍,只需要了解的是一个值拷贝到另外一个值。不过在es5 当中时,拷贝赋值的过程,就引用类型来说是只拷贝的上一个数据在堆中的一个指引,不过不理解没关系可以翻看其他技术文章(对应深拷贝和浅拷贝以及数据类型的解释)。
const a1 = [1, 2];
const a2 = a1;
a2[0] = 2;
a1 // [2, 2],改变了 a2 的数据,a1 其实也是跟着变的,堆中的指向其实是同一个地址
const person = { name: "Amy", age: 15 }
const someone = { ...person }
someone // {name: "Amy", age: 15}
*注意点:自定义的属性和拓展运算符对象里面属性的相同的时候:自定义的属性在拓展运算符后面,则拓展运算符对象内部同名的属性将被覆盖掉
let person = { name: "Amy", age: 15 }
let someone = { ...person, name: "Mike", age: 17}
someone // {name: "Mike", age: 17}
为了处理这个问题,一般都会做一个深拷贝,使得两个数据的堆栈地址不一样,这样的话就改变数据就不会做到相互影响。看下面的修改。
const a1 = [1, 2];
// 写法一
const a2 = [...a1];
// 写法二
const [...a2] = a1;
a2[0] = 2;
a1 // [1, 2],a1 没有发生改变
与解构赋值结合
一般的如果对象或者数组很长的时候我们只需要其中的几个时,就可以用到扩展运算结合的方式去拿到,比如说下面的例子;
const [a, ...b] = [1, 2, 3];
a // 1
b // [2,3]
const { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
额外需要注意一点,扩展运算符用于数据的时候只能把 ...
置于最后一位,不然就会出现报错的问题。
const [...a, b] = [1, 2, 3]; // 报错 Rest element must be last element
合并数组操作
如果是以前的合并数组,就会使用的数组的 concat
方法,但是有新的语法之后我们就可以写的更简单一点,但是这两个方法都是一个浅拷贝的处理。
const arr1 = ['a', 'b'];
const arr2 = ['c'];
// ES5 的合并数组
arr1.concat(arr2);
// [ 'a', 'b', 'c']
// ES6 的合并数组
[...arr1, ...arr2]
// [ 'a', 'b', 'c' ]
数组的扩展方法
一般的会在面试的时候问一些基本问题,比如说什么是类数组?这时候就回到说,一般有 length
的属性,但是不能使用 foreach
遍历的就是类数组,像那个获取的 dom
节点下的所有子元素,就是一个类数组,一般就会用 for
循环或者 slice.call
方法去操作。然后会接着问你怎么去处理类数组转换为真正的数组,这样就可以用到 es6
提供的一个新的方法 Array.from()
。看例子;
const lis = document.querySelectorAll('ul li')
Array.from(lis).foreach()
Object.assign
一般用作于对象的合并,用的最多的场景就是参数的合并,将源对象的所有可枚举属性,复制到目标对象。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Set 和 Map 数据结构
说道数据结构可能有的人就会很陌生,不知道是啥,其实很简单,说一个对象 Object 的键值对就是一个简单的数据结构,但是这个结构是有一个弊端,就是 key
值只能是一个字符串格式的,而这个 Map 的数据结构就完全打破了这个情况,可以使用任意的数据区作为 key
值操作,比如可以是一个字符串,可以是一个数组等。
set 结构
Set
数据是一个以 key
为数据的一个集合,不储存 value
,但是储存的 key
值也是不会重复,可以利用这一特性做一个数组去重的操作。
var a = new Set([1, 2, 3, 3, '3']);
a; // Set {1, 2, 3, "3"}
可以看到上面的数据其实是做了一个去重的处理,针对数组的一项 3 去除,但是这样返回的数据不是之前的数组格式了,所以还用到上面说的另外一个数组的 Array.from()
方法去做一个改变。
map 结构
基本上没怎么使用 Map
结构做一些操作,但是这个也必须要记录一下跟 Set
一起。
简单的定义:Map
对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。
做个的对比:Maps
和 Objects
的区别
- 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
- Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
- Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
- Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。
Map 中的 key
const map = new Map()
// key 是字符串
const str = 'hello'
map.set(str, 'world')
map.get(str) // 'world'
map.get('hello') // world ,基本类型的值相等
// key 是对象
const obj = {}
map.set(obj, 'world')
map.get(obj) // world
map.get({}) // undefined,引用类型的值不等 obj !== {}
const func = function() {}
map.set(func, 'world')
map.get(func) // world
map.get(function() {}) // undefined,func !== function() {}
for...of 循环
先说定义:一个数据结构只要部署了 Symbol.iterator
属性,就被视为具有 iterator 接口,就可以用 for...of
循环遍历它的成员。也就是说,for...of
循环内部调用的是数据结构的 Symbol.iterator
方法。
for...of
循环可以使用的范围包括数组、Set 和 Map 结构、某些类似数组的对象(比如 arguments
对象、DOM NodeList 对象)、Generator 对象,以及字符串。
加上我们经常用的 for...in
做下对比:
// 遍历数组
const arr = [1, 2, 3, 4]
for (let i in arr) {
console.log(i) // 0,1,2,3
}
for (let i of arr) {
console.log(i) // 1,2,3,4
}
// 遍历对象
const obj = {
a: 1,
b: 2,
c: 3
}
for (let i in obj) {
console.log(i) // a,b,c
}
for (let i of obj) {
console.log(i) // 报错 Uncaught TypeError: obj is not iterable
}
// 遍历 Map 数据类型
const map = new Map([['Michael', 95], ['Bob', 75], ['Tracy', 85]])
for (let [key, value] of map) {
console.log(key, value)
}
// 遍历 Set 数据类型
const iterable = new Set([1, 1, 2, 2, 3, 3])
for (let i in iterable) {
console.log(i) // 空
}
for (let i of iterable) {
console.log(i) // 1,2,3
}
看例子,总结一下,for...of 可以遍历数组,Map数据和Set数据,但是不能遍历 对象{},for...in 可以遍历数组和对象,但只能获得对象的键名,不能直接获取键值。
Class 类
一般的我们在写一个 JS
插件时候都会使用构造函数的形式生成一个实例对象。
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.say = function() {
return '你好:' + this.name
}
const p = new Person('张三', 12)
按照以往的文章来说,这种写法会让刚学习代码,写面向对象编程的人来说感到困扰。不过就本人来说一开始是学的 JavaScript 也就那样,习惯了就不会有多大差,但为了写新的 es6 语法,就可以通过 es6 的 Class (类)这个概念编写。其实就是外面包一层。
Class Person {
constructor(name, age) {
this.name = name
this.age = age
}
say() {
return '你好:' + this.name
}
}
// 一样的也是需要 new 命令
const p = new Person('张三', 12)
具体使用里面的我也不说太多,大家自行查阅文档。
Promise 对象
promise
这个我博客文章已经有两篇记录文章了,所以这里不在重新赘述了,点下面链接。