首先呢JS的繼承實現是藉助原型鏈,原型鏈即__proto__造成的鏈條。javascript
下面一個例子初步認識下原型鏈: java
function Animal (){ } var cat = new Animal()
那麼__proto__與prototype有什麼區別呢?數組
prototype屬性也叫原型對象, prototype只有函數纔有的屬性, _proto_是全部對象都有的屬性(null和undefined除外),並且指向創造該obj
對象的函數對象的prototype屬性,可是_proto_不是標準的屬性,只有部分瀏覽器實現了,對應的標準的屬性是[[Prototype]],大多數狀況下,大多數狀況下,__proto__能夠理解爲'構造器的原型',即Animal.__proto__===Animal.constructor.prototype(經過Object.create建立對象不適用此對象);瀏覽器
下面看一下各類狀況下_proto_的指向; app
1、Object.create()函數
var p={};ui
var q=Object.create(p)
q.__proto__===q.constructor.prototype//false
q.__proto__===p;//truethis
2、字面量spa
var a={}prototype
a.__proto__===Object.prototype//true
3、構造器(proto指向構造器的prototype)
function Animal(){
}
var cat = new Animal()
cat.__proto__===Animal.prototype//true
var obj = {name: 'jack'} var arr = [1,2,3] var reg = /hello/g var date = new Date var err = new Error('exception') console.log(obj.__proto__ === Object.prototype) // true console.log(arr.__proto__ === Array.prototype) // true console.log(reg.__proto__ === RegExp.prototype) // true console.log(date.__proto__ === Date.prototype) // true console.log(err.__proto__ === Error.prototype) // true
cat.__proto__
是什麼?Animal.__proto__
是什麼?Animal.prototype.__proto__
是什麼?Object.__proto__
是什麼?Object.prototype__proto__
是什麼?一、cat.__proto__===Animal.prototype;
二、Animal.__proto__===Function.prototype//Animal是函數對象
三、Animal.prototype.__proto__===Object.prototype//Animal.prototype是普通對象
四、Object.__proto__===Function.prototype//Object是函數對象
五、Object.prototype.__proto__===null
4、函數對象
全部的函數對象的__proto__都指向Function.prototype,它是一個空函數(empty function)
// 全部的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身 Number.__proto__ === Function.prototype // true Number.constructor == Function //true Boolean.__proto__ === Function.prototype // true Boolean.constructor == Function //true String.__proto__ === Function.prototype // true String.constructor == Function //true Object.__proto__ === Function.prototype // true Object.constructor == Function // true // 全部的構造器都來自於Function.prototype,甚至包括根構造器Object及Function自身 Function.__proto__ === Function.prototype // true Function.constructor == Function //true Array.__proto__ === Function.prototype // true Array.constructor == Function //true RegExp.__proto__ === Function.prototype // true RegExp.constructor == Function //true Error.__proto__ === Function.prototype // true Error.constructor == Function //true Date.__proto__ === Function.prototype // true Date.constructor == Function //true
Math.__proto__ === Object.prototype // true Math.construrctor == Object // true JSON.__proto__ === Object.prototype // true JSON.construrctor == Object //true
再看看自定義的構造器,這裏定義了一個 Person
:
function Person(name) { this.name = name; } var p = new Person('jack') console.log(p.__proto__ === Person.prototype) // true
p
是 Person
的實例對象,p
的內部原型老是指向其構造器 Person
的原型對象 prototype
。
Function.prototype
,甚至包括根構造器Object
及Function
自身。全部構造器都繼承了·Function.prototype·的屬性及方法。如length、call、apply、bind
Function.prototype
也是惟一一個typeof Funtion.prototype
爲 function
的prototype
。其它的構造器的prototype
都是一個對象
知道了全部構造器(含內置及自定義)的__proto__
都是Function.prototype
,那Function.prototype
的__proto__
是誰呢?
Function.prototype.__proto__ === Object.prototype // true
這說明全部的構造器也都是一個普通 JS 對象,能夠給構造器添加/刪除屬性等。同時它也繼承了Object.prototype上的全部方法:toString、valueOf、hasOwnProperty等。
最後Object.prototype的proto指向null,到頂了。
咱們看一下熟知的函數的原型對象
Function.prototype;//function() {} Object.prototype;//Object {} Number.prototype;//Number {[[PrimitiveValue]]: 0} Boolean.prototype;//Boolean {[[PrimitiveValue]]: false} Array.prototype;//[] String.prototype;//String {length: 0, [[PrimitiveValue]]: ""}
說道這裏,必須提的是全部函數對象的原型對象都繼承製原始對象,即fn.prototype.__proto__爲原始對象(原始對象在繼承屬性__proto__中有定義)。這其中比較特別的是Object函數,他的原型對象就是原始對象,即Object.prototype。
var f1 = new Function(); var f2 = Function(); var fn3 = function(){} console.log(f1.prototype.__proto__ === Object.prototype);//true console.log(f2.prototype.__proto__ === Object.prototype);//true console.log(fn3.prototype.__proto__ === Object.prototype);//true console.log(Number.prototype.__proto__ === Object.prototype);//true console.log(Boolean.prototype.__proto__ === Object.prototype);//true
實際上js沒有繼承這個東東,可是__proto__卻起到了相似繼承的做用。咱們所知的全部的對象起源都是一個空對象,咱們把這個空對象叫作原始對象。全部的對象經過__proto__回溯最終都會指向(所謂的指向相似C中的指針,這個原始對象是惟一的,整個內存中只會存在一個原始對象)這個原始對象。用下面的例子佐證
var o = new Object(); o.__proto__;//Object {} o.prototype;//undefined Object.prototype;//Object {} Object.__proto__;//function(){} Object.__proto__.__proto__;//Object {} Object.__proto.prototype;//undefined
var f = new Function(); f.__proto__;//function(){} f.prototype;//Object {} 新的實例對象非原始對象 Function.prototype;//function(){} Function.__proto__;//function(){} Function.__proto__.__proto__;//Object {}
Function.prototype.__proto__;//Object {}
Function.prototype.__proto__.__proto__//null
原始對象的__proto__屬性爲null,而且沒有原型對象。
全部的對象都繼承自原始對象;Object比較特殊,他的原型對象也就是原始對象;因此咱們每每用Object.prototype表示原始對象
//全部的對象都繼承自原始對象 //Object比較特殊,他的原型對象也就是原始對象 //因此咱們每每用Object.prototype表示原始對象 Object.prototype === o.__proto__;//true Object.prototype === Object.__proto__.__proto__;//true Object.prototype === Function.__proto__.__proto__;//true
全部的函數對象都繼承製原始函數對象;Function比較特殊,他的原型對象也就是原始函數對象;因此咱們每每用Function.prototype表示原始函數對象;
而原始函數對象又繼承自原始對象。
//全部的函數對象都繼承製原始函數對象, //Function比較特殊,他的原型對象也就是原始函數對象 Function.prototype === f.__proto__ Function.prototype === Object.__proto__ ;//true Function.prototype === Function.__proto__;//true //因此咱們每每用Function.prototype表示原始函數對象 //而原始函數對象又繼承自原始對象 Function.prototype.__proto__ === Object.prototype;
prototype
屬性了。對於 ECMAScript 中的引用類型而言,
prototype
是保存着它們全部實例方法的真正所在。換句話所說,諸如
toString()
和
valuseOf()
等方法實際上都保存在
prototype
名下,只不過是經過各自對象的實例訪問罷了。 ——《JavaScript 高級程序設計》第三版 P116
var num = new Array()
num
是 Array
的實例,因此 num
繼承了Array
的原型對象Array.prototype
上全部的方法:
Object.getOwnPropertyNames
prototy
中的屬性,返回一個數組:
Object.getOwnPropertyNames(Array.prototype) ["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "filter", "forEach", "some", "every", "map", "indexOf", "lastIndexOf", "reduce", "reduceRight", "copyWithin", "find", "findIndex", "fill", "includes", "keys", "entries", "concat"]
細心的你確定發現了Object.getOwnPropertyNames(Array.property)
輸出的數組裏並無 constructor/hasOwnPrototype
等對象的方法。可是隨便定義的數組也能用這些方法
由於Array.prototype
雖然沒這些方法,可是它有原型對象(__proto__
):
Array.prototype.__proto__ == Object.prototype//Array.prototype是普通對象
Array.prototype
繼承了對象的全部方法,當你用
num.hasOwnPrototype()
時,JS 會先查一下它的構造函數 (
Array
) 的原型對象
Array.prototype
有沒有有
hasOwnPrototype()
方法,沒查到的話繼續查一下
Array.prototype
的原型對象
Array.prototype.__proto__
有沒有這個方法。
Object.getOwnPropertyNames(Function.prototype) ["length", "name", "arguments", "caller", "apply", "bind", "call", "toString", "constructor"]
這些屬性和方法全部的函數對象均可以用。
function Animal(){ } var cat = new Animal() cat.__proto__===Animal.prototype;//true cat.constructor.prototype===Animal.prototype//true cat.__proto__===cat.constructor.prototype;//true
若是改寫
function Animal(){ } Animal.prototype={ getName:function(){} }
var cat = new Animal();
cat.__proto__===Animal.prototype;//true
cat.constructor.prototype===Animal.prototype//false cat.__proto__===cat.constructor.prototype;//false
這樣重寫了Animal的prototype屬性,cat.constructor.prototype不在等於Animal.prototype,
這也很好理解,給Animal.prototype
賦值的是一個對象直接量
{getName: function(){}}
,使用對象直接量方式定義的對象其構造器(
constructor
)指向的是根構造器
Object
,
Object.prototype
是一個空對象
{}
,
{}
天然與
{getName: function(){}}
不等。以下:
cat.constructor===Object; cat.constructor.prototype===Object.prototype;
疑惑點
Function.prototype.__proto__ === Object.prototype //true
再來看下面的:
//原型和原型鏈是JS實現繼承的一種模型。 //原型鏈的造成是真正是靠__proto__ 而非prototype var animal = function(){}; var dog = function(){}; animal.price = 2000; dog.prototype = animal; var tidy = new dog(); console.log(dog.price) //undefined console.log(tidy.price) // 2000 var dog = function(){}; dog.prototype.price = 2000; var tidy = new dog(); console.log(tidy.price); // 2000 console.log(dog.price); //undefin var dog = function(){}; var tidy = new dog(); tidy.price = 2000; console.log(dog.price); //undefined
實例tidy和 原型對象dog.prototype存在一個鏈接。不過,要明確的真正重要的一點就是,這個鏈接存在於實例tidy與構造函數的原型對象dog.prototype之間,而不是存在於實例tidy與構造函數dog之間。
constructor 屬性返回對建立此對象的函數對象的引用。
function a(){}; console.log(a.constructor===Function); //true console.log(a.prototype.constructor===a); //true
函數a
是由Function創造出來,那麼它的constructor指向的Function,a.prototype
是由new a()
方式創造出來,那麼a.prototype.constructor
理應指向a
一、組合繼承
// 組合繼承 function Animal(){ this.name=name||'Animal'; this.sleep=function(){ console.log(this.name+'sleep'); } } Animal.prototype.eat=function(food){ console.log(this.name+'eat'+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.prototype.constructor); console.log(cat.name) console.log(cat.eat('haha'))//可傳參
特色:
能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法
缺點:
推薦指數:★★★★(僅僅多消耗了一點內存)
二、寄生組合繼承
寄生組合繼承 function Animal(){ this.name=name||'Animal'; this.sleep=function(){ console.log(this.name+'sleep'); } } Animal.prototype.eat=function(food){ console.log(this.name+'eat'+food); } function Cat(name){ Animal.call(this); this.name=name||'tom'; } (function(){ var Super=function(){};// 建立一個沒有實例方法的類 Super.prototype=Animal.prototype; Cat.prototype=new Super(); //將實例做爲子類的原型 })() Cat.prototype.constructor = Cat; var cat=new Cat(); console.log(cat.eat('haha'))
特色:
缺點:
推薦指數:★★★★(實現複雜,扣掉一顆星)