1. 程式人生 > >《ES標準入門》&《UNDERSTANDING ECMACHRIPT 6》 讀書摘錄筆記(上)

《ES標準入門》&《UNDERSTANDING ECMACHRIPT 6》 讀書摘錄筆記(上)

原來 cte 不足 hand out 好的 正則 ssi 調用



### 前言
*這兩本書應該是目前ES6相關的比較好的了,網上有電子版本(文末有鏈接)。不過我買了書看,哈哈。這兩篇摘錄筆記分為上下兩部分,本文是上半部分(1-6章),摘錄了兩本書裏一些比較有用的知識點。*

### 目錄

> #####1. 塊級作用域綁定
> #####2. 字符串與正則表達式
> #####3. 函數的擴展
> #####4. 數組的擴展
> #####5. 對象的擴展
#####6. 集合(Set、Map)
> 7 . Symbol和Symbol屬性
>
> 8 . Javascript中的類
>
> 9 . Promise、Generator函數、Async函數
>
> 10 . 代理(Proxy)和反射(Reflection)API
>
> 11 . 修飾器
>
> 12 . Module


### 一、塊級作用域綁定

塊級聲明用於聲明在指定塊級作用域之外無法訪問的變量。塊級作用域存在於:1、函數內部;2、塊中(字符‘{‘和‘}‘之間的區域)。Es6 中存在的兩種變量聲明就是Let和Const聲明。

值得摘錄的有以下六點:

##### 1. 不能重復聲明

var count = 9;
//拋出語法錯誤
let count = 7;

##### 2. Const常量聲明必須進行初始化

const age = 8;
//語法錯誤:常量未初始化
const name;

##### 3. 特殊的For循環
For循環設置循環變量的那部分是一個父作用域,而循環體內部是一個單獨的子作用域。

for(let i = 0;i< 3;i++){
let i = ‘can not change‘;
console.log(i);//輸出‘can not change‘
}

##### 4. 暫時性死區
在區塊中存在let和const命令,則這個區塊對這些命令聲明的變量從一開始就形成封閉的作用域。只要在聲明之前就使用這些變量,就會報錯。

情況一:
var tmp = 123;
if(true){
tmp = ‘abc‘;//ReferenceError
let tmp;
}

情況二:
function test(x = y,y = 2){
return [x,y];
}

test();//報錯

情況三:
let x = x;//ReferenceError: x is not defined

##### 5. Const聲明對象,對象屬性可變

##### 6. 徹底凍結函數

constantize(obj) {//徹底凍結函數
Object.freeze(obj);
Object.keys(obj).forEach((key, i) => {
if (typeof obj[key] === ‘object‘) {
constantize(obj[key]);
}
})
obj.name = 777;//TypeError: Cannot add property name, object is not extensible
return obj;
}


### 二、字符串與正則表達式
Es6加強了對Unicode的支持,並且擴展了字符串對象。正則表達式則增加了修飾符和屬性。

值得摘錄的有以下幾點:

##### 1. codePointAt()與String.fromCodePoint()方法
完全支持UTF-16,接受編碼單元的位置而非字符位置作為參數,返回與字符串中給定位置對應的碼位,即一個整數值:

var text = "??a";

console.log(text.charCodeAt(0)); //55362
console.log(text.codePointAt(0)); //134017

判斷一個字符是2個字節還是4個字節組成:
function is32Bit(c){
return c.codePointAt(0) > 0xFFFF;
}

is32Bit("??"); // true
is32Bit("a"); // false

console.log(String.fromCodePoint(134071)); // "??"

##### 2. normalize()方法
Es6為字符串添加了一個Normalize()方法,它可以提供Unicode的標準化形式,接受一個參數,指明應用哪種Unicode標準化形式(NFC,NFD,NFKC,NFKD)。只需要記住,在對比字符串之前,一定要先把它們標準化為同一種形式:

let normalized = values.map((text) => {
return text.normalize();
})

console.log(this[normalize](‘\u01D1‘) === this[normalize](‘\u004F\u030C‘)) // true

##### 3. includes()、startsWith()、endsWith()、repeat()方法

