一、對象的擴展
1.屬性的簡潔表示法
ES6 允許直接寫入變量和函數,作爲對象的屬性和方法。這樣的書寫更加簡潔。
const foo = 'bar';
const baz = {foo};
baz // {foo: "bar"}
// 等同於
const baz = {foo: foo};
除了屬性簡寫,方法也可以簡寫。
const o = {
method() {
return "Hello!";
}
};
// 等同於
const o = {
method: function() {
return "Hello!";
}
};
屬性的賦值器(setter)和取值器(getter),事實上也是採用這種寫法。
const cart = {
_wheels: 4,
get wheels () {
return this._wheels;
},
set wheels (value) {
if (value < this._wheels) {
throw new Error('數值太小了!');
}
this._wheels = value;
}
}
2.屬性名表達式
JavaScript 定義對象的屬性,有兩種方法。
// 方法一
obj.foo = true;
// 方法二
obj['a' + 'bc'] = 123;
上面代碼的方法一是直接用標識符作爲屬性名,方法二是用表達式作爲屬性名,這時要將表達式放在方括號之內。
ES6 允許字面量定義對象時,用方法二(表達式)作爲對象的屬性名,即把表達式放在方括號內。
let propKey = 'foo';
let obj = {
[propKey]: true,
['a' + 'bc']: 123
};
3.方法的name屬性
函數的name
屬性,返回函數名。對象方法也是函數,因此也有name
屬性。
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
如果對象的方法使用了取值函數(getter)和存值函數(setter),則name屬性不是在該方法上面,而是該方法的屬性的描述對象的get和set屬性上面,返回值是方法名前加上get和set。
const obj = {
get foo() {},
set foo(x) {}
};
obj.foo.name
// TypeError: Cannot read property 'name' of undefined
const descriptor = Object.getOwnPropertyDescriptor(obj, 'foo');
descriptor.get.name // "get foo"
descriptor.set.name // "set foo"
如果對象的方法是一個 Symbol 值,那麼name屬性返回的是這個 Symbol 值的描述。
const key1 = Symbol('description');
const key2 = Symbol();
let obj = {
[key1]() {},
[key2]() {},
};
obj[key1].name // "[description]"
obj[key2].name // ""
4.Object.is()
ES6 提出“Same-value equality”(同值相等)算法,用來解決這個問題。Object.is
就是部署這個算法的新方法。它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
ES6 提出“Same-value equality”(同值相等)算法,用來解決這個問題。Object.is就是部署這個算法的新方法。它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
Object.is('foo', 'foo')
// true
Object.is({}, {})
5.Object.assign()
Object.assign
方法用於對象的合併,將源對象(source)的所有可枚舉屬性,複製到目標對象(target)。
Object.assign
方法的第一個參數是目標對象,後面的參數都是源對象。
注意:
- 如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。
Object.assign
方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那麼目標對象拷貝得到的是這個對象的引用。- 對於這種嵌套的對象,一旦遇到同名屬性,
Object.assign
的處理方法是替換,而不是添加。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
Object.assign可以用來處理數組,但是會把數組視爲對象。
Object.assign([1, 2, 3], [4, 5])
Object.assign只能進行值的複製,如果要複製的值是一個取值函數,那麼將求值後再複製。
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
常見用途
Object.assign
方法有很多用處。
(1)爲對象添加屬性
(2)爲對象添加方法
(3)克隆對象
(4)合併多個對象
(5)爲屬性指定默認值
6.屬性的可枚舉性和遍歷
對象的每個屬性都有一個描述對象(Descriptor),用來控制該屬性的行爲。Object.getOwnPropertyDescriptor
方法可以獲取該屬性的描述對象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
ES6 一共有 5 種方法可以遍歷對象的屬性。
(1)for...in
for...in
循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
(2)Object.keys(obj)
Object.keys
返回一個數組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。
(3)Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames
返回一個數組,包含對象自身的所有屬性(不含 Symbol 屬性,但是包括不可枚舉屬性)的鍵名。
(4)Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols
返回一個數組,包含對象自身的所有 Symbol 屬性的鍵名。
(5)Reflect.ownKeys(obj)
Reflect.ownKeys
返回一個數組,包含對象自身的所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。
以上的 5 種方法遍歷對象的鍵名,都遵守同樣的屬性遍歷的次序規則。
- 首先遍歷所有數值鍵,按照數值升序排列。
- 其次遍歷所有字符串鍵,按照加入時間升序排列。
- 最後遍歷所有 Symbol 鍵,按照加入時間升序排列。
7.Object.getOwnPropertyDescriptors()
Object.getOwnPropertyDescriptor
方法會返回某個對象屬性的描述對象(descriptor)。
const obj = {
foo: 123,
get bar() { return 'abc' }
};
Object.getOwnPropertyDescriptors(obj)
// { foo:
// { value: 123,
// writable: true,
// enumerable: true,
// configurable: true },
// bar:
// { get: [Function: get bar],
// set: undefined,
// enumerable: true,
// configurable: true } }
Object.getOwnPropertyDescriptors方法的另一個用處,是配合Object.create方法,將對象屬性克隆到一個新對象。這屬於淺拷貝。
onst clone = Object.create(Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj));
// 或者
const shallowClone = (obj) => Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
);
另外,Object.getOwnPropertyDescriptors方法可以實現一個對象繼承另一個對象。以前,繼承另一個對象,常常寫成下面這樣。
const obj = {
__proto__: prot,
foo: 123,
};
8.__proto__屬性,Object.setPrototypeOf(),Object.getPrototypeOf()
__proto__屬性
__proto__
屬性(前後各兩個下劃線),用來讀取或設置當前對象的prototype
對象。目前,所有瀏覽器(包括 IE11)都部署了這個屬性。
// es5 的寫法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
// es6 的寫法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是__proto__
前後的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API,只是由於瀏覽器廣泛支持,才被加入了 ES6。標準明確規定,只有瀏覽器必須部署這個屬性,其他運行環境不一定需要部署,而且新的代碼最好認爲這個屬性是不存在的。因此,無論從語義的角度,還是從兼容性的角度,都不要使用這個屬性,而是使用下面的Object.setPrototypeOf()
(寫操作)、Object.getPrototypeOf()
(讀操作)、Object.create()
(生成操作)代替。
Object.setPrototypeOf()
Object.setPrototypeOf
方法的作用與__proto__
相同,用來設置一個對象的prototype
對象,返回參數對象本身。它是 ES6 正式推薦的設置原型對象的方法。如果第一個參數不是對象,會自動轉爲對象。但是由於返回的還是第一個參數,所以這個操作不會產生任何效果。第一個參數不能是undefined 和 null
Object.getPrototypeOf()
該方法與Object.setPrototypeOf
方法配套,用於讀取一個對象的原型對象
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
該方法等同於下面的函數。
function (obj, proto) {
obj.__proto__ = proto;
return obj;
}
9.super關鍵字
ES6 又新增了另一個類似的關鍵字super
,指向當前對象的原型對象。
注意,super
關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。
10.Object.keys(),Object.values(),Object.entries()
Object.keys
方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。
Object.values
方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值。
Object.entries
方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對數組。
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
Object.values會過濾屬性名爲 Symbol 值的屬性。
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']
const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
Object.entries方法的另一個用處是,將對象轉爲真正的Map結構。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
11.對象的擴展運算符
解構賦值:對象的解構賦值用於從一個對象取值,相當於將目標對象自身的所有可遍歷的(enumerable)、但尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
對象的擴展運算符(...)用於取出參數對象的所有可遍歷屬性,拷貝到當前對象之中。
let z = { a: 3, b: 4 };
let n = { ...z };
n // { a: 3, b: 4 }
擴展運算符的參數對象之中,如果有取值函數get
,這個函數是會執行的。
// 並不會拋出錯誤,因爲 x 屬性只是被定義,但沒執行
let aWithXGetter = {
...a,
get x() {
throw new Error('not throw yet');
}
};
// 會拋出錯誤,因爲 x 屬性被執行了
let runtimeError = {
...a,
...{
get x() {
throw new Error('throw now');
}
}
};