摘要:有了原型鏈,就有了繼承,繼承就是一個(gè)對(duì)象像繼承遺產(chǎn)一樣繼承從它的構(gòu)造函數(shù)中獲得一些屬性的訪問(wèn)權(quán)。這里其實(shí)就是一個(gè)原型鏈與繼承的典型例子,開(kāi)發(fā)中可能構(gòu)造函數(shù)復(fù)雜一點(diǎn),屬性定義的多一些,但是原理都是一樣的。
作用域、原型鏈、繼承與閉包詳解
注意:本章講的是在es6之前的原型鏈與繼承。es6引入了類(lèi)的概念,只是在寫(xiě)法上有所不同,原理是一樣的。幾個(gè)面試常問(wèn)的幾個(gè)問(wèn)題,你是否知道
instanceof的原理
如何準(zhǔn)確判斷變量的類(lèi)型
如何寫(xiě)一個(gè)原型鏈繼承的例子
描述new一個(gè)對(duì)象的過(guò)程
也許有些同學(xué)知道這幾個(gè)問(wèn)題的答案,就會(huì)覺(jué)得很小兒科,如果你還不知道這幾個(gè)問(wèn)題的答案或者背后所涉及到的知識(shí)點(diǎn),那就好好看完下文,想必對(duì)你會(huì)有幫助。先不說(shuō)答案,下面先分析一下涉及到的知識(shí)點(diǎn)。什么是構(gòu)造函數(shù)
JavaScript沒(méi)有類(lèi)的概念,JavaScript是一種基于對(duì)象的語(yǔ)言,除了五中值類(lèi)型(number boolean string null undefined)之外,其他的三種引用類(lèi)型(object、Array、Function)本質(zhì)上都是對(duì)象,而構(gòu)造函數(shù)其實(shí)也是普通的函數(shù),只是可以使用構(gòu)造函數(shù)來(lái)實(shí)例化對(duì)象。
事實(shí)上,當(dāng)任意一個(gè)普通函數(shù)用于創(chuàng)建一類(lèi)對(duì)象時(shí),它就被稱作構(gòu)造函數(shù)。像js的內(nèi)置函數(shù)Object、Array、Date等都是構(gòu)造函數(shù)。
在定義構(gòu)造函數(shù)有以下幾個(gè)特點(diǎn):
以大寫(xiě)字母開(kāi)頭定義構(gòu)造函數(shù)
在函數(shù)內(nèi)部對(duì)新對(duì)象(this)的屬性進(jìn)行設(shè)置
返回值必須是this,或者其它非對(duì)象類(lèi)型的值
下面定義一個(gè)簡(jiǎn)單的、標(biāo)準(zhǔn)的構(gòu)造函數(shù):
function Obj(){ this.name = "name" return this // 默認(rèn)有這一行 } var foo = new Obj() // 使用上面定義的構(gòu)造函數(shù)創(chuàng)建一個(gè)對(duì)象實(shí)例原型特性
js原型有5個(gè)特點(diǎn),記住這5條特點(diǎn),相信你一定會(huì)弄明白長(zhǎng)期困擾你的原型關(guān)系。
除了null所有引用類(lèi)型(Object、Array、Function)都有對(duì)象特性,也就是都可以自由擴(kuò)展屬性。
所有引用類(lèi)型都有一個(gè)_proto_屬性(又稱為:隱式屬性),_proto_是一個(gè)普通的對(duì)象。所有的對(duì)象都會(huì)有一個(gè)constructor屬性,constructor始終指向創(chuàng)建當(dāng)前對(duì)象的構(gòu)造函數(shù)
所有的函數(shù)都有一個(gè)prototype屬性(又稱為:顯式屬性),也是一個(gè)普通對(duì)象,這個(gè)prototype有一個(gè)constructor屬性指向該函數(shù)。
所有的引用類(lèi)型的_proto_屬性指向它的構(gòu)造函數(shù)的prototype屬性(比如:obj._proto_指向Object.prototype,obj是定義的一個(gè)普通對(duì)象,Object是js的內(nèi)置函數(shù))
當(dāng)從一個(gè)對(duì)象中獲得某個(gè)屬性時(shí),如果這個(gè)對(duì)象沒(méi)有該屬性,就會(huì)去它的_proto_(也就是它的構(gòu)造函數(shù)的prototype)中去尋找
先來(lái)解釋一下這幾條:
第一條的自由擴(kuò)展性可以通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)看
var obj = {} obj.name = "name" console.log(obj) // {name:"name"}
第二條和第三條是javascript就是這么規(guī)定的,沒(méi)什么好說(shuō)的
第四條可以這么理解,當(dāng)定義一個(gè)引用類(lèi)型的變量var obj = {} 其實(shí)是var obj = new Object()的語(yǔ)法糖,這樣Object就是obj的構(gòu)造函數(shù),根據(jù)第4條規(guī)定,obj._proto_ === Object.prototype,如果不理解可以看看上一章我們講的js內(nèi)置函數(shù)和上面講的構(gòu)造函數(shù)
第五條應(yīng)該好理解,當(dāng)從obj中獲取某個(gè)屬性時(shí),如果obj中沒(méi)有定義該屬性,就會(huì)逐級(jí)去它的_proto_對(duì)象中去尋找,而它的_proto_指向Object的prototype,也就是從Object的prototype對(duì)象中去尋找。
原型鏈與繼承如果上面明白了原型,那么原型鏈就會(huì)很好理解
根據(jù)原型定義的第4條和第5條,很容易發(fā)現(xiàn)通過(guò)對(duì)象的_proto_和函數(shù)的prototype把我們變量和構(gòu)造函數(shù)(自定義的構(gòu)造函數(shù)以及內(nèi)置構(gòu)造函數(shù))像鏈子一樣鏈接起來(lái),所以又叫他原型鏈。
有了原型鏈,就有了繼承,繼承就是一個(gè)對(duì)象像繼承遺產(chǎn)一樣繼承從它的構(gòu)造函數(shù)中獲得一些屬性的訪問(wèn)權(quán)。從下面一個(gè)小例子理解:
function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 原型繼承 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = "cat";
上面例子中在Foo構(gòu)造函數(shù)的prototype中自定義一個(gè)somefn函數(shù)。然后通過(guò)new Foo()創(chuàng)建一個(gè)對(duì)象實(shí)例并賦值給bar變量,此時(shí)bar就等于{name:"bar"}。然后bar.somefn就去bar對(duì)象中尋找somefn這個(gè)屬性,發(fā)現(xiàn)找不到,然后就去它的_proto_(其實(shí)就是Foo的prototype)中尋找,發(fā)現(xiàn)somefn就在Foo的prototype中定義了,就可以愉快的調(diào)用并執(zhí)行somefn了。
這里其實(shí)就是一個(gè)原型鏈與繼承的典型例子,開(kāi)發(fā)中可能構(gòu)造函數(shù)復(fù)雜一點(diǎn),屬性定義的多一些,但是原理都是一樣的。
留一個(gè)問(wèn)題,根據(jù)上面例子,如果執(zhí)行bar.stString(),應(yīng)該去哪里找toString這個(gè)方法? (提示:prototype也是普通對(duì)象,也有自己的_proto_)幾種繼承方式
這幾種都是es5中的繼承,es6中提供了class類(lèi),繼承起來(lái)更方便。
原型繼承上述例子就是一個(gè)原型繼承:
function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 原型繼承 function Cat(){ } Cat.prototype = new Animal(); Cat.prototype.name = "cat"; var cat = new Cat() console.log(cat instanceof Animal); //true console.log(cat instanceof Cat); //true
優(yōu)點(diǎn):
非常純粹的繼承關(guān)系,實(shí)例是子類(lèi)的實(shí)例,也是父類(lèi)的實(shí)例
簡(jiǎn)單,易于實(shí)現(xiàn)
缺點(diǎn)
要想為子類(lèi)新增屬性和方法,必須要在new Animal()這樣的語(yǔ)句之后執(zhí)行,不能放到構(gòu)造器中
無(wú)法實(shí)現(xiàn)多繼承
來(lái)自原型對(duì)象的引用屬性是所有實(shí)例共享的(嚴(yán)重缺點(diǎn))
創(chuàng)建子類(lèi)實(shí)例時(shí),無(wú)法向父類(lèi)構(gòu)造函數(shù)傳參(嚴(yán)重缺點(diǎn))
構(gòu)造繼承function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 構(gòu)造繼承 function Cat(name){ Animal.call(this); this.name = name || "Tom"; } // Test Code var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); // Tom正在睡覺(jué)! // console.log(cat.eat("fish")); // cat.eat is not a function console.log(cat instanceof Animal); // false console.log(cat instanceof Cat); // true
優(yōu)點(diǎn)
解決了1中,子類(lèi)實(shí)例共享父類(lèi)引用屬性的問(wèn)題
創(chuàng)建子類(lèi)實(shí)例時(shí),可以向父類(lèi)傳遞參數(shù)
可以實(shí)現(xiàn)多繼承
缺點(diǎn)
實(shí)例并不是父類(lèi)的實(shí)例,只是子類(lèi)的實(shí)例
只能繼承父類(lèi)的實(shí)例屬性和方法,不能繼承原型屬性/方法
無(wú)法實(shí)現(xiàn)函數(shù)復(fù)用,每個(gè)子類(lèi)都有父類(lèi)實(shí)例函數(shù)的副本,影響性能
實(shí)例繼承function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 實(shí)例繼承 function Cat(name){ var instance = new Animal(); instance.name = name || "Tom"; return instance; } var cat = new Cat(); // 或者可以直接var cat = Cat() console.log(cat.name); console.log(cat.sleep()); // Tom正在睡覺(jué)! console.log(cat.eat("fish")); // Tom正在吃:fish console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // false
優(yōu)點(diǎn)
不限制調(diào)用方式,不管是new Cat()還是Cat(),返回的對(duì)象具有相同的效果
缺點(diǎn)
實(shí)例是父類(lèi)的實(shí)例,不是子類(lèi)的實(shí)例
不支持多繼承
組合繼承function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 組合繼承 function Cat(name){ Animal.call(this); this.name = name || "Tom"; } Cat.prototype = new Animal(); Cat.prototype.constructor = Cat; var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); // Tom正在睡覺(jué)! console.log(cat.eat("fish")); // Tom正在吃:fish console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); // true
優(yōu)點(diǎn)
彌補(bǔ)了方式2的缺陷,可以繼承實(shí)例屬性/方法,也可以繼承原型屬性/方法
既是子類(lèi)的實(shí)例,也是父類(lèi)的實(shí)例
不存在引用屬性共享問(wèn)題
可傳參
函數(shù)可復(fù)用
缺點(diǎn)
調(diào)用了兩次父類(lèi)構(gòu)造函數(shù),生成了兩份實(shí)例(子類(lèi)實(shí)例將子類(lèi)原型上的那份屏蔽了)
寄生繼承var ob = {name:"小明",friends:["小花","小白"]}; function object(o){ function F(){}//創(chuàng)建一個(gè)構(gòu)造函數(shù)F F.prototype = o; return new F(); } //上面再ECMAScript5 有了一新的規(guī)范寫(xiě)法,Object.create(ob) 效果是一樣的 function createOb(o){ var newob = object(o);//創(chuàng)建對(duì)象 newob.sayname = function(){//增強(qiáng)對(duì)象 console.log(this.name); } return newob;//指定對(duì)象 } var ob1 = createOb(ob); ob1.sayname()
寄生繼承原理尚不明白。寄生組合繼承
寄生組合繼承有兩種方式:
第一種:利用創(chuàng)建沒(méi)有實(shí)例方法的函數(shù)
function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; //寄生組合繼承 function Cat(name){ Animal.call(this); this.name = name || "Tom"; } (function(){ // 創(chuàng)建一個(gè)沒(méi)有實(shí)例方法的類(lèi) var Super = function(){}; Super.prototype = Animal.prototype; //將實(shí)例作為子類(lèi)的原型 Cat.prototype = new Super(); Cat.prototype.constructor = Cat; })(); var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); // Tom正在睡覺(jué)! console.log(cat.eat("fish")); // Tom正在吃:fish console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true
第二種:利用Object.create函數(shù)
// 寄生繼承核心方法 function inheritPrototype(Parent, Children){ var prototype = Object.create(Parent.prototype); prototype.constructor = Children; Children.prototype = prototype; } // 父類(lèi) function Animal (name) { // 屬性 this.name = name || "Animal"; // 實(shí)例方法 this.sleep = function(){ console.log(this.name + "正在睡覺(jué)!"); } } // 原型方法 Animal.prototype.eat = function(food) { console.log(this.name + "正在吃:" + food); }; // 子類(lèi) function Cat(name){ Animal.call(this); this.name = name || "Tom"; } inheritPrototype(Animal, Cat) var cat = new Cat(); console.log(cat.name); console.log(cat.sleep()); // Tom正在睡覺(jué)! console.log(cat.eat("fish")); // Tom正在吃:fish console.log(cat instanceof Animal); // true console.log(cat instanceof Cat); //true
Object.create其實(shí)與以下代碼等價(jià)
function object(o){ function f(){} f.prototype = o; return new f(); }
優(yōu)點(diǎn)
最完美的繼承解決方案
缺點(diǎn)
實(shí)現(xiàn)復(fù)雜
解答一下最一開(kāi)始提出的問(wèn)題看到這里應(yīng)該對(duì)原型鏈與繼承的原理有所了解了,再回頭看上面的問(wèn)題,你也會(huì)發(fā)現(xiàn)這都是小兒科。
第一個(gè)問(wèn)題:instanceof原理?
var arr = [] arr instanceof Array
instanceof原理就是利用了原型鏈,當(dāng)執(zhí)行arr instanceof Array時(shí),會(huì)從arr的_proto_一層一層往上找,看是否能不能找到Array的prototype。
我們知道var arr = [] 其實(shí)是var arr = new Array()的語(yǔ)法糖,所以arr的_proto_指向Array的prototype,結(jié)果返回true
第二個(gè)問(wèn)題:如何準(zhǔn)確判斷變量類(lèi)型?
可以使用instanceof幫助我們判斷,而不是typeof
第三個(gè)問(wèn)題:如何寫(xiě)一個(gè)原型鏈繼承的例子?
function Foo () { this.name = "name" this.run = function () { console.log(this.name) } } function Bar () {} Bar.prototype = new Foo() // 從構(gòu)造函數(shù)Foo中繼承 var baz = new Bar() baz.run() // 打印出 "name"
第四個(gè)問(wèn)題:描述new一個(gè)對(duì)象的過(guò)程
創(chuàng)建一個(gè)新的對(duì)象,
獲得構(gòu)造函數(shù)的prototype屬性,并把prototype賦值給新對(duì)象的_proto_,this指向這個(gè)新對(duì)象
執(zhí)行構(gòu)造函數(shù),返回構(gòu)造函數(shù)的內(nèi)容
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://specialneedsforspecialkids.com/yun/107143.html
摘要:有了原型鏈,就有了繼承,繼承就是一個(gè)對(duì)象像繼承遺產(chǎn)一樣繼承從它的構(gòu)造函數(shù)中獲得一些屬性的訪問(wèn)權(quán)。這里其實(shí)就是一個(gè)原型鏈與繼承的典型例子,開(kāi)發(fā)中可能構(gòu)造函數(shù)復(fù)雜一點(diǎn),屬性定義的多一些,但是原理都是一樣的。 作用域、原型鏈、繼承與閉包詳解 注意:本章講的是在es6之前的原型鏈與繼承。es6引入了類(lèi)的概念,只是在寫(xiě)法上有所不同,原理是一樣的。 幾個(gè)面試常問(wèn)的幾個(gè)問(wèn)題,你是否知道 insta...
摘要:綜上所述有原型鏈繼承,構(gòu)造函數(shù)繼承經(jīng)典繼承,組合繼承,寄生繼承,寄生組合繼承五種方法,寄生組合式繼承,集寄生式繼承和組合繼承的優(yōu)點(diǎn)于一身是實(shí)現(xiàn)基于類(lèi)型繼承的最有效方法。 一、前言 繼承是面向?qū)ο螅∣OP)語(yǔ)言中的一個(gè)最為人津津樂(lè)道的概念。許多面對(duì)對(duì)象(OOP)語(yǔ)言都支持兩種繼承方式::接口繼承 和 實(shí)現(xiàn)繼承 。 接口繼承只繼承方法簽名,而實(shí)現(xiàn)繼承則繼承實(shí)際的方法。由于js中方法沒(méi)有簽名...
摘要:函數(shù)式編程前端掘金引言面向?qū)ο缶幊桃恢币詠?lái)都是中的主導(dǎo)范式。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。 JavaScript 函數(shù)式編程 - 前端 - 掘金引言 面向?qū)ο缶幊桃恢币詠?lái)都是JavaScript中的主導(dǎo)范式。JavaScript作為一門(mén)多范式編程語(yǔ)言,然而,近幾年,函數(shù)式編程越來(lái)越多得受到開(kāi)發(fā)者的青睞。函數(shù)式編程是一種強(qiáng)調(diào)減少對(duì)程序外部狀態(tài)產(chǎn)生改變的方式。因此,...
摘要:忍者級(jí)別的函數(shù)操作對(duì)于什么是匿名函數(shù),這里就不做過(guò)多介紹了。我們需要知道的是,對(duì)于而言,匿名函數(shù)是一個(gè)很重要且具有邏輯性的特性。通常,匿名函數(shù)的使用情況是創(chuàng)建一個(gè)供以后使用的函數(shù)。 JS 中的遞歸 遞歸, 遞歸基礎(chǔ), 斐波那契數(shù)列, 使用遞歸方式深拷貝, 自定義事件添加 這一次,徹底弄懂 JavaScript 執(zhí)行機(jī)制 本文的目的就是要保證你徹底弄懂javascript的執(zhí)行機(jī)制,如果...
閱讀 876·2021-11-22 09:34
閱讀 1011·2021-10-08 10:16
閱讀 1822·2021-07-25 21:42
閱讀 1794·2019-08-30 15:53
閱讀 3526·2019-08-30 13:08
閱讀 2185·2019-08-29 17:30
閱讀 3348·2019-08-29 17:22
閱讀 2180·2019-08-29 15:35