從零開始,DIY一個jQuery(1)

http://www.cnblogs.com/vajoy/p/5510743.html


從零開始,DIY一個jQuery(1)

posted @ 2016-07-31 02:47 vajoy 閱讀(803) 評論(9編輯 收藏

從本篇開始會陪大家一起從零開始走一遍 jQuery 的奇妙旅途,在整個系列的實踐中,我們會把 jQuery 的主要功能模塊都瞭解和實現一遍。

這會是一段很長的歷程,但也會很有意思 —— 作爲前端領域的經典之作,jQuery 裏有着太多奇思妙想,如果能夠深入理解它,對於我們穩固js基礎、提升前端大法技能來說大有裨益。

另外,本系列的相關代碼均可以從 我的github 上獲取到。

1. 免 new 實現

我們在使用很多插件的時候,都需要使用 new XXX() 的寫法來實例化一個引用:

var list = new Slip(document.getElementById('slip'), {
  //options
});

jQuery 同樣作爲一個面向對象的工具庫,在我們創建一個實例時卻無需使用 new 語法,節省了一些代碼量:

var $div = $('div');
//不需要如下寫法:
//var $div = new $('div');

這種便捷的形式依賴了工廠模式,其實現非常簡單,把 new 封裝在庫內即可,讓每次調用 jQuery() 時自行在內部進行一次實例化:

複製代碼
(function() {
    var _jQuery = window.jQuery,
        _$ = window.$;

    var version = "0.0.1",
        jQuery = function(selector) {
            console.log(document.querySelector(selector))
        };

    jQuery.prototype = {
        jquery: version,
        constructor: jQuery
    };

    window.$ = window.jQuery = function(selector) {
        return new jQuery(selector);  //notice here~
    };
})();
複製代碼

留意這裏我們走的 IIFE 形式,讓 jQuery 代碼庫形成自己的作用域,避免污染外部變量。

於是乎以上就是咱寫的第一個 JQ 雛形,簡單跑一下:

<div></div>
<script>
    var $div = $('div');  //<div></div>
    console.log($div.jquery);  //0.0.1
</script>

別忘了後續我們還希望能通過 $.extend / $.fn.extend 來擴展 JQ 的靜態方法和原型方法,我們把出口方法抽出來增加這個 extend 的API:

複製代碼
    function Factory(selector){  //抽出構造函數
        return new jQuery(selector);
    }

    Factory.fn = jQuery.prototype;

    Factory.extend = Factory.fn.extend = function(){
        console.log(this)
    };

    window.$ = window.jQuery = init;
複製代碼

這樣我們也能直接通過 $.fn.jquery 來獲取當前 JQ 版本號了。

如果希望可以通過 $.prototype 直接訪問 jQuery 的原型對象,再修改下這句代碼即可:

Factory.prototype = Factory.fn = jQuery.prototype;

2. 寫法優化

事實上我們不太喜歡再寫多一個冗餘的 Factory 構造函數來作爲 window.jQuery 的引用,也不喜歡(在模塊內部)使用 Factory.extend() 來擴展 JQ,它聽起來和 JQ 沒有半毛錢關係。

如果可以,直接把 jQuery 方法作爲接口輸出,且在模塊內部能以 jQuery.extend()  的形式來調用擴展接口,這樣的形式更佳。

也就是說我們希望代碼應該是這樣寫的:

    jQuery.extend = jQuery.fn.extend = function(){
        console.log(this)
    };
    window.jQuery = window.$ = jQuery;

“直接把 jQuery 方法作爲接口輸出”意味着我們要把工廠模式挪入 jQuery 方法中,顯然我們不能這樣改:

    var version = "0.0.1",
        jQuery = function (selector) {
            return new jQuery(selector);
        };

這樣死循環了,調用棧會直接爆掉~

於是我們可以抽出一個 init 方法來做初始化處理(比如簡單地注入檢索到的元素到JQ對象中),把 jQuery 方法中的內容更改爲 return new init(selector) 就行了。

保證兩個前提:

1. this 指向 jQuery 上下文

2. 其原型指向 jQuery 的原型

第一點很好理解,方便我們直接在 init 方法中通過對 this 的操作來處理 JQ 實例上下文,如:

  //注入元素到 JQ 實例對象中
  this[0] = elem;
  this.length = 1;

針對這點,我們不妨把 init 作爲 jQuery.prototype 的屬性方法來實現:

複製代碼
    var version = "0.0.1",
        jQuery = function(selector) {
            return new jQuery.fn.init()  //修改點1
        };

    //方便我們使用 jQuery.fn 來引用 jQuery 原型對象
    jQuery.fn = jQuery.prototype = {
        jquery: version,
        constructor: jQuery
    };

     //修改點2 —— init 作爲原型方法,確保 this 指向正確
    jQuery.fn.init = function( selector ) {
        if ( !selector ) {
            return;
        } else {
            var elem = document.querySelector( selector );
            if ( elem ) {
                this[0] = elem;
                this.length = 1;
            }
        }
    };

    jQuery.extend = jQuery.fn.extend = function(){
        console.log(this)
    };

    window.$ = window.jQuery = jQuery;
複製代碼

然而這時候存在一個問題 —— JQ實例對象無法訪問原型屬性/方法:

    var $div = $('div');
    console.log($div.jquery);  //undefined

原因很簡單——我們還未實現上述提及的第二個前提——“init 原型指向 jQuery 的原型”

在 js 中,實例的內部原型(__proto__)總是指向其構造函數的原型(prototype),而經過我們這番修改,JQ實例的構造函數已經變成了 jQuery.fn.init ,而其原型並非指向 jQuery 的原型,這導致 JQ 實例無法順其原型鏈爬取到 jQuery.prototype。

要實現這個條件,只需要做小小改動——把 jQuery.fn.init 的原型指向 jQuery 的原型(jQuery.prototype / jQuery.fn)即可:

複製代碼
    var init = jQuery.fn.init = function( selector ) {
        if ( !selector ) {
            return;
        } else {
            var elem = document.querySelector( selector );
            if ( elem ) {
                this[0] = elem;
                this.length = 1;
            }
        }
    };

    init.prototype = jQuery.fn;  //修改點
複製代碼

這裏貼下完整代碼:

 View Code

3. 鏈式寫法實現

JQ 裏一個很大的亮點是,它支持鏈式寫法,調用起來非常方便:

$('div').removeClass('hide').css('width', '100px')

其實現其實非常簡單 —— 確保每個調用的方法尾部均返回自身即可,這裏我們新增兩個實例方法做示例:

複製代碼
    jQuery.fn = jQuery.prototype = {
        jquery: version,
        constructor: jQuery,
        setBackground: function(){
            this[0].style.background = 'yellow';
            return this  //返回自身引用
        },
        setColor: function(){
            this[0].style.color = 'blue';
            return this  //返回自身引用
        }
    };

    var init = jQuery.fn.init = function( selector ) {
        if ( !selector ) {
            return this; 
        } else {
            var elem = document.querySelector( selector );
            if ( elem ) {
                this[0] = elem;
                this.length = 1;
            }
            return this;
        }
    };
複製代碼

鏈式調用:

複製代碼
<div>hello world</div>

<script>
    var $div = $('div');
    $div.setBackground().setColor();
</script>
複製代碼

效果如下,槓槓的:

4. 衝突處理

存在某些情況,用戶可能並不想拿 window.$ 甚至 window.jQuery 來引用 JQ 接口,或者已經有其它庫使用了 window.$ 這個變量,如果我們粗暴地改變其引用肯定是不合理的。

so 我們來實現 JQ 中衝突處理的靜態接口 jQuery.noConflict,這意味着在代碼段開始時,就得先保存下當前 window.$ 和 window.jQuery 兩個變量:

複製代碼
(function(){
    var _jQuery = window.jQuery,
        _$ = window.$;

    //var version = "0.0.1"......
})()
複製代碼

然後是實現 noConflict 方法,退耕還林,把保存的變量吐回去即可:

複製代碼
(function(){
    var _jQuery = window.jQuery,
        _$ = window.$;

    //var version = "0.0.1"......

    jQuery.noConflict = function( deep ) {
        //確保window.$沒有再次被改寫
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }

        //確保window.jQuery沒有再次被改寫
        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }

        return jQuery;  //返回 jQuery 接口引用
    };

    window.jQuery = window.$ = jQuery;
})();
複製代碼

deep 參數類型爲 Boolean,若爲真,表示要求連window.jQuery 變量都需要吐回去。

留意在尾部我們返回了 jQuery 的接口引用,這意味着我們可以以

var $$$ = jQuery.noConflict()

的形式來把它賦予新的變量。

接着在外部運行如下代碼:

複製代碼
<head>
    <meta charset="UTF-8">
    <title>DIY A JQ</title>
    <script>
        $ = 'old $';
        jQuery = 'old JQ'
    </script>
    <script src="jQuery.js"></script>
</head>
<body>

<div>hello world</div>

<script>
    var $div = $('div');
    $div.setBackground().setColor();

    var $$$ = $.noConflict(true);
    console.log($); 
    console.log(jQuery); 
    console.log($$$); 
</script>
複製代碼

輸出如下:

 

第一篇就寫到這裏,相關的代碼可以從 我的github 上下載到。

下次我們會試着實現模塊化的寫法,並與時俱進,改用 ES6解構賦值語法 + Rollup 來進行打包以減少可能存在的冗餘代碼段。

共勉~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章