面向對象
對象:是無序屬性的鳩合,其屬性可以包括基礎值、對象或許函數。
new運算符
建立一個用戶定義的對象範例的實例或具有組織函數的內置對象的實例。
當代碼 new Foo(…) 實行時,會發作以下事變:
- 一個繼承自Foo.prototype的新對象被建立
- 運用指定的參數挪用組織函數 Foo ,並將 this 綁定到新建立的對象。
屬性範例
ECMAScript 中有兩種屬性:數據屬性和接見器屬性。
數據屬性
數據屬性包括一個數據值的位置。在這個位置可以讀取和寫入值。數據屬性有 4 個形貌其行動的特徵。
- Configurable:示意可否經由歷程 delete 刪除屬性從而從新定義屬性,可否修正屬性的特徵,或許可否把屬性修正為接見器屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特徵默許值為 true。
- Enumerable:示意可否經由歷程 for-in 輪迴返回屬性。像前面例子中那樣直接在對象上定義的屬性,它們的這個特徵默許值為 true。
- Writable:示意可否修正屬性的值
- Value:包括這個屬性的數據值。讀取屬性值的時刻,從這個位置讀;寫入屬性值的時刻,把新值保留在這個位置。這個特徵的默許值為 undefined。
要修正屬性默許的特徵,必須運用 ECMAScript 5 的 Object.defineProperty()要領。這個要領 吸收三個參數:屬性地點的對象、屬性的名字和一個形貌符對象。
var person = {};
Object.defineProperty(person, "name", {
writable: false,
value: "Nicholas"
});
alert(person.name); //"Nicholas"
person.name = "Greg";
alert(person.name); //"Nicholas"
把 configurable 設置為 false,示意不能從對象中刪除屬性。假如對這個屬性挪用 delete,則 在非嚴厲形式下什麼也不會發作,而在嚴厲形式下會致使毛病。而且,一旦把屬性定義為不可設置的, 就不能再把它變回可設置了。此時,再挪用 Object.defineProperty()要領修正除 writable 以外 的特徵,都邑致使毛病:
var person = {};
Object.defineProperty(person, "name", {
configurable: false,
value: "Nicholas"
});
//拋出毛病
Object.defineProperty(person, "name", {
configurable: true,
value: "Nicholas"
});
接見器屬性
接見器屬性不包括數據值;它們包括一對兒 getter 和 setter 函數(不過,這兩個函數都不是必須的)。 在讀取接見器屬性時,會挪用 getter 函數,這個函數擔任返回有用的值;在寫入接見器屬性時,會挪用 setter 函數並傳入新值,這個函數擔任決議如何處置懲罰數據。接見器屬性有以下 4 個特徵。
- Configurable:示意可否經由歷程 delete 刪除屬性從而從新定義屬性,可否修正屬性的特 性,或許可否把屬性修正為數據屬性。關於直接在對象上定義的屬性,這個特徵的默許值為 true。
- Enumerable:示意可否經由歷程 for-in 輪迴返回屬性。關於直接在對象上定義的屬性,這 5 個特徵的默許值為 true
- Get:在讀取屬性時挪用的函數。默許值為 undefined
- Set:在寫入屬性時挪用的函數。默許值為 undefined
接見器屬性不能直接定義,必須運用 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;
} }
});
book.year = 2005;
alert(book.edition); //2
不一定非要同時指定 getter 和 setter。只指定 getter 意味着屬性是不能寫,嘗試寫入屬性會被疏忽。 在嚴厲形式下,嘗試寫入只指定了 getter 函數的屬性會拋出毛病。相似地,只指定 setter 函數的屬性也不能讀,否則在非嚴厲形式下會返回 undefined,而在嚴厲形式下會拋出毛病。
建立對象
工場形式
用函數來封裝以特定接口建立對象的細節
function createPerson(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var person1 = createPerson("Nicholas", 29, "Software Engineer");
var person2 = createPerson("Greg", 27, "Doctor");
函數 createPerson()可以依據接收的參數來構建一個包括一切必要信息的 Person 對象。可以無數次地挪用這個函數,而每次它都邑返回一個包括三個屬性一個要領的對象。
工場形式雖然處理了建立多個相似對象的題目,但卻沒有處理對象辨認的題目(即如何曉得一個對象的範例)。跟着 JavaScript 的生長,又一個新形式湧現了。
組織函數形式
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.sayName = function(){
alert(this.name);
};
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
優點:
- 沒有顯式地建立對象;
- 直接將屬性和要領賦給了 this 對象;
- 沒有 return 語句。
person1 和 person2 離別保留着 Person 的一個差別的實例。這兩個對象都有一個 constructor(組織函數)屬性,該屬性指向 Person,以下所示
alert(person1.constructor == Person); //true
alert(person2.constructor == Person); //true
對象的 constructor 屬性最初是用來標識對象範例的。然則,提到檢測對象範例,照樣 instan- ceof 操作符要更牢靠一些。我們在這個例子中建立的一切對象既是 Object 的實例,同時也是 Person 的實例,這一點經由歷程 instanceof 操作符可以獲得考證。
alert(person1 instanceof Object); //true
alert(person1 instanceof Person); //true
alert(person2 instanceof Object); //true
alert(person2 instanceof Person); //true
將組織函數看成函數
組織函數與其他函數的唯一區分,就在於挪用它們的體式格局差別。不過,組織函數畢竟也是函數,不 存在定義組織函數的特別語法。任何函數,只需經由歷程 new 操作符來挪用,那它就可以作為組織函數;而 任何函數,假如不經由歷程 new 操作符來挪用,那它跟一般函數也不會有什麼兩樣。
// 看成組織函數運用
var person = new Person("Nicholas", 29, "Software Engineer");
person.sayName(); //"Nicholas"
// 作為一般函數挪用
Person("Greg", 27, "Doctor"); // 增加到window
window.sayName(); //"Greg"
// 在另一個對象的作用域中挪用
var o = new Object();
Person.call(o, "Kristen", 25, "Nurse");
o.sayName(); //"Kristen"
這個例子中的前兩行代碼展現了組織函數的典範用法,即運用 new 操作符來建立一個新對象。
接下來的兩行代碼展現了不運用new 操作符挪用 Person()會湧現什麼效果:屬性和要領都被增加給 window 對象了。當在全局作用域中挪用一個函數時,this對象老是指向Global 對象(在瀏覽器中就是window對象)。因而,在挪用完函數以後,可以經由歷程 window 對象來挪用 sayName()要領,而且還返回了”Greg”。
末了,也可以運用call()(或許apply())在某個特別對象的作用域中挪用Person()函數。這裡是在對象o的作用域中挪用的,因而挪用后o就具有了一切屬性和sayName()要領。
組織函數的題目
運用組織函數的主要題目,就是每一個要領都要在每一個 實例上從新建立一遍。在前面的例子中,person1 和 person2 都有一個名為 sayName()的要領,但那 兩個要領不是同一個 Function 的實例。
以這類體式格局建立函數,會致使差別的作用域鏈和標識符剖析,但建立 Function 新實例的機制仍然是雷同的。因而,差別實例上的同名函數是不相等的,以下代碼可以證實這一點
alert(person1.sayName == person2.sayName); //false
原型形式
我們建立的每一個函數都有一個 prototype(原型)屬性,這個屬性是一個指針,指向一個對象, 而這個對象的用處是包括可以由特定範例的一切實例同享的屬性和要領。假如根據字面意思來明白,那 么 prototype 就是經由歷程挪用組織函數而建立的誰人對象實例的原型對象。運用原型對象的優點是可以 讓一切對象實例同享它所包括的屬性和要領。換句話說,沒必要在組織函數中定義對象實例的信息,而是 可以將這些信息直接增加到原型對象中,以下面的例子所示。
function Person(){}
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function(){
alert(this.name);
};
var person1 = new Person();
person1.sayName(); //"Nicholas"
var person2 = new Person();
person2.sayName(); //"Nicholas"
alert(person1.sayName == person2.sayName); //true
明白原型對象
不管什麼時刻,只需建立了一個新函數,就會依據一組特定的規則為該函數建立一個 prototype屬性,這個屬性指向函數的原型對象。在默許情況下,一切原型對象都邑自動獲得一個 constructor (組織函數)屬性,這個屬性包括一個指向 prototype 屬性地點函數的指針。就拿前面的例子來講, Person.prototype.constructor 指向 Person。而經由歷程這個組織函數,我們還可繼承為原型對象增加其他屬性和要領。
建立了自定義的組織函數以後,其原型對象默許只會獲得 constructor 屬性;至於其他要領,則都是從 Object 繼承而來的。當挪用組織函數建立一個新實例后,該實例的內部將包括一個指針(內部 屬性),指向組織函數的原型對象。ECMA-262 第 5 版中管這個指針叫[[Prototype]]。雖然在劇本中 沒有範例的體式格局接見[[Prototype]],但 Firefox、Safari 和 Chrome 在每一個對象上都支撐一個屬性 __proto__;而在其他完成中,這個屬性對劇本則是完整不可見的。不過,要明白的真正主要的一點就 是,這個銜接存在於實例與組織函數的原型對象之間,而不是存在於實例與組織函數之間。
運用 hasOwnProperty()要領可以檢測一個屬性是存在於實例中,照樣存在於原型中。這個要領(不 要忘了它是從 Object 繼承來的)只在給定屬性存在於對象實例中時,才會返回 true。
有兩種體式格局運用 in 操作符:零丁運用和在 for-in 輪迴中運用。在零丁運用時,in 操作符會在通 過對象可以接見給定屬性時返回 true,不管該屬性存在於實例中照樣原型中。
因為 in 操作符只需經由歷程對象可以接見到屬性就返回 true,hasOwnProperty()只在屬性存在於 實例中時才返回 true,因而只需 in 操作符返回 true 而 hasOwnProperty()返回 false,就可以確 定屬性是原型中的屬性。
原型對象的題目
一切實例在默許情況下都將獲得雷同的屬性值
function Person() {}
Person.prototype = {
constructor: Person,
name : "Nicholas",
age : 29,
job : "Software Engineer",
friends : ["Shelby", "Court"],
sayName : function () {
alert(this.name);
}
};
var person1 = new Person();
var person2 = new Person();
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Court,Van"
alert(person2.friends); //"Shelby,Court,Van"
alert(person1.friends === person2.friends); //true
Person.prototype對象有一個名為friends的屬性,該屬性包括一個字符串數組。然後, 建立了 Person 的兩個實例。接着,修正了 person1.friends 援用的數組,向數組中增加了一個字符 串。因為 friends 數組存在於 Person.prototype 而非 person1 中,所以方才提到的修正也會經由歷程 person2.friends(與 person1.friends 指向同一個數組)反應出來。
組合運用組織函數形式和原型形式
組織函數形式用於定義實例屬性,而原型形式用於定義要領和同享的屬性。效果,每一個實例都邑有本身的一份實例屬性的副本,但同時又同享着對要領的援用,最大限制地節省了內存。別的,這類混成形式還支撐向組織函數通報參數;可謂是集兩種形式之長
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Shelby", "Court"];
}
Person.prototype = {
constructor : Person,
sayName : function(){
alert(this.name);
}
}
var person1 = new Person("Nicholas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor");
person1.friends.push("Van");
alert(person1.friends); //"Shelby,Count,Van"
alert(person2.friends); //"Shelby,Count"
alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true
動態原型形式
經由歷程搜檢某個應當存在的要領是不是有用,來決議是不是須要初始化原型。
function Person(name, age, job){
//屬性
this.name = name;
this.age = age;
this.job = job;
//要領
if (typeof this.sayName != "function"){
Person.prototype.sayName = function(){
alert(this.name);
};
}
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName();
寄生組織函數形式
這類形式的基礎思想是建立一個函數,該函數的作用僅僅是封裝建立對象的代碼,然後再返回新建立的對象
function Person(name, age, job){
var o = new Object();
o.name = name;
o.age = age;
o.job = job;
o.sayName = function(){
alert(this.name);
};
return o;
}
var friend = new Person("Nicholas", 29, "Software Engineer");
friend.sayName(); //"Nicholas"
在這個例子中,Person 函數建立了一個新對象,並以響應的屬性和要領初始化該對象,然後又返 回了這個對象。除了運用 new 操作符並把運用的包裝函數叫做組織函數以外,這個形式跟工場形式實在 是如出一轍的。組織函數在不返回值的情況下,默許會返回新對象實例。而經由歷程在組織函數的末端增加一個 return 語句,可以重寫挪用組織函數時返回的值。
這個形式可以在特別的情況下用來為對象建立組織函數。假定我們想建立一個具有分外要領的特別數組。因為不能直接修正 Array 組織函數,因而可以運用這個形式。
function SpecialArray(){
//建立數組
var values = new Array();
values.push.apply(values, arguments);
//增加要領
values.toPipedString = function(){
return this.join("|");
};
//返回數組
return values;
}
var colors = new SpecialArray("red", "blue", "green");
alert(colors.toPipedString()); //"red|blue|green"
須要申明:起首,返回的對象與組織函數或許與組織函數的原型屬性之間沒有關聯;也就是說,組織函數返回的對象與在組織函數外部建立的對象沒有什麼差別。為此,不能依靠 instanceof 操作符來肯定對象範例。因為存在上述題目,我們發起在可以運用其他形式的情況下,不要運用這類形式。
穩妥組織函數形式
所謂穩妥對象,指的是沒有大眾屬性,而且其要領也不援用 this 的對象。穩妥對象最適合在 一些平安的環境中(這些環境中會制止運用 this 和 new),或許在防備數據被其他應用順序(如 Mashup 順序)修改時運用。穩妥組織函數遵照與寄生組織函數相似的形式,但有兩點差別:一是新建立對象的 實例要領不援用 this;二是不運用 new 操作符挪用組織函數。根據穩妥組織函數的請求,可以將前面 的 Person 組織函數重寫以下。
function Person(name, age, job){
//建立要返回的對象
var o = new Object();
//可以在這裏定義私有變量和函數
//增加要領
o.sayName = function(){
alert(name);
};
//返回對象
return o;
}
在以這類形式建立的對象中,除了運用 sayName()要領以外,沒有其他方法接見 name 的值。 可以像下面運用穩妥的 Person 組織函數。
縱然有其他代碼會給這個對象增加要領或數據成員,但也不可能有別的方法接見傳 入到組織函數中的原始數據。穩妥組織函數形式供應的這類平安性,使得它異常適合在某些平安實行環 境——比方,ADsafe(www.adsafe.org)和 Caja(http://code.google.com/p/goog… )供應的環境—— 下運用。
繼承
原型鏈
組織函數、原型和實例的關聯:每一個組織函數都有一個原型對象,原型對象都包括一個指向組織函數的指針,而實例都包括一個指向原型對象的內部指針。
function SuperType(){
this.property = true;
}
SuperType.prototype.getSuperValue = function(){
return this.property;
}
function SubType(){
this.subproperty = false;
}
//繼承了 SuperType
SubType.prototype = new SuperType();
SubType.prototype.getSubValue = function (){
return this.subproperty;
}
var instance = new SubType();
alert(instance.getSuperValue()); //true
題目:包括援用範例值的原型屬性會被一切實例同享;在建立子範例的實例時,不能向超範例的組織函數中通報參數
借用組織函數
在子範例組織函數的內部挪用超範例組織函數;函數只不過是在特定環境中實行代碼的對象, 因而經由歷程運用 apply()和 call()要領也可以在(將來)新建立的對象上實行組織函數,以下所示
function SuperType() {
this.colors = ["red", "blue", "green"]
}
function SubType(){
//繼承了 SuperType
SuperType.call(this);
}
var instance1 = new SubType();
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
var instance2 = new SubType();
alert(instance2.colors); //"red,blue,green"
代碼中“借調”了超範例的組織函數。經由歷程運用 call()要領(或 apply()要領 也可以),我們實際上是在(將來將要)新建立的 SubType 實例的環境下挪用了 SuperType 組織函數。 這樣一來,就會在新 SubType 對象上實行 SuperType()函數中定義的一切對象初始化代碼。效果, SubType 的每一個實例就都邑具有本身的 colors 屬性的副本了。
上風:相關於原型鏈而言,借用組織函數有一個很大的上風,即可以在子範例組織函數中向超範例組織函數通報參數。
題目:假如僅僅是借用組織函數,那末沒法防止組織函數形式存在的題目——要領都在組織函數中定義,因而函數復用就無從談起了。而且,在超範例的原型中定義的要領,對子範例而言也是不可見的,結 果一切範例都只能運用組織函數形式。考慮到這些題目,借用組織函數的手藝也是很少零丁運用的。
組合繼承
思緒:運用原型鏈完成對原型屬性和要領的繼承,而經由歷程借用組織函數來完成對實例屬性的繼承。
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(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(){
alert(this.age);
};
var instance1 = new SubType("Nicholas", 29);
instance1.colors.push("black");
alert(instance1.colors); //"red,blue,green,black"
instance1.sayName(); //"Nicholas";
instance1.sayAge(); //29
var instance2 = new SubType("Greg", 27);
alert(instance2.colors); //"red,blue,green"
instance2.sayName(); //"Greg";
instance2.sayAge(); //27
原型式繼承
藉助原型可以基於已有的對象建立新對象,同時還沒必要因而建立自定義範例。
function object(o){
function F(){}
F.prototype = o
return new F()
在object()函數內部,先建立了一個暫時性的組織函數,然後將傳入的對象作為這個組織函數的原型,末了返回了這個暫時範例的一個新實例。從本質上講,object()對傳入个中的對象實行了一次淺複製。e.g.
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = object(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = object(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
// 這相當於建立了 person 對象的兩個副本。
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
ECMAScript 5 經由歷程新增 Object.create()要領範例化了原型式繼承。這個要領吸收兩個參數:一 個用作新對象原型的對象和(可選的)一個為新對象定義分外屬性的對象。在傳入一個參數的情況下, Object.create()與 object()要領的行動雷同。
var person = {
name: "Nicholas",
friends: ["Shelby", "Court", "Van"]
};
var anotherPerson = Object.create(person);
anotherPerson.name = "Greg";
anotherPerson.friends.push("Rob");
var yetAnotherPerson = Object.create(person);
yetAnotherPerson.name = "Linda";
yetAnotherPerson.friends.push("Barbie");
alert(person.friends); //"Shelby,Court,Van,Rob,Barbie"
寄生式繼承
寄生式繼承的思緒與寄生組織函數和工場形式相似,即建立一個僅用於封裝繼承歷程的函數,該函數在內部以某種體式格局來加強對象,末了再像真地是它做了一切事情一樣返回對象。以下代碼樹模了寄生式繼承形式
function createAnother(original){
var clone = object(original) //經由歷程挪用函數建立一個新對象
clone.sayHi = function() { //以某種體式格局來加強這個對象
alert('hi')
}
return clone; //返回這個對象
}
寄生組合式繼承
組合繼承最大的 題目就是不管什麼情況下,都邑挪用兩次超範例組織函數:一次是在建立子範例原型的時刻,另一次是 在子範例組織函數內部。沒錯,子範例最終會包括超範例對象的悉數實例屬性,但我們不得不在挪用子 範例組織函數時重寫這些屬性。
寄生組合式繼承的基礎形式以下所示。
function inheritPrototype(subType, superType){
var prototype = object(superType.prototype); //建立超範例原型的一個副本
prototype.constructor = subType; //為建立的副本增加 constructor 屬性,從而填補因重寫原型而落空的默許的 constructor 屬性
subType.prototype = prototype; //將新建立的對象(即副本)賦值給子範例的原型
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
};
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType);
SubType.prototype.sayAge = function(){
alert(this.age);
}