小編給大家分享一下JavaScript之new操作符是什么,希望大家閱讀完這篇文章后大所收獲,下面讓我們一起去探討吧!
在學習JavaScript的過程中,不可避免的會遇到new操作符,這次就來好好刨根問底一下,也算是加深理解和記憶了。
mdn中是這么定義new操作符的:
new 運算符創建一個用戶定義的對象類型的實例或具有構造函數的內置對象的實例。
在這句話里我們來看一個關鍵詞:具有構造函數。這是個什么意思呢?我們先通過幾個例子來看一下:
//例1let Animal1=function(){this.name=1};let animal=new Animal1; //這里不帶()相當于不傳參數//=>Animal1 {name: 1}//例2let TestObj={}let t1=new TestObj;//=>Uncaught TypeError: TestObj is not a constructor復制代碼我們可以看到,例1成功的執行了new語句,創建出了實例。例2在new一個{}對象時報錯TypeError: TestObj is not a constructor,指出目標不是一個constructor。為什么普通的對象就不能執行new操作符呢?在ECMA規范里有相關的介紹:
If Type(argument) is not Object, return false.
If argument has a[[Construct]]internal method, return true. Return false.
意思就是:
[[Construct]]內部方法,才可以作為構造函數 我們這里的{}就是一個對象,滿足第一個條件,那么顯然,肯定是因為{}沒有[[Construct]]這個內部方法,所以無法使用new操作符進行構造了。
那么我們已經搞定了new操作符的可操作對象,是不是可以去看看它的作用了呢?答案是:NO!我們再來看一個例子:
//例3let testObj={
Fn(){ console.log("構造成功!")
}
}let t3=new testObj.Fn;//=>Uncaught TypeError: testObj.Fn is not a constructor復制代碼what?為什么剛剛還能成功構造的函數,作為方法就不行了呢?其實在MDN中也有直接介紹:
Methods cannot be constructors! They will throw a TypeError if you try to instantiate them.
意思就是,方法不能是構造函數,如果嘗試創建一個方法的實例,就會拋出類型錯誤。這樣說就懂了,但是還沒完,這個說法沒有完全解釋清楚原理,我們再看個例子:
//例4const example = { Fn: function() { console.log(this); }, Arrow: () => { console.log(this); },
Shorthand() { console.log(this); }
};new example.Fn(); // Fn {}new example.Arrow(); // Uncaught TypeError: example.Arrow is not a constructornew example.Shorthand(); // Uncaught TypeError: example.Shorthand is not a constructor復制代碼對照這個例子,我們在ECMA規范查閱,發現所有的函數在創建時都取決于FunctionCreate函數:
FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype)
- If the prototype argument was not passed, then let prototype be the intrinsic object %FunctionPrototype%.
- If "kind" is not Normal, let allocKind be "non-constructor".
這個函數的定義可以看出
Normal的函數被創建時,它才是可構造的函數,否則他就是不可構造的。在我們這個例子中,Arrow的類型為Arrow,而ShortHand的類型是Method,因此都不屬于可構造的函數,這也解釋了例3所說的"方法不能作為構造函數"。
搞清楚了new操作符可以操作的目標,終于可以神清氣爽的來看看它的作用了(不容易呀TAT)。
我們舉一個簡單的例子來具體看看它的作用:
function Animal(name){ this.name=name; console.log("create animal");
}let animal=new Animal("大黃"); //create animalconsole.log(animal.name); //大黃Animal.prototype.say=function(){ console.log("myName is:"+this.name);
}
animal.say(); //myName is:大黃復制代碼我們從這個例子來分析一下,首先我們看這一句:
let animal=new Animal("大黃");復制代碼可以看到,執行new操作符后,我們得到了一個animal對象,那么我們就知道,new操作符肯定要創建一個對象,并將這個對象返回。再看這段代碼:
function Animal(name){ this.name=name; console.log("create animal");
}復制代碼同時我們看到結果,確實輸出了create animal,我們就知道,Animal函數體在這個過程中被執行了,同時傳入了參數,所以才執行了我們的輸出語句。但我們的函數體里還有一句this.name=name體現在哪里呢?就是這一句:
console.log(animal.name); //大黃復制代碼
執行完函數體后,我們發現返回對象的name值就是我們賦值給this的值,那么不難判斷,在這個過程中,this的值指向了新創建的對象。最后還有一段:
Animal.prototype.say=function(){ console.log("myName is:"+this.name);
}
animal.say(); //myName is:大黃復制代碼animal對象調用的是Animal函數原型上的方法,說明Animal在animal對象的原型鏈上,那么在哪一層呢?我們驗證一下:
animal.__proto__===Animal.prototype; //true復制代碼
那我們就知道了,animal的__proto__直接指向了Animal的prototype。
除此之外,如果我們在構造函數的函數體里返回一個值,看看會怎么樣:
function Animal(name){ this.name=name; return 1;
}new Animal("test"); //Animal {name: "test"}復制代碼可以看到,直接無視了返回值,那我們返回一個對象試試:
function Animal(name){ this.name=name; return {};
}new Animal("test"); //{}復制代碼我們發現返回的實例對象被我們的返回值覆蓋了,到這里大致了解了new操作符的核心功能,我們做一個小結。
new操作符的作用:
this綁定到新創建的對象_proto__指向構造函數的prototype{})說了這么多理論的,最后我們親自動手來實現一個new操作符吧~
var _myNew = function (constructor, ...args) { // 1. 創建一個新對象obj
const obj = {}; //2. 將this綁定到新對象上,并使用傳入的參數調用函數
//這里是為了拿到第一個參數,就是傳入的構造函數
// let constructor = Array.prototype.shift.call(arguments);
//綁定this的同時調用函數,...將參數展開傳入
let res = constructor.call(obj, ...args)
//3. 將創建的對象的_proto__指向構造函數的prototype
obj.__proto__ = constructor.prototype
//4. 根據顯示返回的值判斷最終返回結果
return res instanceof Object ? res : obj;
}復制代碼上面是比較好理解的版本,我們可以簡化一下得到下面這個版本:
function _new(fn, ...arg) { const obj = Object.create(fn.prototype); const res = fn.apply(obj, arg); return res instanceof Object ? res : obj;復制代碼大功告成!
看完了這篇文章,相信你對JavaScript之new操作符是什么有了一定的了解,想了解更多相關知識,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。