如題,這個問題我曾經在支付寶的電話面試裡面最後一個問題被問到過,後來也沒有去看到底為何不需要new,現在我們就來剖析下。
而且當你在看jquery原始碼的時候,如果一開始就搞不懂這樣的問題,抑或jQuery.fn.init.prototype =jQuery.fn 這樣的問題也搞不懂的話,那基本後面的東西都是懵的。
首先回顧下,我們一般是如何寫外掛的,通常是這樣的
function Kissy () {
}
Kissy.prototype.alert = function () {
}
// 例項化必須要有,否則無法呼叫原型鏈上的alert方法
var kissy = new Kissy()
kissy.alert()
// 如果我們直接這樣呼叫
Kissy().alert()
// 就會報錯alert不是一個function。這裡涉及到原型繼承,new 一個建構函式和 執行普通函式的區別,不在贅述。
迴歸一個知識點,有助於我們理解後面我們講解的內容
建構函式有return值怎麼辦?
建構函式裡沒有顯式呼叫return時,預設是返回this物件,也就是新建立的例項物件。
當建構函式裡呼叫return時,分兩種情況:
1.return的是五種簡單資料型別:String,Number,Boolean,Null,Undefined。
這種情況下,忽視return值,依然返回this物件。
2.return的是Object
這種情況下,不再返回this物件,而是返回return語句的返回值。
如何改造上面的程式碼,可以不用new函式,直接呼叫到alert方法呢?很簡單。改造如下:
function Kissy () {
return Kissy.prototype
}
當我們直接呼叫Kissy()函式的時候,不是返回的null或undefined而是返回Kissy的原型,即返回了一個原型物件,這個物件上是有alert方法的,這就好像我們熟悉的下面的程式碼一樣
var obj = {
name: `zj`,
getName: function() {
console.log(this.name)
}
}
obj.getName() // zj
接下來,我們仿造jquery原始碼又來改造下:
function Kissy () {
return new Kissy.fn.init()
}
Kissy.fn = Kissy.prototype = {
constructor: Kissy,
init: function() {
console.log(`init`)
}
}
Kissy.fn.init.prototype = Kissy.fn
Kissy.fn.alert = function() {
alert(`0000`)
}
Kissy().alert() // 00000
無非就是在Kissy上面增加了一個fn屬性,這個屬性指向了Kissy.prototype。這樣的目的是便於我們開發外掛的時候,在原型上增加方法可以直接這麼寫Kissy.fn.alert 僅此而已。為何建構函式不直接返回
Kissy.fn呢 而是在中間搞了一個init.讀過jquery原始碼的都知道,jquery.fn.init 這個函式實際是jquery的初始化函式。這裡就不在展開了。
return new Kissy.fn.init()
建構函式返回了一個Kissy.fn.init()這個函式的一個例項。當然就會繼承這個例項上的原型,而原型又被我們重置了
Kissy.fn.init.prototype = Kissy.fn
所以就能呼叫到Kissy.fn上面的alert方法了,不信大家可以試試哦。
再來看jquery的原始碼,對比剛剛分析的程式碼,是不是基本一模一樣,只是jquery原始碼fn.init方法有比較多初始化的內容。
jQuery = function( selector, context ) {
return new jQuery.fn.init( selector, context, rootjQuery );
},
jQuery.fn = jQuery.prototype = { //fn即對應prototype
constructor: jQuery,
init: function( selector, context, rootjQuery ) {
...
return this;
}
...
}
jQuery.fn.init.prototype = jQuery.fn;