`
天梯梦
  • 浏览: 13630395 次
  • 性别: 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)

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

分享到:
评论

相关推荐

    从零开始学jquery

    从零开始学jquery,一个非常好的教程!!!

    jquery从零开始学习

    jquery从零开始学习 pdf.适合刚刚jquery入门学习

    从零开始学JQuery.pdf

    从零开始学JQuery

    jquery 入门文档 从零开始学校jquery

    从零开始学习jQuery (一) 开天辟地入门篇 从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 从零开始学习jQuery (五) 事件...

    从零开始学习jQuery.pdf

    从零开始学习jQuery.pdf 从零开始学习jQuery.pdf

    jQueryEasyUI从零开始学源码part1

    《jQueryEasyUI从零开始学》源码,一共分2个部分,这是第一部分

    Ajax入门:从零开始学习jQuery pdf

    从零开始学习jQuery pdf,第一篇:开天辟地入门篇;第二篇:jQuery最重要的部分——万能的选择器;第三章:管理Jquery包装集;第四章:使用jQuery操作元素的属性与样式;第五篇:事件与事件对象……  通过一步步的...

    jquery 从零开始

    jquery从零开始学习,包括例子和详细的说明,很适合初学者

    从零开始学习jQuery

    从零开始学习jQuery

    从零开始学习jQuery-张子秋

    从零开始学习jQuery (一) 开天辟地入门篇 从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 从零开始学习jQuery (五) ...

    从零开始学习jQuery pdf

    从零开始学习jQuery pdf,第一篇:开天辟地入门篇;第二篇:jQuery最重要的部分——万能的选择器;第三章:管理Jquery包装集;第四章:使用jQuery操作元素的属性与样式;第五篇:事件与事件对象……

    从零开始学习JQuery

    从零开始学习jQuery (一) 开天辟地入门篇 从零开始学习jQuery (二) 万能的选择器 从零开始学习jQuery (三) 管理jQuery包装集 从零开始学习jQuery (四) 使用jQuery操作元素的属性与样式 从零开始学习jQuery (五) ...

    从零开始学习jQuery.doc

    从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc从零开始学习jQuery.doc

    《jQuery: Novice to Ninja》- 2017 英文原版

    jQuery: Novice to Ninja is your fast track to mastering jQuery—the all-conquering JavaScript framework. Used by over half the world’s top 10,000 websites, jQuery is the fastest, most efficient way ...

    Beginning jQuery:From the Basics of jQuery to Writing your Own Plug-ins

    从jquery基础到自己编写插件,包括书和源码 作者: Franklin, Jack, Ferguson, Russ

    从零开始学习jquery

    从零开始学习jquery,适合从零开始学习js的朋友们,挺实用的!

    从零开始学习jQuery系列教程

    本系列文章将带您进入jQuery的精彩世界, 其中有很多作者...本篇文章是入门第一篇, 主要是简单介绍jQuery, 通过简单示例指导大家如何编写jQuery代码以及搭建开发环境. 详细讲解了如何在Visual Studio中配合使用jQuery.

Global site tag (gtag.js) - Google Analytics