从零开始,DIY一个jQuery2

在上篇文章我们简单实现了一个 jQuery 的基础结构,不过为了顺应潮流,这次咱把它改为模块化的写法,此举得以有效提升项目的可维护性,因此在后续也将以模块化形式进行持续开发。

模块化开发和编译需要用上 ES6 和 rollup,具体原因和使用方法请参照我之前的《冗余代码都走开——前端模块打包利器 Rollup.js 入门》一文。

本期代码均挂在我的github上,有需要的童鞋自行下载。

1. 基本配置

为了让 rollup 得以静态解析模块,从而减少可能存在的冗余代码,我们得用上 ES6 的解构赋值语法,因此得配合 babel 辅助开发。

在目录下我们新建一个 babel 配置“.babelrc”:

{
  "presets": ["es2015-rollup"]
}

以及 rollup 配置“rollup.comfig.js”:

var rollup = require( 'rollup' );
var babel = require('rollup-plugin-babel');

rollup.rollup({
    entry: 'src/jquery.js',plugins: [ babel() ]
}).then( function ( bundle ) {
    bundle.write({
        format: 'umd''rel/jquery.js'
    });
});

其中入口文件为“src/jquery.js”,并将以 umd 模式输出到 rel 文件夹下。

别忘了确保已安装了三大套:

npm i babel-preset-es2015-rollup rollup rollup-plugin-babel

后续咱们直接执行:

node rollup.config.js

即可实现打包。

2. 模块拆分

从模块功能性入手,我们暂时先简单地把上次的整个 IIFE 代码段拆分为:

src/jquery.js  //出口模块
src/core.js  //jQuery核心模块
src/global.js  //全局变量处理模块
src/init.js  //初始化模块

它们的内容分别如下:

jquery.js:

import jQuery from './core';
import global from './global';
import init from './init';

global(jQuery);
init(jQuery);

export default jQuery;

core.js:

var version = "0.0.1" (selector,context) {

          return new jQuery.fn.init(selector,context);
      };



jQuery.fn = jQuery.prototype = {
    jquery: version,constructor: jQuery,setBackground: (){
        this[0].style.background = 'yellow';
        this
    },setColor: this[0].style.color = 'blue'
    }
};


export default jQuery;

init.js:

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

    jQuery.fn.init.prototype = jQuery.fn;
};



export default init;

global.js:

var global = (jQuery){
    //走模块化形式的直接绕过
    if(typeof module === 'object' && typeof module.exports !== 'undefined') return;

    var _jQuery = window.jQuery,_$ = window.$;

    jQuery.noConflict = ( deep ) {
        确保window.$没有再次被改写
        if ( window.$ === jQuery ) {
            window.$ = _$;
        }

        确保window.jQuery没有再次被改写
        if ( deep && window.jQuery === jQuery ) {
            window.jQuery = _jQuery;
        }

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

    window.jQuery = window.$ = jQuery;
};

export default global;

留意在 global.js 中我们先加了一层判断,如果使用者走的模块化形式,那是无须考虑全局变量冲突处理的,直接绕过该模块即可。

执行打包后效果如下(rel/jquery.js)

( (global,factory) {
  typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
  typeof define === 'function' && define.amd ? define(factory) :
  (global.jQuery = factory());
}(this,function () { 'use strict';

  /**
   * Created by vajoy on 2016/8/1.
   */

  ;
  var jQuery =  jQuery(selector,context) {

       {
      jquery: version,1)"> setBackground() {
          ;
          ;
      },1)"> setColor() {
          ;
      }
  };

  var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ?  (obj) {
    typeof obj;
  } : return obj && typeof Symbol === "function" && obj.constructor === Symbol ? "symbol" :  obj;
  };

  *
   * Created by vajoy on 2016/8/2.
   */
  var global$1 =  global(jQuery) {
      走模块化形式的直接绕过
      if ((typeof exports === 'undefined' ? 'undefined' : _typeof(exports)) === 'object' && typeof module !== 'undefined') ;

       window.$;

      jQuery.noConflict =  (deep) {
          确保window.$没有再次被改写
          if (window.$ === jQuery) {
              window.$ = _$;
          }

          确保window.jQuery没有再次被改写
          if (deep && window.jQuery === jQuery) {
              window.jQuery = _jQuery;
          }

          return jQuery;       };

      window.jQuery = window.$ = jQuery;
  };

   init(jQuery) {
      jQuery.fn.init = selector) {
              ;
          }  {
               document.querySelector(selector);
               (elem) {
                   elem;
                  ;
              }
              ;
          }
      };

      jQuery.fn.init.prototype = jQuery.fn;
  };

  global$1(jQuery);
  init(jQuery);

   jQuery;

}));
View Code

3. extend 完善

