看這本書是在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實現繼承