對象的創建
- 使用Object構造函數來創建一個對象
- 使用對象字面量創建一個對象
- 工廠模式創建對象
- 構造函數模式創建對象
- 原型模式創建對象
本身也有缺陷,就是實例共享了引用類型friends,從下面的代碼執行結果可以看到,
兩個實例的friends的值是一樣的,這可能不是我們所期望的。 - 原型模式和構造函數組合使用創建對象
使用最廣泛、認同度最高的創建對象的方法。
- 動態原型模式創建對象
這個模式的好處在於看起來更像傳統的面向對象編程,
具有更好的封裝性,因爲在構造函數裏完成了對原型創建。
這也是一個推薦的創建對象的方法。
對象的擴展
對象(object)是 JavaScript 最重要的數據結構。ES6 對它進行了重大升級,
數據結構本身發生了改變。
1、屬性的簡潔表示法
ES6 允許直接寫入變量和函數,作爲對象的
屬性簡寫
上面代碼表明,ES6 允許在對象之中,直接寫變量。
方法簡寫。
下面是一個實際的例子。
// 屬性簡寫
var a = "first";
var b = {a};
console.log(b) // {a:"first"}
//方法簡寫
var obj = {
method (){
retrun "hello world";
}
}
//等同於
var obj = {
method: function (){
retrun "hello world";
}
}
2、屬性名錶達式
let propName = "value";
let obj = {
[propName]:true,
["one"+"child"]:1323
}
obj[propName] // true
obj[onechild] // 1323
重點內容
① 如果屬性名錶達式不能與簡潔表示法同時使用。否則會報錯
② 屬性名錶達式如果是一個對象,默認情況下會自動將對象轉爲字符串,如果有多個屬性名對象會產生覆蓋。
3、方法的 name 屬性
函數的name屬性,返回函數名。對象方法也是函數,因此也有name屬性。
const person = {
sayName() {
console.log('hello!');
},
};
person.sayName.name // "sayName"
上面代碼中,方法的name屬性返回函數名(即方法名)。
如果對象的方法使用了取值函數(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"
有兩種特殊情況:bind方法創造的函數,name屬性返回bound加上原函數的名字;Function構造函數創造的函數,name屬性返回anonymous。
4、對象屬性的可枚舉性和遍歷
可枚舉性
Object.getOwnPropertyDescriptor方法可以獲取該屬性的描述對象。
let obj = { foo: 123 };
Object.getOwnPropertyDescriptor(obj, 'foo')
// {
// value: 123,
// writable: true,
// enumerable: true,
// configurable: true
// }
描述對象的enumerable屬性,稱爲“可枚舉性”,如果該屬性爲false,就表示某些操作會忽略當前屬性。
目前,有四個操作會忽略enumerable爲false的屬性。
for…in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
Object.keys():返回對象自身的所有可枚舉的屬性的鍵名。
JSON.stringify():只串行化對象自身的可枚舉的屬性。
Object.assign(): 忽略enumerable爲false的屬性,只拷貝對象自身的可枚舉的屬性。
這四個操作之中,前三個是 ES5 就有的,最後一個Object.assign()是 ES6 新增的。
其中,只有for…in會返回繼承的屬性,其他三個方法都會忽略繼承的屬性,只處理對象自身的屬性。
實際上,引入“可枚舉”(enumerable)這個概念的最初目的,就是讓某些屬性可以規避掉for…in操作,
不然所有內部屬性和方法都會被遍歷到。
比如,對象原型的toString方法,以及數組的length屬性,就通過“可枚舉性”,從而避免被for…in遍歷到。
Object.getOwnPropertyDescriptor(Object.prototype, 'toString').enumerable
// false
Object.getOwnPropertyDescriptor([], 'length').enumerable
// false
因此for…in不會遍歷到這兩個繼承自原型的屬性。
另外,ES6 規定,所有 Class 的原型的方法都是不可枚舉的。
Object.getOwnPropertyDescriptor(class {foo() {}}.prototype, 'foo').enumerable//false
大多數時候,我們只關心對象自身的屬性。所以,儘量不要用for…in循環,而用Object.keys()代替。
屬性的遍歷
ES6 一共有 5 種方法可以遍歷對象的屬性。
(1)for…in:循環遍歷對象自身的和繼承的可枚舉屬性(不含 Symbol 屬性)。
(2)Object.keys(obj):返回一個數組,包括對象自身的(不含繼承的)所有可枚舉屬性(不含 Symbol 屬性)的鍵名。
附:Object.values(obj)(這個是遍歷對象的值)
參數:obj:要返回其枚舉自身屬性的對象
(3)Object.getOwnPropertyNames(obj):返回一個數組,包含對象自身的所有屬性(包含不可枚舉屬性,不含 Symbol 屬性)的鍵名。
返回一個數組,包含對象自身的所有屬性
遍歷可以獲取key和value
使用Object.keys方法將對象的鍵名生成一個數組,然後遍歷這個數組
(4)Object.getOwnPropertySymbols(obj):返回一個數組,包含對象自身的所有 Symbol 屬性的鍵名。
使用 Generator 函數將對象重新包裝一下
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
for (let [key, value] of entries(obj)) {
console.log(key, '->', value);
}
// a -> 1
// b -> 2
// c -> 3
(5)Reflect.ownKeys(obj)返回一個數組,包含對象自身的所有鍵名,不管鍵名是 Symbol 或字符串,也不管是否可枚舉。
上面代碼中,Reflect.ownKeys方法返回一個數組,包含了參數對象的所有屬性。
這個數組的屬性次序是這樣的,
首先是數值屬性2和10,其次是字符串屬性b和a,最後是 Symbol 屬性。
以上的 5 種方法遍歷對象的鍵名,都遵守同樣的屬性遍歷的次序規則。
首先遍歷所有數值鍵,按照數值升序排列。
其次遍歷所有字符串鍵,按照加入時間升序排列。
最後遍歷所有 Symbol 鍵,按照加入時間升序排列。
5、super 關鍵字
我們知道,this關鍵字總是指向函數所在的當前對象,
ES6 類似的關鍵字super,指向當前對象的原型對象。
const proto = {
foo: 'hello'
};
const obj = {
foo: 'world',
find() {
return super.foo;
}
};
Object.setPrototypeOf(obj, proto);
obj.find() // "hello"
上面代碼中,對象obj.find()方法之中,通過super.foo引用了原型對象proto的foo屬性。
注意,super關鍵字表示原型對象時,只能用在對象的方法之中,用在其他地方都會報錯。
// 報錯
const obj = {
foo: super.foo
}
// 報錯
const obj = {
foo: () => super.foo
}
// 報錯
const obj = {
foo: function () {
return super.foo
}
}
上面三種super的用法都會報錯,因爲對於 js 引擎來說,這裏的super都沒有用在對象的方法之中。
第一種寫法是super用在屬性裏面,
第二種和第三種寫法是super用在一個函數裏面,然後賦值給foo屬性。
目前,只有對象方法的簡寫法可以讓 JavaScript 引擎確認,定義的是對象的方法。
JavaScript 引擎內部,super.foo等同於Object.getPrototypeOf(this).foo(屬性)或Object.getPrototypeOf(this).foo.call(this)(方法)。
const proto = {
x: 'hello',
foo() {
console.log(this.x);
},
};
const obj = {
x: 'world',
foo() {
super.foo();
}
}
Object.setPrototypeOf(obj, proto);
obj.foo() // "world"
上面代碼中,super.foo指向原型對象proto的foo方法,但是綁定的this卻還是當前對象obj,因此輸出的就是world。
** 6. 對象的擴展運算符**
解構賦值
對象的解構賦值用於從一個對象取值,相當於將目標對象自身的所有可遍歷的可枚舉的,
尚未被讀取的屬性,分配到指定的對象上面。所有的鍵和它們的值,都會拷貝到新對象上面。
let { x, y, ...z } = { x: 1, y: 2, a: 3, b: 4 };
x // 1
y // 2
z // { a: 3, b: 4 }
上面代碼中,變量z是解構賦值所在的對象。它獲取等號右邊的所有尚未讀取的鍵(a和b),將它們連同值一起拷貝過來。
由於解構賦值要求等號右邊是一個對象,所以如果等號右邊是undefined或null,就會報錯,因爲它們無法轉爲對象。解構賦值必須是最後一個參數,否則也會報錯。
let { ...z } = null; // 運行時錯誤
let { ...z } = undefined; // 運行時錯誤
let { ...x, y, z } = someObject; // 句法錯誤
let { x, ...y, ...z } = someObject; // 句法錯誤
注意,解構賦值的拷貝是淺拷貝,即如果一個鍵的值是複合類型的值(數組、對象、函數)、
那麼解構賦值拷貝的是這個值的引用,而不是這個值的副本。
上面代碼中,x是解構賦值所在的對象,拷貝了對象obj的a屬性。
a屬性引用了一個對象,修改這個對象的值,會影響到解構賦值對它的引用。
另外,擴展運算符的解構賦值,不能複製繼承自原型對象的屬性。
上面代碼中,對象o3複製了o2,但是隻複製了o2自身的屬性,沒有複製它的原型對象o1的屬性。
下面是另一個例子。
const o = Object.create({ x: 1, y: 2 });
o.z = 3;
let { x, ...newObj } = o;
let { y, z } = newObj;
x // 1
y // undefined
z // 3
上面代碼中,變量x是單純的解構賦值,所以可以讀取對象o繼承的屬性;變量y和z是擴展運算符的解構賦值,
只能讀取對象o自身的屬性,所以變量z可以賦值成功,變量y取不到值。
ES6 規定,變量聲明語句之中,如果使用解構賦值,擴展運算符後面必須是一個變量名,而不能是一個解構賦值表達式,所以上面代碼引入了中間變量newObj
解構賦值的一個用處,是擴展某個函數的參數,引入其他操作。
function baseFunction({ a, b }) {
// ...
}
function wrapperFunction({ x, y, ...restConfig }) {
// 使用 x 和 y 參數進行操作
// 其餘參數傳給原始函數
return baseFunction(restConfig);
}
上面代碼中,原始函數baseFunction接受a和b作爲參數,函數wrapperFunction在baseFunction的基礎上進行了擴展,能夠接受多餘的參數,並且保留原始函數的行爲。
如果擴展運算符後面是一個空對象,則沒有任何效果。
{...{}, a: 1}
// { a: 1 }
如果擴展運算符後面不是對象,則會自動將其轉爲對象。
// 等同於 {...Object(1)}
{...1} // {}
上面代碼中,擴展運算符後面是整數1,會自動轉爲數值的包裝對象Number{1}。
由於該對象沒有自身屬性,所以返回一個空對象。
下面的例子都是類似的道理。
// 等同於 {...Object(true)}
{...true} // {}
// 等同於 {...Object(undefined)}
{...undefined} // {}
// 等同於 {...Object(null)}
{...null} // {}
但是,如果擴展運算符後面是字符串,它會自動轉成一個類似數組的對象,返回的不是空對象。
{...'hello'}
// {0: "h", 1: "e", 2: "l", 3: "l", 4: "o"}
對象的擴展運算符等同於使用Object.assign()方法。
let new = { ...a };
// 等同於
let new = Object.assign({}, a);
上面的例子只是拷貝了對象實例的屬性,
如果想完整克隆一個對象,還拷貝對象原型的屬性(深拷貝),可以採用下面的寫法。
//拷貝寫法一
const clone1 = {
__ proto__: Object.getPrototypeOf(obj),
…obj
};
// 拷貝寫法二
const clone2 = Object.assign(
Object.create(Object.getPrototypeOf(obj)),
obj
);
// 拷貝寫法三
const clone3 = Object.create(
Object.getPrototypeOf(obj),
Object.getOwnPropertyDescriptors(obj)
)
上面代碼中,寫法一的__proto__屬性在非瀏覽器的環境不一定部署,因此推薦使用寫法二和寫法三。
擴展運算符可以用於合併兩個對象。
let ab = { …a, …b };
// 等同於
let ab = Object.assign({}, a, b);
如果用戶自定義的屬性,放在擴展運算符後面,則擴展運算符內部的同名屬性會被覆蓋掉。
let aWithOverrides = { ...a, x: 1, y: 2 };
// 等同於
let aWithOverrides = { ...a, ...{ x: 1, y: 2 } };
// 等同於
let x = 1, y = 2, aWithOverrides = { ...a, x, y };
// 等同於
let aWithOverrides = Object.assign({}, a, { x: 1, y: 2 });
上面代碼中,a對象的x屬性和y屬性,拷貝到新對象後會被覆蓋掉。
這用來修改現有對象部分的屬性就很方便了。
let newVersion = {
...previousVersion,
name: 'New Name' // Override the name property
};
上面代碼中,newVersion對象自定義了name屬性,其他屬性全部複製自previousVersion對象。
如果把自定義屬性放在擴展運算符前面,就變成了設置新對象的默認屬性值。
let aWithDefaults = { x: 1, y: 2, ...a };
// 等同於
let aWithDefaults = Object.assign({}, { x: 1, y: 2 }, a);
// 等同於
let aWithDefaults = Object.assign({ x: 1, y: 2 }, a);
與數組的擴展運算符一樣,對象的擴展運算符後面可以跟表達式。
const obj = {
...(x > 1 ? {a: 1} : {}),
b: 2,
}
ES5 比較兩個值是否相等,只有兩個運算符:相等運算符()和嚴格相等運算符(=)。它們都有缺點,前者會自動轉換數據類型,後者的NaN不等於自身,以及+0等於-0。
ES6 提出“Same-value equality”(同值相等)算法,Object.is就是部署這個算法用來解決這個問題。。
它用來比較兩個值是否嚴格相等,與嚴格比較運算符(===)的行爲基本一致。
不同之處只有兩個:一是+0不等於-0,二是NaN等於自身。
Object.assign()
基本用法
Object.assign方法用於對象的合併,將源對象的所有可枚舉屬性,複製到目標對象(target)。
const target = { a: 1 };
const source1 = { b: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
Object.assign方法的第一個參數是目標對象,後面的參數都是源對象。
注意,如果目標對象與源對象有同名屬性,或多個源對象有同名屬性,則後面的屬性會覆蓋前面的屬性。
const target = { a: 1, b: 1 };
const source1 = { b: 2, c: 2 };
const source2 = { c: 3 };
Object.assign(target, source1, source2);
target // {a:1, b:2, c:3}
如果只有一個參數,Object.assign會直接返回該參數。
const obj = {a: 1};
Object.assign(obj) === obj // true
如果該參數不是對象,則會先轉成對象,然後返回。
typeof Object.assign(2) // "object"
由於undefined和null無法轉成對象,所以如果它們作爲參數,就會報錯。
Object.assign(undefined) // 報錯
Object.assign(null) // 報錯
如果非對象參數出現在源對象的位置(即非首參數),那麼處理規則有所不同。首先,這些參數都會轉成對象,如果無法轉成對象,就會跳過。
這意味着,如果undefined和null不在首參數,就不會報錯。
let obj = {a: 1};
Object.assign(obj, undefined) === obj // true
Object.assign(obj, null) === obj // true
其他類型的值(即數值、字符串和布爾值)不在首參數,也不會報錯。但是,除了字符串會以數組形式,拷貝入目標對象,其他值都不會產生效果,因爲只有字符串的包裝對象,會產生可枚舉屬性。
Object(true) // {[[PrimitiveValue]]: true}
Object(10) // {[[PrimitiveValue]]: 10}
Object('abc') // {0: "a", 1: "b", 2: "c", length: 3, [[PrimitiveValue]]: "abc"}
上面代碼中,布爾值、數值、字符串分別轉成對應的包裝對象,可以看到它們的原始值都在包裝對象的內部屬性[[PrimitiveValue]]上面,這個屬性是不會被Object.assign拷貝的。
只有字符串的包裝對象,會產生可枚舉的實義屬性,那些屬性則會被拷貝。
Object.assign拷貝的屬性是有限制的,只拷貝源對象的自身屬性(不拷貝繼承屬性),也不拷貝不可枚舉的屬性(enumerable: false)
Object.assign({ a: 'b' }, { [Symbol('c')]: 'd' })
// { a: 'b', Symbol(c): 'd' }
(1)淺拷貝
Object.assign方法實行的是淺拷貝,而不是深拷貝。也就是說,如果源對象某個屬性的值是對象,那麼目標對象拷貝得到的是這個對象的引用。
源對象obj1的a屬性的值是一個對象,Object.assign拷貝得到的是這個對象的引用。
這個對象的任何變化,都會反映到目標對象上面。
(2)同名屬性的替換
對於這種嵌套的對象,一旦遇到同名屬性,Object.assign的處理方法是替換,而不是添加。
const target = { a: { b: 'c', d: 'e' } }
const source = { a: { b: 'hello' } }
Object.assign(target, source)
// { a: { b: 'hello' } }
(3)數組的處理
Object.assign可以用來處理數組,但是會把數組視爲對象。
上面代碼中,Object.assign把數組視爲屬性名爲 0、1、2 的對象,
因此源數組的 0 號屬性4覆蓋了目標數組的 0 號屬性1。
(4)取值函數的處理
Object.assign只能進行值的複製,如果要複製的值是一個取值函數,那麼將求值後再複製。
const source = {
get foo() { return 1 }
};
const target = {};
Object.assign(target, source)
// { foo: 1 }
上面代碼中,source對象的foo屬性是一個取值函數,Object.assign不會複製這個取值函數,只會拿到值以後,將這個值複製過去。
常見用途
Object.assign方法有很多用處。
(1)爲對象添加屬性,通過Object.assign方法,將x屬性和y屬性添加到Point類的對象實例。
class Point {
constructor(x, y) {
Object.assign(this, {x, y});
}
}
(2)給對象添加方法
使用了對象屬性的簡潔表示法,直接將兩個函數放在大括號中,再使用assign方法添加到SomeClass.prototype之中。
Object.assign(SomeClass.prototype, {
someMethod(arg1, arg2) {
···
},
anotherMethod() {
···
}
});
// 等同於下面的寫法
SomeClass.prototype.someMethod = function (arg1, arg2) {
···
};
SomeClass.prototype.anotherMethod = function () {
···
};
(3)克隆對象
function clone(origin) {
return Object.assign({}, origin);
}
上面代碼將原始對象拷貝到一個空對象,就得到了原始對象的克隆。
這種方法克隆,只能克隆原始對象自身的值,不能克隆它繼承的值。如果想要保持繼承鏈,可以採用下面的代碼。
function clone(origin) {
let originProto = Object.getPrototypeOf(origin);
return Object.assign(Object.create(originProto), origin);
}
(4)合併多個對象
將多個對象合併到某個對象。
const merge =
(target, ...sources) => Object.assign(target, ...sources);
如果希望合併後返回一個新對象,可以改寫上面函數,對一個空對象合併。
const merge =
(...sources) => Object.assign({}, ...sources);
(5)爲屬性指定默認值
Object.getOwnPropertyDescriptors()
ES5 的Object.getOwnPropertyDescriptor()方法會返回某個對象屬性的描述對象(descriptor)。
ES6 引入了Object.getOwnPropertyDescriptors()方法,返回指定對象所有自身屬性(非繼承屬性)的描述對象。
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()方法返回一個對象,所有原對象的屬性名都是該對象的屬性名,對應的屬性值就是該屬性的描述對象。
該方法的實現非常容易。
function getOwnPropertyDescriptors(obj) {
const result = {};
for (let key of Reflect.ownKeys(obj)) {
result[key] = Object.getOwnPropertyDescriptor(obj, key);
}
return result;
}
該方法的引入目的,主要是爲了解決Object.assign()無法正確拷貝get屬性和set屬性的問題。
上面代碼中,source對象的foo屬性的值是一個賦值函數,Object.assign方法將這個屬性拷貝給target1對象,結果該屬性的值變成了undefined。
這是因爲Object.assign方法總是拷貝一個屬性的值,而不會拷貝它背後的賦值方法或取值方法。
這時,Object.getOwnPropertyDescriptors()方法配合Object.defineProperties()方法,就可以實現正確拷貝。
__proto__屬性,Object.setPrototypeOf(),Object.getPrototypeOf()
JavaScript 語言的對象繼承是通過原型鏈實現的。ES6 提供了更多原型對象的操作方法。
__proto__屬性
用來讀取或設置當前對象的prototype對象。目前,所有瀏覽器(包括 IE11)都部署了這個屬性。
// es5 的寫法
const obj = {
method: function() { ... }
};
obj.__proto__ = someOtherObj;
// es6 的寫法
var obj = Object.create(someOtherObj);
obj.method = function() { ... };
該屬性沒有寫入 ES6 的正文,而是寫入了附錄,原因是__proto__前後的雙下劃線,說明它本質上是一個內部屬性,而不是一個正式的對外的 API,所以不要使用這個屬性,而是使用下面的Object.setPrototypeOf()(寫操作)、Object.getPrototypeOf()(讀操作)、Object.create()(生成操作)代替。
實現上,__proto__調用的是Object.prototype.proto,具體實現如下。
Object.defineProperty(Object.prototype, '__proto__', {
get() {
let _thisObj = Object(this);
return Object.getPrototypeOf(_thisObj);
},
set(proto) {
if (this === undefined || this === null) {
throw new TypeError();
}
if (!isObject(this)) {
return undefined;
}
if (!isObject(proto)) {
return undefined;
}
let status = Reflect.setPrototypeOf(this, proto);
if (!status) {
throw new TypeError();
}
},
});
function isObject(value) {
return Object(value) === value;
}
如果一個對象本身部署了__proto__屬性,該屬性的值就是對象的原型。
Object.getPrototypeOf({ __proto__: null })
// null
Object.setPrototypeOf()
Object.setPrototypeOf方法的作用與__proto__相同,用來設置一個對象的prototype對象,返回參數對象本身。
它是 ES6 正式推薦的設置原型對象的方法。
// 格式
Object.setPrototypeOf(object, prototype)
// 用法
const o = Object.setPrototypeOf({}, null);
該方法等同於下面的函數。
function setPrototypeOf(obj, proto) {
obj.__proto__ = proto;
return obj;
}
下面是一個例子。
let proto = {};
let obj = { x: 10 };
Object.setPrototypeOf(obj, proto);
proto.y = 20;
proto.z = 40;
obj.x // 10
obj.y // 20
obj.z // 40
上面代碼將proto對象設爲obj對象的原型,所以從obj對象可以讀取proto對象的屬性。
如果第一個參數不是對象,會自動轉爲對象。但是由於返回的還是第一個參數,所以這個操作不會產生任何效果。
Object.setPrototypeOf(1, {}) === 1 // true
Object.setPrototypeOf('foo', {}) === 'foo' // true
Object.setPrototypeOf(true, {}) === true // true
由於undefined和null無法轉爲對象,所以如果第一個參數是undefined或null,就會報錯。
Object.setPrototypeOf(undefined, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.setPrototypeOf(null, {})
// TypeError: Object.setPrototypeOf called on null or undefined
Object.getPrototypeOf()
該方法與Object.setPrototypeOf方法配套,用於讀取一個對象的原型對象。
Object.getPrototypeOf(obj);
下面是一個例子。
function Rectangle() {
// ...
}
const rec = new Rectangle();
Object.getPrototypeOf(rec) === Rectangle.prototype
// true
Object.setPrototypeOf(rec, Object.prototype);
Object.getPrototypeOf(rec) === Rectangle.prototype
// false
如果參數不是對象,會被自動轉爲對象。
// 等同於 Object.getPrototypeOf(Number(1))
Object.getPrototypeOf(1)
// Number {[[PrimitiveValue]]: 0}
// 等同於 Object.getPrototypeOf(String('foo'))
Object.getPrototypeOf('foo')
// String {length: 0, [[PrimitiveValue]]: ""}
// 等同於 Object.getPrototypeOf(Boolean(true))
Object.getPrototypeOf(true)
// Boolean {[[PrimitiveValue]]: false}
Object.getPrototypeOf(1) === Number.prototype // true
Object.getPrototypeOf('foo') === String.prototype // true
Object.getPrototypeOf(true) === Boolean.prototype // true
如果參數是undefined或null,它們無法轉爲對象,所以會報錯。
Object.getPrototypeOf(null)
// TypeError: Cannot convert undefined or null to object
Object.getPrototypeOf(undefined)
// TypeError: Cannot convert undefined or null to object
Object.keys(),Object.values(),Object.entries()
Object.keys()
ES5 引入了Object.keys方法,返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵名。
var obj = { foo: ‘bar’, baz: 42 };
Object.keys(obj)
// [“foo”, “baz”]
ES2017 引入了跟Object.keys配套的Object.values和Object.entries,作爲遍歷一個對象的補充手段,供for…of循環使用。
Object.values()返回對象自身的可遍歷屬性。
const obj = Object.create({}, {p: {value: 42}});
Object.values(obj) // []
上面代碼中,Object.create方法的第二個參數添加的對象屬性(屬性p),如果不顯式聲明,默認是不可遍歷的,因爲p的屬性描述對象的enumerable默認是false,Object.values不會返回這個屬性。只要把enumerable改成true,Object.values就會返回屬性p的值。
const obj = Object.create({}, {p:
{
value: 42,
enumerable: true
}
});
Object.values(obj) // [42]
Object.values會過濾屬性名爲 Symbol 值的屬性。
Object.values({ [Symbol()]: 123, foo: 'abc' });
// ['abc']
如果Object.values方法的參數是一個字符串,會返回各個字符組成的一個數組。
Object.values('foo')
// ['f', 'o', 'o']
上面代碼中,字符串會先轉成一個類似數組的對象。字符串的每個字符,就是該對象的一個屬性。因此,Object.values返回每個屬性的鍵值,就是各個字符組成的一個數組。
如果參數不是對象,Object.values會先將其轉爲對象。由於數值和布爾值的包裝對象,都不會爲實例添加非繼承的屬性。所以,Object.values會返回空數組。
Object.values(42) // []
Object.values(true) // []
Object.entries()
Object.entries()方法返回一個數組,成員是參數對象自身的(不含繼承的)所有可遍歷(enumerable)屬性的鍵值對數組。
Object.entries的基本用途是遍歷對象的屬性。
et obj = { one: 1, two: 2 };
for (let [k, v] of Object.entries(obj)) {
console.log(
`${JSON.stringify(k)}: ${JSON.stringify(v)}`
);
}
// "one": 1
// "two": 2
Object.entries方法的另一個用處是,將對象轉爲真正的Map結構。
const obj = { foo: 'bar', baz: 42 };
const map = new Map(Object.entries(obj));
map // Map { foo: "bar", baz: 42 }
自己實現Object.entries方法,非常簡單。
// Generator函數的版本
function* entries(obj) {
for (let key of Object.keys(obj)) {
yield [key, obj[key]];
}
}
// 非Generator函數的版本
function entries(obj) {
let arr = [];
for (let key of Object.keys(obj)) {
arr.push([key, obj[key]]);
}
return arr;
}
Object.fromEntries()
Object.fromEntries()方法是Object.entries()的逆操作,用於將一個鍵值對數組轉爲對象。
Object.fromEntries([
['foo', 'bar'],
['baz', 42]
])
// { foo: "bar", baz: 42 }
該方法的主要目的,是將鍵值對的數據結構還原爲對象,因此特別適合將 Map 結構轉爲對象。
// 例一
const entries = new Map([
['foo', 'bar'],
['baz', 42]
]);
Object.fromEntries(entries)
// { foo: "bar", baz: 42 }
// 例二
const map = new Map().set('foo', true).set('bar', false);
Object.fromEntries(map)
// { foo: true, bar: false }
該方法的一個用處是配合URLSearchParams對象,將查詢字符串轉爲對象。
Object.fromEntries(new URLSearchParams('foo=bar&baz=qux'))
// { foo: "bar", baz: "qux" }