JavaScript 函數的特性與原型鏈講解

開篇語

此次給你們帶來一個關於 JavaScript 函數與原型鏈之間的關係的分享,將會從函數開始講起,一直講到整個原型鏈是什麼樣子的,但願能給你們帶來幫助。app

函數的三種使用方式

JavaScript 中的函數大體有如下三種使用方式:函數

1、做爲普通函數來使用

例如優化

// 定義函數
function foo() {}
// 調用函數
foo();
複製代碼

2、做爲構造函數來使用

當一個函數被用來建立新對象的時候,就把它叫作:構造函數。ui

例如this

// 按照慣例,做爲構造函數的函數名首字母須要大寫
function Foo() {}
const obj = new Foo();
複製代碼

這時對於對象 obj 來講,函數 Foo 就叫作它的構造函數。spa

3、做爲對象來使用

訪問對象的屬性可使用點操做符和中括號來訪問prototype

例如code

function foo() {}
foo.name = 'tom';
foo['age'] = 20;
複製代碼

以上這三種使用方式中,以普通函數來調用的方式你們都懂,這裏再也不贅述。本文主要講解的就是當一個函數被做爲構造函數來使用和被做爲對象來使用的時候,分別是什麼樣的,以及它們之間與原型鏈的關係是什麼樣的。cdn

預留問題

在講接下來的內容以前,咱們先來看幾個問題:對象

// 首先,定義一個函數,將會做爲構造函數
function Foo() {}

// 實例化出來一個對象
const obj = new Foo();

// 在 Object 的原型上定義一個屬性:objProp
Object.prototype.objProp = '我是 Object 原型上的屬性';

// 在 Function 的原型上定義一個屬性:funcProp
Function.prototype.funcProp = '我是 Function 原型上的屬性';

// 你預想一下,如下這些分別會輸出什麼?
console.log(obj.objProp) // ?
console.log(obj.funcProp) // ?

console.log(Foo.objProp) // ?
console.log(Foo.funcProp) // ?

console.log(Object.objProp) // ?
console.log(Object.funcProp) // ?

console.log(Function.objProp) // ?
console.log(Function.funcProp) // ?

console.log(Array.objProp) // ?
console.log(Array.funcProp) // ?

複製代碼

構造函數

接下來會講解函數的第二個特性,做爲構造函數來使用的時候,它是什麼樣的。

使用 new 操做符建立對象

當咱們使用 new 操做符來從構造函數建立對象的時候,會經歷如下幾個步驟:

  1. 建立一個空對象:{}
  2. 把這個空對象的原型鏈 __proto__ 指向構造函數的原型對象。
  3. 把新對象做爲上下文來調用構造函數,即綁定 this,這樣新對象才能訪問到構造函數中的屬性。
  4. 若是該函數沒有返回對象,則返回這個新對象。

文字描述不太好理解,咱們用代碼來描述一遍,而後再對比着看一下:

// 新建一個構造函數
function Foo() {}
// 使用 new 操做符實例化一個對象
const obj_foo = new Foo()
// ========建立對象的過程:
// 1. 建立一個空對象:`{}`。
const obj = {};
// 2. 原型鏈的連接過程
obj.__proto__ = Foo.prototype;
// 3. 把新對象做爲函數的上下文,即綁定 this
Foo.apply(obj, arguments);
// 4. 由於 Foo 沒有返回對象,因此就返回這個新對象
return obj;
// ========END
// 這時 obj_foo 就是返回出來的新對象了
複製代碼

__proto__ 是什麼?

首先你要記住:只有對象纔會有 __proro__ 這個屬性。它是 js 得以實現原型鏈的關鍵,由於它會指向構造函數的原型對象,即 prototype 屬性上的對象,而構造函數原型對象上的 __proto__ 又指向上一級,即:構造函數.prototype.__proto__ -> 上一級構造函數.prototype,以此類推層層往上,就造成了咱們所說的原型鏈。若是不理解,請繼續看下文:

prototype 是什麼?

前面一直提到 構造函數.prototype,這個屬性是函數特有的一個屬性,它是構造函數的原型對象,全部由它構造出來的對象都會從這個原型對象上繼承屬性和方法(參考 MDN:JavaScript 是一種基於原型的語言,每一個對象都擁有一個原型對象,對象以其原型爲模板,從原型繼承方法和屬性)。就像前面的例子:const obj_foo = new Foo(),obj_foo 的原型對象就是構造它的函數 Foo 的 prototype,obj_foo 從 Foo.prototype 繼承了方法和屬性。

