从本篇开始会陪大家一起从零开始走一遍 jQuery 的奇妙旅途,在整个系列的实践中,我们会把 jQuery 的主要功能模块都了解和实现一遍。

这会是一段很长的历程,但也会很有意思 —— 作为前端领域的经典之作,jQuery 里有着太多奇思妙想,如果能够深入理解它,对于我们稳固js基础、提升前端大法技能来说大有裨益。

另外,本系列的相关代码均可以从 我的github 上获取到。

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

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;

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

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;

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

然而这时候存在一个问题 —— 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

相关文章:

  • 2021-10-09
  • 2019-06-25
  • 2021-10-18
  • 2021-11-04
  • 2021-12-29
  • 2022-12-23
  • 2021-08-06
  • 2022-12-23
猜你喜欢
  • 2021-05-03
  • 2021-09-26
  • 2021-06-15
  • 2021-08-04
  • 2021-04-15
  • 2021-11-17
相关资源
相似解决方案