聊一聊JS的原型鏈之高級篇

首先呢JS的繼承實現是藉助原型鏈,原型鏈即__proto__造成的鏈條。javascript

下面一個例子初步認識下原型鏈: java

 

function Animal (){

}
var cat = new Animal()
咱們建立了一個Animal這個構造函數,而後實例化出一個對象cat,當咱們輸出這個實例化對象cat的時候,這個實例化對象裏面有一個__proto__屬性,這個__proto__屬性指向的是建立本身的那個構造函數Animal的原型prototype,
也就是說這個實例化對象cat的__proto__指向的是Animal裏面的prototype這個原型對象,由於prototype是一個對象所以裏面確定也會有一個__proto__。而這個__proto__指向的是建立Animal這個構造函數的對象, 能夠想象一下誰建立了Animal? 確定是一個對象建立了Animal。 而這個對象就是 Function。有對象那麼確定就會有__proto__.那麼咱們能夠想象一下還有什麼能夠建立出來對象嗎? 這個時候咱們就不得不說一句"萬物皆是對象".所以對象的頂端也就是null。

那麼__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

  

 

 

 

  1. cat.__proto__ 是什麼?
  2. Animal.__proto__ 是什麼?
  3. Animal.prototype.__proto__ 是什麼?
  4. Object.__proto__ 是什麼?
  5. 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

  

JavaScript中有內置(build-in)構造器/對象共計12個(ES5中新加了JSON),這裏列舉了可訪問的8個構造器。剩下如Global不能直接訪問,Arguments僅在函數調用時由JS引擎建立,Math,JSON是以對象形式存在的,無需new。它們的 proto是Object.prototype。以下
 
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 

pPerson 的實例對象,p 的內部原型老是指向其構造器 Person 的原型對象 prototype

因此 全部的構造器都來自於 Function.prototype,甚至包括根構造器ObjectFunction自身。全部構造器都繼承了·Function.prototype·的屬性及方法。如length、call、apply、bind

Function.prototype也是惟一一個typeof Funtion.prototype爲 functionprototype。其它的構造器的prototype都是一個對象

 知道了全部構造器(含內置及自定義)的__proto__都是Function.prototype,那Function.prototype__proto__是誰呢?

Function.prototype.__proto__ === Object.prototype // true

  這說明全部的構造器也都是一個普通 JS 對象,能夠給構造器添加/刪除屬性等。同時它也繼承了Object.prototype上的全部方法:toString、valueOf、hasOwnProperty等

 最後Object.prototype的proto指向null,到頂了。

 

 什麼是Prototype

咱們看一下熟知的函數的原型對象

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; 
在 ECMAScript 核心所定義的所有屬性中,最回味無窮的就要數 prototype 屬性了。對於 ECMAScript 中的引用類型而言, prototype 是保存着它們全部實例方法的真正所在。換句話所說,諸如 toString()valuseOf() 等方法實際上都保存在 prototype 名下,只不過是經過各自對象的實例訪問罷了。  ——《JavaScript 高級程序設計》第三版 P116

當咱們建立一個數組時:

var num = new Array()
num 是 Array 的實例,因此 num 繼承了Array 的原型對象Array.prototype上全部的方法:

咱們能夠用一個 ES5 提供的新方法: 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)指向的是根構造器 ObjectObject.prototype是一個空對象 {}{}天然與 {getName: function(){}}不等。以下:
cat.constructor===Object;

cat.constructor.prototype===Object.prototype;

  

疑惑點

Function.prototype.__proto__ === Object.prototype //true
其實這一點我也有點困惑,不過也能夠試着解釋一下。 Function.prototype是個函數對象,理論上他的__proto__應該指向 Function.prototype,就是他本身,本身指向本身,沒有意義。 JS一直強調萬物皆對象,函數對象也是對象,給他認個祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保證原型鏈可以正常結束。

  再來看下面的:

//原型和原型鏈是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

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'))//可傳參

  

特色:

 能夠繼承實例屬性/方法,也能夠繼承原型屬性/方法

  1. 既是子類的實例,也是父類的實例
  2. 不存在引用屬性共享問題
  3. 可傳參
  4. 函數可複用

缺點:

  1. 調用了兩次父類構造函數,生成了兩份實例(子類實例將子類原型上的那份屏蔽了)

推薦指數:★★★★(僅僅多消耗了一點內存)

 

二、寄生組合繼承

寄生組合繼承
		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'))

  

特色:

  1. 堪稱完美

缺點:

  1. 實現較爲複雜

推薦指數:★★★★(實現複雜,扣掉一顆星)

相關文章
相關標籤/搜索