1. 程式人生 > 實用技巧 >紅寶書4-第八章物件、類與面向物件程式設計(2)

紅寶書4-第八章物件、類與面向物件程式設計(2)

@

目錄


1.物件標識及相等判定

問題引入:

es6之前 === 的侷限性,如下:

1.這些是===符合預期的情況
console.log(true === 1);  // false 
console.log({} === {});   // false 
console.log("2" === 2);   // false 
2.這些情況在不同 JavaScript 引擎中表現不同,但仍被認為相等
console.log(+0 === -0);   // true 
console.log(+0 === 0);    // true 
console.log(-0 === 0);    // true 
3.要確定 NaN 的相等性,必須使用極為討厭的 isNaN()
console.log(NaN === NaN); // false 
console.log(isNaN(NaN));  // true

利用 Object.is()進行判定

這個方法與===很像,但同時也考慮 到了上述邊界情形。這個方法必須接收兩個引數

console.log(Object.is(true, 1));  // false 
console.log(Object.is({}, {}));   // false 
console.log(Object.is("2", 2));   // false
// 正確的 0、-0、+0 相等/不等判定 
console.log(Object.is(+0, -0));   // false 
console.log(Object.is(+0, 0));    // true 
console.log(Object.is(-0, 0));    // false 
// 正確的 NaN 相等判定 
console.log(Object.is(NaN, NaN)); // true

要檢查超過兩個值,遞迴地利用相等性傳遞即可:

function recursivelyCheckEqual(x, ...rest) 
{   return Object.is(x, rest[0]) && (rest.length < 2 || recursivelyCheckEqual(...rest)); } 

2.增強的物件語法

1. 屬性值簡寫

簡寫屬性名只要使用變數名(不用再寫冒號)就會自動被解釋為同 名的屬性鍵。如果沒有找到同名變數,則會丟擲 ReferenceError

let name = 'Matt';  
let person = {   name }; 
console.log(person); // { name: 'Matt' }

2. 可計算屬性

在引入可計算屬性之前,如果想使用變數的值作為屬性,那麼必須先宣告物件,然後使用中括號語 法來新增屬性。

有了可計算屬性,就可以在物件字面量中完成動態屬性賦值。中括號包圍的物件屬性鍵告訴執行時 將其作為 JavaScript表示式而不是字串來求值:

const nameKey = 'name'; const ageKey = 'age'; const jobKey = 'job'; 
 
let person = {    [nameKey]: 'Matt',   [ageKey]: 27,   [jobKey]: 'Software engineer' }; 
 
console.log(person); // { name: 'Matt', age: 27, job: 'Software engineer' } 

因為被當作 JavaScript表示式求值,所以可計算屬性本身可以是複雜的表示式,在例項化時再求值:

const nameKey = 'name';  
const ageKey = 'age'; 
const jobKey = 'job'; 
let uniqueToken = 0; 
 
function getUniqueKey(key) {   
return `${key}_${uniqueToken++}`; 
} 
 
let person = {   
	[getUniqueKey(nameKey)]: 'Matt',   
	[getUniqueKey(ageKey)]: 27,   
	[getUniqueKey(jobKey)]: 'Software engineer' }; 
 
console.log(person);  // { name_0: 'Matt', age_1: 27, job_2: 'Software engineer' } 

注意 可計算屬性表示式中丟擲任何錯誤都會中斷物件建立。如果計算屬性的表示式有副 作用,那就要小心了,因為如果表示式丟擲錯誤,那麼之前完成的計算是不能回滾的

3. 簡寫方法名

let person = {    sayName(name) {     console.log(`My name is ${name}`);   } }; 
 
person.sayName('Matt'); // My name is Matt

簡寫方法名對獲取函式和設定函式也是適用的:

let person = {    
	name_: '',   
	get name() {     
		return this.name_;   
		},   
	set name(name) {     
		this.name_ = name;   
		},   
	sayName() {     
		console.log(`My name is ${this.name_}`);   
		} 
	}; 
person.name = 'Matt'; person.sayName(); // My name is Matt 

簡寫方法名與可計算屬性鍵相互相容:

const methodKey = 'sayName';  
 
let person = {   [methodKey](name) {     console.log(`My name is ${name}`);   } } 
 
person.sayName('Matt'); // My name is Matt 

3.物件解構

