紅寶書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()的定義),null 和 undefined 不能被解構,否則會丟擲錯誤。
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