Part1 · JavaScript【深度剖析】
ES 新特性與 TypeScript、JS 性能優化
文章說明:本專欄內容為本人參加【拉鉤大前端高新訓練營】的學習筆記以及思考總結,學徒之心,僅為分享,如若有誤,請在評論區支出,如果您覺得專欄內容還不錯,請點贊、關注、評論,
共同進步!
上一篇:【ECMAScript概述】、【ES6概述】、【var let const】、【陣列與物件的解構】
今天內容有點多,不過大多數都是API用法,只是比較細碎,慢慢嚼,多使用,在使用的程序中加深理解,
七、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模板時,這個方法非常有用:
let pageHTML = `
<div>
<a href="#">
<span>Jake</span>
</a>
</div>`;
由于模板字面量會保持反引號內部的空格,因此在使用時要格外注意,格式正確的模板字串看起來可能會縮進不當,
2.字串插值
模板字面量最常用的一個特性是支持字串插值,也就是可以在一個連續定義中插入一個或多個值,技術上講,**模板字面量不是字串,而是一種特殊的 JavaScript 句法運算式,只不過求值后得到的是字串,**模板字面量在定義時立即求值并轉換為字串實體,任何插入的變數也會從它們最接近的作用域中取值,
字串插值通過在${}中使用一個 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"
八、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
九、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
十、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指向也與普通函式不同,請參考一篇文章:
十一、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
)
在實際使用中,仍然建議使用===來判斷,
十二、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等方法,
十三、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
十四、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
十五、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()方法,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/204891.html
標籤:其他
上一篇:快速排序(分治策略)
