美文网首页程序员技术栈前端进阶之路
打造属于自己的underscore系列 ( 一 )

打造属于自己的underscore系列 ( 一 )

作者: 97e7dbf546de | 来源:发表于2018-12-06 16:54 被阅读15次

    underscore作为开发中比较常用的一个javascript工具库,提供了一套丰富的函数式编程功能,该库并没有拓展原有的javascript原生对象,而是在自定义的_对象上,提供了100多个方法函数。在这个系列中,将从uderscore源码角度, 打造一个自己的underscore框架

    一,框架设计

    1.1 自执行函数

    现代js 库的框架设计,一般都是以自执行函数的形式,自执行函数一般有两种形式

    (function(){
        // 形式一
    }())
    
    (function(){
        // 形式二
    })()
    

    我们知道,函数声明的形式会挂载到window对象作为方法存在,而函数表达式的形式则会挂载在window对象作为属性存在,这都会造成变量污染,而自执行函数的好处在于可以防止变量的污染,函数执行完后便立刻销毁掉。

    1.2 使用风格

    underscore有两种风格形式可以使用,一种是面向对象类型,另一种是函数类型。

    // 例子
    _.map([1, 2, 3], function(n){ return n * 2; });
    _([1, 2, 3]).map(function(n){ return n * 2; });
    

    因此,在定义underscore类的时候需要考虑对象和函数两种场景。当以函数的形式调用时需要把 _ 当作一个构造函数并返回他的实例化。代码如下

    (function(root){
        var _ = function (obj) {
            if (!(this instanceof _)) {
              return new _(obj)
            }
        }
        root._ = _
    }(this))
    

    1.3 使用环境

    现在前端开发重视模块化,以node服务端而论, 我们有commonjs规范,以客户端而论,我们有AMD 和 CMD规范,对应的模块加载器为 requirejs 和 seajs。目前通行的javascript模块规范主要集中在commonjs 和 AMD,因此为了让定义的underscore库能够适用于各种规范。在框架的定义时需检测使用环境并兼容各种规范。

    • 服务端:commonjs规范,检测module.exports 是否存在,满足时通过 module.exports = {} 将 underscore暴露出来,不满足则 通过window对象暴露出来。
    • 客户端: AMD 规范, 检测 define.amd 是否存在,满足时通过 define('**', [], function(){ return '***' })暴露模块
    (function (root) {
      var _ = function (obj) {
        if (!(this instanceof _)) {
          return new _(obj)
        }
      }
      // commonjs 规范 检测 module.exports 是否存在
      if ((typeof module !== 'undefined' && module.exports)) {
        module.exports = {
          _: _
        }
      } else {
        root._ = _;// window 对象暴露方法
      }
      // amd 规范,检测 define.amd 是否存在
      if (typeof define == 'function' && define.amd) {
        define('underscore', [], function () {
          return _;
        });
      }
    
    }(this))
    
    1.3.1 服务端使用
    // commonjs
    const _ = require('./underscore.js')
    console.log(_)
    
    1.3.2 客户端使用
    // AMD
    require(['underscore'], function (underscore) {
      console.log(underscore)
    })
    

    1.4 方法定义

    underscore的调用,既可以通过_.unique(),也可以通过 _().unique(),两种方法效果相同却需要在框架设计时定义两套方法,一套是定义 _ 对象的静态方法,另一套是扩展 _对象原型链上的方法。

    _.uniqe = function() {}
    
    _.prototype.unique = function() {}
    

    为了避免冗余代码,可以将定义好的静态方法复制一份成为原型链上的方法

    (function(root){
        ···
        _.mixins = function() {
            // 复制静态方法到原型上
        }
        _.mixins() // 执行方法
    }(this))
    
    

    mixins 方法的实现,需要遍历 underscore 对象上所有的静态方法,因此需要先完成对 遍历方法 _.each 的实现

    1.41 _.each

    _.each(list, iteratee, [context]) Alias: forEach
    遍历list中的所有元素,按顺序用每个元素当做参数调用 iteratee 函数。如果传递了context参数,则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数:(element, index, list)。如果list是个JavaScript对象,iteratee的参数是 (value, key, list))。返回list以方便链式调用。

    each 的第一个参数按照文档可以支持 数组,类数组,对象三种类型,数组类数组和对象在遍历时的处理方式不同。前者回调函数处理的是 值和下标,后者处理的是 值和属性。

    // 判断数组,类数组方法
    (function(root) {
        ···
        _.each = function (list, callback, context) {
            // context 存在会改变callback 中this 的指向
            var i = 0;
            var key;
            if (isArrayLikeLike(list)) { //  数组,类数组
              for (var i = 0; i < list.length; i++) {
                context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)
              }
            } else { // 对象
              for (key in list) {
                context ? callback.call(context, list[key], key) : callback(list[key], key)
              }
            }
          }
        var isArrayLike = function (collection) {
            // 返回参数 collection 的 length 属性值
            var length = collection.length;
        
            // length是数值,非负,且小于等于MAX_ARRAY_INDEX
            // MAX_ARRAY_INDEX = Math.pow(2, 53) - 1
            return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
        }
    }(this))
    
    
    
    1.4.2 _.mixin

    mixin方法的设计,目的是为了在underscore原型对象上扩展更多的方法,它既可以用来扩展用户自定义的方法,比如

    _.mixin({
      capitalize: function(string) {
        return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();
      }
    });
    _("fabio").capitalize();
    => "Fabio"
    

    当然也可以用来内部拷贝静态方法到原型链的方法上。

    (function(root){
        ···
        var push = Array.prototype.push
        var _ = function (obj) {
            if (!(this instanceof _)) {
              return new _(obj)
            }
            this.wrap = obj // 存储实例对象传过来的参数
          }
        _.mixins = function (obj) {
            _.each(obj, function (value, key) {
              _.prototype[key] = function () {
                var args = [this.wrap]
                push.apply(args, arguments)
                return value.apply(this, args)
              }
            })
          }
      _.mixins(_)
    }(this))
    

    其中关注点在arguments 的处理上,静态方法需要传递目标源作为方法的参数 即_.unique(目标源, 回调函数),而实例方法的目标源存储在构造对象的属性中 ,即_(目标源).unique(回调函数),因此定义实例方法时需要合并属性和回调函数。即Array.prorotype.push.apply([this.wrap], arguments),之后将他作为参数传递给静态方法并返回处理结果。

    将类数组转成数组的方法

    • Array.prototype.slice.call(类数组)
    • var a = []; Array.prototype.push.apply(a, 类数组); console.log(a);
    • var a = []; Array.prototype.concat.apply(a, 类数组); console.log(a);
    • ES6方法 Array.from(类数组)
    • ES6扩展运算符 var args = [...类数组]

    1.5 链式调用

    1.5.1 _.chain()

    返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直道 value 方法调用为止。

    underscore中方法的调用返回的是处理后的值,因此无法支持方法的链式调用。如果需要链式调用,需要使用chain()方法,chain的使用会使每次调用方法后返回underscore的实例对象,直到 调用value方法才停止返回。

    (function(root){
        ···
        // chain方法会返回 _ 实例,并且标注该实例是否允许链式调用的
        _.chain = function(obj) {
            var instance = _(obj);
            instance.chain = true; 
            return instance
          }
        // 增加是否支持链式调用的判断,如果支持,则返回该实例,不支持则直接返回结果,
        var chainResult = function (instance, obj) {
            return instance.chain ? _(obj).chain() : obj
          }
        _.mixins = function (obj) {
            _.each(obj, function (value, key) {
              _.prototype[key] = function () {
                var args = [this.wrap]
                push.apply(args, arguments)
                return chainResult(this, value.apply(this, args)) // 修改实例方法的返回值,返回值通过chainResult 包装,根据chainResult的判断结果改变返回值
              }
            })
          }
    }(this))
    
    1.5.2 value()

    因为链式调用会使underscore的方法返回他的实例对象,所以当需要结束这一调用行为时,需要使用value()。 value()方法会返回调用的结果。

    (function(root){
        ···
        _.value = function(instance) {
            return instance.wrap
        }
    }(this))
    
    未完待续。。。

    本文为博主原创文章,转载请注明出处 https://www.cnblogs.com/kidflash/p/10077643.html

    相关文章

      网友评论

        本文标题:打造属于自己的underscore系列 ( 一 )

        本文链接:https://www.haomeiwen.com/subject/qgmwcqtx.html