進擊JavaScript之(四)原型與原型鏈

本文您將看到如下內容:數組

  • 傳統構造函數的問題
  • 一些相關概念
  • 認識原型
  • 構造、原型、實例三角結構圖
  • 對象的原型鏈
  • 函數的構造函數Function

一句話說明什麼是原型:原型就是一個JavaScript對象,原型能存儲咱們的方法,構造函數建立出來的實例對象可以引用原型中的方法。瀏覽器

1、傳統構造函數的問題

有以下代碼服務器

function Foo(){
    this.sayHello = function(){
     }
}

因爲對象是調用new Foo()所建立出來的,所以每個對象在建立的時候,函數 sayHello 都會唄建立一次閉包

那麼有沒一個對象都含有一個獨立的,不一樣的,可是功能邏輯同樣的函數,好比:{} == {}框架

在代碼中方法就會消耗性能,最典型的資源就越是內存函數

這裏最好的方法就是將函數放在構造函數以外,那麼在構造函數中引用該函數便可性能

function sayHello () {}
function Foo () {
    this.say = sayHello;
}

會在開發中變得困難:引入框架危險,代碼繁冗很差維護。解決方法就是若是外面的函數不佔用其名字,並且在函數名下。this

每個函數在定義的時候,有一個神祕對象(就是原型對象,暫且這麼稱呼)被建立出來。spa

每個由構造函數建立的對象都會默認的鏈接到該神祕對象上。prototype

var f1 = new Foo();
var f2 = new Foo();
f1.sayHello();    //若是f1沒有sayHello那麼就會在Foo.prototype中去找

由構造函數建立出來的衆多對象共享一個對象就是:構造函數.prototype

只須要將共享的東西,重複會多佔用內存的東西放到構造函數.prototype中,那麼全部的對象就能夠共享了。

function Foo(){}
Foo.prototype.sayHello = function(){
    console.log("….");
}
var f1 = new Foo();
f1.sayHello();
var f2 = new Foo();
f2.sayHello();
console.log(f1.sayHello === f2.sayHello); // true

2、一些相關概念

類class:在JS中就是構造函數

  • 在傳統的面嚮對象語言中,使用一個叫類的東西定義模板,而後使用模板建立對象。
  • 在構造方法中也具備相似的功能,所以也稱其爲類

實例(instance)與對象(object)

  • 實例通常是指某一個構造函數建立出來的對象,咱們稱爲XXXX 構造函數的實例
  • 實例就是對象。對象是一個泛稱
  • 實例與對象是一個近義詞

鍵值對與屬性和方法

  • 在JS中鍵值對的集合稱爲對象
  • 若是值爲數據(非函數),就稱該鍵值對爲屬性
  • 若是值爲函數(方法),就稱該鍵值對爲方法method

父類與子類(基類和派生類)

  • 傳統的面嚮對象語言中使用類來實現繼承那麼就有父類、子類的概念
  • 父類又稱爲基類,子類又稱爲派生類
  • 在JS中沒有類的概念,在JS中經常稱爲父對象,子對象,基對象,派生對象。

3、認識原型

在JavaScript中,原型也是一個對象,經過原型能夠實現對象的屬性繼承,JavaScript的對象中都包含了一個[[Prototype]]內部屬性,這個屬性所對應的就是該對象的原型。

[[Prototype]]做爲對象的內部屬性,是不能被直接訪問的。因此爲了方便查看一個對象的原型,Firefox和Chrome中提供了__proto__這個非標準(不是全部瀏覽器都支持)的訪問器(ECMA引入了標準對象原型訪問器"Object.getPrototype(object)")。

下面經過一個例子來看看原型相關概念:

function Person() {}
// 神祕對象就是Person.prototype
//那麼只有使用構造函數才能夠訪問它
var o = new Person();
//之前不能直接使用o來訪問神祕對象
//如今有了__proto__後,
o.__proto__也能夠直接訪問神祕對象
//那麼o.__proto__ === Person.prototype

神祕對象(原型)中都有一個屬性constructor,翻譯爲 構造器 。表示該原型是與什麼構造函數聯繫起來的。

__proto__有什麼用?能夠訪問原型。因爲在開發中除非特殊要求,不要使用實例去修改原型的成員,所以該屬性開發時使用較少。可是在調試過程當中很是方便,能夠輕易的訪問原型進行查當作員

若是在早期的瀏覽器中使用實例須要訪問原型如何處理?可使用實例對象訪問構造器,而後使用構造器訪問原型

var o = new Person();
o.constructor.prototype

若是給實例繼承自原型的屬性賦值

