原文:http://blog.e10t.net/talk-abo...javascript
牛客上有這麼一道 JavaScript 的題目。java
//填寫內容讓下面代碼支持a.name = 「name1」; b.name = 「name2」; function obj(name){ 【1】 } obj.【2】 = "name2"; var a = obj("name1"); var b = new obj;
【1】和【2】是填寫的內容,【2】的答案是 prototype.name
,沒爭議。面試
問題是【1】,參考答案竟然是 if(name){ this.name = name;}return this;
,這麼隨便地玩弄 this
不就是明擺着污染全局變量嗎?暴力賦值不可取。安全
下面的一些高票討論還說了一大堆解釋的廢話,連他本身都說本身好羅嗦。對,你不但羅嗦,並且尚未改錯。註釋裏都說了給 window 的屬性賦值,還不自知出問題,真是誤人子弟。app
先來分析一下題目,a 和 b 都從 obj 來,爲何同名的屬性值不同?能夠看出,是對 obj 這個函數的調用方式不同,a 是 obj 函數的調用結果,而 b 則是 obj 做爲構造函數調用的結果。因此這題的重點應該是如何區分_函數調用_和_構造函數調用_。函數
一個關鍵字 new
決定了不一樣。new
的做用是什麼呢?MDN 上說了,面試也會考你的,簡單來講是三步,new foo
:this
生成一個繼承於 foo.prototype 的對象.net
foo 會被調用,其中的 this
值會被綁定爲 1 中的對象prototype
若是 foo 沒有返回一個對象(注意是對象!),則返回 1 的對象code
從 2 就能夠看出 this
值會被 new
綁定爲一個肯定的對象,而不是像普通函數調用中那樣本身不可預料,要看上下文的進程。
因而就能夠在這裏作文章。先來判斷 this
的值。
if (this instanceof obj) {}
instanceof
會檢查 this
的原型鏈上是否存在 foo.prototype
。也就是說能判斷是否知足第 1 條,確保了對象能從 prototype
中讀取到 name
屬性。(畢竟代碼中並無給 b 的賦值中傳入)
instanceof
並非完美的判斷方法,可是在這裏足夠了,後面會談到這個問題。
if (this instanceof obj) { // new 調用 } else { // 非 new 調用 return { name: name } }
非 new 調用的狀況下,直接返回一個新對象就 OK 了。
而在 new 調用的狀況下,能夠看到 function obj(name)
定義的時候是有參數的,調用的時候卻沒參數,這就要當心了,爲了安全起見,仍是判斷一下爲妙。
if (this instanceof obj) { // new 調用 if (name !== undefined) { this.name = name } } else { // 非 new 調用 return { name: name } }
通常來講,判斷會寫成 if (name)
,可是碰到 null
、0
、false
就 GG 了,因此仍是謹慎點吧。
問題到這裏就能夠比較完美地解答了。
『instanceof
會檢查 this
的原型鏈上是否存在 foo.prototype
』,爲何說得這麼拗口,是由於須要表達出 instanceof
原本就不是真的用來檢測是否調用 new
的方法。
在題目裏面,要求的是 a 須要從原型鏈上讀取到特定的屬性值,因此 instanceof
的做用恰好在這裏能符合要求而已。
函數調用除了題目中的方法還有第三種方法,那就是 foo.call
、foo.apply
,並且也能爲函數指定 this
的值(因此還有 bind
)。所以是存在方法調戲 instanceof
的。
foo.prototype.name = 'foo' var midman = new foo('fake foo') var a = foo.call(midman) var b = foo.call(midman, 'b') a // undefined, WTF?! b // undefined, WTF?!
這裏的 foo
調用的方式是做爲函數來調用,可是爲 this
綁定的值是從 foo
上 new
出來的,換句話說,其原型鏈上存在 foo.prototype
,因而就騙過了 instanceof
。
因而 ES2015 來搭救你了,新增了一個 new.target
。因而修改爲:
if (new.target !== undefined) { // new 調用 if (name !== undefined) { this.name = name } } else { // 非 new 調用 return { name: name } }