ES6 新特性

ES6 新特性

欢迎访问我的博客https://qqqww.com/,祝码农同胞们早日走上人生巅峰,迎娶白富美~~~

声明:本文参考文章如下:

  1. 业界大佬阮一峰老师的ES6标准入门
  2. 一位道友的理解 JavaScript 中的 for…of 循环
  3. github上的You-Dont-Know-JS

Babel 转码

请移步我的另一篇博客ES6-Babel转码

声明变量

ES6 中常用 letconst 来声明变量,下面介绍 letconst

let

ES6 新增了let命令,用来声明变量。它的用法类似于var,但是所声明的变量,只在let命令所在的代码块内有效

引入 let 的好处

我们在 ES5 中使用 var 来声明变量,但是var变量提升往往会带来一些问题:

1
2
3
4
5
6
7
// 使用 var
console.log(a) // 输出 undefined
var a = 1

// 使用 let
console.log(b) // 输出 ReferenceError
let b = 1

一般我们认为应该先声明变量再使用,但是这里使用var声明的变量却能提前使用,而不报错,本身在逻辑性上可能会觉得不太好,但是还没有看到具体会引起什么错误,那么再看下面的代码:

1
2
3
4
5
6
7
8
9
var a = new Date() // Tue Jan 29 2019 20:31:15 GMT+0800 (中国标准时间)
function fn () {
cosole.log(a)
if (false) {
a = 'hello'
console.log(a)
}
}
fn() // undefined

上述代码由于if代码块使用内层的变量,外面的使用外层的变量,看似互不影响,但是由于var存在变量提升,导致内层的a覆盖了外层的a,甚至有可能将局部变量提升为全局变量,以后有可能引起内存泄漏

所以引入letlet声明的变量有自己独立的作用域块,let声明的变量只在let所在的块内起作用,不再受外部影响,形成暂时性死区,有效防止变量提升问题

暂时性死区:在代码块内,使用let命令声明变量之前,该变量都是不可用的

我们将上述代码中的 var 换为 let 看看

1
2
3
4
5
6
7
8
9
let a = new Date() // Tue Jan 29 2019 20:31:15 GMT+0800 (中国标准时间)
function fn () {
cosole.log(a)
if (false) {
a = 'hello'
console.log(a)
}
}
fn() // Tue Jan 29 2019 20:31:15 GMT+0800 (中国标准时间)

这时候就不存在变量提升的问题了

let 规则

使用let声明的变量可以重新赋值,但是不能在同一作用域内重新声明

const

constlet 引入的好处基本一样,但是为什么还要引入呢?看看下面的 const 规则:

使用const声明的变量必须赋值初始化,但是不能在同一作用域类重新声明也无法重新赋值

它们的区别就在于规则,其他几乎一模一样

模板字面量

ES6中的模板字面量就是通过一种更加简便的方法去拼接字符串,在以前我们常通过+或者concat()等方法去拼接字符串

1
2
3
4
5
6
7
8
9
const dog1 = {
name: 'dahuang',
age: 10
}
const dog2 = {
name: 'xiaohei',
age: 10
}
let mes = dog1.name + 'and' + dog2.name + 'are dog'

下面用模板字面量去拼接字符串

1
let mes = `${dog1.name} and ${dog2.name} are dog`

一对反引号搞定

解构赋值

ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)

以前为变量赋值,只能直接指定值,下面来自阮一峰老师的文档里的一个小例子

1
2
3
let a = 1
let b = 2
let c = 3

ES6 允许下面这样

1
let [a, b, c] = [1, 2, 3]

上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值

解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于undefinednull无法转为对象,所以对它们进行解构赋值,都会报错

1
2
let { prop: x } = undefined // TypeError
let { prop: y } = null // TypeError

数组的解构赋值

1
2
3
const arr = [1, 2, 3]
const [a, b, c] = arr
console.log(a, b, c) // 1, 2, 3

对象的解构赋值

1
2
let { foo1, foo2 } = { foo1: 'a', foo2: 'b' }
// foo1 => 'a' foo2 => 'b'

