摘要:是對(duì)象或者實(shí)例中內(nèi)置的,其指向的是產(chǎn)生該對(duì)象的對(duì)象的在瀏覽器中提供了讓我們可以訪問(wèn),通過(guò)的指向形成的一個(gè)鏈條,就稱(chēng)做原型鏈,原型鏈的整個(gè)鏈路是實(shí)例對(duì)象構(gòu)造函數(shù)的的。
原型和原型鏈
原型prototype,在創(chuàng)建新函數(shù)的時(shí)候,會(huì)自動(dòng)生成,而prototype中也會(huì)有一個(gè)constructor,回指創(chuàng)建該prototype的函數(shù)對(duì)象。
__proto__是對(duì)象或者實(shí)例中內(nèi)置的[[prototype]],其指向的是產(chǎn)生該對(duì)象的對(duì)象的prototype,在瀏覽器中提供了__proto__讓我們可以訪問(wèn),通過(guò)__proto__的指向形成的一個(gè)鏈條,就稱(chēng)做原型鏈,原型鏈的整個(gè)鏈路是:實(shí)例對(duì)象- ->構(gòu)造函數(shù)的prototype- ->Object的prototype- ->null。
我們?cè)谠L問(wèn)對(duì)象的屬性或者方法的時(shí)候,首先從本對(duì)象尋找,如果本對(duì)象不存在該屬性或者方法時(shí),就會(huì)沿著原型鏈向上尋找,直至找到該屬性或者方法,或者到null時(shí)停止。
這也解釋了為什么數(shù)組對(duì)象上沒(méi)有push,pop,shift,unshift等方法,卻可以訪問(wèn)。
constructorconstructor屬性指向的是生成該函數(shù)(對(duì)象)的函數(shù)(對(duì)象),例如
var a = function(){}; var b = new a(); var c = {}; var d = []; //以下皆為true console.log(b.constructor === a) //因?yàn)閷?shí)例b是由構(gòu)造函數(shù)產(chǎn)生的 console.log(a.constructor === Function)//函數(shù)a實(shí)際是Function的實(shí)例,同理 console.log(c.constructor === Object)//空對(duì)象c是Object的實(shí)例 console.log(d.constructor === Array)//空對(duì)象c是Object的實(shí)例 console.log(Object.constructor === Function)//Object自身就是一個(gè)構(gòu)造函數(shù),同理 console.log(Array.constructor === Function)//Array自身也是一個(gè)構(gòu)造函數(shù) //--------------------------------------------------------------- //首先__proto__指向的是產(chǎn)生該對(duì)象的對(duì)象的prototype, //也即a.prototype,prototype中也的constructor,回指創(chuàng)建該prototype的函數(shù)對(duì)象,也即函數(shù)a console.log(b.__proto__.constructor === a)
這里順便說(shuō)一下instanceof,A instanceof B 是在 A 的原型鏈中找 B 的 prototype,找到返回 true,找不到返回 false
//有個(gè)奇怪的現(xiàn)象,下面都返回true,這是為什么呢? //因?yàn)镴S中一切都繼承自O(shè)bject,除了最頂層的null, //所以在Function的原型鏈中能找到Object.prototype console.log(Function instanceof Object) //而Object自身就是一個(gè)構(gòu)造函數(shù),因此在Object的原型鏈中也能找到Function.prototype console.log(Object instanceof Function)通過(guò)原型鏈實(shí)現(xiàn)繼承
由上面的分析,我們可以利用原型鏈實(shí)現(xiàn)繼承的邏輯,繼承是面向?qū)ο笾械囊粋€(gè)很重要的概念
function Dog(name){ this.name = name; this.say1 = function(){ console.log(this.name) } } Dog.prototype.say2 = function(){ console.log(this.name) } Dog.prototype.test = 1 //say本來(lái)應(yīng)該是所有Dog實(shí)例的共有方法, //如果放在構(gòu)造函數(shù)中,那么就會(huì)導(dǎo)致沒(méi)辦法數(shù)據(jù)共享,每一個(gè)實(shí)例都有自己的屬性和方法的副本,這是對(duì)資源的極大浪費(fèi) //如果放在Dog.prototype中,那么利用原型鏈的特性,就可以讓所有實(shí)例共用一個(gè)方法, //需要注意的是,由于共用了一個(gè)方法,對(duì)屬性的更改是對(duì)所有實(shí)例透明的 var dog1 = new Dog("lalala"); let dog2 = new Dog("wahaha"); dog1.test++;//2 dog2.test++;//3 console.log(dog1.say1 === dog2.say1)// false console.log(dog1.say2 === dog2.say2)// true //現(xiàn)在,我們可以嘗試著去實(shí)現(xiàn)繼承了 //我們是通過(guò)原型鏈去實(shí)現(xiàn)繼承的, //之前的原型鏈?zhǔn)牵篋og實(shí)例 --> Dog函數(shù) --> Object --> null //那么現(xiàn)在的原型鏈需要改成 Dog實(shí)例 --> Dog函數(shù) --> Dog父類(lèi)(Animal函數(shù)) --> Object --> null //第一種方案,改變Dog函數(shù)的prototype,讓他指向Animal的實(shí)例 function Animal(){ this.species = "unknown"; } Dog.prototype = new Animal(); //這里改變后會(huì)導(dǎo)致prototype中的constructor改變 Dog.prototype.constructor = Dog; //第二鐘方案,改變Dog函數(shù)的prototype,讓他指向Animal的prototype function Animal(){} Animal.prototype.species = "unknown"; Dog.prototype = Animal.prototype; //這里改變后會(huì)導(dǎo)致prototype中的constructor改變 Dog.prototype.constructor = Dog; //第三種方案,調(diào)用apply或call,將Animal的this綁定到Dog中 function Animal(){ this.species = "unknown"; } function Dog(name){ Animal.apply(this, arguments); this.name = name; } //第四種方法,通過(guò)Object.create()方法實(shí)現(xiàn)繼承,過(guò)濾掉了父類(lèi)實(shí)例屬性,Dog.prototype中就沒(méi)有了Animal的實(shí)例化數(shù)據(jù)了 //這種方法也是ES6中Class被babel編譯成ES5所用的方法 function Animal(){ this.species = "unknown"; } function Dog(name){ Animal.apply(this, arguments); this.name = name; } //這里模擬了 Dog.prototype = Object.create(Animal.prototype) var f = function(){}; f.prototype = Animal.pototype; Dog.prototype = new f(); Dog.__proto__ = Animal; //這里改變后會(huì)導(dǎo)致prototype中的constructor改變 Dog.prototype.constructor = Dog; //現(xiàn)在就能訪問(wèn)到Animal中的species屬性了 var dog = new Dog("lalala"); dog.species;//unknown
以上這些就是利用原型鏈實(shí)現(xiàn)繼承的一些方法
ES6的class類(lèi)有了以上的知識(shí),我們就可以研究一下ES6的class類(lèi)了,這個(gè)語(yǔ)法糖能讓我們更容易的實(shí)現(xiàn)類(lèi)和繼承,其提供了extends,static,super等關(guān)鍵字
//這是es6的代碼實(shí)現(xiàn) class Parent { static l(){ console.log(222) } constructor(m){ this.m = m } get(){ return this.m; } } class Child extends Parent { constructor(n){ super(4); this.n = n; } get(){ return this.n } set(a){ this.n = a; } } //這是利用babel編譯之后的es5的實(shí)現(xiàn) //_createClass是一個(gè)自執(zhí)行函數(shù),作用給構(gòu)造函數(shù)綁定靜態(tài)方法和動(dòng)態(tài)方法 //對(duì)于靜態(tài)的static關(guān)鍵字聲明的變量,會(huì)直接綁定在函數(shù)對(duì)象上,作為靜態(tài)屬性(方法) //對(duì)于在class中聲明的函數(shù)方法,則會(huì)綁定在構(gòu)造函數(shù)的prototype上,通過(guò)Object.definePropety方法 var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); //如果父函數(shù)沒(méi)有返回值或者返回值不為object或者function,則返回子類(lèi)的this function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn"t been initialised - super() hasn"t been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } //_inherits就是extends關(guān)鍵字發(fā)揮的作用,實(shí)現(xiàn)了繼承的功能。利用&&的短路特性,對(duì)superClass做了容錯(cuò)性處理,然后將子類(lèi)Object.create()傳了兩個(gè)參數(shù),一個(gè)參數(shù)是父類(lèi)superClass.prototype,作用在上面解釋繼承的方法時(shí)講過(guò)了,第二個(gè)參數(shù)是一個(gè)鍵值對(duì),key代表著屬性,value則和Object.definePropety中descriptor一樣,這里改變constructor的目的,也在解釋繼承時(shí)講過(guò)了,最后將subClass.__proto__指向superClass function _inherits(subClass, superClass) { //...省略 subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } //_classCallCheck是保證構(gòu)造函數(shù)不能被當(dāng)成普通函數(shù)調(diào)用,需要用new關(guān)鍵字 function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var Parent = function () { _createClass(Parent, null, [{ key: "l", value: function l() { console.log(222); } }]); function Parent(m) { _classCallCheck(this, Parent); this.m = m; } _createClass(Parent, [{ key: "get", value: function get() { return this.m; } }]); return Parent; }(); var Child = function (_Parent) { _inherits(Child, _Parent); function Child(n) { _classCallCheck(this, Child); //由于在_inherits中將subClass(child).__proto__指向了superClass(Parent),所以這里即是Parent.call(this,4),即這里執(zhí)行的是super函數(shù),super也可以調(diào)用父類(lèi)的靜態(tài)方法, //如果父函數(shù)沒(méi)有返回值或者返回值不為object或者function,則返回子類(lèi)的this var _this = _possibleConstructorReturn(this, (Child.__proto__ || Object.getPrototypeOf(Child)).call(this, 4)); _this.n = n; return _this; } _createClass(Child, [{ key: "set", value: function set(a) { this.n = a; } }]); return Child; }(Parent);總結(jié)
通過(guò)以上分析,對(duì)原型和原型鏈有了更加深入和清晰的了解,也熟悉了constructor和instanceof的用法,加深了基于原型鏈的繼承方式的了解,理清了這塊知識(shí)。
在對(duì)ES6的class通過(guò)babel編譯后的源碼的分析中,也了解到了Object.create和Object.setPrototypeOf的用法,挖掘了如何去模擬super,extends和static的實(shí)現(xiàn)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/97051.html
摘要:接下來(lái)我們來(lái)聊一下的原型鏈繼承和類(lèi)。組合繼承為了復(fù)用方法,我們使用組合繼承的方式,即利用構(gòu)造函數(shù)繼承屬性,利用原型鏈繼承方法,融合它們的優(yōu)點(diǎn),避免缺陷,成為中最常用的繼承。 JavaScript是一門(mén)面向?qū)ο蟮脑O(shè)計(jì)語(yǔ)言,在JS里除了null和undefined,其余一切皆為對(duì)象。其中Array/Function/Date/RegExp是Object對(duì)象的特殊實(shí)例實(shí)現(xiàn),Boolean/N...
摘要:實(shí)現(xiàn)思路使用原型鏈實(shí)現(xiàn)對(duì)原型方法和方法的繼承,而通過(guò)借用構(gòu)造函數(shù)來(lái)實(shí)現(xiàn)對(duì)實(shí)例屬性的繼承。繼承屬性繼承方法以上代碼,構(gòu)造函數(shù)定義了兩個(gè)屬性和。 JS面向?qū)ο蟮某绦蛟O(shè)計(jì)之繼承的實(shí)現(xiàn)-組合繼承 前言:最近在細(xì)讀Javascript高級(jí)程序設(shè)計(jì),對(duì)于我而言,中文版,書(shū)中很多地方翻譯的差強(qiáng)人意,所以用自己所理解的,嘗試解讀下。如有紕漏或錯(cuò)誤,會(huì)非常感謝您的指出。文中絕大部分內(nèi)容引用自《Java...
摘要:由一個(gè)問(wèn)題引發(fā)的思考這個(gè)方法是從哪兒蹦出來(lái)的首先我們要清楚數(shù)組也是對(duì)象,而且是對(duì)象的實(shí)例也就是說(shuō),下面兩種形式是完全等價(jià)的只不過(guò)是一種字面量的寫(xiě)法,在深入淺出面向?qū)ο蠛驮透拍钇恼吕铮覀兲岬竭^(guò)類(lèi)會(huì)有一個(gè)屬性,而這個(gè)類(lèi)的實(shí)例可以通過(guò)屬性訪 1.由一個(gè)問(wèn)題引發(fā)的思考 let arr1 = [1, 2, 3] let arr2 = [4, 5, 6] arr1.c...
摘要:下面來(lái)看一個(gè)例子繼承屬性繼承方法在這個(gè)例子中構(gòu)造函數(shù)定義了兩個(gè)屬性和。組合繼承最大的問(wèn)題就是無(wú)論什么情況下都會(huì)調(diào)用兩次超類(lèi)型構(gòu)造函數(shù)一次是在創(chuàng)建子類(lèi)型原型的時(shí)候另一次是在子類(lèi)型構(gòu)造函數(shù)內(nèi)部。 組合繼承 組合繼承(combination inheritance),有時(shí)候也叫做偽經(jīng)典繼承,指的是將原型鏈和借用構(gòu)造函數(shù)的技術(shù)組合到一塊,從而發(fā)揮二者之長(zhǎng)的一種繼承模式。其背后的思路是使用原型鏈...
閱讀 1436·2019-08-30 12:54
閱讀 1923·2019-08-30 11:16
閱讀 1656·2019-08-30 10:50
閱讀 2507·2019-08-29 16:17
閱讀 1327·2019-08-26 12:17
閱讀 1423·2019-08-26 10:15
閱讀 2434·2019-08-23 18:38
閱讀 826·2019-08-23 17:50