很多時候我們需要復(fù)制目標(biāo)對象而非借助原型鏈訪問,比如對象拷貝掰读、各類繼承方法汪诉,這里總結(jié)下Js的屬性訪問方法以及注意事項
可以根據(jù)是否在原型鏈上與可枚舉來區(qū)分:
獲取對象直接包含的屬性的方法:
Object.keys(obj) //返回可枚舉屬性 字符串?dāng)?shù)組
Object.entries(obj) //返回可以枚舉屬性 鍵值對數(shù)組
Object.getOwnPropertyNames(obj)
Object.getOwnPropertySymbols(obj)
Object.getOwnPropertyDescriptors(obj)
Object.getOwnPropertyDescriptor(obj,prop)
不僅返回自身屬性,還能訪問原型鏈上屬性的只有一個方法(語句)
for..in //遍歷對象可枚舉屬性列表
需要注意這些方法的返回值:Object.entries(...)
不僅返回屬性還返回值括儒,組成鍵值對茧痒,Object.getOwnPropertyDescriptor(obj,prop)
需要對象以及具體的屬性值肮韧,返回整個property descriptor對象,Object.getOwnPropertyDescriptors(...)
返回一個property descriptor對象數(shù)組旺订。
比較符合使用習(xí)慣的是
Object.keys(...)
與Object.getOwnPropertyNames(...)
弄企,通過返回的代表屬性的字符串來進行某種操作。
涉及到具體的描述区拳,比如訪問器屬性拘领,就需要
Object.getOwnPropertyDescriptors(...)
這類方法
除去訪問方法,另外還有對應(yīng)的檢測方法(運算符)樱调,檢測存在性约素,均返回布爾值:
in
obj.hasOwnProperty(prop) //Object?.prototype?.has?OwnProperty(...)
obj.propertyIsEnumerable(prop) //Object?.prototype?.property?IsEnumerable(...)
我們可以對比這些方法來記憶:
for..in
與 obj.propertyIsEnumerable(prop)
、Object.keys(obj)
: 針對可枚舉屬性笆凌,前者查找原型鏈
in
與 obj.hasOwnProperty(prop)
:針對所有屬性(包括Symbol)圣猎,前者查找原型鏈
obj.hasOwnProperty(prop)
與 Object.getOwnPropertyNames(obj)
:針對自身屬性,前者可用于屬性值為Symbol的情況乞而,而后者需要同類方法Object.getOwnPropertySymbols(obj)
在用這些方法進行訪問送悔、取值之前,還有兩個重要的方法需要介紹:
Object.assign(target, ...sources)
Object.create(proto, [propertiesObject])
兩個方法都創(chuàng)建了對象:assign將可枚舉屬性的值復(fù)制到target爪模,繼承屬性和不可枚舉屬性是不能拷貝的欠啤,source為多個對象時,相同屬性會被后續(xù)對象合并屋灌;create創(chuàng)建指定原型鏈的對象洁段,第二個參數(shù)指定可枚舉屬性。
可以開始操作了:
function MyClass() {
SuperClass.call(this);
OtherSuperClass.call(this);
}
// 繼承一個類
MyClass.prototype = Object.create(SuperClass.prototype);
// 混合其它
Object.assign(MyClass.prototype, OtherSuperClass.prototype);
// 重新指定constructor
MyClass.prototype.constructor = MyClass;
MyClass.prototype.myMethod = function() {
// do a thing
};
上面是MDN里關(guān)于混入的例子共郭,實際上拷貝或者繼承的用法核心便是如此祠丝,借用或者拷貝屬性,具體一點可以是這樣:
function copy(target, source, overlay) {
for (var key in source) {
if (source.hasOwnProperty(key)
&& (overlay ? source[key] != null : target[key] == null)
) {
target[key] = source[key];
}
}
return target;
}
function mixin(target, source) {
for (var key in source) {
if (!(var key in target)) {
target[key] = source[key];
}
}
return target;
}
通過for.. in語句 獲得可枚舉屬性落塑,并篩選出直接屬性纽疟,當(dāng)然透過target[key] = source[key];
也清楚這和Object.assign()
一樣只能淺拷貝罐韩。
或者更具體的繼承用法:
function inherits(clazz, baseClazz) {
var clazzPrototype = clazz.prototype;
function F() {}
F.prototype = baseClazz.prototype;
clazz.prototype = new F();
// clazz.prototype = Object.create(baseClazz.prototype)
for (var prop in clazzPrototype) {
clazz.prototype[prop] = clazzPrototype[prop];
}
clazz.prototype.constructor = clazz;
clazz.superClass = baseClazz;
}
是否把基類原型鏈上的方法拷貝過來憾赁、是否覆蓋、是否只往上追溯一層原型鏈這些都視具體的應(yīng)用場景而定散吵。inherits會傾向于繼承關(guān)系(保持原型鏈的聯(lián)系)龙考,copy用于混入某些屬性(組合)蟆肆。
接下來總結(jié)一些注意事項
參考MDN上的分類,有這些容易忽略的情況:屬性是否為訪問描述符晦款,原始類型包裝炎功,原生方法覆蓋,以及異常處理是否中斷執(zhí)行缓溅。
1.Object.assign()
使用了方法使用源對象的[[Get]]和目標(biāo)對象的[[Set]]蛇损,所以源對象的屬性為訪問器的話,只能獲得[[Get]]的值坛怪,如果要完整拷貝需要結(jié)合Object.getOwnPropertyDescriptor()
和Object.defineProperty()
2.Object.assign()
的source參數(shù)可以是基本值淤齐,基本值會封裝為對象,null 和 undefined 會被忽略袜匿,并且只有字符串的包裝對象才可能有自身可枚舉屬性更啄。
- 數(shù)據(jù)描述符與訪問描述符的enumerable屬性默認(rèn)為 false。如果使用直接賦值的方式創(chuàng)建對象的屬性居灯,則這個屬性的enumerable為true祭务,這是相對于
Object.defineProperty(...)
方法而言,比如
const obj = {
foo: 1,
get bar() {
return 2;
}
};//"foo"與"bar"均可枚舉可配置
var o = {};
Object.defineProperty(o, "a", { value : 1 });
//"a"不可枚舉不可寫不可配置
- 原生方法可能被自定義的同名函數(shù)覆蓋怪嫌,這時候可以直接使用切換上下文的原生方法
var foo = {
hasOwnProperty: function() {
return false;
},
bar: 'Here be dragons'
};
({}).hasOwnProperty.call(foo, 'bar'); // true
// 也可以使用 Object 原型上的 hasOwnProperty 屬性
Object.prototype.hasOwnProperty.call(foo, 'bar'); // true
-
Object.assign
不會跳過那些值為null
和undefined
的源對象义锥,在出現(xiàn)錯誤的情況下,例如岩灭,如果屬性不可寫缨该,會引發(fā)TypeError,如果在引發(fā)錯誤之前添加了任何屬性川背,則可以更改target對象贰拿。Object.create
如果propertiesObject
參數(shù)是null
或非原始包裝對象,同樣拋出一個TypeError熄云。
6.拷貝中常見等號賦值的操作如target[key] = source[key]
膨更,clazz.prototype[prop] = clazzPrototype[prop]
,這個表達式同時有[[Get]]和[[Put]]的操作缴允,需要注意屬性設(shè)置[[Put]]可能發(fā)生屏蔽的狀況:如果target本來就具有key屬性荚守,那么賦值語句只是修改;如果沒有练般,就在其[[Prototype]]上尋找對應(yīng)key矗漾,①找到并且key為可寫的話,target會新增屏蔽屬性薄料,如果只讀則會被忽略(嚴(yán)格模式下報錯)敞贡,②key為setter,那么target不會新增key屬性摄职,只是會調(diào)用setter誊役。