亚洲国产日韩欧美一区二区三区,精品亚洲国产成人av在线,国产99视频精品免视看7,99国产精品久久久久久久成人热,欧美日韩亚洲国产综合乱

從零開始,DIY一個(gè)jQuery(2)

Original 2016-11-11 17:31:16 373
abstrakt:在上篇手記我們簡單實(shí)現(xiàn)了一個(gè) jQuery 的基礎(chǔ)結(jié)構(gòu),不過為了順應(yīng)潮流,這次咱把它改為模塊化的寫法,此舉得以有效提升項(xiàng)目的可維護(hù)性,因此在后續(xù)也將以模塊化形式進(jìn)行持續(xù)開發(fā)。1. 基本配置為了讓 rollup 得以靜態(tài)解析模塊,從而減少可能存在的冗余代碼,我們得用上 ES6 的解構(gòu)賦值語法,因此得配合 babel 輔助開發(fā)。在目錄下我們新建一個(gè) babel 配置“.babelrc”:{ 

在上篇手記我們簡單實(shí)現(xiàn)了一個(gè) jQuery 的基礎(chǔ)結(jié)構(gòu),不過為了順應(yīng)潮流,這次咱把它改為模塊化的寫法,此舉得以有效提升項(xiàng)目的可維護(hù)性,因此在后續(xù)也將以模塊化形式進(jìn)行持續(xù)開發(fā)。

1. 基本配置

為了讓 rollup 得以靜態(tài)解析模塊,從而減少可能存在的冗余代碼,我們得用上 ES6 的解構(gòu)賦值語法,因此得配合 babel 輔助開發(fā)。

在目錄下我們新建一個(gè) 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

后續(xù)咱們直接執(zhí)行:

node rollup.config.js

即可實(shí)現(xiàn)打包。

2. 模塊拆分

從模塊功能性入手,我們暫時(shí)先簡單地把上次的整個(gè) IIFE 代碼段拆分為:

src/jquery.js  //出口模塊
src/core.js  //jQuery核心模塊
src/global.js  //全局變量處理模塊
src/init.js  //初始化模塊

它們的內(nèi)容分別如下:

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 module === 'object' && typeof module.exports !== '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 中我們先加了一層判斷,如果使用者走的模塊化形式,那是無須考慮全局變量沖突處理的,直接繞過該模塊即可。

執(zhí)行打包后效果如下(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 接口來擴(kuò)展 JQ 的靜態(tài)方法/實(shí)例方法,也可以簡單地實(shí)現(xiàn)對象的合并和深/淺拷貝。這是非常重要且實(shí)用的功能,在這里我們得完善它。

在 core.js 中我們新增如下代碼段:

jQuery.extend = jQuery.fn.extend = function() {    var options, 
        target = arguments[ 0 ] || {},  //target為要被合并的目標(biāo)對象
        i = 1,
        length = arguments.length,
        deep = false; //默認(rèn)為淺拷貝
    // 若第一個(gè)參數(shù)為Boolean,表示其為決定是否要深拷貝的參數(shù)
    if ( typeof target === "boolean" ) {
        deep = target;        // 那么 target 參數(shù)就得往后挪一位了
        target = arguments[ i ] || {};
        i++;
    }    // 若 target 類型不是對象的處理
    if ( typeof target !== "object" && typeof target !== "function" ) {
        target = {};
    }    // 若 target 后沒有其它參數(shù)(要被拷貝的對象)了,則直接擴(kuò)展jQuery自身(把target合并入jQuery)
    if ( i === length ) {
        target = this;
        i--;  //減1是為了方便取原target(它反過來變成被拷貝的源對象了)    }    for ( ; i < length; i++ ) {        // 只處理源對象值不為 null/undefined 的情況
        if ( ( options = arguments[ i ] ) != null ) {            // TODO - 完善Extend        }
    }    // 返回修改后的目標(biāo)對象
    return target;
};

該段代碼可以判斷如下寫法并做對應(yīng)處理:

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

其它情況會(huì)被繞過(返回空對象)。

我們繼續(xù)完善內(nèi)部的遍歷:

 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 ];                // 避免自己合自己,導(dǎo)致無限循環(huán)
                if ( target === copy ) {                    continue;
                }                // 深拷貝,且確保被拷貝屬性值為對象/數(shù)組
                if ( deep && copy && ( isObject( copy ) ||
                    ( copyIsArray = isArray( copy ) ) ) ) {                    //被拷貝屬性值為數(shù)組
                    if ( copyIsArray ) {
                        copyIsArray = false;                        //若被合并屬性不是數(shù)組,則設(shè)為[]
                        clone = src && isArray( src ) ? src : [];
                    } else {  //被拷貝屬性值為對象
                        //若被合并屬性不是數(shù)組,則設(shè)為{}
                        clone = src && isObject( src ) ? src : {};
                    }                    // 右側(cè)遞歸直到最內(nèi)層屬性值非對象,再把返回值賦給 target 對應(yīng)屬性
                    target[ name ] = jQuery.extend( deep, clone, copy );                    // 非對象/數(shù)組,或者淺拷貝情況(注意排除 undefined 類型)
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }    // 返回被修改后的目標(biāo)對象
    return target;

這里需要留意的有,我們會(huì)通過 

jQuery.extend( deep, clone, copy )

來遞歸生成被合并的 target 屬性值,這是為了避免擴(kuò)展后的 target 屬性和被擴(kuò)展的 copyObj 屬性引用了同一個(gè)對象,導(dǎo)致互相影響。

通過 extend 遞歸解剖 copyObj 源對象的屬性直到最內(nèi)層,最內(nèi)層屬性的值(上方代碼里的 copy)大致有這么兩種情況:

1. copy 為空對象/空數(shù)組:

   for ( ; i < length; i++ ) { //遍歷被拷貝對象
        // 只處理源對象值不為 null/undefined 的情況
        if ( ( options = arguments[ i ] ) != null ) {            //空數(shù)組/空對象沒有可枚舉的元素/屬性,這里會(huì)忽略        }
    }    // 返回被修改后的目標(biāo)對象
    return target;    //直接返回空數(shù)組/空對象

2. copy 為非對象(如“vajoy”):

    if ( deep && copy && ( jQuery.isPlainObject( copy ) ||
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {                                //不會(huì)執(zhí)行這里
                
                } else if ( copy !== undefined ) {// 執(zhí)行這里
                    target[ name ] = copy;
                }
            }
        }
    }    // 返回如 ['vajoy'] 或者 {'name' : 'vajoy'}
    return target;

從而確保 target 所擴(kuò)展的每一層屬性都跟 copyObj 的是互不關(guān)聯(lián)的。

P.S. jQuery 里的深拷貝實(shí)現(xiàn)其實(shí)比較簡單,如果希望能做到更全面的兼容,可以參考 lodash 中的實(shí)現(xiàn)。

4. 建立基礎(chǔ)工具模塊

在上方的 extend 代碼塊中其實(shí)存在兩個(gè)不合理的地方:

1. 僅通過 Object.toString.call(obj) === "[object Object]" 作為對象判斷條件在我們擴(kuò)展對象的邏輯中有些片面,適合擴(kuò)展的對象應(yīng)當(dāng)是“純粹/簡單”(plain)的 js Object 對象,但在某些瀏覽器中,像 document 在 Object.toSting 調(diào)用時(shí)也會(huì)返回和 Object 相同結(jié)果; 
2. 像 Object.hasOwnProperty 和 Object.prototype.toString.call 等方法在我們后續(xù)開發(fā)中會(huì)經(jīng)常使用上,如果能把它們寫到一個(gè)模塊中封裝起來復(fù)用就更好了。

基于上述兩點(diǎn),我們新增一個(gè) var.js 來封裝這些常用的輸出:

export var class2type = {};  //在core.js中會(huì)被賦予各類型屬性值

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構(gòu)造函數(shù)字符串"function Object() { [native code] }",用于判斷 plainObj

然后在 core.js 導(dǎo)入所需接口即可:

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

我們進(jìn)一步修改 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 ) ) {  //修改點(diǎn)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 ) ||  //修改點(diǎn)2
                    ( copyIsArray = jQuery.isArray( copy ) ) ) ) {                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray( src ) ? src : [];  //修改點(diǎn)3
                    } else {
                        clone = src && jQuery.isPlainObject( src ) ? src : {};
                    }
                    target[ name ] = jQuery.extend( deep, clone, copy );
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }    return target;
};//新增修改點(diǎn)1,class2type注入各JS類型鍵值對,配合 jQuery.type 使用,后面會(huì)用上"Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach(function(name){
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});//新增修改點(diǎn)2jQuery.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 ) 形式創(chuàng)建的 {} 是沒有prototype的
        if ( !proto ) {            return true;
        }        // 簡單對象的構(gòu)造函數(shù)等于最頂層 Object 構(gòu)造函數(shù)
        Ctor = hasOwn.call( proto, "constructor" ) && proto.constructor;        return typeof Ctor === "function" && fnToString.call( Ctor ) === ObjectFunctionString;
    },
    isFunction: function( obj ) {        return jQuery.type( obj ) === "function";
    },    //獲取類型(如'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、isFunction、type 四個(gè) jQuery 靜態(tài)方法,其中 isPlainObject 比較有趣,為了過濾某些瀏覽器中的 document 等特殊類型,會(huì)對 obj.prototype 及其構(gòu)造函數(shù)進(jìn)行判斷:

