前段时间找工作面试,被问到平常使用哪些es6 的语法,这个问题还是比较常见的,而且会引出几个问题的后续,平时用的有一些但是没专门看着去总结,突然被问的时候脑回路转的不够快,只说了经常用的 let const 之类的,事实上用的远不止这些,回来的时候就在思考这个问题和用过的一些,特写一篇记录总结一下,当然写的也是现阶段操作的,像那些不常用或者没用过的暂时不做记录。

let,const,var

letconst 对比之前的 var 都是一个定义变量的关键字,但是这肯定是有些区别的,不然也不会出这两个。

我们都知道在 es5 时有两个作用域,第一个是全局作用域,另一个是函数作用域,那么到了es6 有新增了一个作用域就是块级作用域。

var

其中使用 var 定义时会有变量提升的问题,导致一些奇怪的问题,比如说在函数内容没有写 var 定义变量时,会导致这个变量可以在函数外部访问,函数在运行完之后并不会被垃圾回收机制回收,这就可以在接下来继续使用,基本上了解这个就行了,其他的地方注意一下在开发上是没问题的。如果一定要去规范那就尝试用 es6 提供的 letconst 来定义一个变量。

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 函数中再相加。这个一般没啥好说的,无非是几种用法,最常用的就是

  1. 做一个克隆拷贝
  2. 与解构赋值结合
  3. 合并数组操作

克隆拷贝

对于拷贝这个东西来说的话又是另外的事情,本章不多做介绍,只需要了解的是一个值拷贝到另外一个值。不过在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 对象保存键值对。任何值(对象或者原始值) 都可以作为一个键或一个值。

做个的对比:MapsObjects 的区别

  • 一个 Object 的键只能是字符串或者 Symbols,但一个 Map 的键可以是任意值。
  • Map 中的键值是有序的(FIFO 原则),而添加到对象中的键则不是。
  • Map 的键值对个数可以从 size 属性获取,而 Object 的键值对个数只能手动计算。
  • Object 都有自己的原型,原型链上的键名有可能和你自己在对象上的设置的键名产生冲突。

map和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 这个我博客文章已经有两篇记录文章了,所以这里不在重新赘述了,点下面链接。

- THE END -

Promise,ES6 # 28 阅读
分享到:
Like

目录

    评论 (2)

    • 昵称
    • 邮箱
    • 网址
    1. 鸟叔
      鸟叔
      该评论仅登录用户及评论双方可见
      Windows 7 · Google Chrome
      1. lus
        lus 博主
        回复 @鸟叔:
        该评论仅登录用户及评论双方可见
        MacOS · Google Chrome

    🙋🏻‍♂️ 这里是我的个人站点,我在这里记录关于产品、设计、前端相关的内容,欢迎关注!

    联系我

    菜单

    热门文章

    最新评论

    水无心的头像 宇宙超级无敌美少女梦梦的头像 luss的头像 娇娇的头像 HEYHUA的头像 真z的头像 关山映月的头像 若志奕鑫的头像 yangyuji的头像 米斯特宫的头像 哥斯拉的头像 wu先生的头像 阿锋的头像 鸟叔的头像