對象的屬性類型包含:數據屬性、訪問器屬性javascript
數據屬性vue
訪問器屬性java
沒有value和writable,多了一對get和set。數組
無論writable真假,只要configurable爲真,經過Object.defineProperty就能把特性改成訪問器屬性。瀏覽器
Object.getOwnPropertyDescriptor(o,」prop」);
:讀取屬性特性。安全
Object.getOwnPropertyDescriptors(o);
:讀取全部屬性特性。閉包
犀牛書裏把訪問器accessor稱爲存取器,我以爲這樣更形象。可使用直接量語法的擴展語法來定義屬性,好比下面兩種方法:app
var o={
name:'tiedan',
get ga(){return this.name},
set ga(value){this.name=value}
}
var p={name:'tiedan'};
Object.defineProperty(p,'ga',{
set:function(value){this.name},
get:function (){return this.name}});
console.log(Object.getOwnPropertyDescriptors(o));
console.log(Object.getOwnPropertyDescriptors(p));
/*{ name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get ga],set: [Function: set ga],enumerable: true,configurable: true } } { name:{ value: 'tiedan',writable: true,enumerable: true,configurable: true }, ga:{ get: [Function: get],set: [Function: set],enumerable: false,configurable: false } }*/
//方括號裏有細微區別。
複製代碼
使用Object.defineProperty
或者Ojbect.defineProperties
,不顯示定義configurable、writable、enumerable,則這三個特性默認值都爲false函數
vue的核心實現就是利用
Object.definProperty
劫持屬性的get和set特性,來實現雙向綁定的。測試
Object.preventExtensions
對象不能添加新屬性(可刪除原有屬性)。可經過Object.isExtensible來檢測對象是否能擴展。
Ojbect.seal
能夠密封對象,密封后的對象不能刪除和添加屬性。全部屬性特性的[[configurable]]都爲false。可經過Object.isSeal來檢測對象是否爲密封對象。
Object.freeze
能夠凍結對象,比Object.seal更進一步,對象不能有任何變化。全部特性均爲false。可經過Object.isFrozen來檢測對象是否爲凍結對象。
經過Object.getOwnPropertyNames
看到的Object.prototype的屬性,這部分屬性都是可繼承的。
[ 'constructor', //給全部對象的構造器屬性,建立函數時,就會自動建立函數原型,和其constructor屬性,若是重寫原型,則繼承這個
'__defineGetter__', //一些瀏覽器的非標準方法,在Object.defineProperty不支持的時候,能夠嘗試用這個定義屬性的特性。
'__defineSetter__', //同上相似
'hasOwnProperty', //檢測對象是否有自有的屬性
'__lookupGetter__', //非標準方法,用來返回命名屬性的Getter方法
'__lookupSetter__', //同上相似
'isPrototypeOf', //是否爲檢測對象的原型。其原理同instanceof同樣都是查找原型鏈,只是instanceof後面是構造函數
'propertyIsEnumerable',//屬性是不是可枚舉的,這個和Object.getOwnPropertyDescriptor()裏獲得的enumberable屬性是同樣的
'toString', //
'valueOf', //
'__proto__', //通常是內部屬性,一個原型指針
'toLocaleString' ] //
複製代碼
也能夠經過Object.getOwnPropertyNames
看看Object的屬性和方法。
犀牛書例子,最早由道格拉斯.克羅克福德提出,這我的在兩本書裏出現N屢次。下面的是犀牛書p122的例子,紅皮書相似的例子在p169。 Object.create()
建立一個新對象,參數是新對象的原型。create方法有兩個參數,第二個參數與defineProperties的第二個參數同樣。
function inherit(p){
if(p==null) throw TypeError();
//首先排除null,自己typeof就可能有null。Object.create是能夠傳null的
if(Object.create){
return Object.create(p);
}
var t=typeof p;
if(t!="object"&&t!="function")throw TypeError;//不能是基本類型值
function a(){}
a.prototype=p;
return new a();
}
複製代碼
new的實現大體以下:
o = {};
o.__proto__=f.prototype;
f.apply(o,arguments);
複製代碼
因此new Object();
不如直接用{}
執行快就是由於這個緣由嗎?
自定義的new以下:
function New(f) {
//返回一個func
return function () {
o = {};
o.__proto__=f.prototype;
f.apply(o, arguments);//繼承父類的屬性
return o; //返回一個Object
}
}
複製代碼
經過new fn();
或者Object.create(p);
其實都是返回新對象的方法。
「生產對象」,因此通常直接調用。不使用new(使用也能返回正常對象)。
function Person(name,age){
var o=new Object();
o.name=name;
o.age=age;
return o;
}
複製代碼
弊端:解決了對象建立問題,但沒有解決對象識別問題
function Person(name,age){
this.name=name;
this.age=age;
}
複製代碼
沒有顯示建立對象,直接將屬性和方法賦給this,(能夠)沒有return。經過new來調用。
實際上步驟:
var o={};//建立新對象
o.__proto__=fn.prototype;//對象的原型指針賦值
fn.apply(o,arguments);//this指向新對象,執行構造函數代碼
return o;//返回新對象
複製代碼
new fn()
就是相似上面的過程。構造函數fn的執行實際上就是給o對象賦值操做的過程。
弊端:方法重複定義,即使放到全局也只適合對象調用,還容易污染全局做用域。
function Person(){
}
Person.prototype.name='tiedan';
Person.prototype.age='1';
複製代碼
對於原型有兩種寫法:
fn.prototype.p1=value1;
fn.protptype.p2=value2;
複製代碼
fn.prototype={
p1:value1,
p2:value2
}
複製代碼
第二種看起來更清楚,可是存在問題,由於這至關於重寫原型對象。以前定義好的一些屬性方法可能會丟失,constructor屬性須要顯示從新指向構造函數。要不就沒了。在第二種重寫原型以前不能先new fn,這會致使重寫原型以後切斷了構造函數fn和最初的原型之間的聯繫。由於prototype屬性自己存儲的就是一個指針。而對象和原型之間的聯繫僅靠__proto__存儲的一個指針。至關於原型鏈出了問題,沒法經過這個屬性找到新的原型對象,得到新對象的屬性和方法。說白了就是fn和obj指向了不一樣的原型對象。
function Person(name,age){
this.name=name;
this.age=age;
}
Person.prototype.height=100;
var p1=new Person("tiedan",36);
Person.prototype={
constructor:Person,
height:180
}
var p2=new Person("someone",29);
console.log(p1 instanceof Person);//false,由於其__proto__指向的已經不是最新的Person.prototype。經管如此,p1.constructor.prototype仍是對的。
console.log(p1.height);//100
console.log(p2 instanceof Person);//false
console.log(p2.height);//180
複製代碼
弊端:除了上面所說的,還有就是由於原型對象的屬性和方法都是實例共享的。那麼對於屬性是引用類型,好比數組,你們也是共享的同一個引用對象。修改這類屬性值,實際上會影響到全部實例。
建議屬性值是引用類型的,不要用原型模式,除非你就須要這種共享引用類型的方式。
in
運算符有兩種用法,一種用於for...in。另外一種在單獨使用時,in
操做符會在經過對象可以訪問給定屬性時返回true,不管該屬性存在於實例中仍是原型中!
function hasPrototypeProperty(object,name){
return !object.hasOwnProperty(name)&&(name in object);
}
複製代碼
function Person(name,age){
this.name=name;
this.age=age;
if(typeof this.sayName!='function'){
Person.prototype.sayName=function(){console.log(this.name);}
}
}
複製代碼
相對比較完美的一種模式。
使用這種模式,一樣不能用對象字面量重寫原型。思考一下new的實現步驟,就能明白,構造函數裏重寫原型,則必然會在o對象建立以後切斷與原有原型的聯繫。
function Person(name,age){
var o=new Object();
o.name=name;
o.age=age;
return o;
}
var tiedan=new Person('鐵蛋',1);
console.log(tiedan instanceof Person);//false
複製代碼
這個模式和工廠模式的函數徹底同樣,只是這個直接經過new來調用。測試了一下,當作構造函數使用,末尾return會代替構造函數正常的return值。
弊端:返回的對象和構造函數沒什麼關係。不能依賴instanceof來檢測對象。(返回的其實是裏面的o類型)
durable object持久對象 克羅克福德發明
function Person(name,age){
var o=new Object();
o.sayName=function(){
alert(name);
};
return o;
}
var tiedan=Person('鐵蛋');
複製代碼
相似於寄生構造函數模式。沒有公共屬性,不引用this對象。適合在安全環境或防止數據被其餘應用程序改動時使用。1.不引this,2.不用new
實際上是利用了閉包原理。即使對象被添加屬性和方法,也沒法篡改原始值。
繼承分爲接口繼承和實現繼承
紅皮書p162說es中沒法實現接口繼承,其實es4版本是實現了接口繼承的(as3.0)。說不定哪天interface就從保留字變關鍵字了。
別忘記默認原型:Object.prototype
肯定原型和實例的關係,instanceof
,isPropertyOf
謹慎的定義方法,方法覆蓋,紅皮書p166必須用SuperType實例替換SubType的原型後再定義原型方法,不然原型對象一重寫就沒了。
原型鏈的問題:
相似以前原型模式引用類型的問題。SubType.prototype=new SuperType();
實際上就是重寫原型。原型屬性包含引用類型就容易出現意料以外的狀況。
constructor stealing 明明是偷非說是借~
function SubType(age){
SuperType.call(this,"tiedan");
this.age=age;
}
複製代碼
將借用構造函數和原型鏈技術結合
借用構造函數獲得實例屬性
原型鏈技術繼承原型屬性和方法,同時還能夠擴充本身的原型方法
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.prop=…
複製代碼
又是道格拉斯.克羅克福德... 提到了object(o)函數,犀牛書里名爲inherit(p)
var person={};
var anotherPerson=Object.create(person);
anotherPerson.prop=...
//繼承的引用類型會被實例共享
複製代碼
Object.create在使用一個參數的時候,行爲和object(o);相同,使用兩個參數的時候,第二個參數和Object.defineProperties的第二個參數格式相同。以這種方式指定的屬性會覆蓋原型對象的同名屬性。
其實inherit也能夠加第二個參數,也就是作一個淺複製,這能夠參看犀牛書的淺複製。extend方法。
var p2=Object.create({},{"hehe":{value:30 }});
console.log(p2);//{}
//什麼都看不見,由於和Object.defineProperties同樣,enumerable不顯示定義默認爲false,因此看不到。
複製代碼
克羅克福德推廣的
function createAnother(o){
var anotherPerson=Object.create(o);
anotherPerson.sayHello=function(){console.log('hello');}
return anotherPerson;
}
var anotherPerson=createAnother({});
anotherPerson.sayHello();
複製代碼
主要考慮對象而不是自定義類型和構造函數的狀況下使用
寄生式繼承的思路與寄生構造函數和工廠模式相似。建立一個用來封裝繼承過程的函數。在函數內部以某種方式加強對象,最後像本身作了全部工做同樣返回對象。
以前的組合式繼承最大的問題是要調用兩次父類構造函數,一次是借用構造函數,一次是重寫子類原型。其實不必調用兩次構造函數。能夠只調用一次借用構造函數,另外一次寄生式new一個空構造函數。只爲了複製父類原型屬性和方法。
function SuperType(name){
this.name=name;
this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}
function subType(name,age){
SuperType.call(this,name);//第二次調用
this.age=age;
}
SubType.prototype=new SuperType();//第一次調用
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){console.log(this.age);}
複製代碼
改成:
function inheritPrototype(SubType,SuperType){
var prototype=Object.create(SuperType.prototype);//建立對象
prototype.constructor=SubType;//加強對象
SubType.prototype=prototype;//指定對象,依然重寫原型啊...
}
function SuperType(name){
this.name=name;
this.colors=['red','green','blue'];
}
SuperType.prototype.sayName=function(){console.log(this.name);}
function SubType(name,age){
SuperType.call(this,name);//第二次調用
this.age=age;
}
inheritPrototype(SubType,SuperType);//第一次調用
SubType.prototype.sayAge=function(){console.log(this.age);}
複製代碼
上面例子依然重寫了原型啊。因此,順序很重要。不然,最後兩行換一下位置,sayAge就被蓋掉了。