深刻淺出Object.defineProperty()

首先描述一下定義以及用法javascript

Object.defineProperty()的做用就是直接在一個對象上定義一個新屬性,或者修改一個已經存在的屬性。java

Object.defineProperty(obj, prop, desc)
obj 須要定義屬性的當前對象
prop 當前須要定義的屬性名
desc 屬性描述符
通常經過爲對象的屬性賦值的狀況下,對象的屬性能夠修改也能夠刪除,可是經過Object.defineProperty()定義屬性,經過描述符的設置能夠進行更精準的控制對象屬性。數組

屬性的特性以及內部屬性
javacript 有三種類型的屬性瀏覽器

命名數據屬性:擁有一個肯定的值的屬性。這也是最多見的屬性
命名訪問器屬性:經過getter和setter進行讀取和賦值的屬性
內部屬性:由JavaScript引擎內部使用的屬性,不能經過JavaScript代碼直接訪問到,不過能夠經過一些方法間接的讀取和設置。好比,每一個對象都有一個內部屬性[[Prototype]],你不能直接訪問這個屬性,但能夠經過Object.getPrototypeOf()方法間接的讀取到它的值。雖然內部屬性一般用一個雙呂括號包圍的名稱來表示,但實際上這並非它們的名字,它們是一種抽象操做,是不可見的,根本沒有上面兩種屬性有的那種字符串類型的屬性
<script>
var obj={};
Object.defineProperty(obj,"name",{
get:function(){
return document.querySelector("#name").innerHTML;
},
set:function(val){
document.querySelector("#name").innerHTML=val;
}
});
obj.name="Jerry";
</script>
語法節
Object.defineProperty(obj, prop, descriptor)
參數節
obj
要在其上定義屬性的對象。
prop
要定義或修改的屬性的名稱。
descriptor
將被定義或修改的屬性描述符。
返回值節
被傳遞給函數的對象。app

在ES6中,因爲 Symbol類型的特殊性,用Symbol類型的值來作對象的key與常規的定義或修改不一樣,而Object.defineProperty 是定義key爲Symbol的屬性的方法之一。函數

描述節
該方法容許精確添加或修改對象的屬性。經過賦值操做添加的普通屬性是可枚舉的,可以在屬性枚舉期間呈現出來(for...in 或 Object.keys 方法), 這些屬性的值能夠被改變,也能夠被刪除。這個方法容許修改默認的額外選項(或配置)。默認狀況下,使用 Object.defineProperty() 添加的屬性值是不可修改的。性能

屬性描述符節
對象裏目前存在的屬性描述符有兩種主要形式:數據描述符和存取描述符。數據描述符是一個具備值的屬性,該值多是可寫的,也可能不是可寫的。存取描述符是由getter-setter函數對描述的屬性。描述符必須是這兩種形式之一;不能同時是二者。this

數據描述符和存取描述符均具備如下可選鍵值(默認值是在使用Object.defineProperty()定義屬性的狀況下):prototype

configurable
當且僅當該屬性的 configurable 爲 true 時,該屬性描述符纔可以被改變,同時該屬性也能從對應的對象上被刪除。默認爲 false。
enumerable
當且僅當該屬性的enumerable爲true時,該屬性纔可以出如今對象的枚舉屬性中。默認爲 false。
數據描述符同時具備如下可選鍵值:日誌

value
該屬性對應的值。能夠是任何有效的 JavaScript 值(數值,對象,函數等)。默認爲 undefined。
writable
當且僅當該屬性的writable爲true時,value才能被賦值運算符改變。默認爲 false。
存取描述符同時具備如下可選鍵值:

get
一個給屬性提供 getter 的方法,若是沒有 getter 則爲 undefined。當訪問該屬性時,該方法會被執行,方法執行時沒有參數傳入,可是會傳入this對象(因爲繼承關係,這裏的this並不必定是定義該屬性的對象)。
默認爲 undefined。
set
一個給屬性提供 setter 的方法,若是沒有 setter 則爲 undefined。當屬性值修改時,觸發執行該方法。該方法將接受惟一參數,即該屬性新的參數值。
默認爲 undefined。
描述符可同時具備的鍵值
configurable enumerable value writable get set
數據描述符 Yes Yes Yes Yes No No
存取描述符 Yes Yes No No Yes Yes
若是一個描述符不具備value,writable,get 和 set 任意一個關鍵字,那麼它將被認爲是一個數據描述符。若是一個描述符同時有(value或writable)和(get或set)關鍵字,將會產生一個異常。

記住,這些選項不必定是自身屬性,若是是繼承來的也要考慮。爲了確認保留這些默認值,你可能要在這以前凍結 Object.prototype,明確指定全部的選項,或者經過 Object.create(null)將__proto__屬性指向null。

// 使用 __proto__
var obj = {};
var descriptor = Object.create(null); // 沒有繼承的屬性
// 默認沒有 enumerable,沒有 configurable,沒有 writable
descriptor.value = 'static';
Object.defineProperty(obj, 'key', descriptor);

// 顯式
Object.defineProperty(obj, "key", {
enumerable: false,
configurable: false,
writable: false,
value: "static"
});

