深入理解JS原型和繼承

前言

 在學習JS中的原型,原型鏈,繼承這些知識之前,我們先學習下基礎知識:函數和對象的關係。

 我們一直都知道,函數也是對象的一種,因為通過instanceof就可以判斷出來。但是函數和對象的關係並不是簡單的包含和被包含的關係,這兩者之間的關係還是有點複雜的。接下來我們就來捋一捋。

首先,闡述一點,對象都是通過函數創建的

對於下面這種類型的代碼,一般叫做“語法糖”

<code>

var

obj = {a:

10

,b:

20

};

var

arr = [

5

,

'x'

,

true

];/<code>

但是,其實上面這段代碼的實質是下面這樣的:

<code> 
 
 

var

obj =

new

Object

(); obj.a =

10

; obj.b =

20

;

var

arr =

new

Array

(); arr[

0

] =

5

; arr[

1

] =

'x'

; arr[

2

] =

true

;/<code>


而Object和Array都是函數,可以自己用typeof函數進行驗證。

所以,可以得出:對象都是通過函數創建的

正文

說完了前言,接下來我們進入正題。

1. 原型prototype

在前言中,我們說了函數也是一種對象,所以函數也是屬性的集合,同時,也可以對函數進行自定義屬性。

每個函數都有一個屬性——prototype。這個prototype的屬性值是一個對象(屬性的集合),默認只有一個叫做constructor的屬性,指向這個函數本身。 如下圖所示:


深入理解JS原型和繼承


上圖中,SuperType是一個函數,右側的方框就是它的原型。

原型既然作為對象(屬性的集合),除了constructor外,還可以自定義許多屬性,比如下面這樣的:


深入理解JS原型和繼承


當然了,我們也可以在自己定義的方法的prototype中增加我們自己的屬性,比如像下面這樣的:

<code>

function

Fn

(

)

{ } Fn.prototype.name =

'張三'

; Fn.prototype.getAge =

function

(

)

{

return

12

; };/<code>


深入理解JS原型和繼承


那麼問題來了:函數的prototype到底有何用呢?

在解決這個問題之前,我們還是先來看下另一個讓人迷糊的屬性:_proto_


2. “隱式原型”proto

我們先看一段非常常見的代碼:

<code>

function

Fn

(

)

{ } Fn.prototype.name =

'張三'

; Fn.prototype.getAge =

function

(

)

{

return

12

; };

var

fn =

new

Fn();

console

.log(fn.name);

console

.log(fn.getAge ());/<code>

即,Fn是一個函數,fn對象是從Fn函數new出來的,這樣fn對象就可以調用Fn.prototype中的屬性。

但是,因為每個對象都有一個隱藏的屬性——“_proto_”,這個屬性引用了創建這個對象的函數的prototype。即:fn._proto_ === Fn.prototype

那麼,這裡的_proto_到底是什麼呢?

其實,這個__proto__是一個隱藏的屬性,javascript不希望開發者用到這個屬性值,有的低版本瀏覽器甚至不支持這個屬性值。

<code>

var

obj = {};

console

.log(obj.__proto__);/<code>


深入理解JS原型和繼承


<code>

console

.log

(

Object

.prototype

);/<code>


深入理解JS原型和繼承


從上面來看,obj.__proto__和Object.prototype的屬性一樣!為什麼呢?

原因是:obj這個對象本質上是被Object函數創建的,因此obj.proto=== Object.prototype。我們可以用一個圖來表示。


深入理解JS原型和繼承


即,每個對象都有一個_proto_屬性,指向創建該對象的函數的prototype。


說一下自定義函數的prototype:

自定義函數的prototype本質上就是和 var obj = {} 是一樣的,都是被Object創建,所以它的__proto__指向的就是Object.prototype。

但是,Object.prototype確實一個特例——它的__proto__指向的是null,切記切記!!!


深入理解JS原型和繼承


另外一個問題:函數也是一種對象,函數也有__proto__嗎?

答:當然也不例外啦!

下面用一段代碼和一張圖來說明這個問題,看完相信就有個比較直觀的理解啦!

<code>

function

fn

(

x, y

)

{

return

x+y; }

console

.log(fn(

10

,

20

));

var

fn1 =

new

Function

(

"x"

,

"y"

,

"return x+y;"

);

console

.log(fn1(

5

,

6

));/<code>


用圖表示就是:


深入理解JS原型和繼承


從上圖可以看出:自定義函數Foo.__proto__指向Function.prototype,Object.__proto__指向Function.prototype。

但是,為什麼有Function.__proto__指向Function.prototype呢?

其實原因很簡單:Function也是一個函數,函數是一種對象,也有__proto__屬性。既然是函數,那麼它一定是被Function創建。所以Function是被自身創建的。所以它的__proto__指向了自身的Prototype

最後一個問題:Function.prototype指向的對象,它的__proto__是不是也指向Object.prototype?

答案是肯定的。因為Function.prototype指向的對象也是一個普通的被Object創建的對象,所以也遵循基本的規則。如下圖:


深入理解JS原型和繼承


說了這麼多,我們將上面這些圖片整合到一整個圖片,便於整體理解,圖片如下:


深入理解JS原型和繼承


3. instanceof

主要是說明下instanceof的判斷規則是如何進行的。先看如下代碼和圖片:

<code>

function

fn

(

)

{ }

var

f1 =

new

fn();

console

.log(f1

instanceof

fn);

console

.log(f1

instanceof

Object

);/<code>


深入理解JS原型和繼承


instanceof的判斷規則為:

假設instanceof運算符的第一個變量是一個對象,暫時稱為A;第二個變量一般是一個函數,暫時稱為B。

instanceof的判斷規則是:沿著A的__proto__這條線來找,同時沿著B的prototype這條線來找,如果兩條線能找到同一個引用,即同一個對象,那麼就返回true。如果找到終點還未重合,則返回false。

結合這個判斷規則,上面的代碼和圖示相信很容易看懂了。


4. 原型繼承

首先說一下什麼是原型鏈:

 訪問一個對象的屬性時,先在基本屬性中查找,如果沒有,再沿著_proto_這條鏈向上找,這就是原型鏈。

舉一個例子說明下吧:

在實際應用中如何區分一個屬性到底是基本的還是從原型中找到的呢?

答案就是:hasOwnProperty這個函數,特別是在for…in…循環中,一定要注意。


深入理解JS原型和繼承


但是!!f1本身並沒有hasOwnProperty這個方法,那是從哪裡來的呢?答案很簡單,是從Object.prototype中來的。看下圖:


深入理解JS原型和繼承

對象的原型鏈是沿著__proto__這條線走的,因此在查找f1.hasOwnProperty屬性時,就會順著原型鏈一直查找到Object.prototype。由於所有對象的原型鏈都會找到Object.prototype,因此所有對象都會有Object.prototype的方法。這就是所謂的“繼承”。


分享到:


相關文章: