前言
仅将自己的理解做整理、归类并结合实际遇到的问题做记录,更推荐阅读 ECMAScript 6 入门。
扩展篇
数组的扩展
- 解构赋值
“ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)”
作用:可以快速取得数组或对象当中的元素或属性,而无需使用arrx或者objkey等传统方式进行赋值
代码语言:javascript复制let [a,b,c] = [1,2,3];
- 扩展运算符
“扩展运算符是三个点,它如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列”
作用:把数组或类数组对象展开成一系列用逗号隔开的值
代码语言:javascript复制console.log(...[1,2,3])
//1 2 3
console.log([1,2,3].join())
//1,2,3
console.log(typeof(...[1,2,3]))
//Uncaught SyntaxError: Unexpected token ...
console.log(typeof([1,2,3].join()))
//string
* 为什么typeof(...[1,2,3])报错Uncaught SyntaxError: Unexpected token ...???
常用
代码语言:javascript复制let [head,...tail] = [1,2,3,4]; //head:1,tail:[2,3,4]
代码语言:javascript复制let colors = ["red","green","blue"];
let [...cloneColors] = colors;
console.log(cloneColors) //["red", "green", "blue"]
1.将数组转为函数的参数,替代 apply 方法
代码语言:javascript复制Math.max.apply(null,[14,3,77])//ES5
Math.max(...[14,3,77])//ES6
2.合并数组
代码语言:javascript复制var arr1=['a','b']
var arr2=['c','d']
console.log(arr1.concat(arr2))//ES5
console.log([...arr1,...arr2])//ES6
- Array.from()
“Array.from方法用于将两类对象转成真正的数组”
- Array.of()
“Array.of方法用于将一组值转换为数组”
常用
代码语言:javascript复制console.log(Array.of(3,11,8))//[3, 11, 8]
- copyWithin()
代码语言:javascript复制复制替换数据
Array.prototype.copyWithin(target,start=0,end=this.length)
target:必选,从该位置开始替换 start:替换内容的起点 end:到该位置前停止读取
代码语言:javascript复制console.log([1,2,3,4,5].copyWithin(0,3,4))//[4, 2, 3, 4, 5]
console.log(['a','b','c'].copyWithin(0,1,3))//["b", "c", "c"]
- etc. find()、fill()、entries()、keys()、values()、includes、数组的空位(非重点关注)
对象的扩展
- 属性简洁表示、属性名表达式、方法的name属性、Object.is()、Object.assign():'合并对象-浅复制'、属性可枚举性
- 遍历对象的属性
1.for...in
2.Object.keys()
代码语言:javascript复制var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
- 对象的解构赋值
代码语言:javascript复制对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
字符串的扩展
- 字符串遍历 for...of
console.log('foo'.split(''))//["f", "o", "o"]//ES5
代码语言:javascript复制for(let codePoint of 'foo'){
console.log(codePoint)//'f',"o","o"//ES6
}
- 返回字符串给定位置的字符at()
console.log('lallallal'.indexOf('a'))//1 Number//ES5
代码语言:javascript复制console.log('lallallal'.charAt('a'))//1 String//ES6
- includes():是否找到参数字符串、startsWith()/endswith():参数字符串是否在源字符串头/尾部...
函数的扩展
- 函数参数的默认值
function log(x,y='world'){
console.log(x,y)
}
log('moximoxi')//moximoxi world
- rest参数(...变量名)
function test(...values){
console.log(values)
for(var val of values){
console.log(val)
}//for...of可以不仅可以遍历字符串,还可以遍历数组 ES6
}
test(1,2,3)//[1, 2, 3]
- name属性
name属性:function foo(){} foo.name//"foo"
- 箭头函数 →→→
函数名=参=>返回值
- 尾调用、尾递归
某个函数的最后一步是调用另一个函数
正则的扩展
数值的扩展
承诺篇
Promise
from MDN:
代码语言:javascript复制var promise1 = new Promise(function(resolve, reject) {
setTimeout(function() {
resolve('foo');
}, 300);
});
promise1.then(function(value) {
console.log(value);
// expected output: "foo"
});
console.log(promise1);
// expected output: [object Promise]
Generator
from CSDN:
代码语言:javascript复制function* helloGenerator() {
console.log("this is generator");
}
var h = helloGenerator();
h.next();
代码语言:javascript复制function* helloGenerator() {
yield "hello";
yield "generator";
return;
}
var h = helloGenerator();
console.log(h.next());//{ value: 'hello', done: false }
console.log(h.next());//{ value: 'generator', done: false }
console.log(h.next());//{ value: 'undefined', done: true }
yield实际就是暂缓执行的标示,每执行一次next(),相当于指针移动到下一个yield位置。
总结一下,Generator函数是ES6提供的一种异步编程解决方案。通过yield标识位和next()方法调用,实现函数的分段执行。
async
迭代器=>Generator=>async
1.迭代器:不暴露对象的内部表示的情况下,能够遍历整个元素
代码语言:javascript复制遍历 ~ Traverse 访问一个集合(广义)的每个元素 迭代 ~ Iterate 反复调用同一个过程最终达成目的(迭代是循环的一种方式),这个过程如果是一个函数,那就是递归,如果是一个循环体,那就是狭义上的迭代。递归和迭代之间的关系、转换、优化等等又是另一个故事了。
function makeIterator (arr) {
let nextIndex = 0;
//返回一个迭代器方法
return {
next: () => {
if(nextIndex < arr.length){
return {value: arr[nextIndex ], done: false}
}else{
return {done: true}
}
}
}
}
const it = makeIterator([1,2,3]);
console.log('1:', it.next());
console.log('2:', it.next());
console.log('3:', it.next());
console.log('end:', it.next());
2.Generator函数执行后会返回一个迭代器
3.async函数是Generator的语法糖
async 函数返回一个Promise对象,可以使用 then 方法添加回调函数
实际遇到
代码语言:javascript复制(function(){
let pro=new Promise((resolve,reject)=>{
resolve(true)
})
console.log(pro)//promise对象
})()
(async function(){
let pro=await new Promise((resolve,reject)=>{
resolve(true)
})
console.log(pro)//true
//(async函数的返回值是一个promise对象;waite命令后面是一个Promise对象,如果不是,会被转成一个立即resolve的Promise对象)
})()
数据结构篇
Set、Map
- Set
Set 是成员值唯一的数据结构,类似于数组 Array
所以,有一种重要且好用的去重方法:
代码语言:javascript复制var set1 = Array.from(new Set([1,1,2,2,33,'aa','aa','bb'
]))
console.log(set1)//[1, 2, 33, "aa", "bb"]
或:
var set1 = [...new Set([1,1,2,2,33,'aa','aa','bb'
])]
console.log(set1)//[1, 2, 33, "aa", "bb"]
常用
Set 实例的方法分为两大类:操作方法(用于操作数据)、遍历方法(用于遍历成员)
分类 | 方法 |
---|---|
操作方法 | Set.prototype.add(value):添加某个值,返回 Set 结构本身Set.prototype.delete(value):删除某个值,返回一个布尔值,表示删除是否成功。Set.prototype.has(value):返回一个布尔值,表示该值是否为Set的成员。Set.prototype.clear():清除所有成员,没有返回值。 |
遍历方法 | Set.prototype.keys():返回键名的遍历器Set.prototype.values():返回键值的遍历器Set.prototype.entries():返回键值对的遍历器Set.prototype.forEach():使用回调函数遍历每个成员 |
- Map
Map 是可以用非字符串当作键的键值对数据结构,类似于对象 Object
Map 结构提供了“值—值”的对应,是一种更完善的 Hash 结构实现。
常用
Map 实例的方法分为两大类:操作方法(用于操作数据)、遍历方法(用于遍历成员)
分类 | 方法 |
---|---|
操作方法 | Map.prototype.set(key, value):设置Map.prototype.get(key):获取 Map.prototype.has(key):判断存在 Map.prototype.delete(key):删除 Map.prototype.clear():清除 |
遍历方法 | Map.prototype.keys():返回键名的遍历器。Map.prototype.values():返回键值的遍历器。Map.prototype.entries():返回所有成员的遍历器。Map.prototype.forEach():遍历 Map 的所有成员。 |
Proxy、Reflect
- Proxy
Proxy 用于修改某些操作的默认行为,等同于在语言层面做出修改,所以属于一种“元编程”(meta programming),即对编程语言进行编程。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy 这个词的原意是代理,用在这里表示由它来“代理”某些操作,可以译为“代理器”。
Proxy就是对象的拦截器
典例:set()方法:用validator拦截person.age的创建;
代码语言:javascript复制let validator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (!Number.isInteger(value)) {
throw new TypeError('The age is not an integer');
}
if (value > 200) {
throw new RangeError('The age seems invalid');
}
}
// 对于满足条件的 age 属性以及其他属性,直接保存
obj[prop] = value;
}
};
let person = new Proxy({}, validator);
person.age = 100;
person.age // 100
person.age = 'young' // The age is not an integer
person.age = 300 // The age seems invalid
- Reflect
将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。
为了修改某些Object方法的返回结果,让其变得更合理。
例:用defineProperty,不抛出错误,返回false:
代码语言:javascript复制// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
重点食用:使用 Proxy 实现观察者模式——结合Vue的双向绑定原理
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
代码语言:javascript复制const person = observable({
name: '张三',
age: 20
});
function print() {
console.log(`${person.name}, ${person.age}`)
}
observe(print);
person.name = '李四';
// 输出
// 李四, 20
上面代码中,数据对象person是观察目标,函数print是观察者。一旦数据对象发生变化,print就会自动执行。
下面,使用 Proxy 写一个观察者模式的最简单实现,即实现observable和observe这两个函数。 思路是observable函数返回一个原始对象的 Proxy 代理,拦截赋值操作,触发充当观察者的各个函数。
代码语言:javascript复制const queuedObservers = new Set();
const observe = fn => queuedObservers.add(fn);
const observable = obj => new Proxy(obj, {set});
function set(target, key, value, receiver) {
const result = Reflect.set(target, key, value, receiver);
queuedObservers.forEach(observer => observer());
return result;
}
上面代码中,先定义了一个Set集合,所有观察者函数都放进这个集合。 然后,observable函数返回原始对象的代理,拦截赋值操作。拦截函数set之中,会自动执行所有观察者。
lterator和for...of循环
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
lterator有点难理解了 传送门 结合迭代器、生成器理解
默认调用 Iterator 接口(即Symbol.iterator方法)的场合:
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
代码语言:javascript复制let set = new Set().add('a').add('b').add('c');
let [x,y] = set;
// x='a'; y='b'
let [first, ...rest] = set;
// first='a'; rest=['b','c'];
(2)扩展运算符
扩展运算符(...)也会调用默认的 Iterator 接口。
代码语言:javascript复制// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
上面代码的扩展运算符内部就调用 Iterator 接口。
实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
代码语言:javascript复制let arr = [...iterable];
(3)yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
代码语言:javascript复制let generator = function* () {
yield 1;
yield* [2,3,4];
yield 5;
};
var iterator = generator();
iterator.next() // { value: 1, done: false }
iterator.next() // { value: 2, done: false }
iterator.next() // { value: 3, done: false }
iterator.next() // { value: 4, done: false }
iterator.next() // { value: 5, done: false }
iterator.next() // { value: undefined, done: true }
(4)其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
代码语言:javascript复制for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
模块化篇
Class
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
∴ JS实现继承还是通过原型链的方式!
ES5:
代码语言:javascript复制function Point(x, y) {
this.x = x;
this.y = y;
}
Point.prototype.toString = function () {
return '(' this.x ', ' this.y ')';
};
var p = new Point(1, 2);
ES6:
代码语言:javascript复制class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' this.x ', ' this.y ')';
}
}
Modlule
代码语言:javascript复制ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。 ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。
// circle.js
export function area(radius) {
return Math.PI * radius * radius;
}
export function circumference(radius) {
return 2 * Math.PI * radius;
}
代码语言:javascript复制// main.js
import { area, circumference } from './circle';
console.log('圆面积:' area(4));
console.log('圆周长:' circumference(14));
编程风格的变化
(1) let 取代 var
(2) 在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
(3) 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
代码语言:javascript复制// bad
const a = "foobar";
const b = 'foo' a 'bar';
// acceptable
const c = `foobar`;
// good
const a = 'foobar';
const b = `foo${a}bar`;
(4) 善用解构赋值⭐
(5) 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。
代码语言:javascript复制// bad
const a = {};
a.x = 3;
// if reshape unavoidable
const a = {};
Object.assign(a, { x: 3 });
// good
const a = { x: null };
a.x = 3;
(6) 使用扩展运算符(...)拷贝数组。⭐
代码语言:javascript复制// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i ) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
(7)立即执行函数可以写成箭头函数的形式
代码语言:javascript复制(() => {
console.log('Welcome to the Internet.');
})();
(8)善用Map⭐
(9)Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require。
(10)ESLint