寫在前面 註:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程式設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什麼問題的,歡迎留言指出。 1.數據屬性 數據屬性的4個特性: Configurable:①表示能否通過delete刪除屬性 ...
寫在前面
註:這個系列是本人對js知識的一些梳理,其中不少內容來自書籍:Javascript高級程式設計第三版和JavaScript權威指南第六版,感謝它們的作者和譯者。有發現什麼問題的,歡迎留言指出。
1.數據屬性
數據屬性的4個特性:
- Configurable:①表示能否通過delete刪除屬性從而重新定義,②能否修改屬性的特性,③能否把屬性修改為訪問器屬性。對象直接量里預設值true。
- Enumerable:表示能否通過for-in迴圈返回屬性。對象直接量里預設值true。
- Writable:表示能否修改屬性的值。對象直接量里預設值true。
- Value:包含這個屬性的數據值。對象直接量里預設值undefined。
//查看對象直接量的屬性的屬性特性預設值
var people = {
name:'jaychou',
sayName:function () {
console.log(this.name);
}
};
/**{value: "jaychou", writable: true, enumerable: true, configurable: true}*/
console.log(Object.getOwnPropertyDescriptor(people,'name'));
/**{value: ƒ, writable: true, enumerable: true, configurable: true}*/
console.log(Object.getOwnPropertyDescriptor(people,'sayName'));
//getOwnPropertyDescriptor對於繼承屬性和不存在的屬性,返回undefined
要修改屬性預設的特性,使用Object.defineProperty()方法,接收3個參數:對象,屬性名字和描述符對象。
//修改屬性預設特性:
Object.defineProperty(person,'job',{
emumerable:false,//不可枚舉
value:'singer',
writable:false,//不可寫
configurable:true
});
/**{name: "jaychou", sayName: ƒ, job: "singer"}*/
console.log(person);
for(var prop in person){
//列印name,sayName
console.log(prop);
}
//會報錯
try{
person.job = 'director';
}catch (e) {
//Cannot assign to read only property 'job' of object
console.log(e);
}
可以多次調用Object.defineProperty()方法修改同一個屬性,但在把configurable特性設置為false之後就會有限制了:
Object.defineProperty(person,'height',{
configurable:false,//不可配置
writable:true,
value:172
});
try{
Object.defineProperty(person,'height',{
configurable:true,//出錯
enumerable:true,//出錯
value:175,//正常
writable:false,//writable從true變false可以,false變true也會出錯
});
}catch (e) {
//Cannot redefine property: height at Function.defineProperty
console.log(e);
}
try{
delete person.height;
}catch (e) {
//設置成不可配置後也不可刪除:Cannot delete property 'height' of #<Object>
console.log(e);
}
另外,調用 Object.defineProperty()方法時,如果不指定,configurable、enumerable 和 writable 特性的預設值都是 false。如果是修改已有屬性,則無此限制。
2.存儲器屬性
存儲器屬性不包含數據值,只包含包含 getter 和 setter 函數(非必需)。 在讀取存儲器屬性時,會調用 getter 函數,這個函數負責返回有效的值;在寫入存儲器屬性時,會調用 setter 函數並傳入新值,這個函數負責決定如何處理數據。4個屬性特性如下:
- Configurable:①表示能否通過delete刪除屬性從而重新定義,②能否修改屬性的特性,③能否把屬性修改為數據屬性。對象直接量的預設值true
- Enumerable:表示能否通過for-in迴圈返回屬性。對象直接量的預設值true
- Get:在讀取屬性時調用的函數。對象直接量預設值undefined
- Set:在寫入屬性時調用的函數。對象直接量的預設值undefined
定義存儲器屬性最簡單的方法是使用對象直接量語法的拓展寫法:
var p = {
x:3.0,
y:4.0,
//r是可讀寫的存取器屬性
get r(){return Math.sqrt(this.x*this.x+this.y*this.y);},
set r(newValue){
var oldvalue = Math.sqrt(this.x*this.x+this.y*this.y);
var ratio = newValue/oldvalue;
this.x *= ratio;
this.y *= ratio;
},
//theta是只讀存取器屬性
get theta(){return Math.atan2(this.y,this.x);}
}
console.log(p.r);
p.r = 25;
使用Object.defineProperty()方法定義存儲器屬性:
var book = {
_year:2004,
edition:1
};
Object.defineProperty(book,"year",{
get:function () { return this._year; },
set:function (newValue) {
if(newValue>2004){
this._year = newValue;
this.edition += newValue - 2004;
}
}
})
/**{get: ƒ, set: ƒ, enumerable: false, configurable: false}*/
console.log(Object.getOwnPropertyDescriptor(book,'year'));
如例子所示,使用存儲器屬性的常見方式,即設置一個屬性的值會導致其他屬性發生變化。還有一種常見就是現在流行的類似於Vue的響應式原理,就是把data中的屬性都使用defineProperty修改為存儲器屬性,可以監聽到數據的變化。
3.定義多個屬性
經常要創建或修改多個屬性,這時候可以使用Object.defineProperties()方法,它接收2個參數,要添加或修改屬性的對象和一個映射表,包含名稱和屬性描述符。
var book1 = {};
Object.defineProperties(book1,{
_year:{
value:'2008'
},
editor:{
enumerable:true,
value:'2'
},
year:{
get:function () {
return this._year;
},
set:function (newValue) {
this._year = newValue;
this.edition += newValue - 2004;
}
}
});
4.對象的可擴展性
對象的可拓展性表示是否可以給對象添加新屬性。所有內置對象和自定義對象都是顯式可擴展的,宿主對象的可擴展性是由Javascript引擎定義的。
1.查詢對象可拓展性
var teacher = {age:25};
//true:代表可拓展
console.log(Object.isExtensible(teacher));
2.轉換為不可拓展(“鎖定對象”)
Object.preventExtensions(teacher);
//false
console.log(Object.isExtensible(teacher));
try{
teacher.subject = 'math';
}catch (e) {
//TypeError: Cannot add property subject, object is not extensible
console.log(e);
}
轉換成不可拓展的操作是不可逆的,而且只能影響到對象本身的可拓展性,如果給一個不可拓展對象的原型添加屬性,這個不可拓展對象同樣會繼承這些新屬性。
5.密封對象
密封對象比鎖定對象更高一層,除了不可拓展以外,對象的所有自身屬性都設置成了不可配置的。同樣密封對象操作是不可逆的。
var tea1 = {subject:'math'};
//false:代表未密封
console.log(Object.isSealed(tea1));
Object.seal(tea1);
try{
Object.defineProperty(tea1,'subject',{
//enumerable:false,//出錯
//configurable:true,//出錯
writable:false//和上面說的一樣,writable從true變成false可以,false變成true則出錯
});
}catch (e) {
console.log('出錯..');
console.log(e);
}
//true:已密封
console.log(Object.isSealed(tea1));
6.凍結對象
凍結比密封對象多的效果是:可以將它自有的所有數據屬性設置為只讀(如果對象的存取器屬性具有setter方法,存取器屬性將不受影響,仍可以通過給屬性賦值調用它們)。
var tea2 = {subject:'Chinese'};
//false:代表未凍結
console.log(Object.isFrozen(tea2));
Object.freeze(tea2);
try{
tea2.subject = 'math';
}catch (e) {
//TypeError: Cannot assign to read only property 'subject' of object
console.log(e);
}
//true:已凍結
console.log(Object.isFrozen(tea2));
7.屬性特性規則總結
- 如果對象是不可拓展的,則可以編輯已有的自有屬性,但不能給它添加新屬性。
- 如果屬性是不可配置的,則不能修改它的可配置性和可枚舉性。
- 如果存取器屬性是不可配置的,則不能修改其getter和setter方法,也不能將它轉換為數據屬性。
- 如果數據屬性是不可配置的,則不能將它轉換為存取器屬性。
- 如果數據屬性是不可配置的,則不能將它的可寫性從false修改為true,但可以從true修改為false。
- 如果數據屬性是不可配置且不可寫的,則不能修改它的值。然而可配置但不可寫屬性的值是可以修改的(做法:先將它標記為可寫的,然後修改它的值,最後轉換為不可寫的)。