如上章所说,我们可以通过 $.extend / $.fn.extend 接口来扩展 JQ 的静态方法/实例方法,也可以简单地实现对象的合并和深/浅拷贝。这是非常重要且实用的功能,在这里我们得完善它。

core.js 中我们新增如下代码段:

jQuery.extend = jQuery.fn.extend = () {
    var options,target = arguments[ 0 ] || {},1)">target为要被合并的目标对象
        i = 1 arguments.length,deep = false; 默认为浅拷贝

     若第一个参数为Boolean,表示其为决定是否要深拷贝的参数
    if ( typeof target === "boolean" ) {
        deep = target;

         那么 target 参数就得往后挪一位了
        target = arguments[ i ] || {};
        i++;
    }

     若 target 类型不是对象的处理
    typeof target !== "object" && typeof target !== "function" ) {
        target = {};
    }

     若 target 后没有其它参数(要被拷贝的对象)了,则直接扩展jQuery自身(把target合并入jQuery)
    if ( i === length ) {
        target = ;
        i--;  减1是为了方便取原target(它反过来变成被拷贝的源对象了)
    }

    for ( ; i < length; i++ ) {

         只处理源对象值不为 null/undefined 的情况
        if ( ( options = arguments[ i ] ) != null ) {

             TODO - 完善Extend
        }
    }

     返回修改后的目标对象
     target;
};

该段代码可以判断如下写法并做对应处理:

$.extend( targetObj,copyObj1[,copyObj2...] )
$.extend( truetrue,copyObj )

其它情况会被绕过(返回空对象)

我们继续完善内部的遍历:

    var isObject = (obj){
        return Object.prototype.toString.call(obj) === "[object Object]"
    };
    var isArray = return Object.prototype.toString.call(obj) === "[object Array]"
    };

    for ( ; i < length; i++ ) { 遍历被拷贝的源对象

         name,clone,copy;
             遍历源对象属性
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                 避免自己合自己,导致无限循环
                if ( target === copy ) {
                    continue;
                }

                 深拷贝,且确保被拷贝属性值为对象/数组
                if ( deep && copy && ( isObject( copy ) ||
                    ( copyIsArray = isArray( copy ) ) ) ) {

                    被拷贝属性值为数组
                     ( copyIsArray ) {
                        copyIsArray = false;
                        若被合并属性不是数组,则设为[]
                        clone = src && isArray( src ) ? src : [];

                    } else {  被拷贝属性值为对象
                        若被合并属性不是数组,则设为{}
                        clone = src && isObject( src ) ? src : {};
                    }

                     右侧递归直到最内层属性值非对象,再把返回值赋给 target 对应属性
                    target[ name ] = jQuery.extend( deep,copy );

                     非对象/数组,或者浅拷贝情况(注意排除 undefined 类型)
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

     返回被修改后的目标对象
    return target;

这里需要留意的有,我们会通过 

jQuery.extend( deep,copy )

来递归生成被合并的 target 属性值,这是为了避免扩展后的 target 属性和被扩展的 copyObj 属性引用了同一个对象,导致互相影响。

通过 extend 递归解剖 copyObj 源对象的属性直到最内层,最内层属性的值(上方代码里的 copy)大致有这么两种情况:

1. copy 为空对象/空数组:

    遍历被拷贝对象

        空数组/空对象没有可枚举的元素/属性,这里会忽略
return target;    直接返回空数组/空对象

2. copy 为非对象(如“vajoy”):

                if ( deep && copy && ( jQuery.isPlainObject( copy ) || jQuery.isArray( copy ) ) ) ) {
                                不会执行这里

                
                } if ( copy !== undefined ) { 执行这里
                    target[ name ] = 返回如 ['vajoy'] 或者 {'name' : 'vajoy'}
    return target;

从而确保 target 所扩展的每一层属性都跟 copyObj 的是互不关联的。

P.S. jQuery 里的深拷贝实现其实比较简单,如果希望能做到更全面的兼容,可以参考 lodash 中的实现。

4. 建立基础工具模块

在上方的 extend 代码块中其实存在两个不合理的地方:

1. 仅通过 Object.toString.call(obj) === "[object Object]" 作为对象判断条件在我们扩展对象的逻辑中有些片面,适合扩展的对象应当是“纯粹/简单”(plain)的 js Object 对象,但在某些浏览器中,像 document 在 Object.toSting 调用时也会返回和 Object 相同结果;
2. 像 Object.hasOwnProperty 和 Object.prototype.toString.call 等方法在我们后续开发中会经常使用上,如果能把它们写到一个模块中封装起来复用就更好了。

关于 plainObject 的概念可以点这里了解。

基于上述两点,我们新增一个 var.js 来封装这些常用的输出:

export var class2type = {};  在core.js中会被赋予各类型属性值

export const toString = class2type.toString; 等同于 Object.prototype.toString

export const getProto = Object.getPrototypeOf;

export const hasOwn = class2type.hasOwnProperty;

export const fnToString = hasOwn.toString; 等同于 Object.toString/Function.toString

export const ObjectFunctionString = fnToString.call( Object ); 顶层Object构造函数字符串"function Object() { [native code] }",用于判断 plainObj

然后在 core.js 导入所需接口即可:

import { class2type,toString,getProto,hasOwn,fnToString,ObjectFunctionString } from './var.js';

我们进一步修改 extend 接口代码为:

jQuery.extend = jQuery.fn.extend =  {},i = 1 target;

        target = arguments[ i ] ||typeof target !== "object" && !jQuery.isFunction( target ) ) {  修改点1
        target =;
        i-- Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||  修改点2
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

                    ;
                        clone = src && jQuery.isArray( src ) ? src : [];  修改点3

                    }  {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }

                    target[ name ] = target;
};