对象字面量的写法

1
2
3
4
let name = 'dahuang'
let color = 'yellow'
const dog = {name, color}
console.log(dog)

字符串的解构赋值

1
2
3
4
5
6
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"

数值和布尔值的解构赋值

解构赋值时,如果等号右边是数值和布尔值,则会先转为对象

1
2
3
4
5
let {toString: s} = 123;
s === Number.prototype.toString // true

let {toString: s} = true;
s === Boolean.prototype.toString // true

上面代码中,数值和布尔值的包装对象都有toString属性,因此变量s都能取到值

函数参数的解构赋值

1
2
3
4
5
function add([x, y]){
return x + y
}

add([1, 2]) // 3

函数add的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量xy

我们用Babel在线转换工具把上述ES6代码转化为ES5代码看看,实际上就是讲作为参数的数组分别结构成了xy再返回他们的和

1
2
3
4
5
6
7
8
9
10
11
12
13
"use strict";

var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();

function add(_ref) {
var _ref2 = _slicedToArray(_ref, 2),
x = _ref2[0],
y = _ref2[1];

return x + y;
}

add([1, 2]); // 3

其他

当然对于解构赋值还有很多新玩法,例如可以添加默认值,这里阮一峰老师写的很详细,请移步ES6标准入门

for…of

for...of 语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的 for...of 循环,以替代 for...inforEach() ,并支持新的迭代协议。for...of 允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构

我们先来看看以前的方法

for 循环

1
2
3
4
let arr = [1, 2, 3, 4, 5, 6]
for (let i = 1; i < arr.length; i++) {
console.log(arr[i])
}

缺点:需要跟踪计时器和退出条件

虽然 for 循环在循环数组时的确具有优势,但是某些数据结构不是数组,因此并非始终适合使用 loop 循环

for…in循环

1
2
3
4
let arr = [1, 2, 3, 4, 5, 6]
for (const index in arr) {
console.log(arr[index])
}

缺点for...in 循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性或者方法,这些属性或方法也会出现在循环中,这就无意间浪费了资源

forEach()循环

数组方法,只用于数组中,局限性大

for…of循环

1
2
3
4
5
const arrs = [1, 2, 3, 4, 5, 6]
for (const arr of arrs) {
console.log(arr)
}
// 可忽略索引

而且可以随时退出或者停止for…of循环

1
2
3
4
5
6
7
const arrs = [1, 2, 3, 4, 5, 6]
for (const arr of arrs) {
if (arr % 2 === 0) {
continue
}
console.log(arr)
}

且不用担心向对象中添加新的属性。for…of 循环将只循环访问对象中的值

展开运算符

ES6中提供了展开运算符为...

1
2
const dogs = ['dahuang', 'xiaohei', 'xiaobai']
console.log(...dogs) // dahuang xiaohei xiaobai

数组拼合

1
2
3
4
5
const dogs = ['dahuang', 'xiaohei', 'xiaobai']
const peoples = ['xiaoming', 'zhangsan', 'zhaosi', 'wangwu']
const animals = [...dogs, ...peoples]
console.log(animals)
// ['dahuang', 'xiaohei', 'xiaobai', 'xiaoming', 'zhangsan', 'zhaosi', 'wangwu']

这实际上已经完成了以前拼合数组使用的concat()的功能

剩余参数

使用展开运算符将数组展开为多个元素, 使用剩余参数可以将多个元素绑定到一个数组中

  1. 将变量赋数组值
1
2
3
4
const goods = [20, 200, 'pen', 'book', 'ruler']
const [price, totalCount, ...studyGoods] = goods
console.log(price, totalCount, studyGoods)
// 前两个参数对应前两个,后面的作为数组传入 studyGoods
  1. 可变剩余参数作为函数形参使用
1
2
3
4
5
6
7
function add (...numbers) {
let sum = 0
for (const number of numbers) {
sum += number
}
return sum
}

ES6 箭头函数

箭头函数是 ES6中的一大亮点 ,下面看简单的 ES5 中的函数 和 ES6 中的箭头函数

ES5 中的函数

