• 从零开始,DIY一个jQuery(1)


    从本篇开始会陪大家一起从零开始走一遍 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 = Factory;

    这样我们也能直接通过 $.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;  //修改点

    这里贴下完整代码:

        var version = "0.0.1",
            jQuery = function(selector) {
                return new jQuery.fn.init(selector)
            };
    
        jQuery.fn = jQuery.prototype = {
            jquery: version,
            constructor: jQuery
        };
    
        var init = jQuery.fn.init = function( selector, context, root ) {
            if ( !selector ) {
                return;
            } else {
                var elem = document.querySelector( selector );
                if ( elem ) {
                    this[0] = elem;
                    this.length = 1;
                }
            }
        };
    
        init.prototype = jQuery.fn;
    
        jQuery.extend = jQuery.fn.extend = function(){
            console.log(this)
        };
    
        window.$ = window.jQuery = jQuery;
    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 来进行打包以减少可能存在的冗余代码段。

    共勉~

  • 相关阅读:
    【转】 java中Class对象详解和类名.class, class.forName(), getClass()区别
    106. Construct Binary Tree from Inorder and Postorder Traversal
    105. Construct Binary Tree from Preorder and Inorder Traversal
    107. Binary Tree Level Order Traversal II
    109. Convert Sorted List to Binary Search Tree
    108. Convert Sorted Array to Binary Search Tree
    110. Balanced Binary Tree
    STL容器迭代器失效问题讨论
    113. Path Sum II
    112. Path Sum
  • 原文地址:https://www.cnblogs.com/vajoy/p/5510743.html
Copyright © 2020-2023  润新知