let tempArray = [1, "4", "uu", 5];
let tempStr = "test123uuunit582";

console.log(tempArray.includes(5)); //true
console.log(tempStr.includes("test123", 1)); //從第二個位置進行匹配

console.log(tempStr.startsWith("test")); //true
console.log(tempStr.startsWith("test123", 0)); //從第一個位置進行匹配

console.log(tempStr.endsWith("12",6)); //從第六個位置進行匹配
console.log(tempStr.repeat(3)); //test123uuunit582test123uuunit582test123uuunit582

##### 4. 模板字符串
領域專用語言(DSL),可以生成、查詢並操作其他語言裏的內容,且可以免受註入攻擊(XSS、SQL等)。

> A、去掉模板字符串中的換行


$(‘#list‘).html(`
<ul>
<li>first</li>
<li>second</li>
</ul>
`.trim());

> B、引用模板字符串本身


//寫法一
let str = ‘return ‘ + ‘`ni hao ${name}`‘;
let func = new Function(‘name‘,str);
let data = func(‘uct‘);
//寫法二
let str2 = ‘(name) => `ni hao ${name}`‘;
let func2 = eval.call(null,str2);
let data2 = func2(‘uct‘);

console.log(data + ‘ ‘ + data2);

> C、標簽模板


alert`123`;
//等同於
alert(123);
var a = 1,b = 2;
tag`Hello ${a + b} world ${a * b}`;
//等同於
tag([‘Hello ‘,‘ world , ‘‘],1,2);//第一個參數是數組,存放模板中沒有變量替換的部分,後續參數是變量。

> D、String.raw()轉義模板字符串



String.raw`Hi\n${3+4}`;//"Hi\\n5"

[關於new Function][newFunction]

[關於eval][eval]

[newFunction]: https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Function
[eval]:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/eval

##### 5.U修飾符與Y修飾符
U修飾符,即為“Unicode模式”,用來正確處理大於\uFFFF的Unicode字符。Y修飾符,叫作“粘連”(Sticky)修飾符,它的後一次匹配都從上一次匹配成功的下一個位置開始。

> A、點(.)字符匹配(匹配除換行符外的所有字符)

var s = "??";
/^.$/.test(s); // false
/^.$/u.test(s); // true

> B、返回字符串長度的函數


function codePointLength(){
var result = text.match(/[\s\S]/gu);
return result ? result.length : 0;
}

> C、y修飾符隱含了頭部匹配的標誌(^)


const REGEX = /a/y;
REGEX.lastIndex = 2;
REGEX.exec("xaya"); // null
REGEX.lastIndex = 3;
const match = REGEX.exec("xaya");
match.index; // 3



### 三、函數的擴展

值得摘錄的有以下幾點:


##### 1.函數參數的默認值

> A、參數變量默認聲明

function test(x = 1){
let x = 2;//error
}

> B、惰性求值
let x = 10;
function testLazy(p = x + 1){
console.log(p);
}

testLazy(); // 11
let x = 12;
testLazy(); // 13

> C、非尾部設置默認值,參數不可省略

function f(x = 1,y){
return [x,y];
}
f(,1); //報錯

> D、默認參數的臨時死區TDZ

function add(first = second,second){
return first + second;
}

console.log(add(1,1)); // 2
console.log(add(undefined,1)); // 拋出錯誤
//當初次調用add()時,綁定first和second被添加到一個專屬於函數參數的臨時死區

//表示調用add(1,1) 時的Javascript 代碼
let first = 1;
let second = 2;
//表示調用add(undefined,1) 時的Javascript 代碼
let first = second;
let second = 1; //所以拋出錯誤;


##### 2.默認參數表達式
初次解析函數聲明時不會調用getValue()方法,只有當調用add()函數且不傳入第二個參數時才會調用:


constructor() {
this.value = 5;
}

getValue(){
return this.value ++;
}

add(first,second = this.getValue()){
console.log(‘plus: ‘, first + second );
}
...
Es6Function.add(1,1); // plus: 2
Es6Function.add(1); // plus: 6
Es6Function.add(1); // plus: 7



##### 3. 只要函數參數使用了默認值、解構賦值或者擴展運算符,那麽函數內部就不能嚴格模式

const doSomething = (a,b = a,...c) => { //error
‘use strict‘;
//code
}

##### 4. 不定參數限制使用

> 每個函數最多只能聲明一個不定參數,而且一定要放在所有參數的末尾。

function any(a,...b,last){} //報錯

> 不定參數不能用於對象字面量Setter中

let object = {

//語法錯誤,不可以在Setter中使用不定參數
set name(...value){}
}

> 取數組最大值

let values = [25,34,11,99];

console.log(Math.max(...values)); // 99


##### 5. 箭頭函數

> A、箭頭函數中的This值取決於該函數外部非箭頭函數的this值,且不能通過call()、apply()或bind()方法來改變this值

thisScope(){
setTimeout(()=>{
console.log(‘id: ‘,this.id);
},100);
}
...
var id = 55;
Es6Function.thisScope.call({id:100}); // id : 100

> B、函數體內不可民使用Arguments對象,該對象在函數體內不存在,可以用Rest參數(...)代替

testArguments(){
setTimeout(() => {
console.log(‘args: ‘,arguments)
}, 100);
}
...
Es6Function.testArguments(1,2,5,6); // args: Arguments(4) [1, 2, 5, 6, callee: (...), Symbol(Symbol.iterator): ?]

##### 6. 尾調用優化

> 尾調用就是指某個函數的最後一步是調用另一個函數,只在嚴格模式下開啟。

"use strict";

function doSomething(){
//優化後,立即返回結果
return doSomethingElse();
}

function doSomething2(){
//無法優化,無返回
doSomethingElse();
}

> 非嚴格模式下實現尾遞歸優化,用“循環” 替換 “遞歸”

function sum(x,y){
if(y > 0){
return sum( x + 1,y - 1);
} else {
return x;
}

}
sum(1,1000000);// Uncaught RangeError: Maximum call stack size exceeded(...)

//用蹦床函數可以將遞歸執行轉為循環執行
function trampoline(f){
while(f && f instanceof Function){
f = f();
}
return f;
}

//返回一個函數,然後執行該函數,而不是在函數裏調用函數,避免遞歸,消除調用棧過大問題

### 四、數組的擴展

值得摘錄的有以下幾點:

##### 1. 擴展運算符(...)

擴展運算符背後調用的是遍歷器接口(Symbol.iterator),如果一個對象沒有部署該接口就無法轉換

> 替代數組的 Apply方法

//Es5 的寫法

function f(x,y,z){}

var args = [1,2,3];

f.apply(null,args);

//Es6 的寫法

function f(x,y,z){}

var args = [1,2,3];

f(...args);

> 合並數組

Es5 合並數組

var arr1 = [1,2];
var arr2 = [3,4];
var arr3 = [5];

arr1.concat(arr2,arr3);

Es6 合並數組

[...arr1,...arr2,...arr3];

> 能夠識別 32 位的 Unicode 字符的取字符串長度的方法

function length(str){
return [...str].length;
}

length(‘x\uD83D\uDE80y‘); // 3

##### 3. Array.from()方法,用於將類似數組對象和可遍歷對象(包括Set和Map結構)轉為真正的數組
Array.from()方法除了支持遍歷器接口轉換外,還支持類數組對象(類數組對象的本質就是有Length屬性)

Array.from({length:3});
// [undefined,undefined,undefined]

> 映射轉換,Array.from()的第二個參數用來遍歷類數組對象並返回

translate() {
return Array.from(arguments, (value) => value + 1);
}

console.log(NewArray.translate(1,2,3)); // [2,3,4]

##### 4. 為所有數組添加新的方法


> Array.of(),為了彌補數組構造函數Array()的不足

Array.of(3,11,8); // [3,11,8]
Array.of(undefined); // [undefined] , 通過Vue 顯示到頁面則為 [null]
Array(3); // [,,]

> copyWithin(),把數組內部指定位置成員復制到其他位置(會覆蓋其他成員)

//將3號位復制到0號位
[1,2,3,4,5].copyWithin(0,3,4); //[ 4, 2, 3, 4, 5 ]
// -2相當於3號位,-1相當於4號位
[1,2,3,4,5].copyWithin(0,-2,-1); //[ 4, 2, 3, 4, 5 ]

> find(),返回符合條件的第一個(可能有多個)成員,如果沒有就返回undefined

testFind(){ // 返回 3
return [1,2,3,4,5].find((value,index,arr)=>{
return value > 2;
})
}

> findIndex(),返回第一個符合條件的成員的位置,如果沒有就返回 -1

testFindIndex(){ // 返回 -1
return [1,2,3,4,5].findIndex((value,index,arr)=>{
return value > 6;
})
}

> fill(),填充一個數組,接受第二第三個參數(起始位置和結束位置)

testFill(){
console.log([‘a‘,‘b‘,‘c‘].fill(8,1,2)); // ["a", 8, "c"]
return new Array(3).fill(8); //[ 8, 8, 8 ]
}

> includes(),返回一個布爾值,表示某個數組是否包含給定的值,它與indexOf 比起來更加直觀,並且可以判斷NaN的情況

testIncludes(){
return [1,2,NaN].includes(NaN); // true
}

> entries(),keys(),values()方法,它們都返回一個遍歷器對象

testEntries(){
for(let [key,value] of [‘a‘,‘b‘,‘c‘].entries()){
console.log(key,value); // 0 "a" 1 "b" 2 "c"
}
let letter = [‘a‘,‘b‘,‘c‘];
//手動調用遍歷器對象的Next方法進行遍歷
let entries = letter.entries();
console.log(entries.next().value); //[0, "a"]
console.log(entries.next().value); //[1, "b"]
console.log(entries.next().value); //[2, "c"]
}

### 五、對象的擴展
ECMAScript6 中,為了使某些任務更易完成,在全局Object 對象上引入了一些新方法

值得摘錄的有以下幾點:

##### 1. 可計算屬性名

let suffix = " name";

var person = {
["first" + suffix]: "Nicholas", // Es5 版本,這裏的名稱是不可以計算的
["last" + suffix]: "Zakas",
}

console.log(person["first name"]); // "Nicholas"
console.log(person["last name"]); // "Zakas"

##### 2. Object.is(),彌補全等(===)運算符的特殊情況判斷不一致

testObjectIs(){
console.log(+0 == -0); // true
console.log(+0 === -0); // true
console.log(Object.is(+0 , -0)); // false
console.log(NaN == NaN); // false
console.log(NaN === NaN); // false
console.log(Object.is(NaN , NaN)); // true
}

##### 3. Object.assign(),只會復制對象自身可枚舉屬性,忽略enumerable為false的屬性


> 多個對象復制具有同名屬性,排位靠後的源對象會覆蓋排位靠前的

testObjectAssign(){
let receiver = {};
Object.assign(receiver,{
type:‘girl‘,
name:‘superman‘,
},{
type:‘boy‘,
})
return receiver.type + " : " + receiver.name; // boy : superman
}

> 對象淺復制(源對象某個屬性是對象,只復制這個對象的引用)

let target = { a: { b: ‘c‘, d: ‘e‘ } };
let source = { a: { b: ‘hello‘ } }
console.log(Object.assign(target, source)); // {a: {b:‘hello‘}}
// a屬性被整體覆蓋掉,不會得到 {a : {b:‘hello‘,d:‘e‘}} 的結果

##### 3. Object.values()

> 只返回對象自身可遍歷屬性

testObjectValues() { // p 的屬性描述對象的 enumerable 默認是 false
var obj = Object.create({}, { p: { value: 42 } });
return Object.values(obj); // []
}

> Object.values 會過濾屬性名為Symbol值的屬性

console.log(Object.values({ [Symbol()]: 123, any: ‘abc‘ })); // [‘abc‘]

##### 3. Object.entries()

> 轉換對象為真正的Map結構

testObjectEntries(){
let obj = { a: ‘bar‘, b: 23 };
let map = new Map(Object.entries(obj));
console.log(map.get(‘a‘)); // bar
}


### 六、集合(Set、Map)
Es6 中的Set和Map核心思想跟Java中的Set和Map類似,Set集合成員是唯一的,沒有重復,Set本身是構造函數用來生成Set數據結構;Map類型是一種存儲著許多鍵值對的有序列表,它的鍵名的判斷是通過Object.is()方法來實現的(Set也是通過這個方法來判斷兩個值是否一致),所以 5 和字符串"5"會被判定為兩種類型。

值得摘錄的有以下幾點:

##### 1. Set

> 添加元素用add()方法,刪除用delete()方法,判斷存在用has(),清空用clear()

testSet() {
let set = new Set();
set.add(5);
set.add(‘5‘);
console.log(set.has(5)); // true
set.delete(5);
console.log(set.has(5)); // false
console.log(set.size); // 1
set.clear();
console.log(set.has(‘5‘)); // false
console.log(set.size); // 0
}

> 數組去重

testArrayUniq(){
let set = new Set([1,2,3,3,3,3,4,4,4,4,5]);
let arr = [...set];
console.log(arr); //[ 1, 2, 3, 4, 5 ]
return arr; //[ 1, 2, 3, 4, 5 ]
}

> 改變原來的Set結構

changeSet(){
let set = new Set([1, 2, 3]);
//方法二
set = new Set([...set].map((val) => val * 2));

//方法二
set = new Set(Array.from(set, val => val * 2));
return set; // [ 4, 8, 12 ]
}

##### 2. Map
Map結構提供了“值-值”對應,而Object結構提供了“字符串-值”對應,Map結構是一種更加完善的Hash結構實現

> 設置值set(),獲取值get(),判斷存在has(),刪除delete(),清空用clear(),多次對同一個鍵賦值,後面覆蓋前面

testMap(){
const m = new Map();
const o = {p:‘hello uct‘};

m.set(o,‘content‘);
m.set(o,‘new content‘)
console.log(m.get(o)); // new content
console.log(m.has(o)); // true
m.delete(o);
console.log(m.has(o)); // false
}

> Map 和 對象互相轉換

//Map轉為對象
strMapToObj(strMap){//Map to Object
let obj = Object.create(null);
for(let [k,v] of strMap){
obj[k] = v;
}
return obj;
}
//對象轉為Map
objToStrMap(obj){// Object to Map
let strMap = new Map();
for(let k of Object.keys(obj)){
strMap.set(k,obj[k]);
}
return strMap;
}

##### 3. WeakMap
WeakMap只接受對象作為鍵名(Null除外)

> WeakMap 應用的典型場景就是以DOM節點作為鍵名的場景

const listener = new WeakMap();

listener.set(element1,handler1);
listener.set(element2,handler2);

element1.addEventListener(‘click‘,listener.get(element1),false);
element2.addEventListener(‘click‘,listener.get(element2),false);

//一旦DOM對象消失,與它綁定的監聽函數也會自動消失

> 部署私有屬性

const _counter = new WeakMap();
const _action = new WeakMap();

class Countdown {
constructor(counter, action) {
_counter.set(this, counter);//weakmap的私有屬性
_action.set(this, action); //weakmap的私有屬性
}
dec() {
let counter = _counter.get(this);
if (counter < 1) return;
counter--;
console.log(counter);
_counter.set(this, counter);
if (counter === 0) {
_action.get(this)();
}
}
}

export default Countdown;

### 結語
*本來覺得沒有必要寫這個筆記,但是,看和寫真的是兩回事。書中的例子大都是抽象化的,也就是為了讓大家看起來直觀,去掉了上下文,有些還有錯。自己動手寫的話(運用實例),印象會非常深刻,在真實開發環境中會想到去用它。筆記的下篇已經在寫了,相對上篇,我更期待下篇,NSL...*

### 參考鏈接



[ECMAScript 6 入門](http://es6.ruanyifeng.com/)

[UNDERSTANDING ES6 英文原版](https://leanpub.com/understandinges6/read)

《ES標準入門》&《UNDERSTANDING ECMACHRIPT 6》 讀書摘錄筆記(上)