jQuery原理(附帶部分new的原理)

文章 jQuery誕生記-原理與機(jī)制 讀后感

要點(diǎn)總結(jié):

  1. 只要new表達(dá)式之后的constructor返回(return)一個引用對象(數(shù)組,對象,函數(shù)等),都將覆蓋new創(chuàng)建的匿名對象(我的理解:this還是那個this,但new以后的返回卻不是那個this);
  2. 如果返回(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原理

總體思路:

  1. 原生的獲取對象或?qū)ο蟮奶幚矶继爆?,用函?shù)將其包裝,使其簡單化。
  2. 將對元素的處理方法通過原型繼承的方式進(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)講解:

  1. 原生
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";
};
  1. 化繁為簡,進(jìn)行包裝
var $ = function(id) {
    return document.getElementById(id);
};

$("button").onclick = function() {
    $("image1").style.display = "none";
    $("image2").style.display = "none";
};
  1. 樣式處理還是繁瑣,繼續(xù)簡化,封裝一個方法到元素對象上去
var $ = function(id) {
    return document.getElementById(id);
};

HTMLElement.prototype.hide = function() {
    this.style.display = "none";
};

$("button").onclick = function() {
    $("image1").hide();
    $("image2").hide();
};
  1. 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(); 
};
  1. 上述方案有問題,原因請參見文章開頭關(guān)于new的解釋:
    new F()返回的不是this對象了,而是DOM對象。其原型對象是構(gòu)造函數(shù).prototype,而該構(gòu)造函數(shù)已經(jīng)不是F了。所以new F()的返回值根本訪問不到hide方法。

  2. 那就用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(); 
};
  1. 上面的方法,元素的獲取直接在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(); 
};
  1. 能不能不用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();    
};
  1. 獲取元素不僅僅只用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();    
};
  1. 解決上面提出的問題,遍歷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();  
};
  1. $("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();  
};
  1. 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();  
};
  1. 每次擴(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();  
};
  1. 我們看這段代碼
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();  
};
  1. 但是這個時候我們發(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();  
};
  1. 在init方法中,判斷第一個參數(shù),如果是節(jié)點(diǎn),直接 this[0] = this_node
  2. 每個擴(kuò)展方法都要 $.fn.functionName, 太繁瑣
$.fn.extend({
    css: function() {},
    attr: function() {},
    data: function() {},
    // ...
});

over!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容