又由於 構造函數.prototype 是一個對象,因此它就會有 __proto__ 這個屬性,說明原型對象也是由一個構造函數建立出來的,即:構造函數.prototype.__proto__ -> 上一級構造函數.prototype

這下你能理解 __proto__、prototype 和原型鏈之間的關係了吧,正由於有了它們倆 JavaScript 才得以實現原型鏈。


特殊的構造函數

先來回顧一下 JavaScript 中的一些內建構造函數吧,好比:ObjectFunctionNumberArrayString等。

既然是構造函數,就能夠用來建立對象,好比:

var a = new Number('123'); // a === 123 is false
// 由於 Number.prototype 上有 toString() 方法,因而 a 也就繼承了該方法:
a.toString(); // a === '123' is true
var b = new String(123); // b === '123' is false
a instanceof Number; // is true
b instanceof String; // is true
複製代碼

有了上述印象,接下來就跟你們講解其中最核心最特殊的兩個構造函數:ObjectFunction

Object 構造函數

用來建立對象,例如:

const obj = new Object();
複製代碼

Object 除了是一個構造函數能夠用來建立對象之外,它還擁有一個很硬核的能力:派生全部的其它構造函數

這裏用了「派生」二字,在理解上是把它當作一個基類或者說父類來看待的。

也就是:全部的其它構造函數都是由 Object 這個構造函數派生出來的(子類)

用代碼解釋一下(注:JavaScript 中沒有明確的類的概念):

// 父類 Object
class Object {}
// 子類 MyFunction
class MyFunction extends Object {}
複製代碼

正由於是由 Object 構造出來的,那麼構造函數的原型鏈就指向了 Object 的原型對象(參考 new 的第二步過程能夠加以理解):

全部構造函數.prototype.__proto__ === Object.prototype

重點

由於 Function 也是一個構造函數,那麼就能夠推導出:

Function.prototype.__proto__ === Object.prototype

Function 構造函數

用來建立函數對象,例如:

const sum = new Function('a', 'b', 'return a + b');
sum instanceof Function; // true
複製代碼

在這裏把 sum 叫函數對象,是由於它確實是由 Function 構造出來的對象,可是它跟咱們日常定義一個 sum 函數是同樣的,這就是我當初的迷惑點所在,即函數有多種表示形態,能夠是普通函數,能夠是構造函數也能夠是函數對象。

咱們不可貴出:全部的函數對象都是由 Function 構造出來的

證實:sum.__proto__ === Function.prototype

重點

進一步能夠推導出:Object.__proto__ === Function.prototype

階段小結

  1. 函數有三種形態:普通函數、構造函數、函數對象;
  2. 當使用 new 操做符調用構造函數,會建立一個新對象;
  3. __proto__ 只有對象纔有這個屬性,它指向對象的構造函數的原型對象;
  4. prototype 是構造函數的原型對象;
  5. 全部的構造函數都是由 Object 建立出來的;
  6. 全部的函數對象都是由 Function 建立出來的。

函數是如何肯定形態的

前面咱們講了構造函數、函數對象這些東西,那麼函數是如何肯定最終形態的呢?答案是:在使用的時候。

使用 new 來調用它,那麼它就是一個能夠建立對象的構造函數,若是使用點或者中括號來調用它,那麼它就又變成了一個對象(注:排除 prototype 屬性)。

我在前面標了兩個重點公式:

// 1
Function.prototype.__proto__ === Object.prototype
// 2
Object.__proto__ === Function.prototype
複製代碼

再次解釋一下:

一、看到 Function.prototype 就說明 Function 此時是被當作構造函數來使用的,前面說過全部構造函數都是由 Object 構造出來的。

二、看到 Object.__proto__ 就說明 Object 此時是被當作對象來使用的,前面說過全部函數對象都是由 Function 建立出來的。

Object 和 Function 之間的關係很是特殊,根據不一樣的形態能夠互生。

解決預留問題

這下咱們就能夠來解決預留問題了。

// 首先,定義一個函數,將會做爲構造函數
function Foo() {}

// 實例化出來一個對象
const obj = new Foo();

// 在 Object 的原型上定義一個屬性:objProp
Object.prototype.objProp = '我是 Object 原型上的屬性';

