摘要:命令通過構(gòu)造函數(shù)新建實(shí)例對象,實(shí)質(zhì)就是將實(shí)例對象的原型,指向構(gòu)造函數(shù)的屬性,然后在實(shí)例對象上執(zhí)行構(gòu)造函數(shù)。
大部分面向?qū)ο蟮木幊陶Z言,都是以“類”(class)作為對象體系的語法基礎(chǔ)。JavaScript語言中是沒有class的概念的(ES6之前,ES6中雖然提供了class的寫法,但實(shí)現(xiàn)原理并不是傳統(tǒng)的“類”class概念,僅僅是一種寫法), 但是它依舊可以實(shí)現(xiàn)面向?qū)ο蟮木幊蹋@就是通過JavaScript中的“原型對象”(prototype)來實(shí)現(xiàn)的。
prototype 屬性請看這樣一個例子:
function Person(name, gender) { this.name = name; this.gender = gender; this.sayHello = function() { console.log("Hello,I am", this.name, ". I"m a", this.gender); }; }
這樣定義了一個構(gòu)造函數(shù),我們創(chuàng)建對象就可以使用這個構(gòu)造函數(shù)作為模板來生成。不過以面向?qū)ο蟮乃枷雭砜矗浑y發(fā)現(xiàn)其中的一點(diǎn)問題:name和gender屬性是每個實(shí)例都各不相同,作為一個自身的屬性沒有問題,而sayHello方法,每個實(shí)例對象應(yīng)該都有,而且都一樣,給每個實(shí)例對象一個全新的、完全不同(雖然代碼內(nèi)容一樣,但JavaScript中每個sayHello的值都在內(nèi)存中多帶帶存在)的sayHello方法是沒有必要的。
var zs = new Person("zhang san", "male"), xh = new Person("xiao hong", "female"); zs.sayHello(); // Hello,I am zhang san . I"m a male xh.sayHello(); // Hello,I am xiao hong . I"m a female zs.sayHello === xh.sayHello; // false
上面代碼中展示了zs.sayHell和xh.sayHello這兩個作用相同,而且看起來代碼內(nèi)容也是完全一樣的對象,實(shí)際是兩個獨(dú)立的,互不相關(guān)的對象。
面向?qū)ο笏枷胫校菍⒐驳摹⒊橄蟮膶傩院头椒ㄌ崛〕鰜恚鳛橐粋€基類,子類繼承這個基類,從而繼承到這些屬性和方法。而JavaScript中則可以通過prototype屬性來實(shí)現(xiàn)類似的作用。以下是上面代碼的改進(jìn)示例:
function Person(name, gender) { this.name = name; this.gender = gender; } Person.prototype.sayHello = function() { console.log("Hello,I am", this.name, ". I"m a", this.gender); }; var zs = new Person("zhang san", "male"), xh = new Person("xiao hong", "female"); zs.sayHello(); // Hello,I am zhang san . I"m a male xh.sayHello(); // Hello,I am xiao hong . I"m a female zs.sayHello === xh.sayHello; // true
這時將sayHello方法定義到Person對象上的prototype屬性上,取代了在構(gòu)造函數(shù)中給每個實(shí)例對象添加sayHello方法。可以看到,其還能實(shí)現(xiàn)和之前相同的作用,而且zs.sayHell和xh.sayHello是相同的內(nèi)容,這樣就很貼近面向?qū)ο蟮乃枷肓恕D敲?b>zs和xh這兩個對象,是怎么訪問到這個sayHello方法的呢?
在瀏覽器控制臺中打印出zs,將其展開,可以看到下面的結(jié)果:
zs; /** * Person gender: "male" name: "zhang san" __proto__: Object constructor: function Person(name, gender) arguments: null caller: null length: 2 name: "Person" prototype: Object sayHello:function() arguments:null caller:null length:0 name:"" prototype:Object */
zs這個對象只有兩個自身的屬性gender和name,這和其構(gòu)造函數(shù)Person的模板相同,并且可以在Person對象的__proto__屬性下找到sayHello方法。那么這個__proto__是什么呢?它是瀏覽器環(huán)境下部署的一個對象,它指的是當(dāng)前對象的原型對象,也就是構(gòu)造函數(shù)的prototype屬性。
現(xiàn)在就可以明白了,我們給構(gòu)造函數(shù)Person對象的prototype屬性添加了sayHello方法,zs和xh這兩個通過Person構(gòu)造函數(shù)產(chǎn)生的對象,是可訪問到Person對象的prototype屬性的,所以我們定義在prototype下的sayHello方法,Person的實(shí)例對象都可以訪問到。
關(guān)于構(gòu)造函數(shù)的new命令原理是這樣的:
constructor 屬性創(chuàng)建一個空對象,作為將要返回的對象實(shí)例
將這個空對象的原型,指向構(gòu)造函數(shù)的prototype屬性
將這個空對象賦值給函數(shù)內(nèi)部的this關(guān)鍵字
開始執(zhí)行構(gòu)造函數(shù)內(nèi)部的代碼
prototype下有一個屬性constructor,默認(rèn)指向此prototype對象所在的構(gòu)造函數(shù)。
如上例中的zs下__proto__的constructor值為function Person(name, gender)。
由于此屬性定義在prototype屬性上,所以它可以在所有的實(shí)例對象中獲取到。
zs.constructor; // function Person(name, gender) { // this.name = name; // this.gender = gender; // } zs.hasOwnProperty("constructor"); // false zs.constructor === Person; // true zs.constructor === Function; // false zs.constructor === Object; // false
將constructor屬性放在prototype屬性中的一個作用是,可以通過這個屬性來判斷這個對象是由哪個構(gòu)造函數(shù)產(chǎn)生的,上面代碼中,zs是由Person構(gòu)造函數(shù)產(chǎn)生的,而不是Function或者Object構(gòu)造函數(shù)產(chǎn)生。
constructor屬性的另一個作用就是:提供了一種繼承的實(shí)現(xiàn)模式。
function Super() { // ... } function Sub() { Sub.superclass.constructor.call(this); // ... } Sub.superclass = new Super();
上面代碼中,Super和Sub都是構(gòu)造函數(shù),在Sub內(nèi)部的this上調(diào)用Super,就會形成Sub繼承Super的效果,miniui中是這樣實(shí)現(xiàn)繼承的:
mini.Control = function(el) { mini.Control.superclass.constructor.apply(this, arguments); // ... } // 其中的superclass指代父類的prototype屬性
我們自己寫一個例子:
// 父類 function Animal(name) { this.name = name; this.introduce = function() { console.log("Hello , My name is", this.name); } } Animal.prototype.sayHello = function() { console.log("Hello, I am:", this.name); } // 子類 function Person(name, gender) { Person.superclass.constructor.apply(this, arguments); this.gender = gender; } Person.superclass = new Animal(); // 子類 function Dog(name) { Dog.superclass.constructor.apply(this, arguments); } Dog.superclass = new Animal();
基本原理就是在子類中使用父類的構(gòu)造函數(shù)。在Person和Dog中均沒有對name屬性和introduce方法進(jìn)行操作,只是使用了父類Animal的構(gòu)造函數(shù),就可以將name屬性和introduce方法繼承來,請看下面例子:
var zs = new Person("zhang san", "male"); zs; // Person {name: "zhang san", gender: "male"} zs.sayHello(); // Uncaught TypeError: zs.sayHello is not a function(…) zs.introduce(); // Hello , My name is zhang san var wangCai = new Dog("旺財(cái)"); wangCai; // Dog {name: "旺財(cái)"} wangCai.introduce(); // Hello , My name is 旺財(cái)
確實(shí)實(shí)現(xiàn)了我們需要的效果。可是我們發(fā)現(xiàn)在調(diào)用zs.sayHello()時報錯了。為什么呢?
其實(shí)不難發(fā)現(xiàn)問題,我們的Person.superclass是Animal的一個實(shí)例,是有sayHello方法的,但是我們在Perosn構(gòu)造函數(shù)的內(nèi)部,只是使用了Person.superclass.constructor。而Person.superclass.constructor指的僅僅是Animal構(gòu)造函數(shù)本身,并沒有包括Animal.prototype,所以沒有sayHello方法。
一種改進(jìn)方法是:將自定義的superclass換為prototype,即:
function Person(name, gender) { Person.prototype.constructor.apply(this, arguments); this.gender = gender; } Person.prototype = Animal.prototype; var zs = new Person("zhang san", "male"); zs.sayHello(); // Hello, I am: zhang san zs.introduce() // Hello , My name is zhang san
這樣就全部繼承到了Animal.prototype下的方法。
但是一般不要這樣做,上面寫法中Person.prototype = Animal.prototype; 等號兩端都是一個完整的對象,進(jìn)行賦值時,Person.prototype的原對象完全被Animal.prototype替換,切斷了和之前原型鏈的聯(lián)系,而且此時Person.prototype和Animal.prototype是相同的引用,給Person.prototype 添加的屬性方法也將添加到Animal.prototype,反之亦然,這將引起邏輯混亂。
因此我們在原型上進(jìn)行擴(kuò)展是,通常是添加屬性,而不是替換為一個新對象。
// 好的寫法 Person.prototype.sayHello = function() { console.log("Hello,I am", this.name, ". I"m a", this.gender); }; Person.prototype. // .. 其他屬性 // 不好的寫法 Person.prototype = { sayHello:function(){ console.log("Hello,I am", this.name, ". I"m a", this.gender); }, // 其他屬性方法 ... }JavaScript 原型鏈
JavaScript的所有對象都有構(gòu)造函數(shù),而所有構(gòu)造函數(shù)都有prototype屬性(其實(shí)是所有函數(shù)都有prototype屬性),所以所有對象都有自己的原型對象。
對象的屬性和方法,有可能是定義在自身,也有可能是定義在它的原型對象。由于原型本身也是對象,又有自己的原型,所以形成了一條原型鏈(prototype chain)。
zs.sayHello(); // Hello,I am zhang san . I"m a male zs.toString(); // "[object Object]"
例如上面的zs對象,它的原型對象是Person的prototype屬性,而Person的prototype本身也是一個對象,它的原型對象是Object.prototype。
zs本身沒有sayHello方法,JavaScript通過原型鏈向上繼續(xù)尋找,在Person.prototype上找到了sayHello方法。toString方法在zs對象本身上沒有,Person.prototype上也沒有,因此繼續(xù)沿原型鏈查找,最終可以在Object.prototype上找到了toString方法。
而Object.prototype的原型指向null,由于null沒有任何屬性,因此原型鏈到Object.prototype終止,所以Object.prototype是原型鏈的最頂端。
“原型鏈”的作用是,讀取對象的某個屬性時,JavaScript引擎先尋找對象本身的屬性,如果找不到,就到它的原型去找,如果還是找不到,就到原型的原型去找。如果直到最頂層的Object.prototype還是找不到,則返回undefined。
如果對象自身和它的原型,都定義了一個同名屬性,那么優(yōu)先讀取對象自身的屬性,這叫做“覆蓋”(overiding)。
JavaScript中通過原型鏈實(shí)現(xiàn)了類似面向?qū)ο缶幊陶Z言中的繼承,我們在復(fù)制一個對象時,只用復(fù)制其自身的屬性即可,無需將整個原型鏈進(jìn)行一次復(fù)制,Object.prototype下的hasOwnProperty方法可以判斷一個屬性是否是該對象自身的屬性。
實(shí)例對象、構(gòu)造函數(shù)、prototype之間的關(guān)系可用下圖表示:
instranceof 運(yùn)算符instanceof運(yùn)算符返回一個布爾值,表示指定對象是否為某個構(gòu)造函數(shù)的實(shí)例。由于原型鏈的關(guān)系,所謂的實(shí)例并不一定是某個構(gòu)造函數(shù)的直接實(shí)例,更準(zhǔn)確的描述,應(yīng)該是:返回一個后者的原型對象是否在前者的原型鏈上
zs instanceof Person; // true zs instanceof Object ;// true var d = new Date(); d instanceof Date; // true d instanceof Object; // true原型鏈相關(guān)屬性和方法 Object.prototype.hasOwnProperty()
hasOwnProperty()方法用來判斷某個對象是否含有指定的自身屬性。這個方法可以用來檢測一個對象是否含有特定的自身屬性,和 in 運(yùn)算符不同,該方法會忽略掉那些從原型鏈上繼承到的屬性。
zs.hasOwnProperty("name"); // true zs.hasOwnProperty("gender"); // true zs.hasOwnProperty("sayHello"); // fasle Person.prototype.hasOwnProperty("sayHello"); // true zs.hasOwnProperty("toString"); // fasle Object.prototype.hasOwnProperty("toString"); // trueObject.prototype.isPrototypeOf()
對象實(shí)例的isPrototypeOf方法,用來判斷一個對象是否是另一個對象的原型。
var o1 = {}; var o2 = Object.create(o1); var o3 = Object.create(o2); o2.isPrototypeOf(o3) // true o1.isPrototypeOf(o3) // true
上面代碼表明,只要某個對象處在原型鏈上,isProtypeOf都返回true。
Object.prototype.isPrototypeOf({}) // true Object.prototype.isPrototypeOf([]) // true Object.prototype.isPrototypeOf(/xyz/) // true Object.prototype.isPrototypeOf(Object.create(null)) // false
看起來這個方法和instanceof運(yùn)算符作用類似,但實(shí)際使用是不一樣的。
例如:
zs instanceof Person ; // true; Person.isPrototypeOf(zs);// false Person.prototype.isPrototypeOf(zs); // true
zs instanceof Person可理解為判斷Person.prototype在不在zs的原型鏈上。 而Person.isPrototypeOf(zs)指的就是Person本身在不在zs的原型鏈上,所以返回false,只有Person.prototype.isPrototypeOf(zs)才為 true。
Object.getPrototypeOf()ES5Object.getPrototypeOf方法返回一個對象的原型。這是獲取原型對象的標(biāo)準(zhǔn)方法。
// 空對象的原型是Object.prototype Object.getPrototypeOf({}) === Object.prototype // true // 函數(shù)的原型是Function.prototype function f() {} Object.getPrototypeOf(f) === Function.prototype // true // f 為 F 的實(shí)例對象,則 f 的原型是 F.prototype var f = new F(); Object.getPrototypeOf(f) === F.prototype // true Object.getPrototypeOf("foo"); // TypeError: "foo" is not an object (ES5 code) Object.getPrototypeOf("foo"); // String.prototype (ES6 code)
此方法是ES5方法,需要IE9+。在ES5中,參數(shù)只能是對象,否則將拋出異常,而在ES6中,此方法可正確識別原始類型。
Object.setPrototypeOf()ES5Object.setPrototypeOf方法可以為現(xiàn)有對象設(shè)置原型,返回一個新對象。接受兩個參數(shù),第一個是現(xiàn)有對象,第二個是原型對象。
var a = {x: 1}; var b = Object.setPrototypeOf({}, a); // 等同于 // var b = {__proto__: a}; b.x // 1
上面代碼中,b對象是Object.setPrototypeOf方法返回的一個新對象。該對象本身為空、原型為a對象,所以b對象可以拿到a對象的所有屬性和方法。b對象本身并沒有x屬性,但是JavaScript引擎找到它的原型對象a,然后讀取a的x屬性。
new命令通過構(gòu)造函數(shù)新建實(shí)例對象,實(shí)質(zhì)就是將實(shí)例對象的原型,指向構(gòu)造函數(shù)的prototype屬性,然后在實(shí)例對象上執(zhí)行構(gòu)造函數(shù)。
var F = function () { this.foo = "bar"; }; // var f = new F();等同于下面代碼 var f = Object.setPrototypeOf({}, F.prototype); F.call(f);Object.create()
ES5Object.create方法用于從原型對象生成新的實(shí)例對象,它接收兩個參數(shù):第一個為一個對象,新生成的對象完全繼承前者的屬性(即新生成的對象的原型此對象);第二個參數(shù)為一個屬性描述對象,此對象的屬性將會被添加到新對象。(關(guān)于屬性描述對象可參考:MDN - Object.defineProperty())
上面代碼舉例:
var zs = new Person("zhang san", "male"); var zs_clone = Object.create(zs); zs_clone; // {} zs_clone.sayHello(); // Hello,I am zhang san . I"m a male zs_clone.__proto__ === zs; // true // Person // __proto__: Person // gender: "male" // name: "zhang san" // __proto__: Object
可以 看出 創(chuàng)建的新對象zs_clone的原型為zs,從而獲得了zs的全部屬性和方法。但是其自身屬性為空,若需要為新對象添加自身屬性,則使用第二個參數(shù)即可。
var zs_clone = Object.create(zs, { name: { value: "zhangsan"s clone" }, gender: { value: "male" }, age: { value: "25" } }); zs_clone; // Person {name: "zhangsan"s clone", gender: "male", age: "25"}參考鏈接
JS中的prototype - 軒脈刃
prototype 對象 - JavaScript標(biāo)準(zhǔn)參考教程
更多可見JavaScript 原型鏈
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://specialneedsforspecialkids.com/yun/92368.html
摘要:之前有朋友問怎么去理解原型和原型鏈的問題。理解原型鏈的小技巧將箭頭視作泛化子類到父類關(guān)系那么圖中所有的虛線將構(gòu)成一個繼承層級,而實(shí)線表示屬性引用。原型鏈?zhǔn)菍?shí)現(xiàn)繼承的重要方式,原型鏈的形成是真正是靠而非。 之前有朋友問怎么去理解原型和原型鏈的問題。這個問題,在面試中,很多同學(xué)經(jīng)常都會遇到。這里給大家講講,方便大家記憶。 JavaScript的特點(diǎn)JavaScript是一門直譯式腳本...
摘要:之前有朋友問怎么去理解原型和原型鏈的問題。理解原型鏈的小技巧將箭頭視作泛化子類到父類關(guān)系那么圖中所有的虛線將構(gòu)成一個繼承層級,而實(shí)線表示屬性引用。原型鏈?zhǔn)菍?shí)現(xiàn)繼承的重要方式,原型鏈的形成是真正是靠而非。 之前有朋友問怎么去理解原型和原型鏈的問題。這個問題,在面試中,很多同學(xué)經(jīng)常都會遇到。這里給大家講講,方便大家記憶。 JavaScript的特點(diǎn)JavaScript是一門直譯式腳本...
摘要:為了防止之后自己又開始模糊,所以自己來總結(jié)一下中關(guān)于作用域鏈和原型鏈的知識,并將二者相比較看待進(jìn)一步加深理解。因此我們發(fā)現(xiàn)當(dāng)多個作用域相互嵌套的時候,就形成了作用域鏈。原型鏈原型說完了作用域鏈,我們來講講原型鏈。 畢業(yè)也整整一年了,看著很多學(xué)弟都畢業(yè)了,忽然心中頗有感慨,時間一去不復(fù)還呀。記得從去年這個時候接觸到JavaScript,從一開始就很喜歡這門語言,當(dāng)時迷迷糊糊看完了《J...
摘要:并沒有類繼承模型,而是使用原型對象進(jìn)行原型式繼承。我們舉例說明原型鏈查找機(jī)制當(dāng)訪問一個對象的屬性時,會從對象本身開始往上遍歷整個原型鏈,直到找到對應(yīng)屬性為止。原始類型有以下五種型。此外,試圖查找一個不存在屬性時將會遍歷整個原型鏈。 Javascript 并沒有類繼承模型,而是使用原型對象 prototype 進(jìn)行原型式繼承。 盡管人們經(jīng)常將此看做是 Javascript 的一個缺點(diǎn),然...
摘要:不理解沒關(guān)系,下面會結(jié)合圖例分析上一篇高級程序設(shè)計(jì)筆記創(chuàng)建對象下一篇高級程序設(shè)計(jì)筆記繼承參考之原型鏈的解讀三張圖搞懂的原型對象與原型鏈繼承與原型鏈 文章直接從原型圖解開始的,如果對一些概念不太清除,可以結(jié)合后面幾節(jié)查看 1. 圖解原型鏈 1.1 鐵三角關(guān)系(重點(diǎn)) function Person() {}; var p = new Person(); showImg(https://s...
摘要:由于一般所有的原型鏈最終都會指向頂端的,所以它們都是的。好了現(xiàn)在了,成了所有對象原型鏈的。 JavaScript里任何東西都是對象,任何一個對象內(nèi)部都有另一個對象叫__proto__,即原型,它可以包含任何東西讓對象繼承。當(dāng)然__proto__本身也是一個對象,它自己也有自己的__proto__,這樣一級一級向上,就構(gòu)成了一個__proto__鏈,即原型鏈。當(dāng)然原型鏈不會無限向上,它有...
閱讀 2381·2021-10-09 09:41
閱讀 3172·2021-09-26 09:46
閱讀 835·2021-09-03 10:34
閱讀 3151·2021-08-11 11:22
閱讀 3365·2019-08-30 14:12
閱讀 711·2019-08-26 11:34
閱讀 3344·2019-08-26 11:00
閱讀 1750·2019-08-26 10:26