深刻了解JavaScript中的對象

你們都知道循環一個對象,能夠用for ..in..來作,好比:javascript

var myObject = {foo: 'bar'};
for(var name in myObject) {
   // 這裏能獲得你想要的屬性名
   // 也能夠獲得對應屬性值: myObject[propertyName]
}
複製代碼

這是顯而易見的,可是若是有人在這個對象的原型上添加了一些屬性或方法呢?vue

Object.prototype.baz = 'quux';
for (var name in myObject) {
  alert(name); //foo baz
}
複製代碼

myObject也會跟着改,那麼別人改動原型,對本身的代碼形成很大的影響,結果確定是不能接受的。java

既然對象存在繼承,那麼咱們必須考慮哪些是我本身寫的(私有的),哪些是從原型上繼承的,單純的for ..in..循環已經不能知足需求 了。數組

hasOwnProperty就是用來判斷是不是私有屬性,而不是繼承過來的ui

myObject.hasOwnProperty('foo'); // true
myObject.hasOwnProperty('baz'); // false
複製代碼

若是僅僅想要私有的屬性或方法,咱們就能夠這麼寫了spa

for(var name in myObject) {
  if(myObject.hasOwnProperty(name)){
    alert(name);// 'foo'
  }
}
複製代碼

for...in..循環,中間還要作判斷,是否是很麻煩,不過咱們有專門的方法來解決這些麻煩事。prototype

Object.keys(myObject);//['foo'] 返回一個對象,是一個私有屬性的集合 
複製代碼

說到這裏,確定還有一些人會疑惑,既然for...in能遍歷出對象自身的屬性或方法,也能遍歷出繼承過來的屬性或方法,那麼Object類上還有不少的屬性和方法的,好比說toString,這是被myObject繼承的,可是沒有被for...in遍歷到啊。code

'toString' in myObject; //true
myObject.toString();//"[object Object]"
複製代碼

這裏引入一個概念「可枚舉」enumerable,這個概念的最初目的,就是讓某些屬性能夠規避掉for...in操做,否則全部內部屬性和方法都被遍歷到,就能夠放肆的更改了。cdn

寫到這裏,你應該會明白了,上面提到的toString都是不可枚舉的。你會不會有這樣的問題?若是我在對象裏面寫了一些屬性,可是不想被遍歷出來,是否是也能夠把這些屬性變成不可枚舉的呢?固然是能夠的了。對象

Object.defineProperty(myObject, 'name', {
    value : 'cover',
    enumerable: false//不可枚舉
})
Object.prototype.baz = 'quux';
// myObject:{foo: "bar", name: "cover"}
for (var name in myObject) {
  alert(name); //foo baz
}
複製代碼

經過這種方法定義出來的屬性或方法就能夠變成不可枚舉。

新的知識點:Object.defineProperty,顧名思義,就是爲對象定義屬性。 定義:直接在一個對象上定義一個新的屬性,或者是修改已存在的屬性。最終這個方法會返回該對象。

參數

  • object必需。 要在其上添加或修改屬性的對象。 這多是一個本機JavaScript對象(即用戶定義的對象或內置對象)或DOM對象。
  • propertyname 必需。 一個包含屬性名稱的字符串。
  • descriptor 必需。 屬性描述符。 它能夠針對數據屬性或訪問器屬性。

其中descriptor的參數值是咱們須要重點關注的,該屬性可設置的值有:

  • [value] 屬性的值,默認爲undefined
  • [writable] 該屬性是否可寫,若是設置成 false,則任何對該屬性改寫的操做都無效(但不會報錯),對於像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值爲爲 true
Object.defineProperty(myObject, 'age', {
    value : 18,
    writable:false
});
myObject.age =19;
console.log(myObject.age);//18;
複製代碼
  • [configurable]若是爲false,則任未嘗試刪除目標屬性或修改屬性的行爲將被無效化,對於像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值爲爲 true
Object.defineProperty(myObject, "name", {
    value:"gogo" ,
    configurable: false 
});  
delete myObject.name; 
console.log(myObject.name);// 輸出 gogo
myObject.name = "gg";
console.log(myObject.name); // 輸出gogo
複製代碼
  • [enumerable] 是否能在for-in循環中遍歷出來或在Object.keys中列舉出來。對於像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值爲爲 true。

在調用Object.defineProperty()方法時,若是不指定, configurableenumerablewritable特性的默認值都是false,這跟以前所 說的對於像前面例子中直接在對象上定義的屬性,這個特性默認值爲爲true。並不衝突,以下代碼所示:

//調用Object.defineProperty()方法時,若是不指定
var someOne = { };
someOne.name = 'coverguo';
console.log(Object.getOwnPropertyDescriptor(someOne, 'name'));
//輸出 Object {value: "coverguo", writable: true, enumerable: true, configurable: true}

//直接在對象上定義的屬性,這個特性默認值爲爲 true
var otherOne = {};
Object.defineProperty(otherOne, "name", {
    value:"coverguo" 
});  
console.log(Object.getOwnPropertyDescriptor(otherOne, 'name'));
//輸出 Object {value: "coverguo", writable: false, enumerable: false, configurable: false}
複製代碼

Object.getOwnPropertyDescriptor方法會返回某個對象屬性的描述對象(descriptor)。ES2017 引入了Object.getOwnPropertyDescriptors方法,返回指定對象全部自身屬性(非繼承屬性)的描述對象。

  • [set] 修改器,一旦目標對象訪問該屬性,就會調用這個方法, 並返回結果。默認爲undefined
  • [get] 獲取器,一旦目標對象設置該屬性,就對調用這個方法。默認爲undefined 其實,在vue中就用到了 Object.defineProperty方法,主要用於雙向數據綁定。

扯得有點遠了,咱們再回來看,如何判斷對象上的屬性是否可枚舉?

  • obj.propertyIsEnumerable(prop)
Object.defineProperty(myObject, "name", {
    value:"gogo" ,
    configurable: false ,
    enumerable:false,
});
myObject.age =18
myObject.propertyIsEnumerable('name'); //false
myObject.propertyIsEnumerable('age');//true
複製代碼

另外介紹一個方法:

  • Object.getOwnPropertyNames(obj)

Object.getOwnPropertyNames返回一個數組,包含對象自身的全部屬性(不含 Symbol 屬性,可是包括不可枚舉屬性)的鍵名。是否是和Object.keys(obj)有點像呢?

最後,咱們再來總結一下,有四個操做會忽略enumerablefalse的屬性。

  • for...in循環:只遍歷對象自身的和繼承的可枚舉的屬性。
  • Object.keys():返回對象自身的全部可枚舉的屬性的鍵名。
  • JSON.stringify():只串行化對象自身的可枚舉的屬性。
  • Object.assign(): 忽略enumerablefalse的屬性,只拷貝對象自身的可枚舉的屬性。

最後加一個題外的問題:

關於instanceof 的大概實現原理:

function myInstanceof(left,right){
    let rightVal = right.prototype;
    let letfVal = left.__proto__;
    while(true){
        if(leftVal === null)return false;
        else if(letfVal === rightVal) return true;
        letfVal = letfVal.__proto__;
    }
}
複製代碼

在加上一張著名的圖:

這張圖不太好理解的地方在於 Object.__proto__的值是 Function.prototype,暫時先記住吧。

相關文章
相關標籤/搜索