ES6 标准入门第2版 笔记
看这本书是在markdown在做的笔记,更友好的阅读方式访问: github es6.md(https://github.com/EarlyBirdss/Study-Diary/blob/master/es6.md)
ES6
https://babeljs.io/repl/ 在线转码
let
let 声明一个变量, 只在let命令所在的代码块有效(非常适合在for循环中)
let没有变量提升, 这意味着typeof不再是百分比安全的操作
只要在块级作用域中存在let命令,它所在声明的变量就绑定(binding)在这个区域, 不受外界影响
暂时性死区:在代码块内使用let命令声明变量之前, 该变量都是不可用的
let 不允许在相同作用域内重复声明同一个变量
可以使用let很方便的块级作用域 (IIFE)
const
用来声明常量,一旦声明,其值不可修改(很多规则跟let一样,理解成继承吧)
必须一声明就立即初始化,不能留到以后赋值
对于复杂的数据结构(对象,数组), const只能保证保存该数据的地址不发生变化,不能保证其内部值发生变化。(可以使用Object.freeze()冻结对象)
变量的解构赋值
解构: ES6按照一定模式,从数组和对象中提取值,对变量进行赋值,这称为解构
数组
var [a,b,c] = [1,2,3]; //a=1,b=2,c=3
var [head,,third,...tail]=[1,2,3,4]; //tail=[4]
var [foo] = [];//如果解构不成功,变量的值就等于undefined
let [a,[b],d] = [1,[2,3],4]; //b=2;不完全解构,解构依然可以成功
如果等号右边不是数组(可遍历的结构)就会报错;
默认值:
let [x,y=1] = [0]; //x=0,y=1;
let [x,y=1,z] = [0, undefined,2]; //x=0,y=1,z=2;(必须使用undefined/,因为es6内部使用严格等===)
惰性求值, 如果默认值是表达式, 只用用的时候才求值
对象
对象没有次序,变量必须和属性同名或使用以下方式,才能取得正确的值
var {foo:baz} = {foo: 1, bar: 2}; //baz=1;
字符串
因为字符串自带length属性,
let {length: len} = 'jifag';
字符串被转换为类似数组的对象
const [a,b,c,d,e] = 'hello'; //a='h',b='e'...
解构可以嵌套
var {p: [x,{y}]} = {p:['hello',{y:'world'}]}; // x='hello',y='world';此时p是模式不是变量名
函数参数的解构赋值
酷!
圆括号问题
对于编译器来说, 一个式子是模式还是表达式,没有办法从一开始就知道,带来的问题就是, 如果模式中出现圆括号怎么处理。
建议: 不要在模式中放圆括号
几种情况下在模式中使用圆括号会报错
用途!!!!
交换变量的值 [x,y] = [y,x];
获取从函数返回的多个值 var [a,b,c] = test(); //test return [1,2,3];
函数参数的定义
eg1: 参数是有序值function f([x,y,z]){}; f([1,2,3])
eg2: 参数是无序值function f({x,y,z}){}; f({x:1,y:2,z=3})
提取json数据
函数参数的默认值(好用!!!)
let {log, sin, cos} = Math; //很方便就能把想用的拿出来
reg
u修饰符: 用于处理4个字节的utf=16编码, 如(根本打不出来, 浏览器跟markdown也显示不了好伐, 这个问题忽略)
y修饰符: ‘粘连’ 跟g一样是全局匹配, 但g是从上一次匹配的剩余位置开始匹配,y限制了只能从剩余位置的第一位匹配
var str = 'aaa_aa_a', g = /a+/g, y = /a+/y;
g.exec(str); //执行两次, ['aaa'],['aa']
y.exec(str); //执行两次, ['aaa'],null
Array
() //将类数组(array like)对象和可遍历的对象转化为真正的数组,接受第二次参数类似于数组的map函数Array.from({'0':1, '1': 2, '2': 3}, x => x * x) //[1,4,9];
扩展运算符 (...)
Array.of() //将一组值转化为数组 Array.of(1,2,3) //[1,2,3]
数组实例 Array.prototype.copyWithin(target, start = 0, end = this.length): 在数组内部将指定位置的成员复制到其他位置(覆盖)
数组实例 Array.prototype.find()和findIndex();
find(function(value, index, arr){})有点像filter, find只找到第一个符合条件的值就返回这个值 findIndex返回该值的位置
数组实例 Array.prototype.fill(value, start = 0, end = this.length) //很方便的初始化空数组,如果不是空数组, 数组中已有的元素将被重置 new Array(3).fill(0) //[0,0,0]
数组实例 Array.prototype.entries(),keys(),values() 返回遍历器对象
数组实例 Array.includes() 返回布尔值
数据推导 [for (year of years) if (year > 2000) year];
函数
rest参数(...变量名): 替代arguments function add(...value){}
扩展运算符(...) 像reset参数的逆运算
console.log(...[1,2,3]) //1 2 3 (1,2,3)
替代数组的apply方法
合并数组 [1,2,...more] //more=[3,4];
与解构赋值结合 const [first, ...rest] = [1,2,3,4];//first=1,rest=[2,3,4];
箭头函数
var sum = (num1, num2) => num1 + num2;
如果函数内代码语句多于一行,要用大括号括起来,并使用return返回
简化回调函数 [1,2,3].map(x => x * x); //[1,4,9]; [6,1,8].sort((a,b) => a - b); //[1,6,8]; const number = (...nums) => nums; // number(1,2,3)=>[1,2,3]
对象
属性的简洁表示法 let obj = {x,test(){}}
属性名表达式 let obj = {['a' + 'b']: 'ab'}
属性名表达式与简洁表示法不能同时使用,会报错
Object.is()用来比较两个值是否严格相等,与严格等(===)的行为基本一致,不同之处只有两个
1. +0不等于-0
2. NaN等于自身
Object.assign(): 用来将源对象的所有可枚举属性赋值到目标对象 //Object.assign(target, source1, source2);
Symbol
(研究了大半天终于发现意义在哪里,现在表示当前对象的属性有三种 obj.a、obj['a']、obj[a],一二种是ES5的字符串属性名,第三种是Symbol类型,这样就实现唯一的标识了)
ES6引用一种新的原始数据类型Symbol表示独一无二的值
let s = Symbol();typeof s;//symbol没有‘new’
let s = Symbol('foo'),a={};a[s] = function(){};//不能使用点运算符(a.s),不加引号(a['s']);
Symbol的参数是没有实际意义的,只表示对Symbol实例的描述,主要是为了方便区分
symbol可以用来定义一组常量,保证这组常量的值都是不相等的
魔术字符串:在代码之中多次出现、与代码行程强耦合的某一个集体的字符串或数值。
消除魔术字符串的常用方便就是把它写成一个常量, 此时比较适合用Symbol
Symbol类型的属性名,不会出现在for...in、for...of遍历中,也不会被Object.keys(),Object.getOwnPropertyNames()返回。也不是私有属性。Object.getOwnPropertySymbols可以获取指定对象的所有Symbol属性名
Symbol.for(name):重新使用同一个symbol值
var s1 = Symbol.for('foo'), s2 = Symbol.for('foo');s1 === s2; //true
var s1 = Symbol('foo'), s2 = Symbol.for('foo');s1 === s2; //false
内置的Symbol值
Proxy和Reflect
proxy用于修改某些操作的默认行为,等同于在语言层面做出修改,属于“元程序”(对编程语言进行编程)。代理器,拦截器
作为构造函数Proxy接受两个参数,第一个参数是所要代理的目标对象,第二个参数是配置对象(包含有处理函数),要使Proxy起作用,必须针对Proxy实例进行操作,而不是针对目标对象
var proxy = new Proxy({},{
get: function(target, property){
return 35;
}
});
proxy.time; //35
set();利用Proxy,可以将读取属性的操作转变为执行某个函数,从而实现属性的链式调用
var pipe = (function() {
var pipe;
return function(value) {
get: function(pipeObject, fnName) {
if(fnName == 'get') {
return pipe.reduce(function(val, fn) {
return fn(val);
}, value);
}
pipe.push(window[fnName]);
return pipeObject;
}
}
});
Proxy中设置set方法可以拦截某个属性的赋值操作
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');
}
}
obj[prop] = value;
}
}
let person = new Proxy({}, validator);
person.age = 100;
person.age = 400;//报错
apply()方法拦截函数的调用
has()方法可以隐藏某些属性不被in操作符发现
construct()方法用来拦截new命令
deleteProperty()方法拦截delete操作
defineProperty()方法拦截Object.defineProperty();
enumerate()方法拦截for...in循环
ownKeys()方法拦截Object.keys()
Reflect 将Object某些语言层面的方法部署到Reflect之中
set
新的数据结构,类似于数组,成员的值都是唯一的,没有重复的值
set函数可以接受一个数组(或类数组)let set = new Set([1,2,3,3]); [...set]; //[1,2,3]
属性和方法set.prototype.size ,add(value), delete(value), has(value), clear()
遍历操作 keys(),values(),entries(),forEach();前三个返回遍历器。set结构只有键值没有键名,keys的返回值等于values
WeakSet 内部成员只能是对象,不可遍历,没有clear方法,没有size属性,引用为存储dom节点,不必担心这些节点从文档移除时会引发内存泄漏(没有理解……)
Map
理解: Object提供“字符串——值”的对应,Map提供“值——值”的对应
map接收一个数组作为参数,数组的成员为一个个表示键值对的数组;
var map = new Map([['name', 'Jack'], ['age', '12']]);
map.size; //2
map.has('name'); //true
map.get('name'); //jack
map.set('gender', 'female'); //
map.delete('age');
map.clear();
WeakMap: 只接收对象为键名(null除外)。同样应用于dom, 部署私有属性
Iterator
遍历器,是一种接口,为各种不同的数据结构提供统一的接口的访问机制,任何数据结构只要部署iterator接口就可以完成遍历操作
不断调用next方法 返回({value:value, done: Boolean})
for...of 循环
一个数据结构只要具有Symbol.iterator属性,就被认为是可遍历的。调用Symbol.iterator方法就可以得到当前结构的遍历函数
以下三种结构原生具有iterator接口: 数组, 某些类似数组的对象, set和map结构
var map = new Map([['name', 'aya'], ['gender', 'female']]);
var iter1= map[Symbol.iterator]();
iter1.next(); // Object {"done": false,"value": Array ["name","aya"]}
//类数组调用Symbol.iterator
let iterable = {
'0': 1,
'1': 2,
'2': 3,
length: 3,
[Symbol.iterator]: Array.prototype[Symbol.iterator]
};
let ite = iterable[Symbol.iterator]();
console.log(ite.next());
对象没有默认部署iterator接口,是因为哪个属性先遍历哪个后遍历是不确定的。需要开发者手动指定
// 为对象添加Iterator的一个实例
let obj = {
data: ['hello','world'],
[Symbol.iterator]() {
const self = this;
let index = 0;
return {
next() {
if(index < self.data.length) {
return {
value: self.data[index++],
done: false
};
}else {
return {
value: undefined,
done: true
};
}
}
}
}
}
for(let j of obj){
console.log(j);
}
var i = obj[Symbol.iterator]();
i.next(); //'hello';
调用Iterator接口的场合
1. 解构赋值(会默认调用Symbol.iterator方法)
2. 扩展运算符(...)
3. for...of,Array.from(),Map、Set、WeakMap、WeakSet,Promise.all(),Promise.race();
字符串可以访问Sybmol.iterator方法
for...of(遍历所有数据结构的统一方法)
Generator
概念: Generator是ES6提供的一种异步编程解决方案。状态机。执行Generator会返回一个遍历器对象。
function与函数名之间有一个*号, 函数内部使用yield定义不同的内部状态
yeild就像是暂停标志,generator.next()执行一次就从就返回一个{value:yeild语句后的值,done:},并暂停向下执行,再执行.next()方法再返回再暂停,直到遇到return语句为止,没有return就放回{value: undefined, done: true};
yeild后面的表达式,只有当调用next方法,内部指针指向语句时才会执行(惰性求值)
yeild语句不能出现在普通函数中(foreach改为使用for循环),yield语句如果在表达式中,必须加圆括号
next的参数,会被当做上一条yield语句的返回值
for...of,扩展运算符,解构赋值,Array.from
应用
1. 异步操作的同步化表达: 处理异步操作,改写回调函数
function* main(){
var result = yield request("http://some.url.com");
var resp = JSON.parse(result);
doSomething(resp);
}
2. 在任意对象上部署Iterator接口
function* iterEntries(obj) {
let keys = Object.keys(obj);
for(let i = 0; i < keys.length; i++) {
let key = keys[i];
yield [key, [obj[key]]];
}
}
let testObj = {hello: 'you', world: 'me'};
for(let [key,value] of iterEntries(testObj)){
console.log(key, value);
}
3. 作为数据结构
function* doStuff(){
yield fs.readFile.bind(null, 'hello.txt');
yield fs.readFile.bind(null, 'work.txt');
yield fs.readFile.bind(null, 'as-so-on.txt');
}
Promise
一个对象,用来传递异步操作消息
有两个特点
1. 对象的状态不受外界干扰。Pending,Resolved(Fulfilled),Rejected。只有异步操作的结果可以决定当前是哪一种状态。
2. 一旦状态改变就不会再变,任何时候都可以得到这个结果。promise只能从Pending变为Resolved或Pending变成Rejected。只要一种变化发生就不会再发生变化
实例方法then, catch
Promise.all() 将多个Promise实例包装成一个新的promise实例 promise.all([p1,p2,p3]);
Promise.race() 同样将多个promise实例包装成一个新的promise实例 promise.race([p1,p2,p3]);
区别: all方法,只有参数p全部变成resolved,p才为resolved(rejected同样);race方法,只要有一个参数变成resolved,p就变为resolved(rejected同样);
Promise.resolve() 将现有对象转为promise对象
promise对象可以不带参数, 如果希望得到一个新的promise对象,可以直接调用 var p = Promise.resolve();
Promise.reject() 返回一个新的promise对象,状态为Rejected
promise.done() 总是处于回调链的尾端,保证抛出任何可能出现的错误
promise.finally() 跟promise.done最大的区别的是: finally接受普通函数作为参数,无论如何都会执行
异步操作
ES6的异步编程: 回调函数,事件监听, 发布/订阅, Promise对象
异步: 一个任何分为两段,先执行第一段,然后转而这行其他认识,等做好准备再回头执行第二段(不连续的操作)
Thunk函数: 编译器的传名调用(如var m = 1; f(m+1),//m+1在f内用到才求值,而不是先求值再调用f(2)),实现往事先将函数放到一个临时函数中,再将这个临时函数传入函数体。这个临时函数就叫Thunk函数。
js是传值调用(var m = 1; f(m+1);// 先求值等于2,在调用f(2);Thunk函数替换的不是表达式而是多参数函数,替换为单参数版本,且只接受回调函数作为参数。
Class基本语法
class Point{
constructor(x, y) {
this.x = x;
this.y =y;
}
toString(){
return "x+y= " + this.x + this.y;
}
}
语法糖
类的内部定义的所有方法都是不可枚举的(跟ES5不同)
constructor方法是类的默认方法,new之后自动调用该函数
如果忘记加new调用Class,会报错
Class表达式
const MyClass = class Me {
getClassName(){
return Me.name;
}
}
调用时使用new MyClass();Me只供Class的内部代码使用
不存在变量提升
代码内部为严格模式
extends 实现继承
class ColorPoint extends Point {
constructor(x, y, color) {
super(x, y);
this.color = color;
}
}
super: 关键字,指代了父类的实例(即父类的this对象)
子类必须在constructor方法中调用super, 否则新建实例时会报错。因为如果不调用super方法,子类就得不到this对象
父类只要用prototype就能被子类继承, 父类可以是任意函数
对象总是继承其他对象的,所以可以再任意一个对象中使用super关键字
static 静态方法:在方法前面添加static关键字,不会被实例继承,通过类直接调用
Class只有静态方法没有静态属性
new.target属性: 返回new命令所作用的构造函数(在构造函数中使用)
修饰器
类的修饰(Class)
1. 修饰器是一个表达式,用于修改类的行为
2. 修饰器本质是能在编译时执行的函数
3. 修饰器函数可以接受3个参数: 目标函数(必要),属性名,该属性的描述对象
方法的修饰(Class 内部方法)
不能用于普通的函数(因为函数提升)
@autobind @override @deprecate
Module
Class只是语法糖,并没有解决模块化问题
Module应对大型程序
编码风格
块级作用域let代替var
全局变量应该只能设置常量,优先使用const
静态字符串使用单引号或反引号,动态字符串使用反引号(`)
解构赋值
使用数组成语对变量赋值,优先使用解构赋值
函数的参数如果是对象的成员,优先使用解构赋值
如果函数返回多个值,优先使用对象的解构赋值而不是数组的解构赋值(便于调整顺序)
对象
单行定义的对象,最后一个成员不以逗号结尾;多行定义的对象,最后一个成员使用逗号结尾
对象尽量静态化,如果属性不可避免要添加,使用Object.assign()方法
如果对象的属性名是动态的,创建对象时使用属性表达式定义
使用扩展运算符(...)复制数组
函数
立即执行函数可以写成箭头函数的形式
(() => {
doSomething();
});
使用函数表达式的场合尽量用箭头函数代替(绑定了this)
简单的单行的不会复用的函数用箭头函数
所有的配置项都应该几种在一个对象,放在最后一个参数,布尔值不可以直接作为参数
总是使用Class代替需要prototype的操作(class写法更简洁)
使用extends实现继承