新增修改点1,class2type注入各JS类型键值对,配合 jQuery.type 使用,后面会用上
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach((name){
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

新增修改点2
jQuery.extend( {
    isArray: Array.isArray,isPlainObject: ( obj ) {
         proto,Ctor;

         明显的非对象判断,直接返回false
        if ( !obj || toString.call( obj ) !== "[object Object]" ) {
            ;
        }

        proto = getProto( obj );  获取 prototype

         通过 Object.create( null ) 形式创建的 {} 是没有prototype的
        if ( !proto ) {
            ;
        }

         简单对象的构造函数等于最顶层 Object 构造函数
        Ctor = hasOwn.call( proto,"constructor" ) && proto.constructor;
        typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
    },isFunction: return jQuery.type( obj ) === "function";
    },获取类型(如'function')
    type: ( obj ) {  
        if ( obj == return obj + ""; 'undefined' 或 'null'
        }

        typeof obj === "object" || typeof obj === "function" ?
        class2type[ toString.call( obj ) ] || "object" :
             obj;
    }

});

这里我们新增了isArray、isPlainObject、isFunction、type 四个 jQuery 静态方法,其中 isPlainObject 比较有趣,为了过滤某些浏览器中的 document 等特殊类型,会对 obj.prototype 及其构造函数进行判断:

1. 通过Object.create( null ) 形式创建的 {} ,或者实例对象都是没有 prototype 的,直接返回 2. 判断其构造函数合法性(存在且等于原生的对象构造器 function Object(){ [native code] })

关于第二点,实际是直接判断两个构造器字符串化后是否相同:

Function.toString.call(constructor) === Function.toString.call(Object)

另外,需要留意的是,通过这段代码:

 name.toLowerCase();
});

class2type 对象是变成了这样的:

{
"[object Boolean]":"boolean"
}

所以后续只需要通过 

class2type[ Object.prototype.toString(obj) ]

就能获取 obj 的类型名称。isFunction 接口便是利用这种钩子模式判断传入参数是否函数类型的:

    isFunction: ;
    }

最后。我们执行打包处理:

node rollup.config.js

在 HTML 页面运行下述代码:

    var $div = $('div');
    $div.setBackground().setColor();

    var arr = [1,2,3];
    console.log($.type(arr))

效果如下:

留意 $.type 静态方法是我们上方通过 jQuery.extend 扩展进去的:

新增修改点1,class2type注入各JS类型键值对,配合 jQuery.type 使用
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach( name.toLowerCase();
});

jQuery.extend( {
    type: typeof obj === "function" ?
        兼容安卓2.3- 函数表达式类型不正确情况
        class2type[ toString.call( obj ) ] || "object" obj;
    }

});

它返回传入参数的类型(小写)。该方法在我们下一章也会直接在模块中使用到。

本章先这样吧,得感谢这台风天赏赐了一天的假期,才有了时间写文章,共勉~

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


