第3章 理解對(duì)象
3.1 定義屬性
當(dāng)一個(gè)屬性第一次被添加給對(duì)象時(shí),JavaScript在對(duì)象上調(diào)用一個(gè)名為[[Put]]的內(nèi)部方法。這個(gè)操作不僅指定了初始的值,也定義了屬性的一些特征。
當(dāng)一個(gè)已有的屬性被賦予一個(gè)新值時(shí),調(diào)用的是一個(gè)名為[[Set]]的方法。
//Object實(shí)例化
var person1 = new Object();
person1.name = 'liang'; //調(diào)用[[Put]]的內(nèi)部方法
//對(duì)象字面形式
var person2 = {
name: 'liang'
};
person2.name = 'zhu'; //調(diào)用[[Set]]方法
3.2 屬性探測(cè)
in 操作符在給定對(duì)象中查找一個(gè)給定名稱的屬性,如果找到則返回true。
in操作符會(huì)檢測(cè)自有屬性和原型屬性,如果只要檢測(cè)自有屬性,使用hasOwnProperty()方法。
var person1 = {
name: 'liang',
age: 26
};
console.log('name' in person1); //true
console.log(person1.hasOwnProperty('name')); //true
//tostring是原型屬性,所以用hasOwnProperty檢測(cè)結(jié)果為false
console.log('toString' in person1); //true
console.log(person1.hasOwnProperty('toString')); //false
//可用這樣一個(gè)函數(shù)來鑒別原型屬性,結(jié)果為true則是原型屬性
function check(object, name){
return (name in object) && !object.hasOwnProperty(name);
}
console.log(check(person1,'age')); //false
console.log(check(person1,'toString')); //true
3.3 刪除屬性
delete 操作符針對(duì)單個(gè)對(duì)象屬性調(diào)用名為[[Delete]]的內(nèi)部方法。操作成功時(shí),它返回true。
var person1 = {
name: 'liang',
age: 26
};
delete person1.age;
'age' in person1; //false
同名的自有屬性會(huì)覆蓋原型屬性的值。delete 可以刪除自有屬性,不能刪除原型屬性。
3.4 屬性枚舉
[[Enumerable]]是屬性的一個(gè)內(nèi)部特征,指示屬性是否可枚舉,默認(rèn)為true(可枚舉的)。
for-in 循環(huán)會(huì)枚舉一個(gè)對(duì)象所有的可枚舉屬性,并將屬性賦給一個(gè)變量。
var person1 = {
name: 'liang',
age: 26
};
var property;
for (property in person1){
console.log('Name' + ': ' + property); //枚舉屬性
console.log('Value' + ': ' + person1[property]); //枚舉屬性的值
}
//Name: name Value: liang Name: age Value: 26
Object.keys() 方法,獲取可枚舉屬性的名字的數(shù)組。
for-in 循環(huán)會(huì)遍歷原型屬性,而Object.keys()只返回自有屬性。
var person1 = {
name: 'liang',
age: 26
};
//獲取數(shù)組
var propertys = Object.keys(person1);
propertys; //['name', 'age']
//輸出屬性和屬性值
var i, len;
for(i=0, len=propertys.length; i<len; i++){
console.log('Name' + ': ' + propertys[i]);
console.log('Value' + ': ' + person1[propertys[i]]);
} //Name: name Value: liang Name: age Value: 26
3.5 屬性類型
對(duì)象的屬性有兩種類型:數(shù)據(jù)屬性和訪問器屬性。
數(shù)據(jù)屬性只包含一個(gè)值。
訪問器屬性不包含值,而是定義了一個(gè)當(dāng)屬性被讀取時(shí)調(diào)用的函數(shù)(稱為getter),和一個(gè)當(dāng)屬性被寫入時(shí)調(diào)用的函數(shù)(稱為setter)。
訪問器屬性僅需要getter或setter兩者中的任意一個(gè),也可以兩者都有。
特殊關(guān)鍵字get和set被用在訪問器屬性名字的前面,后面跟著小括號(hào)和函數(shù)體。getter被期望返回一個(gè)值,而setter則接受一個(gè)需要被賦給屬性的值作為參數(shù)。
var person1 = {
_name: 'liang', //命名規(guī)范:下劃線表示該屬性被認(rèn)為是私有的
get name(){
console.log('the name is ');
return this._name;
}, //此處加逗號(hào)
set name(value){
console.log('set name to',value);
this._name = value;
}
};
//注意:使用的是person1.name,而不是person1._name
console.log(person1.name); //the name is 'liang'
console.log(person1._name); //liang
person1.name = 'zhu'; //set name to zhu 'zhu'
console.log(person1.name); //the name is 'zhu'
console.log(Object.keys(person1)); //['_name', 'name']
通常情況下不使用訪問器屬性,但當(dāng)你希望賦值操作會(huì)觸發(fā)一些行為,或讀取的值需要通過計(jì)算所需的返回值,可使用訪問器屬性。
如果只定義getter,該屬性就變成只讀。
如果只定義setter,該屬性就變成只寫。
3.6 屬性特征
3.6.1 通用特征
有兩個(gè)屬性特征是數(shù)據(jù)和訪問器屬性都具有的。
[[Enumerable]],決定了是否可以遍歷該屬性。
[[Configurable]],決定了該屬性是否可配置。delete可以刪除可配置的屬性。
Object.definedProperty() 方法,可用來改變屬性特征。有3個(gè)參數(shù):擁有該屬性的對(duì)象、屬性名、包含需要設(shè)置特征的屬性描述對(duì)象。
var person1 = {
name: 'liang'
};
Object.defineProperty(person1,'name',{
enumerable: false
});
console.log('name' in person1); //true
console.log(Object.keys(person1)); // []
3.6.2 數(shù)據(jù)屬性特征
數(shù)據(jù)屬性額外擁有兩個(gè)特征:
[[Value]] 包含屬性的值。
[[Writable]] 指示該屬性是否可以寫入。
var person1 = {};
Object.defineProperty(person1,'name',{
value: 'liang',
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty() 方法調(diào)用時(shí),會(huì)首先檢查該屬性是否存在,如果不存在,將根據(jù)屬性描述對(duì)象指定的特征創(chuàng)建。調(diào)用這個(gè)方法,如果不指定特征的布爾值,它會(huì)默認(rèn)設(shè)置為false。
3.6.2 訪問器屬性特征
訪問器屬性不需要存儲(chǔ)值,因此沒有[[Value]]和[[Writable]]。
有另外兩個(gè)額外特征:[[Get]]和[[Set]]
之前的例子:
var person1 = {
_name: 'liang', //命名規(guī)范:下劃線表示該屬性被認(rèn)為是私有的,實(shí)際上還是公有的
get name(){
console.log('the name is ');
return this._name;
}, //此處加逗號(hào)
set name(value){
console.log('set name to',value);
this._name = value;
}
};
改寫成如下形式:
var person1 = {
_name: 'liang'
};
Object.defineProperty(person1,'name',{ //注意這里的name,不是_name,也可以用別的字符串來定義訪問器屬性
get:function(){
console.log('Reading name');
return this._name;
},
set:function(value){
console.log('Setting name to ',value);
this._name=value;
},
enumerable:true,
configurable:true
});
person1.name = 'zhang'; //setting name to zhang 'zhang'
3.6.4 定義多重屬性
Object.defineProperties() 可以為一個(gè)對(duì)象同時(shí)定義多個(gè)屬性。
接受兩個(gè)參數(shù):需要改變的對(duì)象、一個(gè)包含所有屬性信息的對(duì)象。
var person1 = {};
Object.defineProperties(person1,{
_name: {
value: 'liang',
enumerable: true,
configurable: true,
writable: true
},
name: {
get:function(){
console.log('reading name');
return this._name;
},
set: function(value){
console.log('setting name to %s',value);
this._name=value;
},
enumerable: true,
configurable: true
}
});
person1.name; //reading name 'liang'
person1._name; //'liang'
3.6.5 獲取屬性特征
Object.getOwnPropertyDescriptor() 方法獲取屬性的特征。
這個(gè)方法只適用于自有屬性。接受兩個(gè)參數(shù):對(duì)象、屬性名。
var person1 = {
name: 'liang'
};
var descriptor = Object.getOwnPropertyDescriptor(person,'name');
descriptor; //{value: 'liang', writable: true, enumerable: true, configurable: true}
3.7 禁止修改對(duì)象
[[Extensible]] 指明該對(duì)象是否可以被修改。有3種方法來鎖定對(duì)象屬性。
Object.preventExtensible() 禁止擴(kuò)展,對(duì)象不能繼續(xù)添加新的屬性。 Object.isExtensible() 判斷是否為可擴(kuò)展的。
Object.seal() 封印對(duì)象,對(duì)象不能繼續(xù)添加新的屬性,且不能刪除和改變屬性類型。 Object.isSealed() 判斷是否被封印的。
Object.freeze() 凍結(jié)對(duì)象,對(duì)象不能繼續(xù)添加新的屬性,且不能刪除和改變屬性類型,還不能寫入任何數(shù)據(jù)屬性。 Object.isFrozen() 判斷是否被凍結(jié)。
var person1 = {
name: 'liang'
}
Object.seal(person1);
delete person1.name; //將無法刪除
console.log(Object.isExtensible(person1)); //false
console.log(Object.isSealed(person1)); //true
第4章 構(gòu)造函數(shù)和原型對(duì)象
4.1 構(gòu)造函數(shù)
構(gòu)造函數(shù)名的首字母要大寫。
function Person(){
//statement
}
構(gòu)造函數(shù)接受一個(gè)命名參數(shù)name并將其賦給this對(duì)象的name屬性。
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person('liang');
person1.sayName(); //'liang'
構(gòu)造函數(shù)中還能用Object.defineProperty() 方法來幫助我們初始化。
function Person(name){
Object.defineProperty(this,'name',{
get:function(){
return name;
},
set:function(newName){
name = newName;
},
enumerable:true,
configurable:true
});
}
4.2 原型對(duì)象
幾乎所有的函數(shù)(除了一些內(nèi)建函數(shù))都有一個(gè)名為prototype的屬性,該屬性是一個(gè)原型對(duì)象,用來創(chuàng)建新的對(duì)象實(shí)例。
所有創(chuàng)建的對(duì)象實(shí)例共享該原型對(duì)象,且這些對(duì)象實(shí)例可以訪問原型對(duì)象的屬性。
當(dāng)你試圖訪問一個(gè)對(duì)象的某個(gè)屬性時(shí),JavaScript首先在自有屬性里查找該名字,如果在自有屬性里沒有找到則查找原型屬性。
4.2.1 [[Prototype]] 屬性
對(duì)象實(shí)例通過內(nèi)部屬性[[Prototype]]跟蹤其原型對(duì)象。該屬性是對(duì)象實(shí)例指向原型對(duì)象的指針。
Object.getPrototypeOf() 方法讀取[[Prototype]]屬性的值。
var obj = {};
console.log(Object.getPrototypeOf(obj) === Object.prototype); // true
//任何一個(gè)泛用對(duì)象,其[[Prototype]]屬性始終指向Object.prototype
isPrototypeOf() 方法檢測(cè)某個(gè)對(duì)象(Object)是否是另一個(gè)對(duì)象(obj)的原型對(duì)象。
var obj = {};
console.log(Object.prototype.isPrototypeOf(obj)); //true
或者說isPrototypeOf() 是檢測(cè)一個(gè)對(duì)象(person1)是否是另一個(gè)對(duì)象(Person)的對(duì)象實(shí)例。
function Person(name){
this.name = name;
this.sayName = function(){
console.log(this.name);
};
}
var person1 = new Person('liang');
Person.prototype.isPrototypeOf(person1); //true
Object.getPrototypeOf(person1) === Person.prototype; //true
4.2.2 在構(gòu)造函數(shù)中使用原型對(duì)象
function Person(name){
this.name = name;
}
console.log(Person.prototype); //{constructor: ?}
Person.prototype.sayName = function(){
return this.name;
};
console.log(Person.prototype); //{sayName: ?, constructor: ?}
var person1 = new Person('liang1');
person1.sayName(); //'liang1'
上例中,sayName() 現(xiàn)在是一個(gè)原型屬性而不是自有屬性。
如下的構(gòu)造函數(shù),sayName是一個(gè)自有屬性,區(qū)分這兩種創(chuàng)建方式。
function Person(name){
this.name = name;
this.sayName = function(){
return this.name;
}
}
console.log(Person.prototype); //{constructor: ?}
原型對(duì)象上存儲(chǔ)其他類型的數(shù)據(jù),存儲(chǔ)引用值時(shí)要注意,這些引用值會(huì)被多個(gè)實(shí)例共享。
function Person(name){
this.name = name;
}
Person.prototype.favorites= [];
var person1 = new Person('liang');
var person2 = new Person('zhu');
person1.favorites.push('pizza');
person2.favorites.push('quinoa');
person1.favorites; //['pizza', 'quinoa']
通過一個(gè)對(duì)象字面形式替換原型對(duì)象,來設(shè)置多個(gè)原型屬性。
function Person(name){
this.name = name;
}
Person.prototype={
sayName: function(){
return this.name;
},
toString: function(){
return '[Person ' + this.name + ']';
}
};
var person1 = new Person('liang');
console.log(person1.sayName()); //'liang'
console.log(person1.toString()); //'[Person liang]'
使用對(duì)象字面形式改寫原型對(duì)象改變了構(gòu)造函數(shù)的屬性,因此它現(xiàn)在指向Object而不是Person。
console.log(person1.constructor === Person); //false
console.log(person1.constructor === Object); //true
這是因?yàn)樵蛯?duì)象具有一個(gè)constructor屬性,這是對(duì)象實(shí)例所沒有的。當(dāng)一個(gè)函數(shù)被創(chuàng)建時(shí),它的prototype屬性也被創(chuàng)建,且該原型對(duì)象的constructor屬性指向該函數(shù)。當(dāng)使用對(duì)象字面形式改寫原型對(duì)象Person.prototype時(shí),其contructor屬性將被置為泛用對(duì)象Object。
為避免這一點(diǎn),手動(dòng)重置constructor屬性:
function Person(name){
this.name = name;
}
Person.prototype={
constructor:Person,
sayName: function(){
return this.name;
},
toString: function(){
return '[Person ' + this.name + ']';
}
};
構(gòu)造函數(shù)、原型對(duì)象、對(duì)象實(shí)例,這三者的關(guān)系:
對(duì)于構(gòu)造函數(shù)來說,prototype是作為構(gòu)造函數(shù)的屬性;對(duì)于對(duì)象實(shí)例來說,prototype是對(duì)象實(shí)例的原型對(duì)象。所以prototype即是屬性,又是對(duì)象。
所有一切對(duì)象的原型頂端,都是Object.prototype。
4.2.3 改變?cè)蛯?duì)象
給定類型的所有對(duì)象實(shí)例共享一個(gè)原型對(duì)象。[[Prototype]]屬性只是一個(gè)指向原型對(duì)象的指針,任何對(duì)原型對(duì)象的改變都立即反應(yīng)到所有引用它的對(duì)象實(shí)例上。
使用Object.seal()或Object.freeze()方法,將無法添加自有屬性或改變凍結(jié)對(duì)象的自有屬性,但可以通過在原型對(duì)象上添加屬性來擴(kuò)展這些對(duì)象實(shí)例。
var person1 = new Person('liang');
var person2 = new Person('zhu');
Object.freeze(person1);
Person.prototype.sayHi = function(){
console.log('Hi');
};
person1.sayHi();
person2.sayHi();
4.2.4 內(nèi)建對(duì)象的原型對(duì)象
所有內(nèi)建對(duì)象都有構(gòu)造函數(shù),因此也都有原型對(duì)象可改變。
Array.prototype.sum = function(){
return this.reduce(function(previous,current){ //reduce()是數(shù)組方法
return previous + current;
});
};
var numbers = [1,2,3,4,5];
numbers.sum(); //15
在sum() 內(nèi)部,this指向數(shù)組的對(duì)象實(shí)例numbers,于是該方法也可以自由使用數(shù)組的其他方法,比如reduce()。
內(nèi)建對(duì)象的原型對(duì)象雖然可以改變,但不建議在生產(chǎn)環(huán)境中使用??梢杂脕碜鰧?shí)驗(yàn)和驗(yàn)證新功能。