欢迎访问我的博客https://qqqww.com/,祝码农同胞们早日走上人生巅峰,迎娶白富美~~~
1 Babel 转码
请移步我的另一篇博客ES6-Babel转码
2 声明变量
在
ES6
中常用let
或const
来声明变量,下面介绍let
和const
2.1 let
ES6 新增了
let
命令,用来声明变量。它的用法类似于var
,但是所声明的变量,只在let
命令所在的代码块内有效
2.1.1 引入 let 的好处
我们在
ES5
中使用var
来声明变量,但是var
的变量提升
往往会带来一些问题:
// 使用 var
console.log(a) // 输出 undefined
var a = 1
// 使用 let
console.log(b) // 输出 ReferenceError
let b = 1
一般我们认为应该先声明变量再使用,但是这里使用
var
声明的变量却能提前使用,而不报错,本身在逻辑性上可能会觉得不太好,但是还没有看到具体会引起什么错误,那么再看下面的代码:
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
,甚至有可能将局部变量提升为全局变量,以后有可能引起内存泄漏
所以引入
let
,let
声明的变量有自己独立的作用域块,let
声明的变量只在let
所在的块内起作用,不再受外部影响,形成暂时性死区,有效防止变量提升问题暂时性死区:在代码块内,使用
let
命令声明变量之前,该变量都是不可用的
我们将上述代码中的
var
换为let
看看
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 (中国标准时间)
这时候就不存在变量提升的问题了
2.1.2 let 规则
使用
let
声明的变量可以重新赋值,但是不能在同一作用域内重新声明
2.2 const
const
和let
引入的好处基本一样,但是为什么还要引入呢?看看下面的const
规则:使用
const
声明的变量必须赋值初始化,但是不能在同一作用域类重新声明也无法重新赋值它们的区别就在于规则,其他几乎一模一样
3 模板字面量
ES6
中的模板字面量就是通过一种更加简便的方法去拼接字符串,在以前我们常通过+
或者concat()
等方法去拼接字符串
const dog1 = {
name: 'dahuang',
age: 10
}
const dog2 = {
name: 'xiaohei',
age: 10
}
let mes = dog1.name + 'and' + dog2.name + 'are dog'
下面用模板字面量去拼接字符串
let mes = `${dog1.name} and ${dog2.name} are dog`
一对反引号搞定
4 解构赋值
ES6 允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)
以前为变量赋值,只能直接指定值,下面来自阮一峰老师的文档里的一个小例子
let a = 1
let b = 2
let c = 3
ES6
允许下面这样
let [a, b, c] = [1, 2, 3]
上面代码表示,可以从数组中提取值,按照对应位置,对变量赋值
解构赋值的规则是,只要等号右边的值不是对象或数组,就先将其转为对象。由于
undefined
和null
无法转为对象,所以对它们进行解构赋值,都会报错
let { prop: x } = undefined // TypeError
let { prop: y } = null // TypeError
4.1 数组的解构赋值
const arr = [1, 2, 3]
const [a, b, c] = arr
console.log(a, b, c) // 1, 2, 3
4.2 对象的解构赋值
let { foo1, foo2 } = { foo1: 'a', foo2: 'b' }
// foo1 => 'a' foo2 => 'b'
对象字面量的写法:
let name = 'dahuang'
let color = 'yellow'
const dog = {name, color}
console.log(dog)
4.3 字符串的解构赋值
const [a, b, c, d, e] = 'hello';
a // "h"
b // "e"
c // "l"
d // "l"
e // "o"
4.4 数值和布尔值的解构赋值
解构赋值时,如果等号右边是数值和布尔值,则会先转为对象
let {toString: s} = 123;
s === Number.prototype.toString // true
let {toString: s} = true;
s === Boolean.prototype.toString // true
上面代码中,数值和布尔值的包装对象都有
toString
属性,因此变量s
都能取到值
4.5 函数参数的解构赋值
function add([x, y]){
return x + y
}
add([1, 2]) // 3
函数
add
的参数表面上是一个数组,但在传入参数的那一刻,数组参数就被解构成变量x
和y
我们用Babel在线转换工具把上述
ES6
代码转化为ES5
代码看看,实际上就是讲作为参数的数组分别结构成了x
和y
再返回他们的和
"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
4.6 其他
当然对于解构赋值还有很多新玩法,例如可以添加默认值,这里阮一峰老师写的很详细,请移步ES6标准入门
5 for…of
for...of
语句创建一个循环来迭代可迭代的对象。在 ES6 中引入的for...of
循环,以替代for...in
和forEach()
,并支持新的迭代协议。for...of
允许你遍历 Arrays(数组), Strings(字符串), Maps(映射), Sets(集合)等可迭代的数据结构等
我们先来看看以前的方法
for 循环
let arr = [1, 2, 3, 4, 5, 6]
for (let i = 1; i < arr.length; i++) {
console.log(arr[i])
}
缺点:需要跟踪计时器和退出条件
虽然 for
循环在循环数组时的确具有优势,但是某些数据结构不是数组,因此并非始终适合使用 loop 循环
for…in循环
let arr = [1, 2, 3, 4, 5, 6]
for (const index in arr) {
console.log(arr[index])
}
缺点: for...in
循环循环访问所有可枚举的属性,意味着如果向数组的原型中添加任何其他属性或者方法,这些属性或方法也会出现在循环中,这就无意间浪费了资源
forEach()循环
数组方法,只用于数组中,局限性大
for…of循环
const arrs = [1, 2, 3, 4, 5, 6]
for (const arr of arrs) {
console.log(arr)
}
// 可忽略索引
而且可以随时退出或者停止for…of循环
const arrs = [1, 2, 3, 4, 5, 6]
for (const arr of arrs) {
if (arr % 2 === 0) {
continue
}
console.log(arr)
}
且不用担心向对象中添加新的属性。for…of 循环将只循环访问对象中的值
6 展开运算符
ES6
中提供了展开运算符为...
const dogs = ['dahuang', 'xiaohei', 'xiaobai']
console.log(...dogs) // dahuang xiaohei xiaobai
6.1 数组拼合
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()
的功能
6.2 剩余参数
使用展开运算符将数组展开为多个元素, 使用剩余参数可以将多个元素绑定到一个数组中
- 将变量赋数组值
const goods = [20, 200, 'pen', 'book', 'ruler']
const [price, totalCount, ...studyGoods] = goods
console.log(price, totalCount, studyGoods)
// 前两个参数对应前两个,后面的作为数组传入 studyGoods
- 可变剩余参数作为函数形参使用
function add (...numbers) {
let sum = 0
for (const number of numbers) {
sum += number
}
return sum
}
6.3 ES6 箭头函数
箭头函数是
ES6
中的一大亮点 ,下面看简单的ES5
中的函数 和ES6
中的箭头函数
ES5
中的函数
var fn = function () { console.log('我是es5') }
ES6
中的箭头函数
const fn = () => { console.log('我是es6') }
// 等价于
const fn = () => console.log('我是es6')
就是讲
ES5
中的function()
去掉,变成() =>
,且当函数体只有一句程序的时候,大括号也能省略
比较ES5中的函数和箭头函数:
- 前者可以使函数声明或者函数表达式,但是后者只能是函数表达式,因此只在表达式有效的时候才能使用
- 什么是表达式有效时?
- 存储在变量中
- 当做参数传递给函数
- 存储在对象属性中
6.4 存储在变量中的时候
其实上面的例子也说明了这点,下面再举两个简单的例子
const fn = numbers => `The pen are ${ numbers }`
// 调用
fn(200) // The pen are 200
const people = (name, age) => console.log(`${ name } is ${ age } years old`)
6.5 当做参数传递给函数的时候
将函数表达式当做参数传递给
map
函数,一般就是用于需要回调的函数
const people = ['zhangsan', 'zhaosi', 'wangwu'].map(name => name.toUpperCase())
6.6 存储在对象属性中的时候
const people = {
name: 'zhangsan',
age: 10,
say: () => console.log('hello')
}
console.dir(people.say) // say() 方法
6.7 箭头函数与this
对于普通函数,
this
的值基于函数如何被调用, 对于箭头函数,this的值基于函数周围的上下文, 换句话说,this的值和函数外面的this
的值是一样的
6.7.1 普通情况下的this
new
对象,
const time = new Date() // 此时的this是Date()构造函数的实例对象
- 上下文
window.setTimeout() // 函数`setTimeout()`是对象`window`下的方法,此时this指向window
- 指定的对象
const arr = Array.prototype.slice.call(arr1)
const arr = Array.prototype.slice.apply(arr1)
// call 或者 apply 第一个参数设置 this 指向,所以此时 this 指向 arr1
此外,this 指向还有很多其他的讲究,详细见You-Dont-Know-JS
6.7.2 箭头函数与this
我们首先来看一个例子
Array.prototype.init = function () {
console.log(this)
setTimeout(function () {
console.log(this)
}, 1000)
}
这时候两次打印出来的
this
一样吗?执行Array.prototype.init()
打印结果出来看一下:很显然,第一个打印出了
Array
构造函数,第二个是Window
,因为setTimeout()
是Window
的方法,所以会改变this
指向,那么使用箭头函数看看
Array.prototype.init = function () {
console.log(this)
setTimeout( () => console.log(this), 1000)
}
Array.prototype.init()
这次就打印出了两个
Array
,this
指向没有发生变化
6.7.3 应用场景
- 一些事件绑定之后需要操作原事件对象时防止
this
改变- 用到一些构造函数的方法的时候,防止该构造函数的
this
被改变- 等等…
7 Symbol
请移步我的另一篇博客ES6中的Symbol
8 class类
请移步我的另一篇博客ES6中的class关键字
9 Promise
请移步我的另一篇博客node-读取文件方法封装
10 Proxy
拦截代理,即在目标对象外层包裹一层拦截,外接对对象的访问都必须要先通过这层拦截,有点过滤的感觉,也有点像“门卫”,小区进门之前都要先过“门卫”
10.1 语法
var proxy = new Proxy(target, handler)
10.2 参数
target
:对象类型,表示需要被拦截的对象handler
:对象类型,表示需要对这个对象target
要做的拦截操作
10.3 例子
var proxy = new Proxy({}, {
get: function(target, property) {
return 35;
}
});
proxy.time // 35
proxy.name // 35
proxy.title // 35
如果
handler
没有设置任何拦截,那就等同于直接通向原对象
var target = {};
var handler = {};
var proxy = new Proxy(target, handler);
proxy.a = 'b';
target.a // "b"
11 Module
请移步我的另一篇博客ES6中Module语法与加载实现
12 参考文章
本文参考文章如下:
- 业界大佬阮一峰老师的ES6标准入门
- 一位道友的理解 JavaScript 中的 for…of 循环
- github上的You-Dont-Know-JS