1.第一步 设置响应头 header(&#39;Access-Control-Allow-Origin:*&#39;); //支持全域名访问,不安全,部署后需要固定限制为客户端网址 header(&#39;Access-Control-Allow-Methods:POST,GET,OPTIONS,D
$.inArray()方法介绍 $.inArray()函数用于在数组中搜索指定的值,并返回其索引值。如果数组中不存在该值,则返回-1; $.inArray(value,array) --value是要查找的值,array是被查找的数组。 有如下实例: &lt;!DOCTYPE html&gt; &l
jquery.serializejson.min.js的妙用 关于这个jquery.serializejson.min.js插件来看,他是转json的一个非常简单好用的插件。 前端在处理含有大量数据提交的表单时,除了使用Form直接提交刷新页面之外,经常碰到的需求是收集表单信息成数据对象,Ajax提
JS 将form表单数据快速转化为object对象(json对象) jaymou 于 2020-03-03 11:11:05 发布 3534 收藏 3 分类专栏: 前端 文章标签: javascript jquery 版权 前端 专栏收录该内容 5 篇文章0 订阅 订阅专栏 直接上代码 /** *
jQuery的区别:$().click()和$(document).on(&#39;click&#39;,&#39;要选择的元素&#39;,function(){})的不同 文章地址:https://www.cnblogs.com/sqh17/p/7746418.html 解决:动态创建的元素的事件
jQuery插件之jquery.spinner数字智能增减插件 参考地址:http://www.helloweba.com/view-blog-282.html 左右加减数字 像京东提交订单时目前使用的是左右加减数字的效果,这个效果直接明了,操作简单。我们使用jquery.spinner.js插件实
layui标签或一般标签均可&lt;div class=&quot;layui-form-item&quot;&gt; &lt;label class=&quot;layui-form-label&quot;&gt;异地仓名称&lt;/label&gt; &lt;div class=&quot;la
网上对于select option 动态添加修改如下, $(&quot;#selectId&quot;).append(&quot;&lt;option value=&#39;&quot;+value+&quot;&#39;&gt;&quot;+text+&quot;&lt;/option&gt;&
jQuery中的 $.extend() 和 $.fn.extend() ANGWH 于 2020-05-24 06:39:59 发布 注意:$.extend是为jQuery类添加添加类方法,用$.调用(类似$.ajax),$.fn.extend则是为jQuery对象添加方法(实例方法),用DOM元素
jquery 循环数组输出显示在html页面 jquery 没有双向数据绑定,但是很多需求确实需要我们从后台接收到数组或者对象循环显示在前台页面上,这时我们可以用字符串拼接,元素添加的方法去实现 js部分如下: 复制代码 $(function(){ var a=[&quot;1aa&quot;,&q
javascript事件委托理解,jQuery .on()方法一步到位实现事件委托 Javascript-概念原理 专栏收录该内容 10 篇文章0 订阅 订阅专栏 本篇文章借鉴自:博客园文章,只为自己巩固下事件委托方面的知识 概述: 什么叫事件委托?他还有一个名字叫做事件代理,(时间代理 事件委托,
JQuery-$.when().done().fail()的使用 原文引用于&#160;Echoo华地于&#160;2022-01-06 14:07:10 发布 jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本。 每个版本都会引入一些新功能。今天我想介绍的,就是从jQuery 1
jQuery tableExport导出 excel 上篇写的是jQuery&#160;导出word,就试试导出excel。看见网上写的很乱,我这就把我写的整理下来,有部分来自网上capy 1. js文件的引用 &lt;script type=&quot;text/javascript&quot;
jQuery的遍历-prev()和next()方法 &lt;div class=&quot;box&quot; id=&quot;box&quot;&gt; &lt;a href=&#39;#&#39; class=&quot;a&quot;&gt; &lt;input type=&quot;tex
attr()和addClass()的区别 方法 addClass() attr()用途&#x9;追加样式&#x9;设置样式对同一个网页元素操作&#x9;&lt;p&gt;test&lt;/p&gt;第1次使用方法&#x9;$(&quot;p&quot;).addClass(&quot;high&quot;);&#x9;$(&quot;p&
前端——函数(匿名函数、自执行函数) FreshLemon_ 于 2019-06-11 17:11:49 发布 函数声明:function box(){} 函数表达式:var box = function(){}; 匿名函数:function(){} (属于函数表达式) 1声明了一个函数: var
js: 获取标签元素data-*属性值的方法 彭世瑜 于 2022-05-23 09:59:50 发布 2165 收藏 1 文章标签: javascript 前端 jquery 版权 标签上有两个属性data-id 和 data-user-name, 需要通过js去获取 &lt;style&gt;
JavaScript函数详解:匿名函数、具名函数、函数传参、不定参、返回值、JS预解析机制 1.具名函数 定义: 调用: 方式1:方法名(); 可以多次调用 方式2:在事件中调用,直接写函数名,不需用括号 2.匿名函数 没有名字的函数 匿名函数在使用时只有两种情况: 1.匿名函数自执行:声明后不需要
如何等待ajax完成再执行相应操作 ajax广泛应用于异步请求,对于大多数业务来说,这是十分方便的,但对于一些特殊的业务,ajax的异步性会起到相反的作用。 例如在ajax请求成功后,后续的操作需要依赖ajax执行成功后的相应操作。 // 声明一个表示状态的全局变量 status var statu
一步一步教你写一个jQuery的插件教程(Plugin) 更新时间:2009年09月03日 02:10:54 作者: 我将会在下面的例子中一个一个的说明上面这几个条件,做完这些事情后我们就会创建一个高亮显示text的简单插件。 jQuery 的plugin开发需要注意的事情, 1. 明确jQuery