Part1 · JavaScript【深度剖析】
ES 新特性與 TypeScript、JS 性能優化
文章說明:本專欄內容爲本人蔘加【拉鉤大前端高新訓練營】的學習筆記以及思考總結,學徒之心,僅爲分享。如如有誤,請在評論區支出,若是您以爲專欄內容還不錯,請點贊、關注、評論。前端
共同進步!express
上一篇:【ECMAScript概述】、【ES6概述】、【var let const】、【數組與對象的解構】編程
今天內容有點多,不過大多數都是API用法,只是比較細碎,慢慢嚼,多使用,在使用的過程當中加深理解。數組
7、ES2015模板字符串
1.模板字面量
ECMAScript 6 新增了使用模板字面量定義字符串的能力。與使用單引號或雙引號不一樣,模板字面量保留換行字符,能夠跨行定義字符串:性能優化
使用鍵盤區域Esc下方【數字1左邊】的反引號數據結構
let myMultiLineString = 'first line\nsecond line'; let myMultiLineTemplateLiteral = `first line second line`; console.log(myMultiLineString); // first line // second line" console.log(myMultiLineTemplateLiteral); // first line // second line console.log(myMultiLineString === myMultiLinetemplateLiteral); // true
因此說,當咱們須要寫HTML模板時,這個方法很是有用:app
let pageHTML = ` <div> <a href="#"> <span>Jake</span> </a> </div>`;
因爲模板字面量會保持反引號內部的空格,所以在使用時要格外注意。格式正確的模板字符串看起來可能會縮進不當。dom
2.字符串插值
模板字面量最經常使用的一個特性是支持字符串插值,也就是能夠在一個連續定義中插入一個或多個值。技術上講,**模板字面量不是字符串,而是一種特殊的 JavaScript 句法表達式,只不過求值後獲得的是字符串。**模板字面量在定義時當即求值並轉換爲字符串實例,任何插入的變量也會從它們最接近的做用域中取值。ide
字符串插值經過在${}中使用一個 JavaScript 表達式實現:函數
let value = 5; let exponent = 'second'; // 之前,字符串插值是這樣實現的: let interpolatedString = value + ' to the ' + exponent + ' power is ' + (value * value); // 如今,能夠用模板字面量這樣實現: let interpolatedTemplateLiteral = `${ value } to the ${ exponent } power is ${ value * value }`; console.log(interpolatedString); // 5 to the second power is 25 console.log(interpolatedTemplateLiteral); // 5 to the second power is 25 // 全部插入的值都會使用 toString()強制轉型爲字符串,並且任何 JavaScript 表達式均可以用於插值。
3.模板字面量標籤函數
模板字面量也支持定義標籤函數(tag function),而經過標籤函數能夠自定義插值行爲。標籤函數會接收被插值記號分隔後的模板和對每一個表達式求值的結果。
標籤函數自己是一個常規函數,經過前綴到模板字面量來應用自定義行爲,以下例所示。標籤函數接收到的參數依次是原始字符串數組和對每一個表達式求值的結果。這個函數的返回值是對模板字面量求值獲得的字符串。
這樣概念解釋很不清楚,經過下方的例子來增強理解:
let a = 6; let b = 9; function simpleTag(strings, ...expressions) { console.log(strings); for(const expression of expressions) { console.log(expression); } return 'foobar'; } let taggedResult = simpleTag`${ a } + ${ b } = ${ a + b }`; // ["", " + ", " = ", ""] // 6 // 9 // 15 console.log(taggedResult); // "foobar"
8、ES2015參數默認值
在 ECMAScript5.1 及之前,實現默認參數的一種經常使用方式就是檢測某個參數是否等於 undefined,若是是則意味着沒有傳這個參數,那就給它賦一個值:
function makeKing(name) { name = (typeof name !== 'undefined') ? name : 'Henry'; return `King ${ name} VIII`; } console.log(makeKing()); // 'King Henry VIII' console.log(makeKing('Louis')); // 'King Louis VIII'
ECMAScript 6 以後就不用這麼麻煩了,由於它支持顯式定義默認參數了。下面就是與前面代碼等價的 ES6 寫法,只要在函數定義中的參數後面用=就能夠爲參數賦一個默認值:
function makeKing(name = 'Henry') { return `King ${ name} VIII`; } console.log(makeKing('Louis')); // 'King Louis VIII' console.log(makeKing()); // 'King Henry VIII'
上面給參數傳 undefined 至關於沒有傳值,不過這樣能夠利用多個獨立的默認值:
function makeKing(name = 'Henry', numerals = 'VIII') { return `King ${ name} ${ numerals}`; } console.log(makeKing()); // 'King Henry VIII' console.log(makeKing('Louis')); // 'King Louis VIII' console.log(makeKing(undefined, 'VI')); // 'King Henry VI'
在使用默認參數時,arguments 對象的值不反映參數的默認值,只反映傳給函數的參數。固然,跟 ES5 嚴格模式同樣,修改命名參數也不會影響 arguments 對象,它始終以調用函數時傳入的值爲準:
function makeKing(name = 'Henry') { name = 'Louis'; return `King ${ arguments[0]}`; } console.log(makeKing()); // 'King undefined' console.log(makeKing('Louis')); // 'King Louis'
默認參數值並不限於原始值或對象類型,也可使用調用函數返回的值:
let romanNumerals = ['I', 'II', 'III', 'IV', 'V', 'VI']; let ordinality = 0; function getNumerals() { // 每次調用後遞增 return romanNumerals[ordinality++]; } function makeKing(name = 'Henry', numerals = getNumerals()) { return `King ${ name} ${ numerals}`; } console.log(makeKing()); // 'King Henry I' console.log(makeKing('Louis', 'XVI')); // 'King Louis XVI' console.log(makeKing()); // 'King Henry II' console.log(makeKing()); // 'King Henry III'
函數的默認參數只有在函數被調用時纔會求值,不會在函數定義時求值。並且,計算默認值的函數只有在調用函數但未傳相應參數時纔會被調用。
箭頭函數一樣也能夠這樣使用默認參數,只不過在只有一個參數時,就必須使用括號而不能省略了:
let makeKing = (name = 'Henry') => `King ${ name}`; console.log(makeKing()); // King Henry
9、ES2015展開數組(Spread)
在 ECMAScript5.1 及之前,咱們從打印出數組中的元素很麻煩,最笨的辦法是:
const arr = ['foo', 'bar', 'baz']; console.log( arr[0], arr[1], arr[2] ) // foo bar baz
可是當數組的個數不肯定是,就不能使用這個方法,而且這個方法屬於硬展,咱們可使用函數的apply方法,第一個參數this指向console對象,第二個參數是要傳遞的數組對象:
const arr = ['foo', 'bar', 'baz']; console.log.apply(console, arr) // foo bar baz
在ES6中,咱們能夠更簡單的使用數組展開的方法,形式同與收集剩餘參數,使用…arr展開數組,這樣寫起來很是方便:
const arr = ['foo', 'bar', 'baz']; console.log(...arr) // foo bar baz
10、ES2015箭頭函數
ECMAScript 6 新增了使用胖箭頭(=>)語法定義函數表達式的能力。很大程度上,箭頭函數實例化的函數對象與正式的函數表達式建立的函數對象行爲是相同的。任何可使用函數表達式的地方,均可以使用箭頭函數:
let arrowSum = (a, b) => { return a + b; }; let functionExpressionSum = function(a, b) { return a + b; }; console.log(arrowSum(5, 8)); // 13 console.log(functionExpressionSum(5, 8)); // 13
若是隻有一個參數,那也能夠不用括號。只有沒有參數,或者多個參數的狀況下,才須要使用括號:
// 如下兩種寫法都有效 let double = (x) => { return 2 * x; }; let triple = x => { return 3 * x; }; // 沒有參數須要括號 let getRandom = () => { return Math.random(); }; // 多個參數須要括號 let sum = (a, b) => { return a + b; }; // 無效的寫法: let multiply = a, b => { return a * b; };
箭頭函數也能夠不用大括號,但這樣會改變函數的行爲。使用大括號就說明包含「函數體」,能夠在一個函數中包含多條語句,跟常規的函數同樣。若是不使用大括號,那麼箭頭後面就只能有一行代碼,好比一個賦值操做,或者一個表達式。並且,省略大括號會隱式返回這行代碼的值:
// 如下兩種寫法都有效,並且返回相應的值 let double = (x) => { return 2 * x; }; let triple = (x) => 3 * x; // 能夠賦值 let value = { }; let setName = (x) => x.name = "Matt"; setName(value); console.log(value.name); // "Matt" // 無效的寫法: let multiply = (a, b) => return a * b;
箭頭函數雖然語法簡潔,但也有不少場合不適用。箭頭函數不能使用 arguments、super 和new.target,也不能用做構造函數。此外,箭頭函數也沒有 prototype 屬性。
另外,箭頭函數中的this指向也與普通函數不一樣,請參考一篇文章:
11、ES2015對象
1.對象字面量的加強
ES6中加強了對象的字面量,在以前,咱們聲明對象只能使用鍵+冒號+值的方式:
let bar = '345' const obj = { name: 'leo', age: '26', bar: bar }
而在ES6中,當咱們的屬性名與其值都爲變量且相同時,咱們能夠省略冒號以及後面的值:
let bar = '345' const obj = { name: 'leo', age: '26', bar }
一樣,當咱們須要使用動態的屬性名時,以前的作法是在對象聲明事後,再對對象賦值動態屬性名的值:
const obj = { name: 'leo', age: '26', bar } obj[Math.random()] = 'random'
在ES6中,咱們能夠直接使用方括號+表達式對對象添加動態的屬性名,這種方式稱之爲【計算屬性名】,方括號內部能夠爲任意表達式,表達式結果做爲最終的屬性名:
const obj = { name: 'leo', age: '26', bar, [Math.random()]: 'random' }
2.Object擴展方法
- Object.assign
此方法能夠將多個源對象中的屬性複製到一個目標對象中,若是對象之間有相同的屬性名,那麼源對象中的屬性就會覆蓋掉目標對象中的屬性。源對象與目標對象都是普通的對象,只不過用處不一樣
const source1 = { a: 123, b: 123 } const source2 = { b: 789, d: 789 } const target = { a: 456, c: 456 } const result = Object.assign(target, source1, source2) console.log(target) console.log(result === target) // { a: 123, c: 456, b: 789, d: 789 } // true
應用場景:
function func (obj) { // obj.name = 'func obj' // console.log(obj) const funcObj = Object.assign({ }, obj) funcObj.name = 'func obj' console.log(funcObj) } const obj = { name: 'global obj' } func(obj) console.log(obj)
assign方法多用於options對象參數設置默認值。
- Object.is
用來判斷兩個值是否相等,在以前咱們使用和=分別判斷值是否相等以及是否全等(值與類型都相等),在==中,js默認使用toString方法來進行隱式轉換,而在ES6中,提供了全新的方法Object.is方法進行判斷:
console.log( // 0 == false // => true // 0 === false // => false // +0 === -0 // => true // NaN === NaN // => false // Object.is(+0, -0) // => false // Object.is(NaN, NaN) // => true )
在實際使用中,仍然建議使用===來判斷。
12、ES2015 Proxy
在 ES6 以前,ECMAScript 中並無相似代理的特性。因爲代理是一種新的基礎性語言能力,不少轉譯程序都不能把代理行爲轉換爲以前的 ECMAScript 代碼,由於代理的行爲其實是無可替代的。爲此,代理和反射只在百分之百支持它們的平臺上有用。能夠檢測代理是否存在,不存在則提供後備代碼。不過這會致使代碼冗餘,所以並不推薦。
1.空代理
最簡單的代理是空代理,即除了做爲一個抽象的目標對象,什麼也不作。默認狀況下,在代理對象上執行的全部操做都會無障礙地傳播到目標對象。所以,在任何可使用目標對象的地方,均可以經過一樣的方式來使用與之關聯的代理對象。
以下面的代碼所示,在代理對象上執行的任何操做實際上都會應用到目標對象。惟一可感知的不一樣就是代碼中操做的是代理對象。
const target = { id: 'target' }; const handler = { }; const proxy = new Proxy(target, handler); // id 屬性會訪問同一個值 console.log(target.id); // target console.log(proxy.id); // target // 給目標屬性賦值會反映在兩個對象上 // 由於兩個對象訪問的是同一個值 target.id = 'foo'; console.log(target.id); // foo console.log(proxy.id); // foo // 給代理屬性賦值會反映在兩個對象上 // 由於這個賦值會轉移到目標對象 proxy.id = 'bar'; console.log(target.id); // bar console.log(proxy.id); // bar // hasOwnProperty()方法在兩個地方 // 都會應用到目標對象 console.log(target.hasOwnProperty('id')); // true console.log(proxy.hasOwnProperty('id')); // true // Proxy.prototype 是 undefined // 所以不能使用 instanceof 操做符 console.log(target instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check console.log(proxy instanceof Proxy); // TypeError: Function has non-object prototype 'undefined' in instanceof check // 嚴格相等能夠用來區分代理和目標 console.log(target === proxy); // false
2.定義捕獲器:
使用代理的主要目的是能夠定義捕獲器(trap)。捕獲器就是在處理程序對象中定義的「基本操做的攔截器」。每一個處理程序對象能夠包含零個或多個捕獲器,每一個捕獲器都對應一種基本操做,能夠直接或間接在代理對象上調用。每次在代理對象上調用這些基本操做時,代理能夠在這些操做傳播到目標對象以前先調用捕獲器函數,從而攔截並修改相應的行爲。
例如,能夠定義一個 get()捕獲器,在 ECMAScript 操做以某種形式調用 get()時觸發。下面的例子定義了一個 get()捕獲器:
const target = { foo: 'bar' }; const handler = { // 捕獲器在處理程序對象中以方法名爲鍵 get() { return 'handler override'; } }; const proxy = new Proxy(target, handler); console.log(target.foo); // bar console.log(proxy.foo); // handler override console.log(target['foo']); // bar console.log(proxy['foo']); // handler override console.log(Object.create(target)['foo']); // bar console.log(Object.create(proxy)['foo']); // handler override
捕獲器能夠定義get、delete、set等方法。
十3、ES2015 class類
ECMAScript 6 新引入的 class 關鍵字具備正式定義類的能力。類(class)是ECMAScript 中新的基礎性語法糖結構,所以剛開始接觸時可能會不太習慣。雖然 ECMAScript 6 類表面上看起來能夠支持正式的面向對象編程,但實際上它背後使用的仍然是原型和構造函數的概念。
1.類的定義
與函數類型類似,定義類也有兩種主要方式:類聲明和類表達式。這兩種方式都使用 class 關鍵字加大括號:
// 類聲明 class Person { } // 類表達式 const Animal = class { };
類能夠包含構造函數方法、實例方法、獲取函數、設置函數和靜態類方法,但這些都不是必需的。空的類定義照樣有效。默認狀況下,類定義中的代碼都在嚴格模式下執行。
與函數構造函數同樣,多數編程風格都建議類名的首字母要大寫,以區別於經過它建立的實例(好比,經過 class Foo {}建立實例 foo):
// 空類定義,有效 class Foo { } // 有構造函數的類,有效 class Bar { constructor() { } } // 有獲取函數的類,有效 class Baz { get myBaz() { } } // 有靜態方法的類,有效 class Qux { static myQux() { } }
2.類構造函數
constructor 關鍵字用於在類定義塊內部建立類的構造函數。方法名 constructor 會告訴解釋器在使用 new 操做符建立類的新實例時,應該調用這個函數。構造函數的定義不是必需的,不定義構造函數至關於將構造函數定義爲空函數。
使用 new 操做符實例化 Person 的操做等於使用 new 調用其構造函數。惟一可感知的不一樣之處就是,JavaScript 解釋器知道使用 new 和類意味着應該使用 constructor 函數進行實例化。
使用 new 調用類的構造函數會執行以下操做。
-
在內存中建立一個新對象。
-
這個新對象內部的[[Prototype]]指針被賦值爲構造函數的 prototype 屬性。
-
構造函數內部的 this 被賦值爲這個新對象(即 this 指向新對象)。
-
執行構造函數內部的代碼(給新對象添加屬性)。
-
若是構造函數返回非空對象,則返回該對象;不然,返回剛建立的新對象。
class Animal { } class Person { constructor() { console.log('person ctor'); } } class Vegetable { constructor() { this.color = 'orange'; } } let a = new Animal(); let p = new Person(); // person ctor let v = new Vegetable(); console.log(v.color); // orange
3.實例、原型、類成員
類的語法能夠很是方便地定義應該存在於實例上的成員、應該存在於原型上的成員,以及應該存在於類自己的成員。
每次經過new調用類標識符時,都會執行類構造函數。在這個函數內部,能夠爲新建立的實例(this)添加「自有」屬性。至於添加什麼樣的屬性,則沒有限制。另外,在構造函數執行完畢後,仍然能夠給實例繼續添加新成員。
每一個實例都對應一個惟一的成員對象,這意味着全部成員都不會在原型上共享:
class Person { constructor() { // 這個例子先使用對象包裝類型定義一個字符串 // 爲的是在下面測試兩個對象的相等性 this.name = new String('Jack'); this.sayName = () => console.log(this.name); this.nicknames = ['Jake', 'J-Dog'] } } let p1 = new Person(), p2 = new Person(); p1.sayName(); // Jack p2.sayName(); // Jack console.log(p1.name === p2.name); // false console.log(p1.sayName === p2.sayName); // false console.log(p1.nicknames === p2.nicknames); // false p1.name = p1.nicknames[0]; p2.name = p2.nicknames[1]; p1.sayName(); // Jake p2.sayName(); // J-Dog
靜態類成員在類定義中使用 static 關鍵字做爲前綴。在靜態成員中,this 引用類自身。其餘全部約定跟原型成員同樣:
class Person { constructor() { // 添加到 this 的全部內容都會存在於不一樣的實例上 this.locate = () => console.log('instance', this); } // 定義在類的原型對象上 locate() { console.log('prototype', this); } // 定義在類自己上 static locate() { console.log('class', this); } } let p = new Person(); p.locate(); // instance, Person {} Person.prototype.locate(); // prototype, {constructor: ... } Person.locate(); // class, class Person {}
4.繼承
ES6 類支持單繼承。使用 extends 關鍵字,就能夠繼承任何擁有[[Construct]]和原型的對象。很大程度上,這意味着不只能夠繼承一個類,也能夠繼承普通的構造函數(保持向後兼容):
class Vehicle { } // 繼承類 class Bus extends Vehicle { } let b = new Bus(); console.log(b instanceof Bus); // true console.log(b instanceof Vehicle); // true function Person() { } // 繼承普通構造函數 class Engineer extends Person { } let e = new Engineer(); console.log(e instanceof Engineer); // true console.log(e instanceof Person); // true
十4、ES2015 Set數據結構
ECMAScript 6 新增的 Set 是一種新集合類型,爲這門語言帶來集合數據結構。Set 在不少方面都像是增強的 Map,這是由於它們的大多數 API 和行爲都是共有的。
使用 new 關鍵字和 Set 構造函數能夠建立一個空集合:
const m = new Set();
// 使用數組初始化集合 const s1 = new Set(["val1", "val2", "val3"]); alert(s1.size); // 3 // 使用自定義迭代器初始化集合 const s2 = new Set({ [Symbol.iterator]: function*() { yield "val1"; yield "val2"; yield "val3"; } }); alert(s2.size); // 3
初始化以後,可使用 add()增長值,使用 has()查詢,經過 size 取得元素數量,以及使用 delete()和 clear()刪除元素:
const s = new Set(); alert(s.has("Matt")); // false alert(s.size); // 0 s.add("Matt") .add("Frisbie"); // add函數返回的依然是set對象,因此可使用鏈式調用 alert(s.has("Matt")); // true alert(s.size); // 2 s.delete("Matt"); alert(s.has("Matt")); // false alert(s.has("Frisbie")); // true alert(s.size); // 1 s.clear(); // 銷燬集合實例中的全部值 alert(s.has("Matt")); // false alert(s.has("Frisbie")); // false alert(s.size); // 0
十5、ES2015 Map數據結構
做爲 ECMAScript 6 的新增特性,Map 是一種新的集合類型,爲這門語言帶來了真正的鍵/值存儲機制。Map 的大多數特性均可以經過 Object 類型實現,但兩者之間仍是存在一些細微的差別。具體實踐中使用哪個,仍是值得細細甄別。
使用 new 關鍵字和 Map 構造函數能夠建立一個空映射:
const m = new Map();
若是想在建立的同時初始化實例,能夠給 Map 構造函數傳入一個可迭代對象,須要包含鍵/值對數組。可迭代對象中的每一個鍵/值對都會按照迭代順序插入到新映射實例中:
// 使用嵌套數組初始化映射 const m1 = new Map([ ["key1", "val1"], ["key2", "val2"], ["key3", "val3"] ]); alert(m1.size); // 3 // 映射期待的鍵/值對,不管是否提供 const m3 = new Map([[]]); alert(m3.has(undefined)); // true alert(m3.get(undefined)); // undefined
const m = new Map() const tom = { name: 'tom' } m.set(tom, 90) console.log(m) console.log(m.get(tom)) // 輸出 // Map { { name: 'tom' } => 90 } // 90 // 90 { name: 'tom' }
一樣,map也有has()、delete()、clear()方法。