1. 程式人生 > >例項物件與new命令

例項物件與new命令

原文地址:https://wangdoc.com/javascript/

建構函式

JavaScript語言的物件體系不是基於類的,而是基於建構函式和原型鏈。JavaScript語言使用建構函式作為物件的模板。所謂建構函式就是專門用來生成例項物件的函式。建構函式就是一個普通的函式,但是有自己的特徵和用法。

var Vehicle = function () {
    this.price = 1000;  
};

上面程式碼中,Vehicle就是建構函式。為了與普通函式區別,建構函式名字的第一個字母通常大寫。
建構函式的特點有兩個。

  • 函式體內部使用了this關鍵字,代表了所要生成物件的例項。
  • 生成物件的時候,必須使用new命令。

    new命令

    基本用法

    new命令的作用就是執行建構函式,返回一個例項物件。
var v = new Vehicle();
v.price() // 1000

使用new命令時,根據需要,建構函式也可以接受引數。

var Vehicle = function (p) {
    this.price = p;
};

var v = new Vehicle(500);

如果忘了使用new命令,直接呼叫建構函式,建構函式就變成普通函式,並不會生成例項物件。而且由於後面會說到的原因,this這時代表全域性物件,將造成一些意想不到的結果。
為了保證建構函式與new

命令一起使用,一個解決辦法是,建構函式內部使用嚴格模式,即第一行加上use strict。這樣的話,一旦忘了使用new命令,直接呼叫建構函式就會報錯。

function Fubar(foo, bar) {
    "use strict";
    this._foo = foo;
    this._bar = bar;
}

Fubar() // TypeError: Cannot set property "_foo" of undefined

由於嚴格模式中,函式內部的this不能指向全域性物件,預設等於undefined,導致不加new呼叫會報錯。
另一個解決方法,建構函式內部判斷是否使用new

命令,如果發現沒有使用,則直接返回一個例項物件。

function Fubar(foo, bar) {
    if (!(this instanceof Fubar)) {
        return new Fubar(foo, bar);
    }
    
    this._foo = foo;
    this._bar = bar;
}

Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1

new命令的原理

使用new命令時,它後面的函式依次執行下面的步驟。

  • 建立一個空物件,作為將要返回的物件例項。
  • 將這個空物件的原型指向建構函式的prototype屬性。
  • 將這個空物件賦值給函式內部的this關鍵字。
  • 開始執行建構函式內部的程式碼。
    也就是說,建構函式內部,this指的是一個新生成的空物件,所以針對this的操作,都會發生在空物件上。
    如果建構函式內部有return語句,而且return後面跟著一個物件,那麼new命令會返回return語句指定的物件;否則,就會不管return語句,返回this物件。
    但是如果return語句返回一個跟this無關的新物件,new命令會返回這個新物件,而不是this物件。這一點需要特別引起注意。
var Vehicle = function () {
    this.price = 1000;
    return {price: 2000};
};
(new Vehicle()).price // 2000

另一方面,如果對普通函式(內部沒有this關鍵字的函式)使用new命令,則會返回一個空物件。

function getMessage() {
    return 'this is a message';
}

var msg = new getMessage();

msg // {}
typeof msg // "object"

new命令簡化的內部流程,可以用下面的程式碼表示。

function _new(constructor, params) {
    // 將arguments物件轉為陣列
    var args = [].slice.call(arguments);
    // 取出建構函式
    var constructor = args.shift();
    // 建立一個空物件,繼承建構函式的prototype屬性
    var context = Object.create(constructor.prototype);
    // 執行建構函式
    var result = constructor.apply(context, args);
    // 如果返回結果是物件,就直接返回,否則返回 context物件
    return (typeof result === "object" && result != null) ? result : context;
}

new.target

函式內部可以使用new.target屬性。如果當前函式是new命令呼叫,new.target指向當前函式,否則為undefined

function f() {
    console.log(new.target === f);
}
f() // false
new f() // true

使用這個屬性,可以判斷函式呼叫的時候,是否使用new命令。

function f() {
    if (!new.target) {
        throw new Error("請使用new命令呼叫!");
    }
}

f() // Uncaught Error: 請使用new命令呼叫!

Object.create()建立例項物件

建構函式作為模板,可以生成例項物件。但是有時候拿不到建構函式,只能拿到一個現有的物件。我們希望以這個現有的物件為模板,生成新的例項物件,這時就可以使用Object.create方法。

var person1 = {
    name: "zhang san",
    age: 38,
    greeting: function () {
        console.log("hello world");
    }
};

var person2 = Object.create(person1);