使用解構,可以在一個類似物件字面量的結構中,宣告多個變數,同時執行多個賦值操作。如果想 讓變數直接使用屬性的名稱,那麼可以使用簡寫語法,比如:

let person = {    name: 'Matt',   age: 27 }; 
 
let { name, age } = person; 
 
console.log(name);  // Matt console.log(age);   // 27 

解構賦值不一定與物件的屬性匹配。賦值的時候可以忽略某些屬性,而如果引用的屬性不存在,則 該變數的值就是 undefined:

也可以在解構賦值的同時定義預設值,這適用於前面剛提到的引用的屬性不存在於源物件中的 情況:

let person = {   name: 'Matt',    age: 27 }; 
 
let { name, job='Software engineer' } = person; 
 
console.log(name); // Matt console.log(job);  // Software engineer 

解構在內部使用函式 ToObject()(不能在執行時環境中直接訪問)把源資料結構轉換為物件。這 意味著在物件解構的上下文中,原始值會被當成物件。這也意味著(根據 ToObject()的定義),nullundefined 不能被解構,否則會丟擲錯誤。

let { length } = 'foobar';  console.log(length);        // 6 
 
let { constructor: c } = 4; console.log(c === Number);  // true 
 
let { _ } = null;           // TypeError 
 
let { _ } = undefined;      // TypeError 

解構並不要求變數必須在解構表示式中宣告。不過,如果是給事先宣告的變數賦值,則賦值表示式 必須包含在一對括號中

let personName, personAge; 
 
let person = {   name: 'Matt',   age: 27 }; 
 
({name: personName, age: personAge} = person); 
 
console.log(personName, personAge); // Matt, 27 

1. 巢狀解構

解構對於引用巢狀的屬性或賦值目標沒有限制。為此,可以通過解構來複制物件屬性

let person = {    name: 'Matt',   age: 27,   job: {     title: 'Software engineer'   } }; let personCopy = {}; 
 
 
({   name: personCopy.name,   age: personCopy.age,   job: personCopy.job } = person); 
 
// 因為一個物件的引用被賦值給 personCopy,所以修改 // person.job 物件的屬性也會影響 personCopy person.job.title = 'Hacker' 
 
console.log(person); // { name: 'Matt', age: 27, job: { title: 'Hacker' } } 
 
console.log(personCopy); // { name: 'Matt', age: 27, job: { title: 'Hacker' } } 

解構賦值可以使用巢狀結構,以匹配巢狀的屬性

在外層屬性沒有定義的情況下不能使用巢狀解構。無論源物件還是目標物件都一樣:

let person = {    
	job: {     
	title: 'Software engineer'   
	} }; 

let personCopy = {}; 
 
// foo 在源物件上是 undefined 
({   foo: {     bar: personCopy.bar   } } = person); // TypeError: Cannot destructure property 'bar' of 'undefined' or 'null'. 
 
// job 在目標物件上是 undefined 
({   job: {     title: personCopy.job.title   } } = person); // TypeError: Cannot set property 'title' of undefined 

2. 部分解構

需要注意的是,涉及多個屬性的解構賦值是一個輸出無關的順序化操作。如果一個解構表示式涉及 多個賦值,開始的賦值成功而後面的賦值出錯,則整個解構賦值只會完成一部分

let person = {    name: 'Matt',   age: 27 }; 
 
let personName, personBar, personAge; 
 
try {   // person.foo 是 undefined,因此會丟擲錯誤   
({name: personName, foo: { bar: personBar }, age: personAge} = person); 
} 
catch(e) {} 
 
console.log(personName, personBar, personAge); // Matt, undefined, undefined 

3. 引數上下文匹配

在函式引數列表中也可以進行解構賦值。對引數的解構賦值不會影響 arguments 物件,但可以在
函式簽名中宣告在函式體內使用區域性變數:

let person = {   name: 'Matt',    age: 27 }; 
 
function printPerson(foo, {name, age}, bar) {   console.log(arguments);   console.log(name, age); } 
 
function printPerson2(foo, {name: personName, age: personAge}, bar) {   console.log(arguments);   console.log(personName, personAge); } 
 
printPerson('1st', person, '2nd'); // ['1st', { name: 'Matt', age: 27 }, '2nd'] // 'Matt', 27 
 
printPerson2('1st', person, '2nd'); // ['1st', { name: 'Matt', age: 27 }, '2nd'] // 'Matt', 27 
8