// 循環使用同一對象
function withValue(value) {
var d = withValue.d || (
withValue.d = {
enumerable: false,
writable: false,
configurable: false,
value: null
}
);
d.value = value;
return d;
}
// ... 而且 ...
Object.defineProperty(obj, "key", withValue("static"));

// 若是 freeze 可用, 防止代碼添加或刪除對象原型的屬性
// (value, get, set, enumerable, writable, configurable)
(Object.freeze||Object)(Object.prototype);
示例節
若是你想了解如何使用Object.defineProperty方法和類二進制標記語法,看看這篇文章。

建立屬性節
若是對象中不存在指定的屬性,Object.defineProperty()就建立這個屬性。當描述符中省略某些字段時,這些字段將使用它們的默認值。擁有布爾值的字段的默認值都是false。value,get和set字段的默認值爲undefined。一個沒有get/set/value/writable定義的屬性被稱爲「通用的」,並被「鍵入」爲一個數據描述符。

var o = {}; // 建立一個新對象

// 在對象中添加一個屬性與數據描述符的示例
Object.defineProperty(o, "a", {
value : 37,
writable : true,
enumerable : true,
configurable : true
});

// 對象o擁有了屬性a,值爲37

// 在對象中添加一個屬性與存取描述符的示例
var bValue;
Object.defineProperty(o, "b", {
get : function(){
return bValue;
},
set : function(newValue){
bValue = newValue;
},
enumerable : true,
configurable : true
});

o.b = 38;
// 對象o擁有了屬性b,值爲38

// o.b的值如今老是與bValue相同,除非從新定義o.b

// 數據描述符和存取描述符不能混合使用
Object.defineProperty(o, "conflict", {
value: 0x9f91102, 
get: function() { 
return 0xdeadbeef; 
} 
});
// throws a TypeError: value appears only in data descriptors, get appears only in accessor descriptors
修改屬性節
若是屬性已經存在,Object.defineProperty()將嘗試根據描述符中的值以及對象當前的配置來修改這個屬性。若是舊描述符將其configurable 屬性設置爲false,則該屬性被認爲是「不可配置的」,而且沒有屬性能夠被改變(除了單向改變 writable 爲 false)。當屬性不可配置時,不能在數據和訪問器屬性類型之間切換。

當試圖改變不可配置屬性(除了value和writable 屬性以外)的值時會拋出TypeError,除非當前值和新值相同。

Writable 屬性
當writable屬性設置爲false時,該屬性被稱爲「不可寫」。它不能被從新分配。

var o = {}; // Creates a new object

Object.defineProperty(o, 'a', {
value: 37,
writable: false
});

console.log(o.a); // logs 37
o.a = 25; // No error thrown
// (it would throw in strict mode,
// even if the value had been the same)
console.log(o.a); // logs 37. The assignment didn't work.

// strict mode
(function() {
'use strict';
var o = {};
Object.defineProperty(o, 'b', {
value: 2,
writable: false
});
o.b = 3; // throws TypeError: "b" is read-only
return o.b; // returns 2 without the line above
}());
如示例所示,試圖寫入非可寫屬性不會改變它,也不會引起錯誤。

Enumerable 特性
enumerable定義了對象的屬性是否能夠在 for...in 循環和 Object.keys() 中被枚舉。

var o = {};
Object.defineProperty(o, "a", { value : 1, enumerable:true });
Object.defineProperty(o, "b", { value : 2, enumerable:false });
Object.defineProperty(o, "c", { value : 3 }); // enumerable defaults to false
o.d = 4; // 若是使用直接賦值的方式建立對象的屬性,則這個屬性的enumerable爲true

for (var i in o) { 
console.log(i); 
}
// 打印 'a' 和 'd' (in undefined order)

Object.keys(o); // ["a", "d"]

o.propertyIsEnumerable('a'); // true
o.propertyIsEnumerable('b'); // false
o.propertyIsEnumerable('c'); // false
Configurable 特性
configurable特性表示對象的屬性是否能夠被刪除,以及除value和writable特性外的其餘特性是否能夠被修改。

var o = {};
Object.defineProperty(o, "a", { get : function(){return 1;}, 
configurable : false } );

// throws a TypeError
Object.defineProperty(o, "a", {configurable : true}); 
// throws a TypeError
Object.defineProperty(o, "a", {enumerable : true}); 
// throws a TypeError (set was undefined previously) 
Object.defineProperty(o, "a", {set : function(){}}); 
// throws a TypeError (even though the new get does exactly the same thing) 
Object.defineProperty(o, "a", {get : function(){return 1;}});
// throws a TypeError
Object.defineProperty(o, "a", {value : 12});

console.log(o.a); // logs 1
delete o.a; // Nothing happens
console.log(o.a); // logs 1
若是o.a的configurable屬性爲true,則不會拋出任何錯誤,而且該屬性將在最後被刪除。

添加多個屬性和默認值節
考慮特性被賦予的默認特性值很是重要,一般,使用點運算符和Object.defineProperty()爲對象的屬性賦值時,數據描述符中的屬性默認值是不一樣的,以下例所示。

var o = {};

