`
天梯梦
  • 浏览: 13794432 次
  • 性别: Icon_minigender_2
  • 来自: 洛杉矶
社区版块
存档分类
最新评论

jQuery:从零开始,DIY一个jQuery(2)

 
阅读更多

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

 

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

 

本期代码均挂在我的github上,有需要的童鞋自行下载(DIY-A-jQuery-master)。

 

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',
        moduleName: 'jQuery',
        dest: '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",
      jQuery = function (selector, context) {

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



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
    }
};


export default jQuery;

 

init.js:

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

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



export default init;

 

global.js:

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

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

    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;
};

export default global;

 

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

 

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

(function (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 version = "0.0.1";
  var jQuery = function jQuery(selector, context) {

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

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

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

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

      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;
  };

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

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

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

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

  return jQuery;

}));

 

 

3. extend 完善

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

 

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

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

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

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

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

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

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

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

            // TODO - 完善Extend
        }
    }

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

 

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

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

 

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

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

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

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

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

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

                // target已有该属性且完全相等,跳出本次循环
                if ( target === copy ) {
                    continue;
                }

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

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

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

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

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

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

 

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

jQuery.extend( deep, clone, copy )

 

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

 

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

 

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

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

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

            //空数组/空对象没有可枚举的元素/属性,这里会忽略
        }
    }

    // 返回被修改后的目标对象
    return target;    //直接返回空数组/空对象

 

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

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

                
                } else if ( copy !== undefined ) {// 执行这里
                    target[ name ] = copy;
                }
            }
        }
    }

    // 返回如 ['vajoy'] 或者 {'name' : 'vajoy'}
    return target;

 

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

 

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 = function() {
    var options, name, src, copy, copyIsArray, clone,
        target = arguments[ 0 ] || {},
        i = 1,
        length = arguments.length,
        deep = false;

    if ( typeof target === "boolean" ) {
        deep = target;

        target = arguments[ i ] || {};
        i++;
    }

    if ( typeof target !== "object" && !jQuery.isFunction( target ) ) {  //修改点1
        target = {};
    }

    if ( i === length ) {
        target = this;
        i--;
    }

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

        if ( ( options = arguments[ i ] ) != null ) {

            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                if ( target === copy ) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject( copy ) ||  //修改点2
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {

                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];  //修改点3

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

                    target[ name ] = jQuery.extend( deep, clone, copy );

                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    return target;
};

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

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

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

        proto = getProto( obj );  //获取 prototype

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

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

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

});

 

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

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

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

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

 

我们执行打包处理:

node rollup.config.js

 

在 HTML 页面运行下述代码:

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

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

 

效果如下:

jQuery:从零开始,DIY一个jQuery(2)

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

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

jQuery.extend( {
    type: function( obj ) {  
        if ( obj == null ) {
            return obj + ""; //'undefined' 或 'null'
        }

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

});

 

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

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

 

转自:http://www.cnblogs.com/vajoy/p/5728755.html

 

更多参考:

jQuery: 操作select option方法集合

jQuery: 插件开发模式详解 $.extend(), $.fn, $.widget()

AngularJS jQuery 共存法则

 

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

 

本文转自:jQuery:从零开始,DIY一个jQuery(2)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    WEB模版-DIY制作

    在IT行业中,Web模板是一种非常实用的设计工具,它允许开发者快速构建和定制网站的外观和布局,无需从零开始编写HTML、CSS和JavaScript代码。"WEB模版-DIY制作"这个主题涉及到的是如何自制Web模板,让我们深入探讨...

    DiY-Page 亮彩水晶模板

    网页模板在IT行业中扮演着重要的角色,它们为设计师提供了快速构建网页的基础框架,节省了从零开始设计的时间。"DiY-Page 亮彩水晶模板"很可能包含了HTML文件、CSS样式表、JavaScript脚本、图像以及其他多媒体资源,...

    DiY-Page 水晶亮蓝风格

    使用"DiY-Page 水晶亮蓝风格",你可以省去从零开始设计页面的时间,只需调整内容和细节就能创建一个与主题相符的网站。 在提供的压缩包"diypager10"中,可能包含以下内容: 1. HTML 文件:这是网页的核心,包含了...

    bootstap-web-page:Itvet

    这使得开发者无需从零开始编写CSS,从而大大提高了开发效率。 3. **组件**:Bootstrap包含一系列可重用的UI组件,如导航条、下拉菜单、模态框、卡片、轮播图等。这些组件不仅美观,而且易于实现和定制,为创建功能...

    3 Android SqliteManager 源码.zip

    3 Android SqliteManager 源码.zip

    基于S7-200 PLC的煤矿排水系统智能控制:三台水泵联动与备援策略

    内容概要:本文详细介绍了基于S7-200 PLC的煤矿排水系统智能控制方案,重点讨论了三台水泵(两台工作水泵和一台备用水泵)的联动与备援策略。系统通过超声波液位传感器实时监测水位,根据不同水位情况自动控制水泵的启停。具体而言,水位低时不启动水泵,水位介于中水位和高水位之间时启动1号水泵,水位超过高水位则启动1号和2号水泵共同工作。若1号或2号水泵出现故障,系统会自动启用3号备用水泵。此外,MCGS6.2组态画面用于实时监控水位和水泵状态,帮助操作员及时应对异常情况,确保矿井安全。 适合人群:从事煤矿自动化控制领域的技术人员、矿业工程管理人员及相关研究人员。 使用场景及目标:适用于需要提高煤矿排水系统自动化水平的场合,旨在提升矿井排水效率和安全性,减少人工干预,确保矿井生产安全。 其他说明:文中提到的技术方案不仅提高了排水系统的可靠性,还为未来的智能化矿山建设提供了有益借鉴。

    scratch少儿编程逻辑思维游戏源码-灌篮之王.zip

    scratch少儿编程逻辑思维游戏源码-灌篮之王.zip

    scratch少儿编程逻辑思维游戏源码-飞翔马里奥(2).zip

    scratch少儿编程逻辑思维游戏源码-飞翔马里奥(2).zip

    scratch少儿编程逻辑思维游戏源码-火柴人大战 中世纪战争.zip

    scratch少儿编程逻辑思维游戏源码-火柴人大战 中世纪战争.zip

    scratch少儿编程逻辑思维游戏源码-几何冲刺(2).zip

    scratch少儿编程逻辑思维游戏源码-几何冲刺(2).zip

    南京证券-低轨卫星互联网启动,天地一体通信迈向6G.pdf

    南京证券-低轨卫星互联网启动,天地一体通信迈向6G

    nginx-1.20.1

    nginx-1.20.1

    sshpass-1.06-8.ky10.aarch

    sshpass-1.06-8.ky10.aarch

    少儿编程scratch项目源代码文件案例素材-我的世界2D(更新北极).zip

    少儿编程scratch项目源代码文件案例素材-我的世界2D(更新北极).zip

    通信行业专题研究:车载全息数字人——AI+Agent新场景,全息投影新方向-20231121-国盛证券-13页.pdf

    通信行业专题研究:车载全息数字人——AI+Agent新场景,全息投影新方向-20231121-国盛证券-13页

    基于S7-200 PLC与组态王的邮件分拣系统设计——智能化控制技术与实践应用研究

    内容概要:本文详细介绍了利用西门子S7-200 PLC和组态王软件构建的邮件分拣系统的具体设计方案和技术细节。首先,文中阐述了硬件部分的设计,包括光电传感器、传送带电机以及分拣机械臂的连接方式,特别是旋转编码器用于精确测量包裹位移的技术要点。接着,展示了PLC编程中的关键代码段,如初始化分拣计数器、读取编码器数据并进行位置跟踪等。然后,描述了组态王作为上位机软件的作用,它不仅提供了直观的人机交互界面,还允许通过简单的下拉菜单选择不同的分拣规则(按省份、按重量或加急件)。此外,针对可能出现的通信问题提出了有效的解决方案,比如采用心跳包机制确保稳定的数据传输,并解决了因电磁干扰导致的问题。最后,分享了一些现场调试的经验教训,例如为减少编码器安装误差对分拣精度的影响而引入的位移补偿算法。 适合人群:从事自动化控制领域的工程师或者对此感兴趣的初学者。 使用场景及目标:适用于需要提高邮件或其他物品自动分拣效率的企业或机构,旨在降低人工成本、提升工作效率和准确性。 其他说明:文中提到的实际案例表明,经过优化后的系统能够显著改善分拣性能,将分拣错误率大幅降至0.3%,并且日均处理量可达2万件包裹。

    scratch少儿编程逻辑思维游戏源码-机械汽车.zip

    scratch少儿编程逻辑思维游戏源码-机械汽车.zip

    光学领域:基于束缚态驱动的手征超表面实现可调谐手征光学响应的研究及其应用

    内容概要:本文详细探讨了在连续介质中利用束缚态驱动设计并实现具有最大和可调谐手征光学响应的平面手征超表面的方法。文中首先介绍了comsol三次谐波和本征手性BIC(束缚态诱导的透明)两种重要光学现象,随后阐述了具体的手征超表面结构设计,包括远场偏振图、手性透射曲线、二维能带图、Q因子图和电场图的分析。最后,通过大子刊nc复现实验验证了设计方案的有效性,并对未来的研究方向进行了展望。 适合人群:从事光学研究的专业人士、高校物理系师生、对光与物质相互作用感兴趣的科研工作者。 使用场景及目标:适用于希望深入了解手征超表面设计原理及其光学响应机制的研究人员,旨在推动新型光学器件的研发和技术进步。 其他说明:本文不仅展示了理论分析和模拟计算,还通过实验证明了设计方法的可行性,为后续研究奠定了坚实的基础。

    少儿编程scratch项目源代码文件案例素材-位图冒险.zip

    少儿编程scratch项目源代码文件案例素材-位图冒险.zip

    少儿编程scratch项目源代码文件案例素材-校园困境2.zip

    少儿编程scratch项目源代码文件案例素材-校园困境2.zip

Global site tag (gtag.js) - Google Analytics