文章 jQuery誕生記-原理與機(jī)制 讀后感
要點(diǎn)總結(jié):
-
關(guān)于new: (原文出處:詳解new function(){}和function(){}())
- 只要new表達(dá)式之后的constructor返回(return)一個引用對象(數(shù)組,對象,函數(shù)等),都將覆蓋new創(chuàng)建的匿名對象(我的理解:this還是那個this,但new以后的返回卻不是那個this);
- 如果返回(return)一個原始類型(無return時其實(shí)為return原始類型undefined),那么就返回new創(chuàng)建的匿名對象(我的理解:返回的是this)。
關(guān)于第1種情況的代碼示例:
var o = new function() {return "圓心"};
alert(o); //將返回顯示“[object object] ”
關(guān)于第2種情況的代碼示例:
var o = new function() {return new String("圓心")};
alert(o); //返回的是“圓心”
-
關(guān)于jQuery原理
總體思路:
- 原生的獲取對象或?qū)ο蟮奶幚矶继爆?,用函?shù)將其包裝,使其簡單化。
- 將對元素的處理方法通過原型繼承的方式進(jìn)行定義。
原文中有一段文字表述可能有問題:
上面代碼顯然是有問題的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的原型對象,尼瑪$.fn.init的prototype原型現(xiàn)在就是個光桿司令啊,喲,正好,$.fn對應(yīng)的原型方法,除了init沒用外,其他hide(), each()就是我們需要的。因此,我們需要加上這么一行:
我覺得應(yīng)該改為:
上面代碼顯然是有問題的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的this對象,$()的這個返回值沒法訪問hide和each方法,why?因?yàn)檫@個返回值的原型對象是$.fn.init.prototype,也就是F.prototype.init.prototype,而hide和each方法是定義在$.fn,也就是F.prototype上,所以根據(jù)原型鏈的查找機(jī)理,$()的這個返回值是訪問不到hide和each方法的。 因此,我們需要加上這么一行:
好了,現(xiàn)在我們用代碼的方式進(jìn)行演進(jìn)講解:
- 原生
var button = document.getElementById("button")
var image1 = document.getElementById("image1")
var image2 = document.getElementById("image2")
button.onclick = function() {
image1.style.display = "none";
image2.style.display = "none";
};
- 化繁為簡,進(jìn)行包裝
var $ = function(id) {
return document.getElementById(id);
};
$("button").onclick = function() {
$("image1").style.display = "none";
$("image2").style.display = "none";
};
- 樣式處理還是繁瑣,繼續(xù)簡化,封裝一個方法到元素對象上去
var $ = function(id) {
return document.getElementById(id);
};
HTMLElement.prototype.hide = function() {
this.style.display = "none";
};
$("button").onclick = function() {
$("image1").hide();
$("image2").hide();
};
- IE6~IE8瀏覽器不認(rèn)識HTMLElement,上述方法不通用。改進(jìn)思路:Function的原型擴(kuò)展大家都認(rèn)識,創(chuàng)建一個函數(shù),然后通過new這個函數(shù)返回HTML元素,再將hide方法綁定到該函數(shù)的原型上。
var F = function(id) {
return document.getElementById(id);
};
F.prototype.hide = function() {
this.style.display = "none";
};
new F("button").onclick = function() {
new F("image1").hide();
new F("image2").hide();
};
上述方案有問題,原因請參見文章開頭關(guān)于new的解釋:
new F()返回的不是this對象了,而是DOM對象。其原型對象是構(gòu)造函數(shù).prototype,而該構(gòu)造函數(shù)已經(jīng)不是F了。所以new F()的返回值根本訪問不到hide方法。那就用this來做橋接,new F()還是返回this,DOM對象以屬性的方式綁定到this上。原型對象上的hide方法可以通過點(diǎn)的方式訪問到DOM對象
var F = function(id) {
this.element = document.getElementById(id);
};
F.prototype.hide = function() {
this.element.style.display = "none";
};
new F("button").element.onclick = function() {
new F("image1").hide();
new F("image2").hide();
};
- 上面的方法,元素的獲取直接在F方法中,但是,實(shí)際情況,考慮到兼容性實(shí)現(xiàn),元素獲取可能會相當(dāng)復(fù)雜,同時方法私有,不能重利用。因此,可以把元素獲取方法放在原型上,便于管理和重用。代碼如下:
var F = function(id) {
return this.getElementById(id);
};
F.prototype.getElementById = function(id) {
this.element = document.getElementById(id);
return this;
};
F.prototype.hide = function() {
this.element.style.display = "none";
};
new F("button").element.onclick = function() {
new F("image1").hide();
new F("image2").hide();
};
- 能不能不用new
var F = function(id) {
return this.getElementById(id);
};
F.prototype.getElementById = function(id) {
this.element = document.getElementById(id);
return this;
};
F.prototype.hide = function() {
this.element.style.display = "none";
};
var $ = function(id) {
return new F(id);
};
$("button").element.onclick = function() {
$("image1").hide();
$("image2").hide();
};
- 獲取元素不僅僅只用id,還有class等其他方式
var F = function(selector, context) {
return this.getNodeList(selector, context);
};
/**
替換掉特殊的getElementById,使用通用的獲取list的方式
*/
F.prototype.getNodeList = function(selector, context) {
context = context || document;
this.element = context.querySelectorAll(selector);
return this;
};
var $ = function(selector, context) {
return new F(selector, context);
};
/**
以下代碼有些問題,因?yàn)楝F(xiàn)在是操作list了。不過可以用來理解流程。
*/
$("button").element.onclick = function() {
$("image1").hide();
$("image2").hide();
};
- 解決上面提出的問題,遍歷list
var F = function(selector, context) {
return this.getNodeList(selector, context);
};
F.prototype.getNodeList = function(selector, context) {
context = context || document;
this.element = context.querySelectorAll(selector);
return this;
};
F.prototype.each = function(fn) {
var i=0, length = this.element.length;
for (; i<length; i+=1) {
fn.call(this.element[i], i, this.element[i]);
}
return this;
};
F.prototype.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
var $ = function(selector, context) {
return new F(selector, context);
};
$("button").element[0].onclick = function() {
$("img").hide();
};
- $("button").element[0] 看上去不爽
var F = function(selector, context) {
return this.init(selector, context);
};
//這個方法已經(jīng)不是獲取list了,應(yīng)該將名字getNodeList換成更準(zhǔn)確的init
F.prototype.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
/**
每一個DOM對象都以 屬性:對象 的方式保存在了this中;
這個屬性名=nodeList中每個DOM對象的索引值
*/
this[i] = nodeList[i];
}
return this;
};
F.prototype.each = function(fn) {
var i=0, length = this.length;
for (; i<length; i+=1) {
fn.call(this[i], i, this[i]);
}
return this;
};
F.prototype.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
var $ = function(selector, context) {
return new F(selector, context);
};
//這時可以不用$("button").element[0]了
$("button")[0].onclick = function() {
$("img").hide();
};
- F名字看著不爽,能不能換一個:F → $.fn
var $.fn = function(selector, context) {
return this.init(selector, context);
};
$.fn.prototype.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
this[i] = nodeList[i];
}
return this;
};
$.fn.prototype.each = function(fn) {
var i=0, length = this.length;
for (; i<length; i+=1) {
fn.call(this[i], i, this[i]);
}
return this;
};
$.fn.prototype.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
var $ = function(selector, context) {
return new $.fn(selector, context);
};
$("button")[0].onclick = function() {
$("img").hide();
};
- 每次擴(kuò)展新方法,都要 $.fn.prototype.functionName=function(){} 嗎?作為插件,每次擴(kuò)展方法都要訪問高級屬性prototype好嗎?插件應(yīng)該把這一類難的高級的幫我們隱藏掉,那我們給他們重新起個名字吧,就讓 $.fn = F.prototype 吧
var F = function(selector, context) {
return this.init(selector, context);
};
var $ = function(selector, context) {
return new F(selector, context);
};
$.fn = F.prototype;
$.fn.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
this[i] = nodeList[i];
}
return this;
};
$.fn.each = function(fn) {
var i=0, length = this.length;
for (; i<length; i+=1) {
fn.call(this[i], i, this[i]);
}
return this;
};
$.fn.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
$("button")[0].onclick = function() {
$("img").hide();
};
- 我們看這段代碼
var F = function(selector, context) {
return this.init(selector, context);
};
var $ = function(selector, context) {
return new F(selector, context);
};
明顯可以合并簡化
var $ = function(selector, context) {
return new $.fn.init(selector, context);
};
然后我們再重新審視整段代碼發(fā)現(xiàn),除了在其他地方用到了 F.prototype,F(xiàn)在其他地方?jīng)]有任何使用,也就是說F可以隨便定義,那我們可以用把F.prototype改成$.prototype,然后把原來的F定義刪除,于是代碼變成了
var $ = function(selector, context) {
return new $.fn.init(selector, context);
};
$.fn = $.prototype;
$.fn.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
this[i] = nodeList[i];
}
return this;
};
$.fn.each = function(fn) {
var i=0, length = this.length;
for (; i<length; i+=1) {
fn.call(this[i], i, this[i]);
}
return this;
};
$.fn.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
$("button")[0].onclick = function() {
$("img").hide();
};
- 但是這個時候我們發(fā)現(xiàn)
var $ = function(selector, context) {
return new $.fn.init(selector, context);
};
這段代碼是有問題的,問題解釋請看我這篇文章開始處這段文字
上面代碼顯然是有問題的,new的是$.fn.init, $.fn.init的返回值是this. 也就是$()的返回值是$.fn.init的this對象,$()的這個返回值沒法訪問hide和each方法,why?因?yàn)檫@個返回值的原型對象是$.fn.init.prototype,也就是F.prototype.init.prototype,而hide和each方法是定義在$.fn,也就是F.prototype上,所以根據(jù)原型鏈的查找機(jī)理,$()的這個返回值是訪問不到hide和each方法的。 因此,我們需要加上這么一行:
那怎么辦呢?在指回去不就得了 $.fn.init.prototype = $.fn
于是整段代碼變成了現(xiàn)在這樣
var $ = function(selector, context) {
return new $.fn.init(selector, context);
};
$.fn = $.prototype;
$.fn.init = function(selector, context) {
var nodeList = (context || document).querySelectorAll(selector);
this.length = nodeList.length;
for (var i=0; i<this.length; i+=1) {
this[i] = nodeList[i];
}
return this;
};
$.fn.init.prototype = $.fn;
$.fn.each = function(fn) {
var i=0, length = this.length;
for (; i<length; i+=1) {
fn.call(this[i], i, this[i]);
}
return this;
};
$.fn.hide = function() {
this.each(function() {
this.style.display = "none";
});
};
$("button")[0].onclick = function() {
$("img").hide();
};
- 在init方法中,判斷第一個參數(shù),如果是節(jié)點(diǎn),直接 this[0] = this_node
- 每個擴(kuò)展方法都要 $.fn.functionName, 太繁瑣
$.fn.extend({
css: function() {},
attr: function() {},
data: function() {},
// ...
});