經過一道面試題來學習原型/原型鏈-函數聲明/函數表達式

例題

如題: html

答案解析

function A() {
    B = function () {console.log(10)}
    return this
};

A.B = function () {console.log(20)};

A.prototype.B = function () {console.log(30)}

var B = function () {console.log(40)}

function B() {console.log(50)}

A.B() // answer 20 【原型與原型鏈】
// 在`A`的原型對象中查找是否有`B`函數而且調用,這裏並未執行`A`函數。
// A.B = function () {console.log(20)};
// 在A的原型對象中添加了`B`函數,中止查找,因此答案爲 20

B() // answer 40 【函數表達式和函數聲明】
// var B = function () {console.log(40)}
// function B() {console.log(50)}
// 同名 -> 函數提高會 > 變量提高
// 換言之 同名的函數表達式和函數聲明同時存在時 老是執行表達式

A().B() // answer 10 【函數表達式和函數聲明】
// A() 執行函數A ==> 1.變量B從新賦值函數 2.返回this(window)
// .B() 執行全局下的B函數 已經被從新賦值 因此輸出10

B() // answer 10 
// 上面的代碼執行過A函數了,此時全局下的B函數輸出10

new A.B() // answer 20【函數表達式和函數聲明】
// new 執行了 A.B = function () {console.log(20)};

new A().B() // answer 30
// new 執行構造函數 A ,全局變量 B 從新賦值函數10
// 此時A() 指針指向哪裏? 
// 首先要知道 new 作了什麼事?
// ==> 建立空對象objA objA.__proto__ = A.prototype
// .B() 在A的原型對象中查找 B; A.prototype 指向函數的原型對象
// A.prototype.B = function () {console.log(30)} 輸出 30
複製代碼

原型和原型鏈

prototype

每個函數都有一個 prototype 屬性。git

function Foo() {}

Foo.prototype; // {constructor,__proto__}
複製代碼

不管何時,只要建立了一個新函數,根據一組特定的規則爲該函數建立一個prototype 屬性,這個屬性指向函數的原型對象。github

那麼這個建立的原型對象是什麼呢?express

{
    constructor: ƒ Foo(),
    __proto__: Object
}
複製代碼

constructor 屬性

每個原型對象都有一個 constructor 屬性函數

建立了自定義的構造函數後,其原型對象只會默認取得 constructor 屬性。這個屬性解決了對象識別問題,便可以經過該屬性判斷出實例是由哪一個構造函數建立的。post

Foo.prototype.constructor === Foo; // true
複製代碼

前面說了,原型對象只會默認取得 constructor 屬性,那麼原型對象的其餘屬性(好比:__proto__ )是這麼來的呢,這就要說到 __proto__ 指針了。ui

proto

每個實例都有一個 __proto__ 指針,指向構造函數的原型對象。this

var foo = new Foo();
foo.__proto__ === Foo.prototype; //true
複製代碼

上面提到的構造函數的原型對象它自己也是一個實例,因此在它內部會有一個__proto__ 指針。spa

構造函數(補充)

ECMAScript 中提供了構造函數來建立新對象。但構造函數自己就是一個函數,與普通函數沒有任何區別,只不過爲了區分,通常將其首字母大寫,但這並非必須的。prototype

函數被 new 關鍵字調用時就是構造函數。

function f(name) {
    console.log("execute");
    this.name = name;
}

var k = new f("k"); // execute =====> 調用new
console.log(k); // {name: "k"}
var h = f("h"); // execute =====> 未調用new
console.log(h); // undefined
複製代碼

從上面代碼能夠看出:

  • 首字母是否大寫並不影響函數 f 做爲構造函數使用,
  • 不使用 new 調用函數就是普通函數,直接執行內部代碼,使用new,函數的角色就成爲了構造函數,建立一個對象並返回。

對象由構造函數經過 new 創造對象的步驟

var obj = {}; // 建立一個空對象
obj.__proto__ = constructor.prototype;//添加__proto__屬性,並指向構造函數的prototype 屬性。
constructor.call(this); // 綁定this
return obj;
複製代碼

new 關鍵字的內部實現機制:

  • 建立一個新對象;
  • 將構造函數的做用域賦值給新對象;
  • 執行構造函數中的代碼;
  • 返回新對象

原型鏈

原型鏈的理論主要基於上述提到的構造函數、實例和原型的關係:

  • 每個構造函數都有一個原型對象
  • 原型對象都包含一個指向構造函數的 constructor 屬性
  • 每個實例都包含一個指向原型對象的 __proto__ 指針 其中最最重要的是第三條,依賴這條關係,層層遞進,就造成了實例與原型的鏈條。

接着上面的探索,構造函數的原型的原型是由 Object 生成的,那麼 Object 的原型是由什麼生成?而原型鏈的終點又是在哪?

Object.prototype.__proto__ // null
null.__proto__; // Uncaught TypeError: Cannot read property '__proto__' of null
// game over
複製代碼

原型的終點是 null,由於 null 沒有 proto 屬性。

最後以一個例子來理解上面所談到的原型與原型鏈

function Foo(){} // 構造函數 Foo
var foo = new Foo() // foo.__proto__ 指向 Foo.prototype
複製代碼

函數聲明、函數表達式

函數聲明 function name(){}

函數聲明是用指定的參數聲明一個函數。一個被函數聲明建立的函數是一個 Function 對象,具備 Function 對象的全部屬性、方法和行爲。

// 函數聲明語法
function name([param[, param[, ... param]]]) { statements }
複製代碼

函數表達式 var name = function(){}

在函數表達式中咱們能夠忽略函數名稱建立匿名函數,並將該匿名函數賦值給變量。

var add = function(a, b) {
    return a + b;  
};

add(2, 3) // 5
複製代碼

固然, 也能夠建立命名函數表達式 Named function expression:

var add = function func(a, b) {
    return a + b;  
};

add(2, 3) // 5
複製代碼

命名函數表達式中函數名稱只能做爲函數體做用域內的局部變量,外部不可訪問。

var a = function pp(v) {
    v++;
    if (v>3) {
        return v;
    } else {
        return pp(v);
    }
}

a(1); // 4
pp(1); // ReferenceError: pp is not defined
複製代碼

函數聲明提高

對於函數聲明建立的函數,能夠在本做用域內任意位置訪問。

a(); // 1

function a() {
    return 1;  
}

a(); // 1
複製代碼

而函數表達式不會。

console.log(a); // undefined (只是變量提高)
a(1); // TypeError: a is not a function

var a = function(v) {
    console.log(v);     
};
複製代碼

函數提高和變量提高的疑惑分析

console.log(fn); // [Function: fn]
var fn = function () {
    console.log(1);
}

function fn() {
    console.log(2);
}

fn() // 1
複製代碼

提高過程

// 函數提高
function fn() {
    console.log(2);
}

// 變量提高
var fn;

fn = function () {
    console.log(1);
}
fn() //最終輸出1
複製代碼

參考


相關文章
相關標籤/搜索