// 在 Function 的原型上定義一個屬性:funcProp
Function.prototype.funcProp = '我是 Function 原型上的屬性';
複製代碼

問題:

一、

console.log(obj.objProp) // ?
複製代碼

答案:

  1. 由於對象的原型鏈指向構造函數的原型對象,因此:obj.__proto__ -> Foo.prototype,
  2. 由於全部構造函數都是由 Object 派生出來的,因此:Foo.prototype.__proto__ -> Object.prototype
  3. 由於 Object.prototype 上有 objProp 這個屬性,因此 obj.objProp === '我是 Object 原型上的屬性'

二、

console.log(obj.funcProp) // ?
複製代碼

答案:

  1. 由於對象的原型鏈指向構造函數的原型對象,因此:obj.__proto__ -> Foo.prototype,
  2. 由於全部構造函數都是由 Object 派生出來的,因此:Foo.prototype.__proto__ -> Object.prototype
  3. 因爲在 Object.prototype 上找不到 funcProp 屬性,根據原型鏈繼續找:Object.prototype.__proto__ -> null
  4. 找不到,返回 undefined

三、

console.log(Foo.objProp) // ?
複製代碼

答案:

  1. Foo.objProp 是把 Foo 當作對象來使用的,此時它是一個函數對象,因此它是由 Function 建立出來的:Foo.__proto__ -> Function.prototype
  2. 因爲在 Function.prototype 上找不到 objProp 屬性,根據原型鏈繼續找:Function.prototype.__proto__ -> Object.prototype
  3. Object.prototype 上找到了 objProp 屬性,因此 Foo.objProp === '我是 Object 原型上的屬性'

四、

console.log(Foo.funcProp) // ?
複製代碼

答案:

  1. Foo.funcProp 是把 Foo 當作對象來使用的,此時它是一個函數對象,因此它是由 Function 建立出來的:Foo.__proto__ -> Function.prototype
  2. Function.prototype 上找到了 funcProp 屬性,因此 Foo.funcProp === '我是 Function 原型上的屬性'

五、

console.log(Object.objProp) // ?
複製代碼

答案:

  1. Object.objProp 是把 Object 當作對象來使用的,此時它是一個函數對象,因此它是由 Function 建立出來的:Object.__proto__ -> Function.prototype
  2. Function.prototype 上找不到 objProp 屬性,根據原型鏈繼續找:Function.prototype.__proto__ -> Object.prototype
  3. Object.prototype 上找到了 objProp 屬性,因此 Object.objProp === '我是 Object 原型上的屬性'

六、

console.log(Object.funcProp) // ?
複製代碼

答案:

  1. Object.funcProp 是把 Object 當作對象來使用,此時它是一個函數對象,因此它是由 Function 建立出來的:Object.__proto__ -> Function.prototype
  2. Function.prototype 上找到了 funcProp 屬性,因此 Object.funcProp === '我是 Function 原型上的屬性'

七、

console.log(Function.objProp) // ?
複製代碼

注:爲了不混淆,Function 表示對象, %Function 表示構造函數

答案:

  1. Function.objProp 是把 Function 當作對象來使用的,此時它是一個函數對象,因此它是由 %Function 建立出來的:Function.__proto__ -> %Function.prototype
  2. %Function.prototype 上找不到 objProp 屬性,根據原型鏈繼續找:%Function.prototype.__proto__ -> Object.prototype
  3. Object.prototype 上找到了 objProp 屬性,因此 Function.objProp === '我是 Object 原型上的屬性'

八、

console.log(Function.funcProp) // ?
複製代碼

答案:

  1. Function.funcProp 是把 Function 當作對象來使用的,此時它是一個函數對象,因此它是由 $Function 建立出來的:Function.__proto__ -> %Function.prototype
  2. %Function.prototype 上找到了 funcProp 屬性,因此 Function.funcProp === '我是 Function 原型上的屬性'

九、

console.log(Array.objProp) // ?
複製代碼

答案:本身嘗試着分析一下吧,歡迎在評論區給出

十、

console.log(Array.funcProp) // ?
複製代碼

答案:本身嘗試着分析一下吧,歡迎在評論區給出

原型鏈圖片

我畫了一張原型鏈的圖片,供你們對比着上述的流程做參考:

若是本文有什麼錯誤之處,還請你們幫忙指出,我會繼續改進。


更新記錄

12-05:從新組織了內容結構,修復文字錯誤,優化原型鏈圖片。

相關文章
相關標籤/搜索