• 从零开始,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 来进行打包以减少可能存在的冗余代码段。

    共勉~

  • 相关阅读:
    JMeter发送get请求并分析返回结果
    在Linux CentOS上搭建Jmeter压测环境
    Jmeter对数据库批量增删改查
    adb下载安装
    JMeter循环读取CSV文件实现接口批量测试
    Jmeter接口测试-MD5加密-请求验签(完整流程)
    fiddler 手机抓包(含https) 完整流程
    火狐兼容selenium版本解决
    selenium启动IE失败,并报错:Unexpected error launching Internet Explorer. Protected Mode settings are not the same for all zones
    基于pywinauto的Windows平台上自动化测试实践(2)
  • 原文地址:https://www.cnblogs.com/vajoy/p/5510743.html
Copyright © 2020-2023  润新知