阮一峰老师的 ES6 入门教程 看了好几遍,发现每次看完都感觉有很多地方不懂,越看越焦虑,现在稍微明白了,这可能是大多数人追求的完美主义,希望每件事情都搞懂心理才踏实。其实,是没必要的,根据”二八定律“,我们只要掌握其中的 20% 常用知识和语法去解决开发中的 80% 问题就够了,剩下不懂的地方可以通过阅读相关文档和资料解决。更重要的原因是前端技术更新迭代如此之快,我们也要跟上步伐,把有限的时间放在正确的事情上,更何况我们还有诗和远方。所以在这里总结一下 ES6 常用语法和开发过程中遇到的一些坑,然后继续前行。
相同点
不同点
const
声明变量时需要赋初始值const
声明的基本类型数值不能被修改,声明的引用类型可以修改属性,但是不能重新赋值改变引用的地址const
程序执行效率更快
console.log(a) // 报错,Uncaught ReferenceError: a is not defined
let a = 1
let a = 2 // 报错,Uncaught SyntaxError: Identifier 'a' has already been declared
if (true) { // 形成块级作用域
var b = 33
let c = 22
}
console.log(b) // 33
console.log(c) // Uncaught ReferenceError: c is not defined
var name = '张三'
let lisi = '李四'
console.log(window.name) // 张三
console.log(window.lisi) // undefined
const d = '11'
const obj = {name: 33}
d = '22' // Uncaught TypeError: Assignment to constant variable.
obj.name = '2323' // {name: 2323}
obj = {} // Uncaught TypeError: Assignment to constant variable
解构赋值使用场景
let x = {
name: 33
}
ley y = [33, 22]
//交换值
[x, y] = [y, x]
console.log(x) // [33, 22]
function fn() {
...
return {name, value}
}
const {name, value} = fn() // 提取函数多个返回值
function getValue({name, value, ...}) { // 参数解构赋值
...
return value
}
let {name, data } = {name, '33', value: 22, data: [3, 2, 4]} //对象解构赋值,提取 JSON数据
function fn () {
for(let [k, v] of map.entries()) { // [k, v] map 解构赋值
...
}
}
import {module1, module2, ... } from './filename.js' // 模块解构赋值
注意事项
解构赋值变量重命名。有时候我们需要转换一下后台返回的键名,这时这个地方用的上
函数参数对象解构赋值时,我们需要给它默认值,这时如果调用函数不传对象就会报错,解决方法是初始化一个空对象
let {x: width, y: height} = {x: '220px', y: '100px'} // 重命名
console.log(width, height) // 220px 100px
function fn ({name='', value=0}) {
...
}
fn() // Uncaught TypeError: Cannot read property 'name' of undefined
// 加一个空对象
function fn ({name='', value=0} = {}) {
...
}
includes
:返回布尔值,判断字符串是否包含某串字符。接受两个参数,第二个参数是起始的位置
startsWith
:返回布尔值,判断字符串开头是否包含某串字符
endsWith
:回布尔值,判断字符串结尾是否包含某串字符
repeat
:返回一个新的字符串,将原字符串重复 n 次
padStart
:返回新的字符串,在字符串前面补全,接受两个参数,第一个参数补全字符串长度,第二个参数补全的字符
padEnd
::返回新的字符串,在字符串后面补全,接受两个参数,第一个参数补全字符串长度,第二个参数补全的字符
trimStart
:去除前面空格
tiemEnd
:去除后面空格
matchAll
: 返回一个正则表达式在当前字符串的所有匹配
let str = 'abcdef'
str.includes('bc') // true
str.includes('ss') // false
str.repeat(2) // "abcdefabcdef"
str.repeat(0) // ''
str.repeat(-1) // 报错,Uncaught RangeError: Invalid count value
str.padStart(10, 1) // "1111abcdef"
str.padEnd(10, 0) // "abcdef0000"
方法
match
:匹配成功返回数组,匹配不成功返回null
replace
:替换字符串,返回一个新的字符串
search
:匹配返回第一个索引的位置,匹配不成功返回 -1
split
:返回数组
exec
:匹配成功返回数组,匹配不成功返回null
test
:返回布尔值 boolean
方法
Number.isInteger
:判断是否是整数Number.isSafeInteger
:判断是否是安全整数Number.isNaN
: 判断是否是NaN
数值Math.trunc()
:去除一个数的小数部分函数拓展
函数默认参数
rest
参数
函数 length
属性。它表示函数参数的个数,如果参数设置默认值,则 length
减 1;rest
参数也不会计入 length
属性
箭头函数注意事项
this
对象,就是定义时所在的对象new
命令arguments
对象yield
命令尾调用优化
尾递归
function fn(a, b, c) {
...
}
fn.length // 3
function fn(a, b=1, c=2) {
...
}
fn.length // 1
let obj = {
name: 'JEFFER',
getName: () => { // 不适合箭头函数
console.log(this.name)
}
obj.getName() // undefined
doc.addEventListener('click', () => {
console.log(this) // 这里的this原本指向dom元素,现在使用箭头函数指向了 window
}
from
:将 Arguments
、DOM
元素集合等伪数组,转化为数组
of
:将多个数据项向转化为数组
find
:查找元素,查找成功返回数组元素,查找不到返回 null
findIndex
:查询下标,查找成功返回数组元素下标,查找不到返回 -1
copyWithin
:数组内部复制
fill
:填充数组
entries
:返回数组下标和元素组成的二维数组
keys
:返回下标的数组
values
:返回元素的数组
includes
:匹配数组项,返回 boolean
flat
: 数组降维
function fn(a, b,c) {
...
console.log(Array.from(Arguments)) // [a, b, c]
}
Array.of(1, 2, 3) // [1, 2, 3]
let arr = [22, 33, 44, 55]
arr.find((item) => item === '33') //33
arr.findIndex((item) => item === '33') // 1
数组迭代方法
数组迭代方法在函数式编程时非常有用,熟练使用函数式编程能大大提高自己的编程效率,写出的代码也很简洁,易读易维护
forEach
:遍历数组,中间可以使用 continue
跳过本次循环,在循环中break
关键字是不生效的
filter
:过滤数组返回一个新数组,不改变原数组。可以进行链式操作
map
:数组映射,返回一个数组,不改变原数组。可以进行链式操作
reduce
:数组汇总,返回一个值。接收两个参数,第二个参数接收默认值
every
:返回一个 boolean
值,遍历结果全部为 true
才为 true
,否则返回 false
some
:返回一个 boolean
值,遍历某次结果返回 true
时会立刻结束循环。这个想中间结束循环很有用,不用再借助 break
结束循环,有利于提高执行效率
注意 :在上面迭代方法中返回新数组使用的是浅拷贝,如果数组中的数值是引用类型,即使返回了新数组如果修改其值,该值指向同一个引用地址也会相互影响
let arr = [32,32,2324, 223]
arr.forEach((item, index) => {
if (...) {
continue // 满足条件想尽早结束本次循环
}
})
arr.filter((item, index) => {
...
return item
}).filter(()=>{...}).map(()=>{...}) // 链式操作
arr.map((item, index) => {
...
return item
}).filter(()=>{...}).map(()=>{...}) // 链式操作
arr.reduce((pre, next) { // 汇总
...
return item
}, [])
arr.ervery((pre, next) {
...
return item
})
arr.some((pre, next) {
if (...) {
...
return true // 结束循环
}
...
return item
})
方法
Object.assign()
:合并对象,浅拷贝,改变原对象
Object.keys()
:返回数组,获取对象的键
Object.values()
:返回数组,获取对象的值
Object.entries()
:返回二维数组,获取对象的键值
Object.fromEntries()
:将二维数组或 map 数据类型转化为对象
Object.create()
:创建对象
Object.is()
:同值相等,NaN
等于NaN
, 0
不等于 -0
Object.getPrototypeOf(obj)
:获取对象原型
Object.setPrototypeOf(obj)
:设置对象原型
Object.entries()
和 Object.fromEntries()
方法可以相互转换
Object.fromEntries
特殊用法,配合 URLSearchParams
对象,将查询字符串转为对象
le obj = { name: '33' }
Object.assign(obj, {value: 11}) // 注意,改变了原对象obj
console.log(obj) // {name, '33', value: 11}
// 二维数组转对象 { foo: "bar", baz: 42 }
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// map 转对象
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries) // { foo: "bar", baz: 42 }
// 将 URL 参数转化为对象
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }
Set
类似于数组结构,存放唯一值
属性
Set.prototype.constructor
:构造器Set.prototype.size
:Set
实例的成员总数方法
Set.prototype.add(value)
:添加某个值,返回 Set 结构本身。Set.prototype.delete(value)
:删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value)
:返回一个布尔值,表示该值是否为Set
的成员。Set.prototype.clear()
:清除所有成员,没有返回值。
s.add(1).add(2).add(2);
// 注意2被加入了两次
s.size // 2
s.has(1) // true
s.has(3) // false
s.delete(2);
s.has(2) // false
迭代方法
Set.prototype.keys()
:返回键名的遍历器Set.prototype.values()
:返回键值的遍历器Set.prototype.entries()
:返回键值对的遍历器Set.prototype.forEach()
:使用回调函数遍历每个成员
let set = new Set(['red', 'green', 'blue']);
for (let item of set.keys()) {
console.log(item);
}
// 这样也行
for (let item of set) {
console.log(item);
}
// red
// green
// blue
for (let item of set.values()) {
console.log(item);
}
// red
// green
// blue
for (let item of set.entries()) {
console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]
set.forEach((value, key) => console.log(key + ' , ' + value))
// "red", "red"
// "green", "green"
// "blue", "blue"
使用场景
// 去除数组的重复成员
[...new Set(array)]
// 去除字符串中重复字符
[...new Set('ababbc')].join('')
// "abc"
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}
// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}
// 差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}
注意事项
Set
集合数据区分数据类型,5
和 “5”
两个值不一样Set
中 NaN
是相等的,多次添加只添加一个Set
和 Array
可以相互转换
let set = new Set()
set.add(5)
set.add('5')
set.add(NaN)
set.add(NaN)
set.add({})
set.add({}) // 两个对象不一样
set.size // 5
// set 最终结果是
// Set(5) {5, "5", NaN,{},{}}
// 数组转 Set
let set1 = new Set(arr)
// Set 转数据
let arr = [...set]
// 或者
let arr = Array.from(set)
Map
类似于对象结构,本质上是键值对,简称 Hash 结构
属性
Map.prototype.constructor
:构造器Map.prototype.size
:Map
实例的成员总数方法
Map.prototype.set(key, value)
:添加某个键值对,返回 整个Map 结构。如果key
已经有值,则键值会被更新,否则就新生成该键。(添加多个可以进行链式操作)
Map.prototype.get(key)
:读取key
对应的键值,如果找不到key
,返回undefined
Map.prototype.has(key)
:返回一个布尔值,表示某个键是否在当前 Map 对象之中
Map.prototype.delete(key)
:删除某个键,返回true
。如果删除失败,返回false
Map.prototype.clear()
:清除所有成员,没有返回值。
迭代方法
用法和 Set
一样,就不粘贴代码了
Map.prototype.keys()
:返回键名的遍历器
Map.prototype.values()
:返回键值的遍历器
Map.prototype.entries()
:返回键值对的遍历器
Map.prototype.forEach()
:使用回调函数遍历每个成员,forEach
可以接受第二个参数用来绑定 this
执行上下文
Map 和数组的转换
数组给我们提供了丰富的 API
,将 Map
结构转换为数组处理数据更方便
const map = new Map()
.set(1, 'a')
.set(2, 'b')
.set(3, 'c');
[...map.keys()]
// [1, 2, 3]
[...map.values()]
// ['a', 'b', 'c']
[...map] or [...map.entries]
// [[1,'a'], [2, 'b'], [3, 'c']]
const map1 = new Map(
[...map].filter(([k, v]) => k < 3)
);
// 产生 Map 结构 {1 => 'a', 2 => 'b'}
const map2 = new Map(
[...map].map(([k, v]) => [k * 2, '_' + v])
);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
get(target, propKey, receiver):拦截对象属性的读取,比如proxy.foo
和proxy['foo']
。
set(target, propKey, value, receiver):拦截对象属性的设置,比如proxy.foo = v
或proxy['foo'] = v
,返回一个布尔值。
has(target, propKey):拦截propKey in proxy
的操作,返回一个布尔值。
deleteProperty(target, propKey):拦截delete proxy[propKey]
的操作,返回一个布尔值。
ownKeys(target):拦截Object.getOwnPropertyNames(proxy)
、Object.getOwnPropertySymbols(proxy)
、Object.keys(proxy)
、for...in
循环,返回一个数组。该方法返回目标对象所有自身的属性的属性名,而Object.keys()
的返回结果仅包括目标对象自身的可遍历属性。
getOwnPropertyDescriptor(target, propKey):拦截Object.getOwnPropertyDescriptor(proxy, propKey)
,返回属性的描述对象。
defineProperty(target, propKey, propDesc):拦截Object.defineProperty(proxy, propKey, propDesc)
、Object.defineProperties(proxy, propDescs)
,返回一个布尔值。
preventExtensions(target):拦截Object.preventExtensions(proxy)
,返回一个布尔值。
getPrototypeOf(target):拦截Object.getPrototypeOf(proxy)
,返回一个对象。
isExtensible(target):拦截Object.isExtensible(proxy)
,返回一个布尔值。
setPrototypeOf(target, proto):拦截Object.setPrototypeOf(proxy, proto)
,返回一个布尔值。如果目标对象是函数,那么还有两种额外操作可以拦截。
apply(target, object, args):拦截 Proxy 实例作为函数调用的操作,比如proxy(...args)
、proxy.call(object, ...args)
、proxy.apply(...)
。
construct(target, args):拦截 Proxy 实例作为构造函数调用的操作,比如new proxy(...args)
。
Reflect
是 ES6
为了操作对象而提供的新 API
每个新东西的出现都是为了解决实际问题,Reflect
的出现同样也是为了解决以下问题:
修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法
方法
Reflect.apply(target, thisArg, args)
:等同于Function.prototype.apply.call(func, thisArg, args)
,用于绑定this
对象后执行给定函数
const ages = [11, 33, 12, 54, 18, 96];
// 旧写法
const youngest = Math.min.apply(Math, ages);
// 新写法
const youngest = Reflect.apply(Math.min, Math, ages);
Reflect.construct(target, args)
:等同于new target(...args)
,这提供了一种不使用new
,来调用构造函数的方法。
Reflect.get(target, name, receiver)
:获取对象的值,例如 target[name]
, receiver
参数可以修改读取this
上下文(后面一样,就不介绍了)
Reflect.set(target, name, value, receiver)
:设置对象的值 target[name] = value
Reflect.defineProperty(target, name, desc)
:等同于 Object.defineProperty(target, name, desc)
Reflect.deleteProperty(target, name):
等同于delete target[name]
,用于删除对象的属性
Reflect.has(target, name)
:等同于name in target
里面的in
运算符
Reflect.ownKeys(target)
:等同于Object.keys(target)
Reflect.isExtensible(target)
:等同于Object.isExtensible(target)
Reflect.preventExtensions(target)
:等同于Object.preventExtensions(target)
Reflect.getOwnPropertyDescriptor(target, name)
:等同于Object.getOwnPropertyDescriptor(target)
Reflect.getPrototypeOf(target)
:等同于Object.getPrototypeOf(target)
Reflect.setPrototypeOf(target, prototype)
:等同于Object.setPrototypeOf(target)
promise
是实现异步编程的一种解决方案,可以通过链式操作,解决回调地狱的问题
使用场景
function loadImageAsync(url) {
return new Promise(function(resolve, reject) {
const image = new Image();
image.onload = function() {
resolve(image);
};
image.onerror = function() {
reject(new Error('Could not load image at ' + url));
};
image.src = url;
});
}
封装ajax
请求接口
const getJSON = function(url) {
const promise = new Promise(function(resolve, reject){
const handler = function() {
if (this.readyState !== 4) {
return;
}
if (this.status === 200) {
resolve(this.response);
} else {
reject(new Error(this.statusText));
}
};
const client = new XMLHttpRequest();
client.open("GET", url);
client.onreadystatechange = handler;
client.responseType = "json";
client.setRequestHeader("Accept", "application/json");
client.send();
});
return promise;
};
getJSON("/posts.json").then(function(json) {
console.log('Contents: ' + json);
}, function(error) {
console.error('出错了', error);
});
方法
Promise.prototype.then
:fullfilled
成功状态之后的回调函数
Promise.prototype.catch()
:rejected
错误时的回调函数
Promise.prototype.finally()
:该方法是 ES2018 引入标准,不管是成功或失败都会执行
Promise.all()
:用于将多个 Promise 实例,包装成一个新的 Promise 实例,
const p = Promise.all([promise1, promise2, promise3]).then(res => {});
上面promise1
、promise2
、promise3
都是Promise
实例,它们都是成功之后才能执行 then
回调函数,返回参数 res
是个数组,可以用来解决同时请求多个接口
Promise.race()
:和上面的 all
方法用法一样,不同的地方在于只要有一个实例执行成功,它就成功了,只要有一个失败它也代表失败了
Promise.allSettled()
:该方法和上面all
和race
用法一样,它主要解决的是不管成功或失败都返回,状态总是fulfilled
,不会变成rejected
,该方法由 ES2020 引入,有时候,我们不关心异步操作的结果,只关心这些操作有没有结束。这时,它就很有用
Promise.any()
:和 race
很相似,不同的地方,是不会因为某个 Promise 变成rejected
状态而结束,有点类似于数组中的 Array.prototype.some
方法,该方法目前是一个第三阶段的提案 。
Promise.resolve()
:Promise
的静态方法,它的使用场景是将现有对象转为 Promise 对象,返回fullfilled
成功状态
Promise.reject()
:Promise
的静态方法,它的使用场景是将现有对象转为 Promise 对象,返回rejected
失败状态
async
函数也是异步编程的一种解决方案,它和 Promise
不同在于可以使用同步的编码方式实现异步编程,除此之外使用它代替 Promise
多次调用 then
方法也会使代码更容易读懂和解耦
async
函数是 Generator
函数的语法糖,它在此基础上做了几点改进:
内置执行器:async
函数自带执行器
更好的语义:async
和await
,比起星号和yield
,语义更清楚了
更广的适用性:async
函数的await
命令后面,可以是 Promise 对象和原始类型的值(数值、字符串和布尔值,但这时会自动转成立即 resolved 的 Promise 对象)。
返回值是 Promise:可以用then
方法指定下一步的操作
async
函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await
命令就是内部then
命令的语法糖
基本用法
const asyncReadFile = async function () {
const f1 = await readFile('/etc/fstab');
const f2 = await readFile('/etc/shells');
console.log(f1.toString());
console.log(f2.toString());
};
使用场景
// 函数声明
async function foo() {}
// 函数表达式
const foo = async function () {};
// 对象的方法
let obj = { async foo() {} };
obj.foo().then(...)
// 迭代数组
async function dbFuc(db) {
let docs = [{}, {}, {}];
for (let doc of docs) {
await db.post(doc);
}
}
// 双重 async/await 并发请求接口数据
async function logInOrder(urls) {
// 并发读取远程URL
const textPromises = urls.map(async url => {
const response = await fetch(url);
return response.text();
});
// 按次序输出
for (const textPromise of textPromises) {
console.log(await textPromise);
}
}
class
是一个语法糖,它本身指向一个构造函数
使用场景
class
可以实现面向对象编程extends
继承,实现抽象和代码复用作用
模块的作用是可以将一个大的程序拆分成相互依赖的小文件,再用简单的方法拼接起来,可以让程序代码更容易维护、扩展功能、阅读和调试。
ES6 VS 其他模块
注意
ES6
引入的是只读操作,最好不要修改它的值基本用法
export
命令输出,后面必须是var, let, const
声明的变量、函数或对象
export default
输出,后面可以是函数、对象或其他数据类型值,不能有var, let, const
关键字,并且一个文件只能有一个 export default
import
命令输入, export
输出的要加大括号,export default
输出的不用加大括号
// a.js
var name = 33
export name; // 报错
export var firstName = 'Michael'; // 正确
export default name // 正确
function fn () {}
export fn; // 报错
export default fn 正确
export function fn () {} // 正确
export default function fn () {} // 正确
export { // 正确
name as other, // 重命名
firstName
}
export default { // 正确
name,
firstName
}
//-----------------------
// b.js 文件
import { name as username } from './a.js' // export 导出的、重命名
import * as all from './a.js' // 导入所有
import name fromm './a.js // export default 导出的
import() 函数
ES2020提案 引入import()
函数,支持动态加载模块,import()
函数可以用在任何地方,不仅仅是模块,非模块的脚本也可以使用。它是运行时执行,也就是说,什么时候运行到这一句,就会加载指定的模块
块级作用域
let
代替 var
,形成块级作用域,没有副作用let
和 const
优先使用 const
,const
声明常量,减少由于修改值出错,同时有利于提高程序的运行效率函数