function Foo();
Foo.prototype.name = "test";
var o1 = new Foo();
var o2 = new Foo();
o1.name = "張三";    // 不是修改原型中的name而是本身增長了一個name屬性
console.log(o1.name + ','+ o2.name);    // 張三,test

4、構造、原型、實例三角結構圖

對於以下代碼:

function Person(){}
var p = new Person()

console.log(Person.prototype.constructor); //function Person(){}
console.log(Person.prototype.constructor.name); //Person
console.log(typeof Person.prototype.constructor); //function

console.log(p.__prop__);
console.log(p.__prop__ === Person.prototype);//true

因而他們的關係圖以下:

構造、原型、實例三角結構圖

5、對象的原型鏈

凡是對象就有原型,原型也是對象。所以凡是給定一個對象,那麼就能夠找到他的原型,原型還有原型,那麼如此下去,就構成一個對象的序列,稱該結構爲原型鏈。

問題:

  1. 原型鏈到底到何時是一個頭?
  2. 一個默認的原型鏈結構是怎樣的?
  3. 原型鏈結構對已知語法的修正

5.1 原型鏈的結構

凡是使用構造函數,建立出對象,而且沒有利用賦值的方式修改原型,就說該對象保留默認的原型鏈。

默認原型鏈結構是什麼樣子呢?

function Person(){}
var p = new Person();
//p 具備默認的原型鏈

默認的原型鏈結構就是:當前對象 -> 構造函數.prototype -> Object.prototype -> null

構造、原型、實例三角結構圖

在實現繼承的時候,有時候會利用替換原型鏈結構的方式實現原型繼承,那麼原型鏈結構就會發送改變

function DunizbCollection(){}
DunizbCollection.prototype = [];
var arr = new DunizbCollection();
// arr -> [] -> Array.prototype -> Object.prototype -> null

6、函數的構造函數Function

在JS中使用Function能夠實例化函數對象 。也就是說在JS中函數與普通對象同樣,也是一個對象類型。函數是JS中的一等公民。

  1. 函數是對象,就可使用對象的動態特性
  2. 函數是對象,就有構造函數建立函數
  3. 函數是對象,能夠建立其它對象
  4. 函數是惟一能夠限定變量做用域的結果

要解決的問題

  1. Function 如何使用
  2. Function 與函數的關係
  3. 函數的原型鏈結構

6.1 函數是Function的實例

語法

new Function( arg0,arg1,arg1,….argN, body );

Function 中的參數所有是字符串

該構造函數的做用是將參數連接起來組成函數

  • 若是參數只有一個,那麼表示函數體
  • 若是參數有多個,最後一個參數表示函數體,前面的全部參數表示函數的參數
  • 若是沒有參數,表示建立一個空函數

舉例:建立一個打印一句話的函數

// 傳統的
function foo () {
    console.log( '你好' );
}
//Function
var func = new Function( 'console.log( "你好" );' );
// 功能上,這裏foo 與 func 等價

再好比,建立一個空函數

//傳統
function foo () {}
//Function
var func = new Function();
func();

傳入函數內一個數字,打印該函數

//傳統
function foo ( num ) {
    console.log( num );
}
//Function
var func = new Function( "num" ,"console.log( num )" );
func();

6.2 函數的原型鏈結構

任意的一個函數,都是至關於Function的實例,相似於{}與new Object()的關係。

function foo () {}

上面的代告訴解析器,有一個對象叫foo,它是一個函數;至關於new Function()獲得一個函數對象

  • 函數應該有什麼屬性?答:__proto__
  • 函數的構造函數是什麼?答:Function
  • 函數應該繼承自Function.prototype
  • Function.prototype繼承自Object.prototype

對於Function,咱們還必須知道

  • Object函數是Function的一個實例
  • Object做爲對象是繼承自Function.prototype的,又「Function.prototype」繼承自Object.prototype

    foo.prototype.__proto__ === Object.prototype // true
  • Function是本身的構造函數
  • 在JS 中任何對象的老祖宗就是Object.prototype
  • 在JS中任何函數的老祖宗就是Function.prototype

下面繪製出 Function 的構造原型實例三角形結構

6.3 爲何要使用Function?

Function是使用字符串構建函數,那麼就能夠在程序運行過程當中構建函數.

之前的函數必須一開始就寫好,再通過預解析,一步一步的運行

假定從服務器裏拿到「[1,2,3,4,5]」,將數組形式的字符串轉換成數組對象

var arr = ( new Function( 'return ' + str + ' ;' ) )();

推薦閱讀

相關文章
相關標籤/搜索