Object.defineProperty ,顧名思義,為對象定義屬性。在js中我們可以通過下面這幾種方法定義屬性
// (1) define someOne property name
geekjc.name = 'geekjc';
//or use (2)
geekjc['name'] = 'geekjc';
// or use (3) defineProperty
Object.defineProperty(someOne, 'name', {
value : 'geekjc'
})
從上面看,貌似使用Object.defineProperty很麻煩,那為啥存在這樣的方法呢?
帶著疑問,我們來看下 Object.defineProperty的定義。
1. what is Object.defineProperty
The Object.defineProperty() method defines a new property directly on an object, or modifies an exisiting property on an object, and returns the object.
從上面得知,我們可以通過Object.defineProperty這個方法,直接在一個對象上定義一個新的屬性,或者是修改已存在的屬性。最終這個方法會返回該對象。
1.1 語法
Object.defineProperty(object, propertyname, descriptor)
1.2 參數(shù)
- object 必需。 要在其上添加或修改屬性的對象。 這可能是一個本機 JavaScript對象(即用戶定義的對象或內置對象)或 DOM 對象。
- propertyname 必需。 一個包含屬性名稱的字符串。
- descriptor 必需。 屬性描述符。 它可以針對數(shù)據(jù)屬性或訪問器屬性。
1.3 屬性的狀態(tài)設置
1.【value】 屬性的值,默認為 undefined。
2.【writable】 該屬性是否可寫,如果設置成 false,則任何對該屬性改寫的操作都無效(但不會報錯),對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。
var geekjc = { };
Object.defineProperty(geekjc, "name", {
value:"geekjc" , //由于設定了writable屬性為false 導致這個量不可以修改
writable: false
});
console.log(geekjc.name); // 輸出 geekjc
someOne.name = "hhhaaa";
console.log(geekjc.name); // 輸出geekjc
3.【configurable]】如果為false,則任何嘗試刪除目標屬性或修改屬性以下特性(writable, configurable, enumerable)的行為將被無效化,對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。 。
var geekjc = { };
Object.defineProperty(geekjc, "name", {
value:"geekjc" ,
configurable: false
});
delete geekjc.name;
console.log(geekjc.name);// 輸出 geekjc
someOne.name = "hhhaaa";
console.log(geekjc.name); // 輸出geekjc
4.【enumerable】 是否能在for-in循環(huán)中遍歷出來或在Object.keys中列舉出來。對于像前面例子中直接在對象上定義的屬性,這個屬性該特性默認值為為 true。
注意 在調用Object.defineProperty()方法時,如果不指定, configurable, enumerable, writable特性的默認值都是false,這跟之前所 說的對于像前面例子中直接在對象上定義的屬性,這個特性默認值為為 true。并不沖突,如下代碼所示:
//調用Object.defineProperty()方法時,如果不指定
var geekjc = { };
geekjc.name = 'geekjc';
console.log(Object.getOwnPropertyDescriptor(geekjc, 'name'));
//輸出 Object {value: "geekjc", writable: true, enumerable: true, configurable: true}
//直接在對象上定義的屬性,這個特性默認值為為 true
var otherOne = {};
Object.defineProperty(otherOne, "name", {
value:"geekjc"
});
console.log(Object.getOwnPropertyDescriptor(otherOne, 'name'));
//輸出 Object {value: "geekjc", writable: false, enumerable: false, configurable: false}
5.【get】一旦目標對象訪問該屬性,就會調用這個方法,并返回結果。默認為 undefined。
6.【set】 一旦目標對象設置該屬性,就會調用這個方法。默認為 undefined。
從上面,可以得知,我們可以通過使用Object.defineProperty,來定義和控制一些特殊的屬性,如屬性是否可讀,屬性是否可枚舉,甚至修改屬性的修改器(setter)和獲取器(getter)
那什么場景和地方適合使用到特殊的屬性呢?
2. 實際運用
在一些框架,如vue、express、qjs等,經常會看到對Object.defineProperty的使用。那這些框架是如何使用呢?
2.1 MVVM中數(shù)據(jù)‘雙向綁定’實現(xiàn)
如vue,qjs等大部分mvvm框架(angular用的是臟處理)都是通過Object.defineProperty來實現(xiàn)數(shù)據(jù)綁定的
2.2 優(yōu)化對象獲取和修改屬性方式
這個優(yōu)化對象獲取和修改屬性方式,是什么意思呢? 過去我們在設置dom節(jié)點transform時是這樣的。
//加入有一個目標節(jié)點, 我們想設置其位移時是這樣的
var targetDom = document.getElementById('target');
var transformText = 'translateX(' + 10 + 'px)';
targetDom.style.webkitTransform = transformText;
targetDom.style.transform = transformText;
通過上面,可以看到如果頁面是需要許多動畫時,我們這樣編寫transform屬性是十分蛋疼的。(┬_┬)
但如果通過Object.defineProperty, 我們則可以
//這里只是簡單設置下translateX的屬性,其他如scale等屬性可自己去嘗試
Object.defineProperty(dom, 'translateX', {
set: function(value) {
var transformText = 'translateX(' + value + 'px)';
dom.style.webkitTransform = transformText;
dom.style.transform = transformText;
}
//這樣再后面調用的時候, 十分簡單
dom.translateX = 10;
dom.translateX = -10;
//甚至可以拓展設置如scale, originX, translateZ,等各個屬性,達到下面的效果
dom.scale = 1.5; //放大1.5倍
dom.originX = 5; //設置中心點X
}
上面只是個簡單的版本,并不是最合理的寫法,但主要是為了說明具體的意圖和方法
2.3 增加屬性獲取和修改時的信息
如在Express4.0中,該版本去除了一些舊版本的中間件,為了讓用戶能夠更好地發(fā)現(xiàn),其有下面這段代碼,通過修改get屬性方法,讓用戶調用廢棄屬性時拋錯并帶上自定義的錯誤信息。
[
'json',
'urlencoded',
'bodyParser',
'compress',
'cookieSession',
'session',
'logger',
'cookieParser',
'favicon',
'responseTime',
'errorHandler',
'timeout',
'methodOverride',
'vhost',
'csrf',
'directory',
'limit',
'multipart',
'staticCache',
].forEach(function (name) {
Object.defineProperty(exports, name, {
get: function () {
throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
},
configurable: true
});
});
其他如設置常量等用途。。。
3. 兼容
最后注意下,Object.defineProperty是ES5的屬性,大部分場景使用是沒問題的, 但在一些場景如IE8以下是使用不到的哈。