原型prototype,在建立新函數的時候,會自動生成,而prototype中也會有一個constructor,回指建立該prototype的函數對象。javascript
__proto__是對象或者實例中內置的[[prototype]],其指向的是產生該對象的對象的prototype,在瀏覽器中提供了__proto__讓咱們能夠訪問,經過__proto__的指向造成的一個鏈條,就稱作原型鏈,原型鏈的整個鏈路是:實例對象- ->構造函數的prototype- ->Object的prototype- ->null。java
咱們在訪問對象的屬性或者方法的時候,首先從本對象尋找,若是本對象不存在該屬性或者方法時,就會沿着原型鏈向上尋找,直至找到該屬性或者方法,或者到null時中止。es6
這也解釋了爲何數組對象上沒有push,pop,shift,unshift等方法,卻能夠訪問。數組
constructor屬性指向的是生成該函數(對象)的函數(對象),例如瀏覽器
var a = function(){}; var b = new a(); var c = {}; var d = []; //如下皆爲true console.log(b.constructor === a) //由於實例b是由構造函數產生的 console.log(a.constructor === Function)//函數a實際是Function的實例,同理 console.log(c.constructor === Object)//空對象c是Object的實例 console.log(d.constructor === Array)//空對象c是Object的實例 console.log(Object.constructor === Function)//Object自身就是一個構造函數,同理 console.log(Array.constructor === Function)//Array自身也是一個構造函數 //--------------------------------------------------------------- //首先__proto__指向的是產生該對象的對象的prototype, //也即a.prototype,prototype中也的constructor,回指建立該prototype的函數對象,也即函數a console.log(b.__proto__.constructor === a)
這裏順便說一下instanceof,A instanceof B 是在 A 的原型鏈中找 B 的 prototype,找到返回 true,找不到返回 falsebabel
//有個奇怪的現象,下面都返回true,這是爲何呢? //由於JS中一切都繼承自Object,除了最頂層的null, //因此在Function的原型鏈中能找到Object.prototype console.log(Function instanceof Object) //而Object自身就是一個構造函數,所以在Object的原型鏈中也能找到Function.prototype console.log(Object instanceof Function)
由上面的分析,咱們能夠利用原型鏈實現繼承的邏輯,繼承是面向對象中的一個很重要的概念app
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原本應該是全部Dog實例的共有方法, //若是放在構造函數中,那麼就會致使沒辦法數據共享,每個實例都有本身的屬性和方法的副本,這是對資源的極大浪費 //若是放在Dog.prototype中,那麼利用原型鏈的特性,就可讓全部實例共用一個方法, //須要注意的是,因爲共用了一個方法,對屬性的更改是對全部實例透明的 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 //如今,咱們能夠嘗試着去實現繼承了 //咱們是經過原型鏈去實現繼承的, //以前的原型鏈是:Dog實例 --> Dog函數 --> Object --> null //那麼如今的原型鏈須要改爲 Dog實例 --> Dog函數 --> Dog父類(Animal函數) --> Object --> null //第一種方案,改變Dog函數的prototype,讓他指向Animal的實例 function Animal(){ this.species = 'unknown'; } Dog.prototype = new Animal(); //這裏改變後會致使prototype中的constructor改變 Dog.prototype.constructor = Dog; //第二鍾方案,改變Dog函數的prototype,讓他指向Animal的prototype function Animal(){} Animal.prototype.species = 'unknown'; Dog.prototype = Animal.prototype; //這裏改變後會致使prototype中的constructor改變 Dog.prototype.constructor = Dog; //第三種方案,調用apply或call,將Animal的this綁定到Dog中 function Animal(){ this.species = 'unknown'; } function Dog(name){ Animal.apply(this, arguments); this.name = name; } //第四種方法,經過Object.create()方法實現繼承,過濾掉了父類實例屬性,Dog.prototype中就沒有了Animal的實例化數據了 //這種方法也是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; //這裏改變後會致使prototype中的constructor改變 Dog.prototype.constructor = Dog; //如今就能訪問到Animal中的species屬性了 var dog = new Dog('lalala'); dog.species;//unknown
以上這些就是利用原型鏈實現繼承的一些方法函數
有了以上的知識,咱們就能夠研究一下ES6的class類了,這個語法糖能讓咱們更容易的實現類和繼承,其提供了extends,static,super等關鍵字this
//這是es6的代碼實現 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的實現 //_createClass是一個自執行函數,做用給構造函數綁定靜態方法和動態方法 //對於靜態的static關鍵字聲明的變量,會直接綁定在函數對象上,做爲靜態屬性(方法) //對於在class中聲明的函數方法,則會綁定在構造函數的prototype上,經過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; }; }(); //若是父函數沒有返回值或者返回值不爲object或者function,則返回子類的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關鍵字發揮的做用,實現了繼承的功能。利用&&的短路特性,對superClass作了容錯性處理,而後將子類Object.create()傳了兩個參數,一個參數是父類superClass.prototype,做用在上面解釋繼承的方法時講過了,第二個參數是一個鍵值對,key表明着屬性,value則和Object.definePropety中descriptor同樣,這裏改變constructor的目的,也在解釋繼承時講過了,最後將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是保證構造函數不能被當成普通函數調用,須要用new關鍵字 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),即這裏執行的是super函數,super也能夠調用父類的靜態方法, //若是父函數沒有返回值或者返回值不爲object或者function,則返回子類的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);