文章目录
- 仅将自己的理解做整理、归类并结合实际遇到的问题做记录,更推荐阅读 ECMAScript 6 入门。
- 解构赋值 “ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)” 作用:可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值 let [a,b,c] = [1,2,3]; 扩展运算符 “扩展运算符是三个点,它如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列” 作用:把数组或类数组对象展开成一系列用逗号隔开的值 console.log(...[1,2,3])//1 2 3console.log([1,2,3].join())//1,2,3console.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 ...??? 常用 let [head,...tail] = [1,2,3,4]; //head:1,tail:[2,3,4] let colors = ["red","green","blue"];let [...cloneColors] = colors;console.log(cloneColors) //["red", "green", "blue"] 1.将数组转为函数的参数,替代 apply 方法 Math.max.apply(null,[14,3,77])//ES5Math.max(...[14,3,77])//ES6 2.合并数组 var arr1=['a','b']var arr2=['c','d']console.log(arr1.concat(arr2))//ES5console.log([...arr1,...arr2])//ES6 Array.from() “Array.from方法用于将两类对象转成真正的数组” Array.of() “Array.of方法用于将一组值转换为数组” 常用 console.log(Array.of(3,11,8))//[3, 11, 8] copyWithin() 复制替换数据 Array.prototype.copyWithin(target,start=0,end=this.length) target:必选,从该位置开始替换 start:替换内容的起点 end:到该位置前停止读取 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() var obj = { 0: 'a', 1: 'b', 2: 'c' };console.log(Object.keys(obj)); // console: ['0', '1', '2'] 对象的解构赋值 对象的属性没有次序,变量必须与属性同名,才能取到正确的值 let { foo, bar } = { foo: "aaa", bar: "bbb" };foo // "aaa"bar // "bbb"
- 字符串遍历 for...of console.log('foo'.split(''))//["f", "o", "o"]//ES5 for(let codePoint of 'foo'){ console.log(codePoint)//'f',"o","o"//ES6} 返回字符串给定位置的字符at() console.log('lallallal'.indexOf('a'))//1 Number//ES5 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" 箭头函数 →→→ 函数名=参=>返回值 尾调用、尾递归 某个函数的最后一步是调用另一个函数
- from MDN: 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]
- from CSDN: function* helloGenerator() { console.log("this is generator"); }var h = helloGenerator();h.next(); 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()方法调用,实现函数的分段执行。
- 迭代器=>Generator=>async 1.迭代器:不暴露对象的内部表示的情况下,能够遍历整个元素 遍历 ~ 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 方法添加回调函数
- (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 Set 是成员值唯一的数据结构,类似于数组 Array 所以,有一种重要且好用的去重方法: 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的创建; 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 // 100person.age = 'young' // The age is not an integerperson.age = 300 // The age seems invalid Reflect 将Object对象的一些明显属于语言内部的方法(比如Object.defineProperty),放到Reflect对象上。 为了修改某些Object方法的返回结果,让其变得更合理。 例:用defineProperty,不抛出错误,返回false: // 老写法try { Object.defineProperty(target, property, attributes); // success} catch (e) { // failure}// 新写法if (Reflect.defineProperty(target, property, attributes)) { // success} else { // failure} 重点食用:使用 Proxy 实现观察者模式 观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。 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 代理,拦截赋值操作,触发充当观察者的各个函数。 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之中,会自动执行所有观察者。
- Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。 lterator有点难理解了 传送门 结合迭代器、生成器理解 默认调用 Iterator 接口(即Symbol.iterator方法)的场合: (1)解构赋值 对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。 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 接口。 // 例一var str = 'hello';[...str] // ['h','e','l','l','o']// 例二let arr = ['b', 'c'];['a', ...arr, 'd']// ['a', 'b', 'c', 'd'] 上面代码的扩展运算符内部就调用 Iterator 接口。 实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。 let arr = [...iterable]; (3)yield* yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。 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)其他场合 由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。 for...ofArray.from()Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))Promise.all()Promise.race()
- ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。 ∴ JS实现继承还是通过原型链的方式! ES5: 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: class Point { constructor(x, y) { this.x = x; this.y = y; } toString() { return '(' + this.x + ', ' + this.y + ')'; }}
- ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。 ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。 // circle.jsexport function area(radius) { return Math.PI * radius * radius;}export function circumference(radius) { return 2 * Math.PI * radius;} // main.jsimport { area, circumference } from './circle';console.log('圆面积:' + area(4));console.log('圆周长:' + circumference(14));
- (1) let 取代 var (2) 在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。 (3) 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。 // badconst a = "foobar";const b = 'foo' + a + 'bar';// acceptableconst c = `foobar`;// goodconst a = 'foobar';const b = `foo${a}bar`; (4) 善用解构赋值⭐ (5) 对象尽量静态化,一旦定义,就不得随意添加新的属性。如果添加属性不可避免,要使用Object.assign方法。 // badconst a = {};a.x = 3;// if reshape unavoidableconst a = {};Object.assign(a, { x: 3 });// goodconst a = { x: null };a.x = 3; (6) 使用扩展运算符(...)拷贝数组。⭐ // badconst len = items.length;const itemsCopy = [];let i;for (i = 0; i < len; i++) { itemsCopy[i] = items[i];}// goodconst itemsCopy = [...items]; (7)立即执行函数可以写成箭头函数的形式 (() => { console.log('Welcome to the Internet.');})(); (8)善用Map⭐ (9)Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require。 (10)ESLint 我是掘金安东尼: 一名人气前端技术博主(文章 100w+ 阅读量) 终身写作者(INFP 写作人格) 坚持与热爱(简书打卡 1000 日) 我能陪你一起度过漫长技术岁月吗(以梦为马) 觉得不错,给个点赞和关注吧(这是我最大的动力 )b( ̄▽ ̄)d
仅将自己的理解做整理、归类并结合实际遇到的问题做记录,更推荐阅读 ECMAScript 6 入门。
- 解构赋值
“ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)”
“ES6 中允许按照一定模式,从数组和对象中提取值,对变量进行赋值,这被称为解构(Destructuring)”
作用:可以快速取得数组或对象当中的元素或属性,而无需使用arr[x]或者obj[key]等传统方式进行赋值
let [a,b,c] = [1,2,3];
- 扩展运算符
“扩展运算符是三个点,它如同rest参数的逆运算,将一个数组转为用逗号分隔的参数序列”
作用:把数组或类数组对象展开成一系列用逗号隔开的值
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 ...???
常用
let [head,...tail] = [1,2,3,4]; //head:1,tail:[2,3,4]
let colors = ["red","green","blue"];
let [...cloneColors] = colors;
console.log(cloneColors) //["red", "green", "blue"]
1.将数组转为函数的参数,替代 apply 方法
Math.max.apply(null,[14,3,77])//ES5
Math.max(...[14,3,77])//ES6
2.合并数组
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方法用于将一组值转换为数组”
常用
console.log(Array.of(3,11,8))//[3, 11, 8]
- copyWithin()
复制替换数据
Array.prototype.copyWithin(target,start=0,end=this.length)
target:必选,从该位置开始替换 start:替换内容的起点 end:到该位置前停止读取
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()
var obj = { 0: 'a', 1: 'b', 2: 'c' };
console.log(Object.keys(obj)); // console: ['0', '1', '2']
- 对象的解构赋值
对象的属性没有次序,变量必须与属性同名,才能取到正确的值
let { foo, bar } = { foo: "aaa", bar: "bbb" };
foo // "aaa"
bar // "bbb"
- 字符串遍历 for...of
console.log('foo'.split(''))//["f", "o", "o"]//ES5
for(let codePoint of 'foo'){
console.log(codePoint)//'f',"o","o"//ES6
}
- 返回字符串给定位置的字符at()
console.log('lallallal'.indexOf('a'))//1 Number//ES5
console.log('lallallal'.charAt('a'))//1 String//ES6
- includes():是否找到参数字符串、startsWith()/endswith():参数字符串是否在源字符串头/尾部...
console.log(codePoint)//'f',"o","o"//ES6
}
- 函数参数的默认值
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"
- 箭头函数 →→→
函数名=参=>返回值
- 尾调用、尾递归
某个函数的最后一步是调用另一个函数
console.log(x,y)
}
log('moximoxi')//moximoxi world
console.log(values)
for(var val of values){
console.log(val)
}//for...of可以不仅可以遍历字符串,还可以遍历数组 ES6
}
test(1,2,3)//[1, 2, 3]
函数名=参=>返回值
某个函数的最后一步是调用另一个函数
from MDN:
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]
from CSDN:
function* helloGenerator() {
console.log("this is generator");
}
var h = helloGenerator();
h.next();
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()方法调用,实现函数的分段执行。
迭代器=>Generator=>async
1.迭代器:不暴露对象的内部表示的情况下,能够遍历整个元素
遍历 ~ 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 方法添加回调函数
(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对象)
})()
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
Set 是成员值唯一的数据结构,类似于数组 Array
Set 是成员值唯一的数据结构,类似于数组 Array
所以,有一种重要且好用的去重方法:
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的创建;
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:
// 老写法
try {
Object.defineProperty(target, property, attributes);
// success
} catch (e) {
// failure
}
// 新写法
if (Reflect.defineProperty(target, property, attributes)) {
// success
} else {
// failure
}
重点食用:使用 Proxy 实现观察者模式
观察者模式(Observer mode)指的是函数自动观察数据对象,一旦对象有变化,函数就会自动执行。
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 代理,拦截赋值操作,触发充当观察者的各个函数。
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之中,会自动执行所有观察者。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令for...of循环,Iterator 接口主要供for...of消费。
lterator有点难理解了 传送门 结合迭代器、生成器理解
默认调用 Iterator 接口(即Symbol.iterator方法)的场合:
(1)解构赋值
对数组和 Set 结构进行解构赋值时,会默认调用Symbol.iterator方法。
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 接口。
// 例一
var str = 'hello';
[...str] // ['h','e','l','l','o']
// 例二
let arr = ['b', 'c'];
['a', ...arr, 'd']
// ['a', 'b', 'c', 'd']
上面代码的扩展运算符内部就调用 Iterator 接口。
实际上,这提供了一种简便机制,可以将任何部署了 Iterator 接口的数据结构,转为数组。也就是说,只要某个数据结构部署了 Iterator 接口,就可以对它使用扩展运算符,将其转为数组。
let arr = [...iterable];
(3)yield*
yield*后面跟的是一个可遍历的结构,它会调用该结构的遍历器接口。
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)其他场合
由于数组的遍历会调用遍历器接口,所以任何接受数组作为参数的场合,其实都调用了遍历器接口。下面是一些例子。
for...of
Array.from()
Map(), Set(), WeakMap(), WeakSet()(比如new Map([['a',1],['b',2]]))
Promise.all()
Promise.race()
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
ES6 的class可以看作只是一个语法糖,它的绝大部分功能,ES5 都可以做到,新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。
∴ JS实现继承还是通过原型链的方式!
ES5:
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:
class Point {
constructor(x, y) {
this.x = x;
this.y = y;
}
toString() {
return '(' + this.x + ', ' + this.y + ')';
}
}
ES6 模块的设计思想是尽量的静态化,使得编译时就能确定模块的依赖关系,以及输入和输出的变量。CommonJS 和 AMD 模块,都只能在运行时确定这些东西。比如,CommonJS 模块就是对象,输入时必须查找对象属性。
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;
}
// main.js
import { area, circumference } from './circle';
console.log('圆面积:' + area(4));
console.log('圆周长:' + circumference(14));
(1) let 取代 var
(2) 在let和const之间,建议优先使用const,尤其是在全局环境,不应该设置变量,只应设置常量。
(3) 静态字符串一律使用单引号或反引号,不使用双引号。动态字符串使用反引号。
// 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方法。
// 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) 使用扩展运算符(...)拷贝数组。⭐
// bad
const len = items.length;
const itemsCopy = [];
let i;
for (i = 0; i < len; i++) {
itemsCopy[i] = items[i];
}
// good
const itemsCopy = [...items];
(7)立即执行函数可以写成箭头函数的形式
(() => {
console.log('Welcome to the Internet.');
})();
(8)善用Map⭐
(9)Module 语法是 JavaScript 模块的标准写法,坚持使用这种写法。使用import取代require。
(10)ESLint
我是掘金安东尼: 一名人气前端技术博主(文章 100w+ 阅读量)
终身写作者(INFP 写作人格)
坚持与热爱(简书打卡 1000 日)
我能陪你一起度过漫长技术岁月吗(以梦为马)
觉得不错,给个点赞和关注吧(这是我最大的动力 )b( ̄▽ ̄)d