記一道經典前端題

taokexia發表於2019-02-20

題目如下:

function Foo() {
    getName = function() { alert(1); }
    return this
}
Foo.getName = function() { alert(2); }
Foo.prototype.getName = function() { alert(3); }
var getName = function () { alert(4); }
function getName() { alert(5); }

// 輸出值
Foo.getName();
getName();
Foo().getName();
getName();
new Foo.getName()
new Foo().getName()
new new Foo().getName()

// 輸出結果為
// 2
// 4
// 1
// 1
// 2
// 3
// 3
複製程式碼

下面對輸出值進行分析:

  1. Foo.getName() 輸出為 2, 訪問的是函式 Foo 上的靜態屬性,輸出為 2。

    現在,嘗試在函式內定義 getName 函式和在 Foo 原型上繫結 getName 函式, 都無法成功執行 Foo.getName(), 而以字面量建立物件的方式建立物件後,則能正常的執行 getName() 函式。

嘗試

通過建立物件執行 getName() 定義在函式內部無法正常執行,即使它是全域性變數。

嘗試

結論: 由於函式本身是物件,通過函式繫結屬性和方法屬於靜態方法 ,可以直接呼叫。繫結在原型上的屬性和方法要建立物件後才能呼叫,在建構函式物件內部定義的方法無法通過物件呼叫。

  1. getName(); 結果輸出為 4,而不是輸出 5。這是因為JS 存在變數宣告提升(所有宣告的變數或宣告的函式都會被提升到當前函式的頂部)。

    故程式碼執行順序為:

    var getName;
    function getName() { alert(5); }
    // ... 省略程式碼
    getName = function () { alert(4) }
    複製程式碼

    最終執行 getName 輸出為 4

    延伸題目:

    console.log( Foo )
    function Foo() {
        console.log(1);
    }
    var Foo = 1
    複製程式碼

    Foo 的輸出結果為?

  2. Foo().getName(); 輸出值為1, 先執行 Foo() 函式,定義全域性變數 getName, 之後呼叫全域性物件的 getName() 方法, 返回 1。

    注意, Foo() 函式返回的 this 指向的是全域性物件 window,所以呼叫的是全域性物件 getName()。 函式裡的 getName 繫結的是全域性物件,通過 Foo 呼叫會報錯。

    Node 下執行這條語句會報錯,因為 node 沒有全域性物件 window, 所以無法呼叫 getName

  3. getName() 呼叫全域性函式, 因為執行 Foo(), 更新了 getName 的值,所以返回 1。

  4. new Foo.getName() 考察了運算子的優先順序。 . 的優先順序高於 new, 相當於執行 new (Foo.getName)(), 相當於執行 getName 的建構函式,返回 2

  5. new Foo().getName() 執行方式為 (new Foo()).getName() 先生成 Foo 物件, 再執行 getName() 函式。 在 new Foo() 返回的是新建立的空物件,由於物件這時還沒繫結屬性 getName, 所以這時呼叫的是原型上的 getName, 結果返回3

    注意: 建構函式 return this,在執行 new 的時候,返回的是新建立的物件。

    延伸題目:

    function A() {
        this.a = 2;
        function B() {
            this.a = 1;
        }
        return B();
    }
    console.log(new A());
    複製程式碼

    a的值是? 如果 return new B(); a的值是?

  6. new new Foo().getName() 可以改寫為 new ((new Foo()).getName)() 先初始化例項,然後將原型物件上的 getName() 作為建構函式執行,結果返回 3

最終程式碼可以優化為

var getName;
function getName() { alert(5); }
function Foo() {
    getName = function() { alert(1); }
    return this
}
Foo.getName = function() { alert(2); }
Foo.prototype.getName = function() { alert(3); }
getName = function () { alert(4); }

Foo.getName(); // 2
getName(); // 4
Foo();
getName(); // 1
getName(); // 1
Foo.getName(); // 2
(new Foo()).getName(); // 3
(new Foo()).getName(); // 3
複製程式碼

相關文章