banner
Aglorice

Aglorice

Life is a coding,I will debug it.
github
twitter
telegram
bilibili

探究構造函數與 new 命令

1684925475841

夜深了......

1. 前言#

​ 在探究 JavaScript 中的構造函數之前,我們需要搞清楚什麼是構造函數,正如其名字一樣 “構造” ,它的作用就是構造,接下來我們來看看它是在構造什麼。

​ 在繼續往下說之前,我們需要明確所謂的構造函數其實就是一個函數,但是它具有自己的特徵和用法,但是我們會發現如何區分和使用構造函數,那就不得不離開 JavaScript 中的new關鍵字了。

2.new 關鍵字#

在說明 JavaScript 中的構造函數時,我們必然不可能繞過它的兩個特點:

  1. 函數體內部使用了 this 關鍵字,代表所要生成的對象實例。
  2. 生成對象的時候,必須使用 new 關鍵字。

1.new 命令的基礎用法#

以下就是一個最基礎的使用new命令來實例化一個構造函數

let MyNew = function (){
        this.say_hello = function (){
            console.log('hello world')
        }
    }
let mynew = new MyNew

上述的操作就是通過 new 命令,讓構造函數MyNew生成一個實例對象,並將結果交給mynewmynew也就得到了構造函數中的屬性。

2. 傳參構造函數#

在實際中我們不可能通過一個構造函數來構造出一大堆相同對象,所以我們可以在new一個構造函數時傳入參數。

let MyNew_1 = function (name){
    this.name = name
}
let mynew_1 = new MyNew_1('張三')
console.log(mynew_1.name) // '張三'

上述的操作就是通過new關鍵字在實例化一個對象的時候傳入對應的參數。

3.new 關鍵字的規範#

因為在使用 new 關鍵字對構造函數進行實例化的時候,在最後它會自動的執行,所以在使用 new 關鍵字的可以發現。

let mynew_2 = new MyNew_1 // 不推薦

let mynew_3 = new MyNew_1() // 推薦

上述的兩種方式雖然都可以,但是更推薦使用第二種,相較於第一種來說,更容易被識別。

4. 注意#

在使用構造函數的時候,如果我們在使用構造函數時忘記了使用new關鍵字,那麼它將會被當成一個函數來執行。

let MyNew_1 = function (name){
        this.name = name
}
let test = MyNew_1()
console.log(test.name) // undefined

上述之所以會輸出undefined,使用為MyNew_1在被當成函數後,它的作用域就是全局的,而內部的this就指向的是windowwindow並沒有聲明這個變量,所以最後輸出了undefined

那麼我們應該怎麼避免這樣的事情發生呢?

1. 使用嚴格模式

除了正常運行模式,ECMAscript5 添加了第二種運行模式:“嚴格模式”(strict mode)。顧名思義,這種模式是的Javascript在更嚴格的條件下運行。

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

使用嚴格模式後,如果你將構造函數當成函數使用,那麼將會直接報錯。結果如下:

image-20230208230605804

之所以會報錯,主要歸功於在嚴格模式中,函數內部的 this 不能指向全局對象,默認等於undefined,導致不加new調用會報錯(JavaScript 不允許對undefined添加屬性)。

2. 使用 instanceof 關鍵字

我們可以通過instanceof關鍵字來判斷。

function Fubar_1(foo, bar) {
    if (!(this instanceof Fubar_1)) {
        return new Fubar(foo, bar);
    }

    this._foo = foo;
    this._bar = bar;
}
console.log(Fubar_1(1, 2)._foo)// 1
console.log((new Fubar_1(1, 2))._foo) // 1

如上述代碼,我們只需要判斷this是否為Fubar_1的實例對象就可以知道該構造函數是否使用了new關鍵字,如果沒有,則幫你new,並將實例化的對象返回。

3.new 關鍵字的原理#

在我們使用new關鍵字的時候,實際上它執行了下面的幾步:

  1. 創建一個空對象,作為將要返回的對象實例。
  2. 將這個空對象的原型,指向構造函數的prototype屬性。
  3. 將這個空對象賦值給函數內部的this關鍵字。
  4. 開始執行構造函數內部的代碼。

轉化為代碼就是如下的操作:

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;
}

// 實例
var actor = _new(Person, '張三', 28);

後面繼續了解下🧐🧐🧐

4. 構造函數中的返回值#

當構造函數中有return語句的時候,我們只需要記住一點如果返回的是一個對象,則最終返回的就是這個對象,如果不是一個對象,則會返回this

let MyNew_2 = function (){
    this.count = 0
    return this.count
}
(new MyNew_2()) === 0 // false
let MyNew_3 = function (){
    this.count = 0
    return {count:this.count}
}
(new MyNew_3()).count === 0 // true

5.new.target#

這是 ECMASript 6 引入一個 新的屬性它主要有兩個作用:

  1. 當我們使用 new 操作符調用構造函數時,new.target 屬性的值為構造函數,否則為 undefined

    function Person (fullName) {
            if (typeof new.target !== 'undefined') {
                this.fullName = fullName;
            } else {
                throw new Error('須通過 new 關鍵字來調用 Person。');
            }
        }
    let student = new Person('aa');
    let student_1 = Person('aa'); // 報錯
    
  2. 可以檢查 new.target 是否被某個特定構造函數所有調用。

    function Person (fullName, age) {
        if (typeof new.target === Person) {
            this.fullName = fullName;
            this.age = age;
        } else {
            throw new Error('必須通過 new 關鍵字來調用 Person。');
        }
    }
    
    function Dog (fullName, age) {
        Person.call(this, fullName, age)
    }
    
    let dog = new Dog('HeHe', 3)
    
    console.log(dog)
    

在 es5 中如果我們也要確定一個構造函數是否使用,那麼我們將第一時間想到instanceof

如下:

function Person (name) {
    if(this instanceof Person){
        this.name = name
    } else {
        throw new Error('未通過new調用')
    }
}
var person2 = Person('Hello') //拋出異常

通過上述的操作,我們就可以判斷構造函數是否使用new關鍵字。但是它不夠好,比如下面這個例子:

function Person (name) {
    if(this instanceof Person){
        this.name = name
    } else {
        throw new Error('未通過new調用')
    }
}
var person1 = new Person('Hello')
var person2 = Person.call(person1,'Hello') //正常執行

上述代碼中使用call方法將 this 設為person1的實例,對於函數本身,無法通過區分Person.call還是new關鍵字得到Person的實例。但是通過new.target 就可以實現😍😍😍

6. 參考鏈接#

載入中......
此文章數據所有權由區塊鏈加密技術和智能合約保障僅歸創作者所有。