underscore源码解析之一: 实现简易版underscore框架

前言

每次询问他人,如何更好更快的深入学习js呢?大部分的回答都是看源码呀~~~
源码的魅力在哪呢?
作为一个已经从事前端好几年的萌新来说,实在惭愧。还没领教过。。。
今天就从易入手且仍不失精华的underscore源码开始吧~~~~
本次是基于1.8.3版本来研究的。

初始版本实现

underscore的通常使用方式都是_.funcName(), 其实就是把 _ 对象当成一个全局对象使用,所有的方法都挂载到 _ 对象上。
基于此,我们的初始版本可以如下实现:

(function() {
	var root = this;
	var _ = {};
	root._ = _;
	_.init = function(obj) {
		return obj
	}
})()
var obj = {name:'hi'};
console.log(_.init(obj));

然而,具体实现肯定不会如此简单,我们往下具体分析。

this 判别

首先,underscore 是一个工具函数库,是可以在多端环境下运行的,所以 var root = this 需要重新判断属于那个环境。
通常使用 underscore库的环境如下:

  • 浏览器
  • node
  • Web Worker
  • 微信小程序

image

  1. 浏览器环境中提供window全局对象 this默认指向window;

  2. node中的全局对象是global this默认指向空对象;

  3. Web Worker 所在的全局对象与主线程不同,是无法访问 Window 对象和 Document 对象。但能通过 self 访问到 Worker 环境中的全局对象,并且window === self;

  4. node的vm模块(沙盒模块),runInContext方法中,是不存在window 和 global变量的,但可通过this访问到全局对象;

  5. 在微信小程序中,window 和 global 都是 undefined,加上又强制使用严格模式,this 为 undefined,挂载就会发生错误,可以默认为空对象{};

所以代码更改如下:

(function() {
	var root = (typeof self === 'object' && self.self === self && self) ||
			(typeof global === 'object' && global.global === global && global) ||
			this ||
			{};
	var _ = {};
	root._ = _;
	_.init = function(obj) {
		return obj
	}
})()
var obj = {name:'hi'};
console.log(_.init(obj));

_ 对象定义

underscore 其实支持两种调用方式:

  • 函数式风格调用;
  • 面向对象风格调用;

上述原始版本的实现就是基于面向对象方式的,那如何实现函式调用呢?
既然要实现函数调用,首先它就得是个函数。
在javascript中,函数就是对象,也可以给函数增加属性的。所以,对 _ 对象定义如下:

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

接下来就是给 _ 对象添加属性(方法)

//添加版本号
_.VERSION = '0.1';

mixin

好的,目前我们基本已经实现了以函数的形式调用,但是以对象的形式调用方法时,就会发现会发生报错。
image
这个时候就需要我们的原型登场啦~~
把所有需要用到的方法都绑定到原型对象上,也就是 _.prototype上,然后再调用不就可以了嘛~

_.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return func.apply(_, args);
      }
    })
    return _;
  };
  _.mixin(_);

至于上述用到的each() functions()方法如下:
(注解即是说明)

var ArrayProto = Array.prototype;
var push = ArrayProto.push;
var MAX_ARRAY_INDEX= Math.pow(2,53) -1;

//判断是否是数组(包含伪数组)
function isArrayLike(collection) {
    var length = collection.length;
    return typeof length === 'number' && length >=0 && length <= MAX_ARRAY_INDEX;
}

_.each = function(obj, callback) {
	var length,i = 0;
	if(isArrayLike(obj)){
		length = obj.length;
		for(;i<length;i++) {
			// 当回调函数返回false时,就中止循环
			if(callback.call(obj[i], obj[i], i) === false){
				break;
			}
		}
	}else{
		// 对象
		for(i in obj) {
			if(callback.call(obj[i], obj[i], i) === false){
				break;
			}
		}
	}
	return obj;
}

_.isFunction = function(obj){
    return typeof obj === 'function' || false;
}
// 用于获取方法标识符数组
_.functions = function(obj) {
	var names = [];
	for(var key in obj){
		if(_.isFunction(obj[key])){
			names.push(key);
		}
	}
	return names.sort();
}

导出

最后一步就是导出啦~

if(typeof exports !== 'undefined' && !exports.nodeType){
    if(typeof module !== 'undefined' && !module.nodeType && module.exports ) {
      exports = module.exports = _;
    } else {
      exports._ = _;
    }
} else {
    root._ = _;
}

总结

那么,最终我们的代码整合就是这样啦(来个全貌)~

(function(){
  // this判别
  var root = (typeof self === 'object' && self.self === self && self) ||
             (typeof global === 'object' && global.global === global && global) ||
             this ||
             {};
  // _定义
  var _ = function(obj) {
    if(obj instanceof _) return obj;
    if(!(this instanceof _)) return new _(obj);
    this._wrapped = obj;
  }

  // this中添加_
  if(typeof exports !== 'undefined' && !exports.nodeType){
    if(typeof module !== 'undefined' && !module.nodeType && module.exports ) {
      exports = module.exports = _;
    }else{
      exports._ = _;
    }
  } else {
    root._ = _;
  }
  _.VERSION = '0.1';

  // 调用mixin 将_上的方法绑定到_.prototype上
  // 会用到 _.each() 
  // _.functions()
  // isArrayLike()
  // _.isFunction()

  var ArrayProto = Array.prototype;
  var push = ArrayProto.push;
  var MAX_ARRAY_INDEX= Math.pow(2,53) -1;

  function isArrayLike(collection) {
    var length = collection.length;
    return typeof length === 'number' && length >=0 && length <= MAX_ARRAY_INDEX;
  }

  _.each = function(obj, callback) { 
    var length,i = 0;
    // 数组和类数组
    if(isArrayLike(obj)){
      length = obj.length;
      for(;i<length;i++) {
        // 当回调函数返回false时,就中止循环
        if(callback.call(obj[i], obj[i], i) === false){
          break;
        }
      }
    }else{
      // 对象
      for(i in obj) {
        if(callback.call(obj[i], obj[i], i) === false){
          break;
        }
      }
    }
    return obj;
  }

  _.isFunction = function(obj){
    return typeof obj === 'function' || false;
  }

  _.functions = function(obj) {
    var names = [];
    for(var key in obj){
      if(_.isFunction(obj[key])){
        names.push(key);
      }
    }
    return names.sort();
  }

  // 自定义方法,添加自己所需方法
  _.log = function(obj){
    return obj;
  }

  _.mixin = function(obj) {
    _.each(_.functions(obj), function(name) {
      var func = _[name] = obj[name];
      _.prototype[name] = function() {
        var args = [this._wrapped];
        push.apply(args, arguments);
        return func.apply(_, args);
      }
    })
    return _;
  };

  _.mixin(_);

})()

参考

underscore 系列之如何写自己的 underscore

posted @ 2021-09-26 11:03  卖萌实习生  阅读(170)  评论(0)    收藏  举报