摘要:對(duì)象對(duì)象創(chuàng)建繼承早期創(chuàng)建對(duì)象的方式對(duì)象字面量創(chuàng)建方式亦可換成因?yàn)橹赶虍?dāng)前對(duì)象的兩種屬性數(shù)據(jù)屬性和訪問(wèn)器屬性數(shù)據(jù)屬性數(shù)據(jù)屬性包含個(gè)數(shù)據(jù)值的位置這個(gè)位置可以讀取和寫(xiě)入值名稱描述表示能否通過(guò)刪除屬性而重新定義屬性能否修改屬性的特性或者能否把屬性修
JavaScript OOP, 對(duì)象, 對(duì)象創(chuàng)建, 繼承
//早期創(chuàng)建對(duì)象的方式 var jonslike = new Object(); jonslike.name = "jon"; jonslike.like = "wow"; jonslike.saylike = function(){ console.log(this.name); }; //對(duì)象字面量創(chuàng)建方式 var jonslike = { name : "jon", like : "wow", saylike : function(){ console.log(jonslike.like); //亦可換成this, 因?yàn)橹赶虍?dāng)前對(duì)象 console.log(this.like); //wow } };
ES的兩種屬性, 數(shù)據(jù)屬性和訪問(wèn)器屬性
數(shù)據(jù)屬性數(shù)據(jù)屬性包含1個(gè)數(shù)據(jù)值的位置, 這個(gè)位置可以讀取和寫(xiě)入值
名稱 | 描述 |
---|---|
[[ Configurable ]] | 表示能否通過(guò)delete刪除屬性而重新定義屬性, 能否修改屬性的特性, 或者能否把屬性修改為訪問(wèn)器屬性, 默認(rèn)值true |
[[ Enumerable ]] | 表示能否通過(guò)for-in循環(huán)返回屬性, 默認(rèn)值true |
[[ Writable ]] | 表示能否修改屬性的值, 默認(rèn)值true |
[[ Value ]] | 包含這個(gè)屬性的數(shù)據(jù)值. 讀取屬性值的時(shí)候, 從這個(gè)位置讀; 寫(xiě)入屬性值的時(shí)候, 把新值保存在這個(gè)位置, 默認(rèn)值undefined |
var person = { name : Jon, //value值變成Jon };
接收3個(gè)參數(shù), 屬性所在的對(duì)象, 屬性的名字, 一個(gè)描述符對(duì)象
描述符對(duì)象的屬性必須是 : configurable, enumerable, writable, value
//設(shè)置為不可寫(xiě) : var person = {}; person.name = "fire"; Object.defineProperty(person, "name", { writable : false, //設(shè)置為只讀, 不可寫(xiě) value : "jon" }); person.name = "mark"; alert(person.name); //輸出還是jon
//設(shè)置為不可配置 : var person = {}; Object.defineProperty(person, "name" , { configurable : false, //設(shè)置為不可配置 value : "jon", }); person.name = "mark"; //無(wú)效! delete person.name; //刪除無(wú)效! alert(person.name); //依然能輸出jon //NOTE : 配置configurable為false時(shí), 其他3各特性也有相應(yīng)的限制訪問(wèn)器屬性
訪問(wèn)器屬性不包括數(shù)據(jù)值;
包含一對(duì)getter 與 setter 函數(shù) (非必須)
讀取訪問(wèn)器屬性時(shí), 會(huì)調(diào)用getter函數(shù),該函數(shù)負(fù)責(zé)返回有效的值;
寫(xiě)入訪問(wèn)器屬性時(shí), 會(huì)調(diào)用setter函數(shù),該函數(shù)負(fù)責(zé)決定如何處理數(shù)據(jù);
名稱 | 描述 |
---|---|
[[ Configurable ]] | 表示能否通過(guò)delete刪除屬性而重新定義屬性, 能否修改屬性的特性, 或者能否把屬性修改為訪問(wèn)器屬性, 默認(rèn)值true |
[[ Enumerable ]] | 表示能否通過(guò)for-in循環(huán)返回屬性, 默認(rèn)值true |
[[ Get ]] | 在讀取屬性時(shí)調(diào)用的函數(shù), 默認(rèn)值undefined |
[[ Set ]] | 在寫(xiě)入屬性時(shí)調(diào)用的函數(shù), 默認(rèn)值undefined |
定義訪問(wèn)器屬性 : 使用Object.defineProperty()方法.
//創(chuàng)建book對(duì)象, var book = { //定義兩個(gè)默認(rèn)的屬性, _year和edition, 下劃線定義的屬性表示只能通過(guò)對(duì)象方法訪問(wèn) _year : 2004, edition : 1 }; Object.defineProperty(book, "year", { get : function(){ return this._year; }, set : function(){ if(newValue > 2004){ this._year = newValue; this.edition += newValue - 2004; } }, }); book.year = 2005; alert(book.edition); //2
定義多個(gè)屬性 : defineProperties()
可以通過(guò)描述符一次過(guò)定義多個(gè)屬性
接收兩個(gè)參數(shù) :
要添加和修改其屬性的對(duì)象
第二個(gè)對(duì)象的屬性與第一個(gè)對(duì)象中要添加或修改的屬性一一對(duì)應(yīng).
var book = {}; Object.defineProperties(book, { _year : { value : 2004, }, edition : { value : 1, }, year : { get : funciton(){ return this._year, }, set : function(){ if(newValue > 2004){ this._year = newValue, this.edition += newValue - 2004, } } } });
讀取屬性的特性 : 使用Object.getOwnPropertyDescriptor();
可以取得給定屬性的描述符
接收兩個(gè)參數(shù) :
屬性所在的對(duì)象, 要讀取其描述符的屬性名稱
返回值 : 一個(gè)對(duì)象, 如果返回的對(duì)象是訪問(wèn)器屬性, 則這個(gè)對(duì)象的屬性有configurable, enumerable, get, set; 如果返回的對(duì)象是數(shù)據(jù)屬性, 則這個(gè)對(duì)象的屬性有configurable, enumerable, writable, value
var book = {}; Object.defineProperties(book, { _year : { value : 2004, }, edition : { value : 1, }, year : { get : funciton(){ return this._year, }, set : function(){ if(newValue > 2004){ this._year = newValue, this.edition += newValue - 2004, } } } }); var descriptor = Object.getOwnPropertyDescriptor(book, "_year"); //數(shù)據(jù)屬性 alert(descriptor.value); //2004(最初的值) alert(descriptor.configurable); //false(最初的值) alert(typeof descriptor.get); // undefined var descriptor = Object.getOwnPropertyDescriptor(book, year); //訪問(wèn)器屬性 alert(descriptor.value); // undefined(訪問(wèn)器沒(méi)有value屬性) alert(descriptor.enumerable); // false alert(typeof descriptor.get); // function(一個(gè)指向getter的指針)創(chuàng)建對(duì)象 工廠模式
工廠模式抽象了創(chuàng)建具體對(duì)象的過(guò)程;
該模式?jīng)]有解決對(duì)象的識(shí)別問(wèn)題(即怎樣知道一個(gè)對(duì)象的類型)
function createPerson(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayJob = function(){ console.log(this.job); }; return o; } var p1 = createPerson("Jon",25,"FrontEnd Developer"); var p2 = createPerson("Mark",24,"DBA"); p1.sayJob(); //FrontEnd Developer p2.sayJob(); //DBA構(gòu)造函數(shù)模式
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayJob = function(){ console.log(this.job); }; } //使用new操作符創(chuàng)建Person的新實(shí)例 /* 調(diào)用構(gòu)造函數(shù)會(huì)經(jīng)歷以下步驟 : 1. 創(chuàng)建一個(gè)新對(duì)象; 2.將構(gòu)造函數(shù)的作用域賦給新對(duì)象(因此this就指向了這個(gè)新對(duì)象) 3.執(zhí)行構(gòu)造函數(shù)中的代碼(為這個(gè)新對(duì)象添加屬性) 4.返回新對(duì)象 */ var p1 = new Person("Jon", 25, "FrontEnd Developer"); var p2 = new Person("Mark", 24, "DBA"); p1.sayJob(); //FrontEnd Developer p2.sayJob(); //DBA //新對(duì)象具有一個(gè)constructor(構(gòu)造函數(shù))屬性, 指向原創(chuàng)建的構(gòu)造函數(shù)(即Person) console.log(p1.constructor == Person); //true console.log(p2.constructor == Person); //true //使用instanceof操作符檢測(cè)對(duì)象類型會(huì)更可靠 console.log(p1 instanceof Object); //Object是終極父類, 所以返回true console.log(p1 instanceof Person); //p1是Person構(gòu)造函數(shù)的實(shí)例 console.log(p2 instanceof Object); //Object是終極父類, 所以返回true console.log(p2 instanceof Person); //p2是Person構(gòu)造函數(shù)的實(shí)例 //構(gòu)造函數(shù)本身也是函數(shù), 所以可以當(dāng)做普通函數(shù)來(lái)調(diào)用(不使用new操作符調(diào)用) Person("Martin", 27, "PHPer"); //添加到window對(duì)象(全局作用域中) window.sayJob(); //PHPer //在另一個(gè)對(duì)象的作用域調(diào)用(使用call()或者apply()) var o1 = new Object(); Person.call(o1, "Kiki", 23, "Singer"); o1.sayJob(); //Singer原型模式
每個(gè)函數(shù)都有一個(gè)prototype(原型)屬性, 是一個(gè)指針, 指向一個(gè)對(duì)象
對(duì)象的用途是包含可以由特定類型的所有實(shí)例共享的屬性和方法;
prototype就是通過(guò)調(diào)用構(gòu)造函數(shù)而創(chuàng)建的那個(gè)對(duì)象實(shí)例的對(duì)象
使用原型對(duì)象的好處是可以讓所有對(duì)象實(shí)例共享它所包含的屬性和方法;
即 :?
不必在構(gòu)造函數(shù)中定義對(duì)象實(shí)例的信息, 而是可以將這些信息直接添加到原型對(duì)象中;
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "f2e"; Person.prototype.sayName = function(){ alert(this.name); }; var p1 = new Person(); p1.sayName(); //jon var p2 = new Person(); p2.sayName(); //jon alert(p1.sayName == p2.sayName); //true
無(wú)論何時(shí), 只要?jiǎng)?chuàng)建了一個(gè)新函數(shù), 就會(huì)根據(jù)一組特定的規(guī)則為該函數(shù)創(chuàng)建一個(gè)prototype屬性, 該屬性指向函數(shù)的 原型對(duì)象
即 : (新函數(shù)會(huì)創(chuàng)建一個(gè)prototype屬性指向原型對(duì)象)
默認(rèn)情況下, 所有原型對(duì)象會(huì)自動(dòng)獲得一個(gè)constructor構(gòu)造函數(shù)屬性, 該屬性包含指向prototype屬性所在函數(shù)的指針
即 : (所有原型對(duì)象獲得一個(gè)constructor(構(gòu)造函數(shù))屬性,包含指向prototype屬性所在函數(shù)的指針)
function Person(){}; //這是(空)構(gòu)造函數(shù),會(huì)有一個(gè)prototype屬性,指向(下面的)原型對(duì)象 //Person.prototype : 這是(構(gòu)造函數(shù)的)原型對(duì)象, 會(huì)自動(dòng)獲得一個(gè)constructor(構(gòu)造函數(shù))屬性, 包含一個(gè)指向prototype屬性所在函數(shù)的指針,在這里即上面的Person()函數(shù); 即Person.prototype.constructor指向(上面的)Person()函數(shù) //下面這些是(構(gòu)造函數(shù)的)原型對(duì)象的自定義屬性s Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "f2e"; Person.prototype.sayName = function(){ alert(this.name); }; //這是實(shí)例,內(nèi)部包含一個(gè)指針(內(nèi)部屬性) [[Prototype]], 指向構(gòu)造函數(shù)的原型對(duì)象(即上面的Person.prototype) var p1 = new Person(); p1.sayName(); //jon var p2 = new Person(); p2.sayName(); //jon alert(p1.sayName == p2.sayName); //true
isPrototypeOf() : 確定是否為給定實(shí)例的原型
getPrototypeOf() [ES5] : 跟上面的功能一樣, 并且這方法可以返回原型對(duì)象給定屬性的值
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; var p1 = new Person(); p1.sayJob(); //FrontEnd Developer //測(cè)試Person是否為p1的原型 console.log(Person.prototype.isPrototypeOf(p1)); //true //如果支持ES5的getPrototypeOf() if(Object.getPrototypeOf){ //測(cè)試Person是否為p1的原型 console.log(Object.getPrototypeOf(p1) == Person.prototype); //true //輸出p1的name屬性的值 console.log(Object.getPrototypeOf(p1).name); //Jon }
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; var p1 = new Person(); p1.job = "DBA"; p1.sayJob(); //DBA
delete操作符可以刪除實(shí)例的屬性
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; var p1 = new Person(); p1.job = "DBA"; p1.sayJob(); //返回自身添加的屬性, DBA delete p1.job; //刪除p1的job屬性 p1.sayJob(); //返回原型的屬性, FrontEnd Developer
hasOwnProperty()可以檢查一個(gè)屬性是位于實(shí)例還是原型中, 屬于實(shí)例會(huì)返回true
in操作符會(huì)在對(duì)象能訪問(wèn)給定屬性時(shí)返回true,無(wú)論是實(shí)例還是原型 : (就是有這個(gè)屬性就會(huì)返回true)
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; var p1 = new Person(); console.log(p1.hasOwnProperty("name")); //實(shí)例中沒(méi)有自己定義的name屬性, 返回false console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的) p1.name = "Mark"; //自己定義一個(gè)實(shí)例中的name屬性, 覆蓋原型繼承而來(lái)的name console.log(p1.hasOwnProperty("name")); //實(shí)例中有自己定義的name屬性(Mark), 返回true console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的) delete p1.name; //刪除p1實(shí)例的name屬性 console.log(p1.hasOwnProperty("name")); //p1的name屬性已經(jīng)被delete操作符刪除, 所以現(xiàn)在又沒(méi)了自身實(shí)例的name屬性, 所以返回false console.log("name" in p1); //true, p1中有name屬性(從Person中的name繼承而來(lái)的)
可以同時(shí)使用hasOwnProperty()和in操作符, 以確定給定的屬性是位于實(shí)例還是原型中 :
in操作符只要能訪問(wèn)給定屬性就返回true, hasOwnProperty()只在屬性屬于實(shí)例才返回true,
因此只要in操作符返回true而hasOwnProperty()返回false, 就能確定給定的屬性是原型的屬性
//obj表示要傳入的實(shí)例名稱, name表示要測(cè)試的實(shí)例屬性 function hasPrototypeProperty(obj, name){ //如果傳入的實(shí)例屬性name不屬于該實(shí)例obj(取反), 并且(&&)實(shí)例obj中有該傳入的屬性name, 則返回 return !obj.hasOwnProperty(name) && (name in obj); } function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; var p1 = new Person(hasPrototypeProperty(p1, "name")); console.log(hasPrototypeProperty(p1, "job")); //p1中還沒(méi)有定義實(shí)例的job屬性, 只使用了原型繼承而來(lái)的job屬性, 所以返回true (hasOwnProperty()返回!false(取反false, 即true), in操作符返回true) p1.job = "DBA"; //p1定義自身的實(shí)例屬性job console.log(hasPrototypeProperty(p1, "job")); //false (!hasOwnProperty(job)為 !true,即false, in返回true)
使用for-in返回能通過(guò)對(duì)象訪問(wèn)的, 可枚舉的屬性(包括原型內(nèi)和實(shí)例內(nèi)的) :
var o = { name : "Jon", age : 25, saySth : function(){} } for(var prop in o){ if(prop){ console.log(prop); } } //name, age, saySth
Object.keys() [ES5]可獲得所有可枚舉的屬性 :
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; //獲得原型中所有可枚舉的屬性 var protoKeys = Object.keys(Person.prototype); console.log(protoKeys); //"name", "age", "job", "sayJob" var p1 = new Person; p1.name = "Mark"; p1.nickname = "MM"; p1.age = 24; p1.fakeAge = 21; p1.job = "DBA"; p1.sayJob(); //如果通過(guò)實(shí)例調(diào)用, 則會(huì)得到該實(shí)例中所有可枚舉的屬性 var keys = Object.keys(p1); console.log(keys); //"name", "nickname", "age", "fakeAge", "job"
Object.getOwnPropertyNames()可以得到所有無(wú)論是否可枚舉的屬性
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; //獲得原型中所有屬性(無(wú)論是否可枚舉) var protoKeys = Object.getOwnPropertyNames(Person.prototype); console.log(protoKeys); //"constructor", "name", "age", "job", "sayJob"
Object.keys() 和 Object.getOwnPropertyNames()都可以替代for-in循環(huán) (IE9+, ...)
使用對(duì)象字面量來(lái)創(chuàng)建新對(duì)象
function Person(){} //這種方式其實(shí)已經(jīng)重寫(xiě)了默認(rèn)的prototype對(duì)象, 此時(shí)constructor屬性已經(jīng)不再指向Person了, 而是指向了Object Person.prototype = { name : "Jon", age : 25, job : "FrontEnd Developer", sayJob : function(){ console.log(this.job); } }; //所以此時(shí)雖然instanceof操作符還能返回正確的結(jié)果, 但constructor已經(jīng)無(wú)法確定對(duì)象的類型了 var f1 = new Person(); console.log(f1 instanceof Person); //true console.log(f1 instanceof Object); //true console.log(f1.constructor == Person); //false console.log(f1.constructor == Object); //true //如果constructor的值很重要, 可以像這樣把它設(shè)置回適當(dāng)?shù)闹?//(修改上面的Person.prototype) Person.prototype = { constructor : Person, //顯式的把constructor設(shè)置為Person name : "Jon", age : 25, job : "FrontEnd Developer", sayJob : function(){ console.log(this.job); } }; //如果像上面一樣把constructor的值顯式的設(shè)置, 那么它會(huì)變成可枚舉, 即[[Enumerable]]的值會(huì)變?yōu)閠rue, 如果要把它設(shè)置回不可枚舉, 可以使用下面的ES5提供的新方法 : //重寫(xiě)整個(gè)示例 function Person(){} Person.prototype = { name : "Jon", age : 25, job : "FrontEnd Developer", sayJob : function(){ console.log(this.job); } }; //重設(shè)構(gòu)造函數(shù)[ES5 only] Object.defineProperty(Person.prototype, "constructor", { enumerable : false, value : Person });
原型的動(dòng)態(tài)性
原型中查找值的方法是一次搜索, 所謂動(dòng)態(tài)性就是在原型對(duì)象上所有的修改都能立即從實(shí)例上反應(yīng)出來(lái), 即使是 先創(chuàng)建實(shí)例, 后修改原型 也是如此
function Person(){}; Person.prototype.name = "Jon"; Person.prototype.age = 25; Person.prototype.job = "FrontEnd Developer"; Person.prototype.sayJob = function(){ console.log(this.job); }; //創(chuàng)建原型實(shí)例 var p1 = new Person(); //創(chuàng)建實(shí)例后再創(chuàng)建原型方法 Person.prototype.sayAge = function(){ console.log(this.age); } //調(diào)用后創(chuàng)建的原型方法 p1.sayAge(); //照樣能工作! 輸出25
//但不能在創(chuàng)建原型實(shí)例后, 重寫(xiě)整個(gè)原型對(duì)象 function Person(){} //創(chuàng)建原型實(shí)例 var p1 = new Person(); //此時(shí)再定義Person的原型對(duì)象 Person.prototype = { constructor : Person, name : "Jon", job : "FrontEnd Developer", sayJob : function(){ console.log(this.job); } }; //記住, 實(shí)例的指針[[ prototype ]]僅指向原型, 而不指向構(gòu)造函數(shù) p1.sayJob(); //出錯(cuò)! Uncaught TypeError: p1.sayJob is not a function
原生對(duì)象的原型
所有原生的引用類型(Object, Array, String, etc...), 都是使用這種原型模式創(chuàng)建的, 都在其構(gòu)造函數(shù)上定義了方法
通過(guò)原生對(duì)象的原型, 不僅可以取得所有默認(rèn)方法的引用, 而且也可以定義新方法. 可以像修改自定義對(duì)象的原型一樣修改原生對(duì)象的原型: 即可以隨時(shí)添加方法(但不推薦) :
console.log(typeof Array.prototype.sort); //function console.log(typeof String.prototype.substr); //function //為原生引用類型String添加方法(不推薦) : String.prototype.startsWith = function(text){ return this.indexOf(text) == 0; } var s1 = "Hi Jon"; console.log(s1.startsWith("Hi")); //true
原型模式的問(wèn)題 :
省略了為構(gòu)造函數(shù)初始化參數(shù)的環(huán)節(jié), 導(dǎo)致所有新建的實(shí)例都會(huì)取得相同的默認(rèn)值
最大的問(wèn)題是其共享的本性所導(dǎo)致的, 對(duì)于引用類型值的屬性來(lái)說(shuō)問(wèn)題非常突出 :
function Person(){} Person.prototype = { constructor : Person, name : "Jon", job : "FrontEnd Developer", friends : ["Lucy","Jeniffer"], sayJob : function(){ console.log(this.job); } }; var p1 = new Person(); var p2 = new Person(); p1.friends.push("Quinene"); console.log(p1.friends); //"Lucy", "Jeniffer", "Quinene" console.log(p2.friends); //"Lucy", "Jeniffer", "Quinene" console.log(p1.friends === p2.friends); //true組合使用構(gòu)造函數(shù)模式和原型模式(最常用)
構(gòu)造函數(shù)模式用于定義實(shí)例屬性, 原型模式用于定義方法和共享的屬性 :
結(jié)果每個(gè)實(shí)例都有自己的一份實(shí)例屬性的副本, 但同時(shí)又共享著對(duì)方法的引用,最大限度的節(jié)省了內(nèi)存:
這種模式還支持向構(gòu)造函數(shù)傳參 :
function Person(name, age, job){ //定義實(shí)例屬性(將來(lái)創(chuàng)建實(shí)例時(shí)不會(huì)相同的屬性s) this.name = name; this.age = age; this. job = job; this. friends = ["Mark", "Martin"]; } Person.prototype = { //構(gòu)造函數(shù)屬性指回Person cosntructor : Person, //定義方法 sayJob : function(){ console.log(this.job); }, //定義共享屬性 country : "China" }; //創(chuàng)建實(shí)例 var p1 = new Person("Jon", 25, "FrontEnd Developer"); var p2 = new Person("Percy", 26, "DBA"); //為實(shí)例p1的friends屬性添加值 p1.friends.push("Jeniffer"); console.log(p1.friends); //"Mark", "Martin", "Jeniffer" console.log(p2.friends); //"Mark", "Martin" console.log(p1.friends == p2.friends); //false console.log(p1.sayJob == p2.sayJob); //true console.log(p1.country == p2.country); //true動(dòng)態(tài)原型模式
動(dòng)態(tài)原型模式把所有信息都封裝在構(gòu)造函數(shù)中, 而通過(guò)在構(gòu)造函數(shù)中初始化原型(僅在必要的情況下), 又保持了同時(shí)使用構(gòu)造函數(shù)和原型的優(yōu)點(diǎn) :?
即 可以通過(guò)檢查某個(gè)應(yīng)該存在的方法是否有效, 來(lái)決定是否需要初始化原型
function Person(name, age, job){ //屬性 this.name = name; this.age = age; this.job = job; //方法 if(typeof this.sayJob != "function"){ Person.prototype.sayJob = function(){ console.log(this.job); } } } var p1 = new Person("Jon", 25, "F2E"); p1.sayJob(); //F2E
這里只在sayJob()方法不存在的情況下, 才會(huì)將它添加到原型中.
這段代碼只會(huì)在初次調(diào)用構(gòu)造函數(shù)時(shí)才會(huì)執(zhí)行.
這里對(duì)原型所做的修改, 也會(huì)立即在所有實(shí)例中得到反映.
if語(yǔ)句檢查的可以是初始化之后應(yīng)該存在的任何屬性和方法—— 不必用一大堆if語(yǔ)句判斷每個(gè)屬性的方法,只要其中檢查一個(gè)即可;
這種模式創(chuàng)建的對(duì)象可以用instanceof操作符確定它的類型
寄生構(gòu)造函數(shù)模式基本思路是創(chuàng)建一個(gè)函數(shù), 這個(gè)函數(shù)作用僅僅是封裝創(chuàng)建對(duì)象的代碼, 然后再返回新創(chuàng)建的對(duì)象.
function Person(name, age, job){ var o = new Object(); o.name = name; o.age = age; o.job = job; o.sayJob = function(){ console.log(this.name); }; return o; } var p1 = new Person("Jon", 25, "F2E"); p1.sayJob(); //F2E
Person函數(shù)創(chuàng)建了一個(gè)新對(duì)象o, 并以相應(yīng)的屬性和方法初始化該對(duì)象, 然后把它返回.
除了使用new操作符并把使用的包裝函數(shù)叫做構(gòu)造函數(shù)外, 這個(gè)模式跟工廠模式其實(shí)是一樣的.
構(gòu)造函數(shù)在不返回值的情況下, 默認(rèn)會(huì)返回新對(duì)象的實(shí)例, 而通過(guò)在構(gòu)造函數(shù)的末尾添加一個(gè)return語(yǔ)句, 可以重寫(xiě)調(diào)用構(gòu)造函數(shù)時(shí)返回的值.
這種模式在特殊的情況下用來(lái)為對(duì)象創(chuàng)建構(gòu)造函數(shù).假設(shè)我們想創(chuàng)建一個(gè)具有額外方法的特殊數(shù)組,
因?yàn)椴荒苤苯有薷腁rray構(gòu)造函數(shù), 因此可以使用這種模式 :
function SpecialArray(){ //創(chuàng)建一個(gè)數(shù)組用于接收傳入的值 var values = new Array(); //然后使用push方法(用構(gòu)造函數(shù)接收到的所有參數(shù))初始化了數(shù)組的值; values.push.apply(values, arguments); //給數(shù)組實(shí)例添加了一個(gè)toPipedString()方法, 該方法返回以短橫線分割的數(shù)組值; values.toPipedString = function(){ return this.join("-"); }; //將數(shù)組以函數(shù)值的形式返回. return values; } var colorsArr = new SpecialArray("red", "blue", "purple"); console.log(colorsArr.toPipedString()); //red-blue-purple //關(guān)于該模式 : 首先, 返回的對(duì)象與構(gòu)造函數(shù)或者構(gòu)造函數(shù)的原型屬性之間沒(méi)有關(guān)系;也就是說(shuō), 構(gòu)造函數(shù)返回的對(duì)象與在構(gòu)造函數(shù)外部創(chuàng)建的對(duì)象沒(méi)有什么不同.為此不能依賴instance操作符來(lái)確定對(duì)象類型. console.log(colorsArr instanceof SpecialArray); //false穩(wěn)妥構(gòu)造函數(shù)模式
穩(wěn)妥對(duì)象 : 沒(méi)有公共屬性, 其方法也不引用this的對(duì)象.
適合在安全的環(huán)境中(禁止使用this和new), 或者在防止數(shù)據(jù)被其他應(yīng)用程序改動(dòng)時(shí)使用
穩(wěn)妥構(gòu)造函數(shù)遵循與寄生構(gòu)造函數(shù)類似的模式, 但有兩點(diǎn)不同 :?
一是新創(chuàng)建對(duì)象的實(shí)力方法不引用this,
二是不適用new操作符調(diào)用構(gòu)造函數(shù) :
function Person(name, age, job){ //創(chuàng)建要返回的對(duì)象 var o = new Object(); //可以在這里定義私有變量和方法 //添加方法 o.sayJob = function(){ console.log(job); } //返回對(duì)象 return o; } /*這種方式創(chuàng)建的對(duì)象中, 除了使用sayJob()方法外, 沒(méi)有其他辦法訪問(wèn)job的值*/ //使用穩(wěn)妥的Person構(gòu)造函數(shù) var p1 = new Person("Jon", 25, "FrontEnd Developer"); p1.sayJob(); //FrontEnd Developer console.log(p1.job); //嘗試直接訪問(wèn)job屬性會(huì)返回undefined繼承
許多OO語(yǔ)言都支持兩種繼承方式 :?
接口繼承 : 只繼承方法簽名
實(shí)現(xiàn)繼承 : 繼承實(shí)際的方法
由于函數(shù)沒(méi)有簽名, 在ES中無(wú)法實(shí)現(xiàn)接口繼承.ES只支持實(shí)現(xiàn)繼承, 而且其 實(shí)現(xiàn)繼承 主要是依靠原型鏈實(shí)現(xiàn)的.
方法簽名由方法名稱和一個(gè)參數(shù)列表(方法的參數(shù)的順序和類型)組成。
方法簽名應(yīng)該如下所示,相應(yīng)的可變參數(shù)分別使用String和Exception聲明:
Log.log(String message, Exception e, Object... objects) {...}
原型鏈利用原型讓一個(gè)引用類型繼承另一個(gè)引用類型的屬性和方法.
簡(jiǎn)單回顧下構(gòu)造函數(shù), 原型, 實(shí)例的關(guān)系 :?
每個(gè)構(gòu)造函數(shù)都有一個(gè)原型對(duì)象( prototype ), 原型對(duì)象都包含一個(gè)指向構(gòu)造函數(shù)的指針( constructor ), 而每個(gè)實(shí)例都包含一個(gè)指向原型對(duì)象的內(nèi)部指針( [[ prototype ]], __proto__ )
那么,假如我們讓原型對(duì)象(prototype)等于另一個(gè)類型的實(shí)例, 那么此時(shí)的原型對(duì)象將包含一個(gè)指向另一個(gè)原型的指針.
相應(yīng)地, 另一個(gè)原型中也包含著一個(gè)指向另一個(gè)構(gòu)造函數(shù)的指針.
假如另一個(gè)原型又是另一個(gè)原型的實(shí)例, 那么上述關(guān)系依然成立, 如此層層遞進(jìn), 就構(gòu)成了實(shí)力與原型的鏈條, 這就是所謂的原型鏈的概念.
實(shí)現(xiàn)原型鏈的基本模式 :
/*定義兩個(gè)類型, SuperType和SubType*/ function SuperType(){ //SuperType自己的屬性 this.property = true; } SuperType.prototype.getSuperValue = function(){ //SuperType自己的方法 return this.property; } function SubType(){ //SubType自己的屬性 this.subproperty = false; } /*SupType通過(guò)創(chuàng)建SuperType()的實(shí)例繼承了SuperType, 并賦值給SubType.prototype, 即SubType的原型對(duì)象實(shí)現(xiàn)的本質(zhì)是重寫(xiě)原型對(duì)象, 代之以一個(gè)新類型的實(shí)例. 換句話說(shuō), 原來(lái)存在于SuperType的實(shí)例中的所有屬性和方法, 現(xiàn)在也存在于SubType.prototype中了 */ SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){ //添加SubType自己的方法, 這樣就在繼承了SuperType的屬性和方法的基礎(chǔ)上又添加了一個(gè)新方法 return this.subproperty; } //創(chuàng)建一個(gè)新實(shí)例 var instance = new SubType(); console.log(instance.getSuperValue()); //true //測(cè)試是否為Object, SuperType, SubType的實(shí)例 console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true
關(guān)系如圖所示 :?
最終結(jié)果 :
instance實(shí)例指向SubType的原型, SubType的原型又指向SuperType的原型.
getSuperValue()方法仍然還在SuperType.prototype中, 但property則位于SubType.prototype中.
這是因?yàn)? property是一個(gè)實(shí)例屬性,而getSuperType()則是一個(gè)原型方法
既然SubType.prototype現(xiàn)在是SuperType的實(shí)例, 那么prototype當(dāng)然就位于該實(shí)例中了.
要注意,實(shí)例的 instance.constructor現(xiàn)在指向的是SuperType, 這是因?yàn)?b>SubType的原型現(xiàn)在指向了另一個(gè)對(duì)象—— SuperType的原型.
而這個(gè)原型對(duì)象的constructor屬性指向的是SuperType.
所有引用類型默認(rèn)都繼承了Object, 而這個(gè)繼承也是通過(guò)原型鏈實(shí)現(xiàn)的.
要記住, 所有函數(shù)的默認(rèn)原型都是Object的實(shí)例, 因此默認(rèn)原型都會(huì)包含一個(gè)內(nèi)部指針指向Object.prototype.
這也正是所有自定義類型都會(huì)繼承toString(), valueOf()的根本原因.
所以, 上面例子展示的原型鏈應(yīng)該還包含另一個(gè)繼承層次 : (完整的原型鏈如下)
使用instanceof 操作符, 測(cè)試實(shí)例和原型鏈中出現(xiàn)過(guò)的構(gòu)造函數(shù), 如果存在就會(huì)返回true
使用isPrototypeOf() 方法, 只要是原型鏈中出現(xiàn)過(guò)的原型, 都可以說(shuō)是該原型鏈所派生的實(shí)例的原型,因此該方法會(huì)返回true
//上面第一段代碼的最后片段 : console.log(instance instanceof Object); //true console.log(instance instanceof SuperType); //true console.log(instance instanceof SubType); //true console.log(Object.prototype.isPrototypeOf(instance)); //true console.log(SuperType.prototype.isPrototypeOf(instance)); //true console.log(SubType.prototype.isPrototypeOf(instance)); //true
給原型添加方法的代碼一定要放在替換原型的語(yǔ)句之后 :
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //從SuperType繼承 SubType.prototype = new SuperType(); //SubType自己的新方法 SubType.prototype.getSubValue = function(){ return this.subproperty; } //SubType繼承的父類方法getSuperValue()被重寫(xiě), 但只會(huì)重寫(xiě)SubType自身的getSuperValue(), 不會(huì)影響上一級(jí)父類原來(lái)的方法, 即如果調(diào)用的是SuperType的getSuperValue()方法的話還是會(huì)返回原來(lái)的true. SubType.prototype.getSuperValue = function(){ return false; } //創(chuàng)建實(shí)例 var ins1 = new SubType(); var ins2 = new SuperType(); console.log(ins1.getSuperValue()); //false, 重寫(xiě)的方法 console.log(ins2.getSuperValue()); //true, SubType重寫(xiě)getSuperValue()方法并不會(huì)影響父類原有的方法
function SuperType(){ this.property = true; } SuperType.prototype.getSuperValue = function(){ return this.property; } function SubType(){ this.subproperty = false; } //從SuperType繼承 SubType.prototype = new SuperType(); /*剛剛把SuperType的實(shí)例賦值給SubType的原型??, 又使用對(duì)象字面量??把原型替換 SubType.prototype = {...}, 所以現(xiàn)在SubType的原型包含的是 一個(gè)屬于Object的實(shí)例而不是SuperType的, 原先的原型鏈已經(jīng)被切斷, SubType與SuperType已經(jīng)沒(méi)有任何關(guān)系了*/ //使用對(duì)象字面量把 原型替換 SubType.prototype = { getSubValue : function(){ return this.subproperty; }, someOtherMethod : function(){ return false; } } var ins1 = new SubType(); console.log(ins1.getSuperValue()); //Uncaught TypeError: ins1.getSuperValue is not a function
最主要的問(wèn)題來(lái)自包含引用類型值的原型.
之前說(shuō)過(guò), 包含引用類型值的原型屬性會(huì)被所有實(shí)例共享.
而這也是為什么要在構(gòu)造函數(shù)中, 而不是原型對(duì)象中定義屬性的原因.
第二個(gè)問(wèn)題是, 在創(chuàng)建子類型的實(shí)例時(shí), 不能向超類型的構(gòu)造函數(shù)傳遞參數(shù)
在通過(guò)原型來(lái)實(shí)現(xiàn)繼承時(shí), 原型實(shí)際上會(huì)變成另一個(gè)類型是實(shí)例( SubType.prototype = new SuperType(); ), 于是, 原先的實(shí)例屬性也就順理成章的變成了現(xiàn)在的原型屬性了.
function SuperType(){ this.colors = ["red", "green", "blue"]; } function SubType(){} SubType.prototype = new SuperType(); var ins1 = new SubType(); console.log(ins1.colors); // "red", "green", "blue" //在ins1添加colors屬性的屬性值 ins1.colors.push("purple"); console.log(ins1.colors); //"red", "green", "blue", "purple" var ins2 = new SubType(); //ins1中添加到colors中的屬性值直接被添加到了SubType()的原型屬性里面, 導(dǎo)致后來(lái)新增的實(shí)例也繼承了這些屬性 console.log(ins2.colors); //"red", "green", "blue", "purple"]
思路 : 在子類型構(gòu)造函數(shù)的內(nèi)部調(diào)用超類型的構(gòu)造函數(shù).
函數(shù)只不過(guò)是在特定環(huán)境中執(zhí)行代碼的對(duì)象, 因此可以通過(guò)apply()和call()方法也可以在(將來(lái))新創(chuàng)建的對(duì)象上執(zhí)行構(gòu)造函數(shù)
function SuperType(){ this.colors = ["green", "blue", "purple"]; } function SubType(){ //繼承自SuperType //當(dāng)SubType(){...}被實(shí)例化后, SuperType()函數(shù)中定義的所有對(duì)象初始化代碼就會(huì)被執(zhí)行 SuperType.call(this); } var ins1 = new SubType(); ins1.colors.push("red"); console.log(ins1.colors); //"green", "blue", "purple", "red" var ins2 = new SubType(); console.log(ins2.colors); //"green", "blue", "purple"
//相比原型鏈, 借用構(gòu)造函數(shù)還有一個(gè)很大的優(yōu)勢(shì), 就是子類型的構(gòu)造函數(shù)可以向超類型的構(gòu)造函數(shù)傳遞參數(shù) function SuperType(name){ //父類構(gòu)造函數(shù)接受一個(gè)name函數(shù), 并賦值給一個(gè)屬性 this.name = name; } function SubType(){ /*在SubType()構(gòu)造函數(shù)中調(diào)用SuperType()構(gòu)造函數(shù)時(shí), 實(shí)際上是為SubType的實(shí)例設(shè)置了name屬性*/ SuperType.call(this, "Jon"); /*為了確保SuperType構(gòu)造函數(shù)不會(huì)重寫(xiě)子類型的屬性, 可以在調(diào)用父類構(gòu)造函數(shù)后,再添加應(yīng)該在子類型中定義的屬性*/ this.age = 25; } var ins1 = new SubType(); console.log(ins1.name); //Jon console.log(ins1.age); //25
如果僅僅是借用構(gòu)造函數(shù), 那么也無(wú)法避免構(gòu)造函數(shù)模式存在的問(wèn)題—— 方法都在構(gòu)造函數(shù)內(nèi)部定義, 那么函數(shù)復(fù)用就無(wú)從談起了.
而且在超類型的原型中定義的方法, 對(duì)子類型而言也是不可見(jiàn)的, 結(jié)果所有類型都只能使用構(gòu)造函數(shù)模式.
所以這種方式也是很少多帶帶使用的
組合繼承( 偽經(jīng)典繼承 )指的是將 原型鏈 與 借用構(gòu)造函數(shù) 的技術(shù)組合到一塊, 從而發(fā)揮二者之長(zhǎng)的一種繼承模式.
思路是, 使用 原型鏈 實(shí)現(xiàn) 對(duì)原型屬性和方法的繼承 , 而通過(guò) 借用構(gòu)造函數(shù) 來(lái)實(shí)現(xiàn)對(duì) 實(shí)例屬性的繼承
這樣, 既通過(guò)在原型上定義方法實(shí)現(xiàn)了函數(shù)復(fù)用, 又能夠保證每個(gè)實(shí)例都有自己的屬性. 所以這成為JavaScript中常用的繼承方式
function SuperType(name){ //父類定義兩個(gè)屬性name和colors this.name = name; this.colors = ["blue", "red", "yellow"]; } //父類定義原型方法sayName SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ //SubType構(gòu)造函數(shù)在調(diào)用SuperType構(gòu)造函數(shù)時(shí)傳入了name參數(shù) SuperType.call(this, name); //然后定義自己的屬性age this.age = age; } //將SuperType的實(shí)例賦值給SubType的原型 SubType.prototype = new SuperType(); //name, colors[], sayName() SubType.prototype.constructor = SubType; //構(gòu)造函數(shù)指回自己 //在該新原型上定義了方法sayAge() SubType.prototype.sayAge = function(){ console.log(this.age); } //兩個(gè)不同的SubType實(shí)例既分別擁有自己的屬性————包括colors屬性, 又可以使用相同的方法了 var ins1 = new SubType("Jon", 25); ins1.colors.push("purple"); console.log(ins1.colors); //"blue", "red", "yellow", "purple" ins1.sayName(); //Jon ins1.sayAge(); //25 var ins2 = new SubType("Mark", 24); console.log(ins2.colors); //"blue", "red", "yellow" ins2.sayName(); //Mark ins2.sayAge(); //24原型式繼承
借助原型可以基于已有的對(duì)象創(chuàng)建新對(duì)象, 同時(shí)還不必因此創(chuàng)建自定義類型.
/* 在object()函數(shù)內(nèi)部, 先創(chuàng)建了一個(gè)臨時(shí)性的構(gòu)造函數(shù)F(){}, 然后將傳入的對(duì)象o作為這個(gè)構(gòu)造函數(shù)F(){}的原型, 最后返回了這個(gè)臨時(shí)類型的新實(shí)例. 從本質(zhì)上講, object()對(duì)傳入其中的對(duì)象o執(zhí)行了一次淺復(fù)制 */ /* 這種繼承方式要求你必須有一個(gè)對(duì)象可以作為另一個(gè)對(duì)象的基礎(chǔ), 把它傳給object()函數(shù),然后再根據(jù)具體需求對(duì)得到的對(duì)象加以修改即可. */ function object(o){ function F(){} F.prototype = o; return new F; } /* 這個(gè)例子中, 可以作為另一個(gè)對(duì)象的基礎(chǔ)是person對(duì)象 */ var person = { name : "Jon", colorsLike : ["black", "white"] } /* 把它(person對(duì)象)傳入到object()函數(shù)中, 然后該函數(shù)就會(huì)返回一個(gè)新對(duì)象( anotherPerson1 和 anotherPerson2 ), 這兩個(gè)新對(duì)象把person作為原型, 所有它們的原型中就包含一個(gè)基本類型值屬性和 一個(gè)引用類型值屬性,這意味著person.colorsLike不僅于person所有,同時(shí)也會(huì)被 anotherPerson1, anotherPerson2共享, 實(shí)際上, 就相當(dāng)于又創(chuàng)建了person對(duì)象的兩個(gè)副本 */ var anotherPerson1 = object(person); //anotherPerson1現(xiàn)在有了person的所有屬性(這里是name和colorsLike[]) console.log(anotherPerson1.name); //person原有的name屬性值, 輸出Jon console.log(anotherPerson1.colorsLike); //person原有的colorsLike[]數(shù)組, 輸出["black", "white"] anotherPerson1.name = "Percy"; //修改anotherPerson1的name屬性為自己的值 anotherPerson1.colorsLike.push("purple"); //添加anotherPerson1自己喜歡的顏色 console.log(anotherPerson1.name); //Percy console.log(anotherPerson1.colorsLike); //["black", "white", "purple"] console.log(person.name); //Jon console.log(person.colorsLike); //person的colorsLike數(shù)組值已經(jīng)被anotherPerson1添加的屬性影響, 此時(shí)也輸出了["black", "white", "purple"] var anotherPerson2 = object(person); console.log(anotherPerson2.name); //Jon console.log(anotherPerson2.colorsLike); //["black", "white", "purple"] anotherPerson2.colorsLike.push("red"); //再push一個(gè) console.log(anotherPerson2.colorsLike); //["black", "white", "purple", "red"] console.log(person.colorsLike); //再度被anotherPerson2新增的值影響, 輸出["black", "white", "purple", "red"]
ES5新增了一個(gè)方法Object.create()規(guī)范了原型式繼承, 該方法接收兩個(gè)參數(shù), 一個(gè)用作新對(duì)象的原型的對(duì)象和一個(gè)(可選)一個(gè)為新對(duì)象定義額外屬性的對(duì)象
瀏覽器支持, IE9+和各現(xiàn)代瀏覽器
還是直接看例子比較直觀 :
傳入一個(gè)參數(shù)的時(shí)候, 這個(gè)方法跟上面object()方法的行為相同 :
var person = { name : "Jon", colorsLike : ["black", "white"] }; console.log(person.colorsLike); // ["black", "white"] //傳入一個(gè)參數(shù)的時(shí)候, 這個(gè)方法跟上面object()方法的行為相同 var anotherPerson = Object.create(person); anotherPerson.name = "Percy"; anotherPerson.colorsLike.push("purple"); console.log(anotherPerson.name); //Percy console.log(anotherPerson.colorsLike); //["black", "white", "purple"] console.log(person.name); //Jon console.log(person.colorsLike); //["black", "white", "purple"]
傳入兩個(gè)參數(shù)的時(shí)候, 第二個(gè)參數(shù)與Object.defineProperties()方法的第二個(gè)參數(shù)格式相同 : 每個(gè)屬性都是通過(guò)自己的描述符定義的, 以這種方式指定任何屬性都會(huì)覆蓋原型對(duì)象上的同名屬性
var person = { name : "Jon", colorsLike : ["black", "white"] }; console.log(person.colorsLike); // ["black", "white"] //傳入兩個(gè)參數(shù) var anotherPerson = Object.create(person, { name : { value : "Martin" } }); console.log(anotherPerson.name); //Martin寄生式繼承
與原型式繼承緊密相關(guān)的思路, 與寄生構(gòu)造函數(shù)和工廠模式類似, 即創(chuàng)建一個(gè)僅用于封裝繼承過(guò)程的函數(shù), 該函數(shù)在內(nèi)部以某種形式來(lái)增強(qiáng)對(duì)象, 最后再像真的是它做了所有工作一樣返回對(duì)象
function object(o){ function F(){} F.prototype = o; return new F; } function createAnother(original){ var clone = object(original); //通過(guò)調(diào)用函數(shù)創(chuàng)建一個(gè)新對(duì)象 clone.sayHi = function(){ //以某種方式增強(qiáng)這個(gè)對(duì)象(添加自身方法或者屬性等) console.log("Good Day!"); }; return clone; //返回該對(duì)象 } var person = { name : "Jon", friends : ["Martin", "Jeniffer"] }; /* 這個(gè)實(shí)例中的代碼 基于person 返回了一個(gè)新對(duì)象————anotherPerson 該對(duì)象不僅具有person所有屬性和方法, 而且還有自己的sayHi()方法 */ /* 在主要考慮對(duì)象而不是自定義類型和構(gòu)造函數(shù)的情況下, 寄生式繼承也是一種有用的方式, 前面示范繼承模式使用的object()函數(shù)并不是必須的, 任何能夠返回新對(duì)象的函數(shù)都適用于此模式 */ var anotherPerson = createAnother(person); anotherPerson.sayHi(); //Good Day!寄生組合式繼承
前面說(shuō)過(guò), 組合繼承是JS中最常用的繼承模式, 不過(guò)它也有自己的不足
組合繼承 最大的問(wèn)題是, 無(wú)論在什么情況下, 都會(huì)調(diào)用兩次父類型構(gòu)造函數(shù), 一次是在創(chuàng)建子類型原型的時(shí)候, 一次是在子類型構(gòu)造函數(shù)內(nèi)部 :
function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"] } SuperType.prototype.sayName = function(){ console.log(this.name); } function SubType(name, age){ SuperType.call(this, name); //第二次調(diào)用SuperType() this.age = age; } /* 第一次 調(diào)用SuperType構(gòu)造函數(shù)時(shí), SubType.prototype會(huì)得到兩個(gè)屬性, name和colors[], 它們都是SuperType的實(shí)例屬性, 只不過(guò)現(xiàn)在位于SubType的原型中; 當(dāng)調(diào)用SubType構(gòu)造函數(shù)時(shí), 又會(huì)再一次調(diào)用一次SuperType構(gòu)造函數(shù), 這一次又在新對(duì)象上創(chuàng)建了實(shí)例屬性name和colors[], 于是, 這兩個(gè)屬性就遮蔽了原型中的兩個(gè)同名屬性 */ SubType.prototype = new SuperType(); //第一次調(diào)用SuperType() SubType.prototype.constructor = SubType; SubType.prototype.sayAge = function(){ console.log(this.age); }
如下圖 :
寄生組合式繼承, 即通過(guò)借用構(gòu)造函數(shù)來(lái)繼承屬性, 通過(guò)原型鏈的混成模式來(lái)繼承方法.
思路是, 不必為了指定子類型的原型而調(diào)用構(gòu)造超類型的構(gòu)造函數(shù), 我們所需要的無(wú)非就是超類型的一個(gè)副本而已 ??
本質(zhì)上, 就是使用寄生式繼承來(lái)繼承超類型的原型, 然后再將結(jié)果指定給子類型的原型.
基本模式如下所示. ??
function object(o){ function F(){} F.prototype = o; return new F(); } /* 寄生組合式繼承的最簡(jiǎn)單形式, 這個(gè)函數(shù)接收兩個(gè)參數(shù), 子類型構(gòu)造函數(shù)和超類型構(gòu)造函數(shù); 在函數(shù)內(nèi)部,第一步是創(chuàng)建超類型原型的一個(gè)副本, 第二步是為創(chuàng)建的的副本添加constructor屬性, 從而彌補(bǔ)因重寫(xiě)而失去默認(rèn)的constructor屬性; 最后一步, 將新創(chuàng)建的對(duì)象(即副本)賦值給子類型的原型,這樣我們就可以調(diào)用inheritPrototype()函數(shù)的語(yǔ)句,去替換前面例子中未知類型原型賦值的語(yǔ)句了(41行) */ function inheritPrototype(subType, superType){ var prototype = object(superType.prototype); //創(chuàng)建對(duì)象 prototype.constructor = subType; //增強(qiáng)對(duì)象 subType.prototype = prototype; //指定對(duì)象 } function SuperType(name){ this.name = name; this.colors = ["red", "blue", "green"]; } SuperType.prototype.sayName = function(){ console.log(this.name); }; function SubType(name, age){ SuperType.call(this, name); this.age = age; } inheritPrototype(SubType, SuperType); //調(diào)用inheritPrototype()函數(shù) SubType.prototype.sayAge = function(){ console.log(this.age); }; var instance1 = new SubType("Jon", 25); instance1.colors.push("black"); console.log(instance1.colors); //"red,blue,green,black" instance1.sayName(); //"Jon" instance1.sayAge(); //25 var instance2 = new SubType("Mark", 24); console.log(instance2.colors); //"red,blue,green" instance2.sayName(); //"Mark" instance2.sayAge(); //24函數(shù)表達(dá)式
第一種是函數(shù)聲明 :
function Person(name){ this.name = name; console.log("name is " + this.name); } Person("Jon"); //name is Jon //函數(shù)聲明支持函數(shù)聲明提升, 即執(zhí)行代碼前會(huì)先讀取函數(shù)聲明, 那么函數(shù)聲明可以放在調(diào)用它的代碼之后而不出錯(cuò) : Person("Jon"); //works ! 輸出name is Jon function Person(name){ this.name = name; console.log("name is " + this.name); }
第二種是函數(shù)表達(dá)式 :
var Person = function(name){ this.name = name; console.log(this.name); } Person("Jon"); //Jon //函數(shù)表達(dá)式不支持函數(shù)聲明提升 Person("Jon"); //Uncaught TypeError: Person is not a function var Person = function(name){ this.name = name; console.log(this.name); }
要在使用條件語(yǔ)句后面執(zhí)行函數(shù)的話, 條件語(yǔ)句內(nèi)的函數(shù)必須使用函數(shù)表達(dá)式的方式定義, 如果使用函數(shù)聲明方式定義, 會(huì)在不同的瀏覽器導(dǎo)致不同問(wèn)題的發(fā)生 :
//條件語(yǔ)句內(nèi)的函數(shù)定義必須使用函數(shù)表達(dá)式 var b = true; if(b){ sayColors = function(){ console.log(this.color); }; }else{ console.log("error!"); }
function createComparisonFunction(propertyName){ //這里返回的就是匿名函數(shù), 它能賦值給一個(gè)變量, 或者以其他的方式調(diào)用 return function(object1, object2){ var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; }遞歸
遞歸函數(shù)是一個(gè)函數(shù)通過(guò)名字調(diào)用自身的情況下構(gòu)成的
//遞歸階乘函數(shù) function factorial(num){ if(num <= 1){ return 1; }else{ return num * factorial(num - 1); } } //注意如下調(diào)用會(huì)產(chǎn)生錯(cuò)誤 var anotherFactorial = factorial; //把factorial()函數(shù)保存在一個(gè)變量中 factorial = null; //把factorial函數(shù)設(shè)置為null console.log(anotherFactorial(3)); //Uncaught TypeError: factorial is not a function
//使用arguments.callee解決上面的問(wèn)題 //arguments.callee是一個(gè)指向當(dāng)前正在執(zhí)行的函數(shù)的指針, 因此可以用它來(lái)實(shí)現(xiàn)對(duì)函數(shù)的遞歸調(diào)用 //嚴(yán)格模式下不允許使用arguments.callee function factorial(num){ if (num <= 1) { return -1; }else{ //arguments.callee代替了函數(shù)名factorial return num * arguments.callee(num - 1); } }
//解決嚴(yán)格模式下不允許使用arguments.callee的問(wèn)題 //使用命名函數(shù)表達(dá)式來(lái)達(dá)成相同的結(jié)果 var factorial = (function f(num){ //創(chuàng)建一個(gè)名為f()的命名函數(shù)表達(dá)式, 賦值給factorial if(num <= 1){ return 1; }else{ return num * f(num - 1); } });閉包
注意匿名函數(shù)與閉包不要混淆.
閉包指的是有權(quán)訪問(wèn) 另一個(gè)函數(shù)作用域中的變量 的函數(shù)
創(chuàng)建閉包常用的方式, 就是在一個(gè)函數(shù)內(nèi)部創(chuàng)建另一個(gè)函數(shù) :
function createComparisonFunction(propertyName){ return function(object1, object2){ //value1和value2訪問(wèn)了外部函數(shù)的變量propertyName, 即使該內(nèi)部函數(shù)被返回或被其他地方調(diào)用, 也不影響它訪問(wèn)外部函數(shù)的propertyName變量(因?yàn)樵撏獠孔兞吭诒緝?nèi)部函數(shù)的作用域內(nèi)) var value1 = object1[propertyName]; var value2 = object2[propertyName]; if (value1 < value2) { return -1; }else if(value1 > value2){ return 1; }else{ return 0; } }; }
理解 :
//定義compare函數(shù) function compare(value1, value2){ if(value1 < value2){ return -1; }else if(value1 > value2){ return 1; }else{ return 0; } } //在全局作用域中調(diào)用函數(shù), 從作用域鏈的優(yōu)先級(jí)來(lái)分的話, 外部函數(shù)的活動(dòng)對(duì)象始終處于第二位, 外部函數(shù)的外部函數(shù)的活動(dòng)對(duì)象處于第三位 ...(以此類推), 直到作為作用域鏈終點(diǎn)的全局執(zhí)行環(huán)境 var result = compare(5, 8); //在調(diào)用compare()函數(shù)時(shí), 會(huì)創(chuàng)建一個(gè)包含arguments, value1, value2的活動(dòng)對(duì)象(在作用域鏈的優(yōu)先級(jí)處于第一位), 全局執(zhí)行環(huán)境的變量對(duì)象(包含result和compare)在compare()執(zhí)行環(huán)境的作用域鏈優(yōu)先級(jí)處于第二位
作用域鏈優(yōu)先級(jí)圖示 :
后臺(tái)的每一個(gè)執(zhí)行環(huán)境都有一個(gè)表示變量的對(duì)象 — — 變量對(duì)象
全局環(huán)境的變量對(duì)象始終存在, 而像compare()函數(shù)這樣的局部環(huán)境的變量對(duì)象, 則只在函數(shù)執(zhí)行的過(guò)程中存在.
創(chuàng)建compare()函數(shù)時(shí), 會(huì)創(chuàng)建一個(gè)預(yù)先包含全局變量對(duì)象的作用域鏈, 該作用域鏈會(huì)被保存在內(nèi)部的[[ Scope ]]屬性中
調(diào)用compare()函數(shù)時(shí), 會(huì)為函數(shù)創(chuàng)建一個(gè)執(zhí)行環(huán)境
然后通過(guò)復(fù)制函數(shù)的[[ Scope ]]屬性中的對(duì)象構(gòu)建起執(zhí)行環(huán)境的作用域鏈
?
未完待續(xù)...
模仿塊級(jí)作用域TODO
私有變量TODO
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/86308.html
摘要:技巧使你的更加專業(yè)這是上關(guān)于技巧的一篇譯文,另外你也可以在本項(xiàng)目看到原文。列舉了一些很實(shí)用的技巧,比如給空內(nèi)容的標(biāo)簽添加內(nèi)容,逗號(hào)分隔列表等等。排序算法看源碼,把它背下來(lái)吧排序算法的封裝。主要幫助初學(xué)者更好的掌握排序算法的實(shí)現(xiàn)。 成為專業(yè)程序員路上用到的各種優(yōu)秀資料、神器及框架 成為一名專業(yè)程序員的道路上,需要堅(jiān)持練習(xí)、學(xué)習(xí)與積累,技術(shù)方面既要有一定的廣度,更要有自己的深度。 Java...
摘要:技巧使你的更加專業(yè)這是上關(guān)于技巧的一篇譯文,另外你也可以在本項(xiàng)目看到原文。列舉了一些很實(shí)用的技巧,比如給空內(nèi)容的標(biāo)簽添加內(nèi)容,逗號(hào)分隔列表等等。排序算法看源碼,把它背下來(lái)吧排序算法的封裝。主要幫助初學(xué)者更好的掌握排序算法的實(shí)現(xiàn)。 成為專業(yè)程序員路上用到的各種優(yōu)秀資料、神器及框架 成為一名專業(yè)程序員的道路上,需要堅(jiān)持練習(xí)、學(xué)習(xí)與積累,技術(shù)方面既要有一定的廣度,更要有自己的深度。 Java...
摘要:第一部分請(qǐng)點(diǎn)擊快速掌握面試基礎(chǔ)知識(shí)一關(guān)鍵字如果使用關(guān)鍵字來(lái)調(diào)用函數(shù)式很特別的形式。該對(duì)象默認(rèn)包含了指向原構(gòu)造函數(shù)的屬性。接下來(lái)通過(guò)例子來(lái)幫助理解屬性包含了構(gòu)造函數(shù)以及構(gòu)造函數(shù)中在上定義的屬性。也就是說(shuō),的回調(diào)函數(shù)后執(zhí)行。 譯者按: 總結(jié)了大量JavaScript基本知識(shí)點(diǎn),很有用! 原文: The Definitive JavaScript Handbook for your next...
摘要:詳解十大常用設(shè)計(jì)模式力薦深度好文深入理解大設(shè)計(jì)模式收集各種疑難雜癥的問(wèn)題集錦關(guān)于,工作和學(xué)習(xí)過(guò)程中遇到過(guò)許多問(wèn)題,也解答過(guò)許多別人的問(wèn)題。介紹了的內(nèi)存管理。 延遲加載 (Lazyload) 三種實(shí)現(xiàn)方式 延遲加載也稱為惰性加載,即在長(zhǎng)網(wǎng)頁(yè)中延遲加載圖像。用戶滾動(dòng)到它們之前,視口外的圖像不會(huì)加載。本文詳細(xì)介紹了三種延遲加載的實(shí)現(xiàn)方式。 詳解 Javascript十大常用設(shè)計(jì)模式 力薦~ ...
摘要:特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 特意對(duì)前端學(xué)習(xí)資源做一個(gè)匯總,方便自己學(xué)習(xí)查閱參考,和好友們共同進(jìn)步。 本以為自己收藏的站點(diǎn)多,可以很快搞定,沒(méi)想到一入?yún)R總深似海。還有很多不足&遺漏的地方,歡迎補(bǔ)充。有錯(cuò)誤的地方,還請(qǐng)斧正... 托管: welcome to git,歡迎交流,感謝star 有好友反應(yīng)和斧正,會(huì)及時(shí)更新,平時(shí)業(yè)務(wù)工作時(shí)也會(huì)不定期更...
摘要:現(xiàn)在回過(guò)頭總結(jié),才又進(jìn)一步的揭開(kāi)了閉包的一層后臺(tái)管理系統(tǒng)解決方案前端掘金基于系列的后臺(tái)管理系統(tǒng)解決方案。什么是繼承大多數(shù)人使用繼承不外乎是為了獲得基于的單頁(yè)應(yīng)用項(xiàng)目模板前端掘金小貼士本項(xiàng)目已升級(jí)至。 關(guān)于js、jq零碎知識(shí)點(diǎn) - 掘金寫(xiě)在前面: 本文都是我目前學(xué)到的一些比較零碎的知識(shí)點(diǎn),也是相對(duì)偏一點(diǎn)的知識(shí),這是第二篇。前后可能沒(méi)有太大的相關(guān)性,需要的朋友可以過(guò)來(lái)參考下,喜歡的可以點(diǎn)個(gè)...
閱讀 670·2023-04-26 02:03
閱讀 1041·2021-11-23 09:51
閱讀 1155·2021-10-14 09:42
閱讀 1748·2021-09-13 10:23
閱讀 972·2021-08-27 13:12
閱讀 848·2019-08-30 11:21
閱讀 1007·2019-08-30 11:14
閱讀 1051·2019-08-30 11:09