第三章
1.var {foo:baz} = {foo:"aaa", bar:"bbb"} //bas = bbb, foo = undefined
对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者
2.嵌套赋值的例子,平时开发经常会用到
let obj = {};
let arr = [];
({foo:obj.prop, bar:arr[0]}) = {foo:123, bar:true};
obj //{prop:123}
arr //[true]
3.只要等号右边的值不是对象,就先将其转为对象。
let {toString:s} = 123;
s === Number.prototype.toString //true
let {toString:s} = true
s === Boolean.prototype.toString //true
4. 交换变量
es6 :[x, y] = [y, x];
想到一种es5的写法 x = [y, y = x][0]
第五章
1.正则表达式的 y修饰符,y修饰符隐含了头部匹配的标志
var s = "aaa_aa_a";
var r1 = /a+/g;
var r2 = /a+/y;
r1.exec(s) //["aaa"]
r2.exec(s) //["aaa"]
r1.exec(s) //["aa"]
r2.exec(s) //null
var s = "aaa_aa_a";
var r = /a+_/y //换一下表达式
r.exec(s) //["aaa_"]
r.exec(s) //["aa_"]
第七章
1.Array.from() 作用是把类数组的对象转化为真正的数组, 适用于(arguments对象,NodeList,Iterable对象,Set,Map)
let arr = {'0':'a', '1':'b', '2':c, length:3};
ES5:
var arr1 = [].slice.call(arr);
ES6
let arr2 = Array.from(arr);
2.Array.includes()
[1,2,3].includes(2) //true 方法属于es7
3.数组推导,可以在[]内用循环创建数组
var years = [1,2,3,4,5,6,7];
[for (year of years) if(year>3)] //[4,5,6,7]
第八章
1.
function m1({x = 0, y = 0}){
return [x, y];
}
function m2({x, y} = {x:0, y:0}){
return [x,y];
}
m1({x:3}) //[3, 0]
m2({x:3}) //[3, undefined] //如果有参数,则不取默认参数,然后对{x,y}赋值
m2() // [0,0] //如果没有参数,取默认参数
2.函数的length属性 如果指定了默认值后,length 属性将失真
(function(a){}).length //1
(function(a=5){}).length //0
(function(a, b, v = 5){}).length //2
3. 函数的length属性不包括rest参数
(function(a) {}).length //1
(function(…a){}).length //0
(function(a, …b){}).length //1
4. 任何类似数组的对象都可以用扩展运算符转为真正的数组(Map,Set,Ceberatir也可以)
var nodeList = document.querySelectorAll('div');
var array = […nodeList];
5. ()=>{console.log(arugments)} 箭头函数函数体内没有arguments对象
6.箭头函数的管道机制,即前一个函数的输出是后一个函数的输入
var s = (a)=> b => a * b
s(2)(3) //6
7 尾递归可以极大节省内存
尾递归的实现往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量都改写成函数的参数。
function fac
torial(n, total==1){
if(n===1) return total;
return tailFactorial(n – 1, n * total);
}
factorial(5);
8 柯里化 的意思是将对参数的函数转换成单参数的形式。
第九章
1.Object.is 跟全等号===类似
不同之处有两个
Object.is(+0, -0); //false
Object.is(NaN, NaN) //true
2.克隆对象
function clone(origin) {
return Object.assign({}, origin);
}
上面的代码将原始对象复制到一个空对象,就得到了原始对象的克隆。
不过,采用这种克隆,只能克隆原始对象自身的值,不能克隆它的继承值。如果想要保持继承链,如下:
function clone(origin){
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
3. 属性的遍历
for…in //遍历包括自身和继承的可枚举属性
Object.keys(obj) //只遍历自身的可枚举属性
Object.getOwnPropertyNames(obj) //遍历自身和继承的属性包括不可枚举的属性
Object.getOwnProertySymbols(obj) //返回自身所有的Symbol属性
Refect.ownKeys();
Refect.enumerate()
以上的遍历排序按一下规则:
1.数字优先,按数字小到大排序
2.其次是字符串,按生成时间排序
3.最后是Symbol的值,按生成时间排序
Object.keys({[Symbol():0, b:0, 10:0, 2:0, a:0]})
//['2', '10', 'b', 'a', Symbol()]
4.Object.setPrototypeOf
可以修改实例的原型链,如
let p = {a:1}
function d(){};
d.protype = p
let a = new d();
let c = new d();
Object.setPrototypeOf(a, {b:1})
a.b = 1;
a.a = undefined;
注意:c.prototype也会受到牵连
5.如果扩展运算符的参数是null 或者 undefined,则会忽略不会报错
let emptyObject = {…null, …undefined} // {} ,不会报错
第十章
1. Symbol是第7种数据类型
2.Symbol属性不会出现在for…in,for…of,Object.keys()等循环中,只有getOwnPropertySymbols可以获取对象的所有Symbol属性名
3.以Symbol值作为名称的属性不会被常规方法遍历得到,可以利用这个特效,为对象定义一些非私有但又希望只用于内部的方法。
let objClass = function (){ this.sym = Symbol(); this[this.sym] = 0; this.add = function(){ this[this.sym]++ }; this.size = function(){ return this[this.sym]; } } var s = new objClass() s.add(); s.size() //1
第十一章
1. Proxy 用于拦截一个对象
var obj = new Proxy({}, get:function(){ }, set:function(){ } })
常用的方法有 get,set.has,deleteProperty,ownKeys等
2.Reflect其实是重新标准化了一下Object的调用
1.修改某些Object方法的返回结果,让其变得更加合理 比如 Object.defineProperty(obj, name, desc)在无法定义属性时会抛出一个错误,而Reflect.defineProperty(obj, name, desc)则会返回false
2.让Object的操作变成函数行为。如:name in obj 和 delete obj[name],等于 Reflect.has(obj, name) 和 Reflect.deleteProperty(obj, name)
3.Reflect对象的方法与Proxy的方法一一对应。这样Proxy就能方便调用Reflect的方法
第十三章
1.Set 类型类似与数组,但成员的值都是唯一的,没有重复。
var s = new Set(); [2,3,4,5,6,2,2].map(x=>s.add(x)) for(i on s){console.log(i)} // 2 3 5 4
2.Array.from方法可以将Set结构转为数组,于是就多了一种数组去重的方法
3. Set().keys, Set().values 方法返回的东西是一样的
4.我们可以很容易的实现用Set实现并集,交集,差集
let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);
并集:
let union = new Set([…a, …b]); //[1,2,3,4]
交集:
let intersect = new Set([…a].filter(x => b.has(x))); //[2,3]
差集:
let difference= new Set([…a].filter(x => !b.has(x))); //[1]
5.WeakSet 与Set类似,但有2个不同
1.WeakSet的成员只能是对象,不能是其他类型的值
2、垃圾回收机制不考虑WeakSet对该对象的引用
6.如果你需要“键值对”的数据结构,Map比Object更合适,主要是Map允许变量作为key
7.WeakMap与WeakSet同样只接受对象作为键名,
let myWeakMap = new WeakMap();
myWeakMap.set(DOM, {clickTime:0});
当这个DOM被删除时,不必担心它被引用,会自动消失。
第十四章
1.如果想要一个对象支持遍历器,可以这么做
如 JQuery.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
2.forEach的缺点是无法中途跳出forEach循环,break或者return都不行
第十五章
1.Cenerator 函数 xx.next() 的返回值结果总是{value:'xxx', done:false/true}
2.next方法可以带一个参数,该参数会被当作上一条yield语句的返回值
function* f(){ for(var i=0; true; i++){ var reset = yield i; if(rest) { i = -1;} } } var g = f(); g.next() // {value:0, done:false} g.next() // {value:1, done:false} g.next(true) //{value:0, done:false}
可以达到在Generator函数运行的不同阶段,从外部向内部注入不同的值,从而调整整个函数行为。
3. yield* 语句返回单个字符串。因为字符串具有Iterator接口,所以能用yield*遍历
let read = (function(){ yield 'hello'; yield* 'hello'; })() read.next().value // "hello" read.next().value // "h"
4.
var clock = function*(_) { while (true) { yield _; console.log('Tick!'); yield _; console.log('Tock!'); } }; let toggle = clock(); toggle.next(); toggle.next(); // Tick! toggle.next(); // Tock! ......
如果使用 toggle 那么用Generator函数不需要一个变量来保存状态
第十六章
1.
//bad promise.then(function(data)) { //success },function(err) { //error }; //good promise.then(function(data)) { //success }).catch(function(err) { //error }); 第二种写法要好于第一种写法,理由是前者更接近同步的写法. Promise.reject() 会出发catch
2.Promise.race() 与 Promise.all() 很像,区别就是 Promise.race()只需要其中一个最快的promise指向完,就触发返回
3.Promise.resolve('foo') 等价于 new Promise(resolve=>resolve('foo'))
所以如果希望得到一个Promise对象,比较方便的方法就算直接调用Promise.resolve方法
第十七章
var gen = function* (){ var f1 = yield readFile('/etc/fstab/'); var f2 = yield readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); } 写成async函数就是这样 var asyncReadFile = async function (){ var f1 = await readFile('/etc/fstab/'); var f2 = await readFile('/etc/shells'); console.log(f1.toString()); console.log(f2.toString()); }
一比较就发现,async函数就算将Cenerator 函数的星号(*)替换成async, 将yield替换成await仅此而已。
第十八章
1.
class Point{ constructor(x, y) { //... } toString() { //... } } Object.keys(Point.prototype) // ["toString"]
toString方法是Point类内部定义的方法,它是不可枚举的。这一点与es5的行为不一致
var Point = function(x,y){ //... } Point.prototype.toString = function() { //... } Object.keys(Point.prototype) // ["toString"]
2.
constructor方法默认返回实例对象this,不过完全可以指定返回另外一个对象
class Foo{ constructor() { return Object.create(null) } } new Foo() instanceof Foo // false
这里类似于ES5的
function Foo(){ return {a:1} } var f = new Foo() // {a:1} f instanceof Foo // false
3. 类和模块的内部默认就算严格模式,所以不需要使用use strict指定运行模式。
考虑到未来所有代码其实都是运行在模块中,所以ES6实际上把整个语言升级到来严格模式
4。如果子类没有定义 constructor 方法,那么这个方法会被默认添加。代码如下
constructor(…args) {
super(…args);
}
也就是说,不管有没有显式定义,任何一个子类都有constructor方法
5.
1.子类的__proto__属性表示构造函数的继承,总是指向父类
2.子类prototype属性的__proto__属性表示的继承,总是指向父类的prototype属性
class A { } class B extends A { } B.__proto__ === A // true B.prototype.__proto___ === A.prototype //true
6。Class的Cenerator方法
如果在某个方法前加上星号(*),就表示该方法是一个Generator函数。
class Foo{ constructor(...args) { this.args = args; } * [Symbol.iterator]() { for (let arg of this.args) { yield arg; } } } for (let x of new Foo('hello', 'world')) { console.log(x); } // hello // world
7. new.target 返回当前的类,用于判断这个函数是不是被new调用
function Person(name) { if (new.target === Person) { this.name = name; } else { throw new Error('必须使用new生成实例') ; } }
第十九章
修饰器不仅可以修饰类,还可以修饰类的属性
class Person { @readonly name() {return `${this.first} ${this.last}`} }
第二十章
1.
function v1(){ ... } function v1(){ ... } export { v1 as streamV1, v2 as streamV2, v2 as streamLatestVersion }
上面的代码v2可以用不同的名字输出2次。
2.如果export处于块级作用域内,会报错。
3.export语句输出值是动态绑定的
export var foo = 'bar';
setTimeout(()=>foo = 'baz', 500);
上面的代码输出变量foo, 值为bar, 500毫秒之后变成baz
4.import命令具有提升效果,会提升到整个模块的头部首先执行。
5.module 命令可以取代import语句,达到整体输入模块的作用
module circle from './circle' == import circle from './circle'
看起来代码量一样啊。。。囧
6.export * from 'circle' ; 可以把整个模块导出
7. 由于ES6 输入的模块变量只是一个“符号链接”,所以这个变量是只读的,对它进行重新赋值会报错。
// lib.js export let obj = {}; //main.js import {obj} from './lib'; obj.prop = 123; //OK obj = {} //TypeError
第二十一章 编程风格建议
-
对象尽量静态化,一旦定义,就不得随意添加新的属性,如果添加属性不可避免,要使用Object.assign方法
//bad const a = {}; a.x = 3; // 如果不可避免 const a = {}; Object.assign(a, {x:3}) //good const a = {x:null} a.x = 3
2.如果对象的属性名是动态的,可以在创造对象时使用属性表达式定义。
//bad const obj = { id:5, name: "nameA" }; obj[getKey('enabled')] = true; //good const obj = { id:5, name: 'nameA' [getKey('enabled')]:true }
3.
如果模块默认输出一个函数,函数名的首字母应该小写。
function makeStyleGuide(){
}
export default makeStyleGuide;
如果模块默认输出一个对象,对象名的首字母应该大写。
const StyleCuide = {
es6:{
}
}
export default StyleGuide