1. 通過Object.create( null ) 形式創(chuàng)建的 {} ,或者實(shí)例對象都是沒有 prototype 的,直接返回 true;2. 判斷其構(gòu)造函數(shù)合法性(存在且等于原生的對象構(gòu)造器 function Object(){ [native code] })

關(guān)于第二點(diǎn),實(shí)際是直接判斷兩個(gè)構(gòu)造器字符串化后是否相同:

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

另外,需要留意的是,通過這段代碼:

//新增修改點(diǎn)1,class2type注入各JS類型鍵值對,配合 jQuery.type 使用,后面會(huì)用上
"Boolean Number String Function Array Date RegExp Object Error Symbol".split(" ").forEach(function(name){
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});

class2type 對象是變成了這樣的:

{
"[object Boolean]":"boolean",
"[object Number]":"number",
"[object String]":"string",
"[object Function]":"function",
"[object Array]":"array",
"[object Date]":"date",
"[object RegExp]":"regexp",
"[object Object]":"object",
"[object Error]":"error",
"[object Symbol]":"symbol"
}

所以后續(xù)只需要通過 

class2type[ Object.prototype.toString(obj) ]

就能獲取 obj 的類型名稱。isFunction 接口便是利用這種鉤子模式判斷傳入?yún)?shù)是否函數(shù)類型的:

   isFunction: function( obj ) {        return jQuery.type( obj ) === "function";
    }

最后。我們執(zhí)行打包處理:

node rollup.config.js

在 HTML 頁面運(yùn)行下述代碼:

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

效果如下:

561179-20160802225425387-420962602.png

留意 $.type 靜態(tài)方法是我們上方通過 jQuery.extend 擴(kuò)展進(jìn)去的:

//新增修改點(diǎn)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- 函數(shù)表達(dá)式類型不正確情況
        class2type[ toString.call( obj ) ] || "object" :
            typeof obj;
    }

});

它返回傳入?yún)?shù)的類型(小寫)。該方法在我們下一章也會(huì)直接在模塊中使用到。


Versionshinweise

Beliebte Eintr?ge