o.a = 1;
// 等同於 :
Object.defineProperty(o, "a", {
value : 1,
writable : true,
configurable : true,
enumerable : true
});


// 另外一方面,
Object.defineProperty(o, "a", { value : 1 });
// 等同於 :
Object.defineProperty(o, "a", {
value : 1,
writable : false,
configurable : false,
enumerable : false
});
通常的 Setters 和 Getters節
下面的例子展現瞭如何實現一個自存檔對象。 當設置temperature 屬性時,archive 數組會獲取日誌條目。

function Archiver() {
var temperature = null;
var archive = [];

Object.defineProperty(this, 'temperature', {
get: function() {
console.log('get!');
return temperature;
},
set: function(value) {
temperature = value;
archive.push({ val: temperature });
}
});

this.getArchive = function() { return archive; };
}

var arc = new Archiver();
arc.temperature; // 'get!'
arc.temperature = 11;
arc.temperature = 13;
arc.getArchive(); // [{ val: 11 }, { val: 13 }]
或

var pattern = {
get: function () {
return 'I alway return this string,whatever you have assigned';
},
set: function () {
this.myname = 'this is my name string';
}
};


function TestDefineSetAndGet() {
Object.defineProperty(this, 'myproperty', pattern);
}


var instance = new TestDefineSetAndGet();
instance.myproperty = 'test';

// 'I alway return this string,whatever you have assigned'
console.log(instance.myproperty);
// 'this is my name string'
console.log(instance.myname);繼承屬性
繼承屬性節
若是訪問者的屬性是被繼承的,它的 get 和set 方法會在子對象的屬性被訪問或者修改時被調用。若是這些方法用一個變量存值,該值會被全部對象共享。

function myclass() {
}

var value;
Object.defineProperty(myclass.prototype, "x", {
get() {
return value;
},
set(x) {
value = x;
}
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // 1
這能夠經過將值存儲在另外一個屬性中解決。在 get 和 set 方法中,this 指向某個被訪問和修改屬性的對象。

function myclass() {
}

Object.defineProperty(myclass.prototype, "x", {
get() {
return this.stored_x;
},
set(x) {
this.stored_x = x;
}
});

var a = new myclass();
var b = new myclass();
a.x = 1;
console.log(b.x); // undefined
不像訪問者屬性,值屬性始終在對象自身上設置,而不是一個原型。然而,若是一個不可寫的屬性被繼承,它仍然能夠防止修改對象的屬性。

function myclass() {
}

myclass.prototype.x = 1;
Object.defineProperty(myclass.prototype, "y", {
writable: false,
value: 1
});

var a = new myclass();
a.x = 2;
console.log(a.x); // 2
console.log(myclass.prototype.x); // 1
a.y = 2; // Ignored, throws in strict mode
console.log(a.y); // 1
console.log(myclass.prototype.y); // 1

  

兼容性問題節
重定義數組對象的 length 屬性節
數組的 length 屬性重定義是可能的,可是會受到通常的重定義限制。(length 屬性初始爲 non-configurable,non-enumerable 以及 writable。對於一個內容不變的數組,改變其 length屬性的值或者使它變爲 non-writable 是可能的。可是改變其可枚舉性和可配置性或者當它是 non-writable 時嘗試改變它的值或是可寫性,這二者都是不容許的。)然而,並非全部的瀏覽器都容許 Array.length 的重定義。

在 Firefox 4 至 22 版本中嘗試去重定義數組的 length 屬性都會拋出一個 TypeError 異常。

有些版本的Chrome中,Object.defineProperty() 在某些狀況下會忽略不一樣於數組當前length屬性的length值。有些狀況下改變可寫性並不起做用(也不拋出異常)。同時,好比Array.prototype.push的一些數組操做方法也不會考慮不可讀的length屬性。

有些版本的Safari中,Object.defineProperty() 在某些狀況下會忽略不一樣於數組當前length屬性的length值。嘗試改變可寫性的操做會正常執行而不拋出錯誤,但事實上並未改變屬性的可寫性。

只在Internet Explorer 9及之後版本和Firefox 23及之後版本中,才完整地正確地支持數組length屬性的從新定義。目前不要依賴於重定義數組length 屬性可以起做用,或在特定情形下起做用。與此同時,即便你可以依賴於它,你也沒有合適的理由這樣作。

Internet Explorer 8 具體案例節
Internet Explorer 8 實現了 Object.defineProperty() 方法,但 只能在 DOM 對象上使用。 須要注意的一些事情:

嘗試在原生對象上使用 Object.defineProperty()會報錯。屬性特性必須設置一些特定的值。對於數據屬性描述符,configurable, enumerable和 writable 特性必須所有設置爲 true;對於訪問器屬性描述符,configurable 必須設置爲 true,enumerable 必須設置爲 false。(?) 任何試圖提供其餘值(?)將致使一個錯誤拋出。從新配置一個屬性首先須要刪除該屬性。若是屬性沒有刪除,就如同從新配置前的嘗試。 詳情能夠參考網址:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Object/definePropertyhttps://www.jianshu.com/p/8fe1382ba135

相關文章
相關標籤/搜索