1
var fn = function () { console.log('我是es5') }

ES6中的箭头函数

1
2
3
const fn = () => { console.log('我是es6') }
// 等价于
const fn = () => console.log('我是es6')

就是讲ES5中的function()去掉,变成() =>,且当函数体只有一句程序的时候,大括号也能省略

比较ES5中的函数和箭头函数

  1. 前者可以使函数声明或者函数表达式,但是后者只能是函数表达式,因此只在表达式有效的时候才能使用
  2. 什么是表达式有效时?
    1. 存储在变量中
    2. 当做参数传递给函数
    3. 存储在对象属性中

存储在变量中的时候

其实上面的例子也说明了这点,下面再举两个简单的例子

1
2
3
const fn = numbers => `The pen are ${ numbers }`
// 调用
fn(200) // The pen are 200
1
const people = (name, age) => console.log(`${ name } is ${ age } years old`)

当做参数传递给函数的时候

将函数表达式当做参数传递给map函数,一般就是用于需要回调的函数

1
const people = ['zhangsan', 'zhaosi', 'wangwu'].map(name => name.toUpperCase())

存储在对象属性中的时候

1
2
3
4
5
6
const people = {
name: 'zhangsan',
age: 10,
say: () => console.log('hello')
}
console.dir(people.say) // say() 方法

箭头函数与this

对于普通函数,this的值基于函数如何被调用, 对于箭头函数,this的值基于函数周围的上下文, 换句话说,this的值和函数外面的this的值是一样的

普通情况下的this

  1. new对象,
1
const time = new Date() // 此时的this是Date()构造函数的实例对象
  1. 上下文
1
window.setTimeout() // 函数`setTimeout()`是对象`window`下的方法,此时this指向window
  1. 指定的对象
1
2
3
const arr = Array.prototype.slice.call(arr1)
const arr = Array.prototype.slice.apply(arr1)
// call 或者 apply 第一个参数设置 this 指向,所以此时 this 指向 arr1

此外,this 指向还有很多其他的讲究,详细见You-Dont-Know-JS

箭头函数与this

我们首先来看一个例子

1
2
3
4
5
6
Array.prototype.init = function () {
console.log(this)
setTimeout(function () {
console.log(this)
}, 1000)
}

这时候两次打印出来的 this 一样吗?执行Array.prototype.init()打印结果出来看一下:

很显然,第一个打印出了Array构造函数,第二个是Window,因为setTimeout()Window的方法,所以会改变this指向,那么使用箭头函数看看

1
2
3
4
5
Array.prototype.init = function () {
console.log(this)
setTimeout( () => console.log(this), 1000)
}
Array.prototype.init()

这次就打印出了两个Arraythis指向没有发生变化

应用场景

  1. 一些事件绑定之后需要操作原事件对象时防止this改变
  2. 用到一些构造函数的方法的时候,防止该构造函数的this被改变
  3. 等等……

Symbol

请移步我的另一篇博客ES6中的Symbol

class类

请移步我的另一篇博客ES6中的class关键字

Promise

请移步我的另一篇博客node-读取文件方法封装

Module

请移步我的另一篇博客ES6中Module语法与加载实现

Proxy

拦截代理,即在目标对象外层包裹一层拦截,外接对对象的访问都必须要先通过这层拦截,有点过滤的感觉,也有点像“门卫”,小区进门之前都要先过“门卫”

语法

1
var proxy = new Proxy(target, handler)

参数

  1. target:对象类型,表示需要被拦截的对象
  2. handler:对象类型,表示需要对这个对象target要做的拦截操作

例子

1
2
3
4
5
6
7
8
9
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});

proxy.time // 35
proxy.name // 35
proxy.title // 35

如果handler没有设置任何拦截,那就等同于直接通向原对象

1
2
3
4
5
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"

本文标题:ES6 新特性

文章作者:王工头

发布时间:2019年01月29日 - 23:54:37

最后更新:2019年01月31日 - 23:41:04

原始链接:https://qqqww.com/ES6新特性整理/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

-------------本文结束感谢大佬们的阅读-------------