underscope

underscope

underscope库解读,学习其中用到的js技巧等。比如,在各种模块中加载的技巧,构造函数跟函数调用合二为一。 underscope的更好替代者为lodash,性能上面更优。使用方式差不多。版本1.9跟1.10的区别在于,后者使用了ES6的方式来加载。重点有以下:

1、构造函数、原型的理解。

2、类的定义。

解读

构造函数

相关代码为:

// Create a safe reference to the Underscore object for use below.
var _ = function(obj) {
  if (obj instanceof _) return obj;
  if (!(this instanceof _)) return new _(obj);
  this._wrapped = obj;
};

解读:

上面的代码看起来非常简洁,其是多个功能的合一体。它既是类的构造函数,又能当一个普通的函数使用。普通函数,是它可以不用new来构造一个类型。上面看起来只有两个return,但实际上有3个retun,省略了当作构造函数时,

this._wrapped = obj;
return this; //当做构造函数时,省略了return 

构造函数

var _ = function(obj){
    this._wrapped = obj;
    return this; //可省略
}
console.log(new _([1,2]));

上面的类构造函数,它也能当作一个函数使用,而且往往容易忘记new关键字。其实相当于专门写了一个工厂函数,用来new _对象。

var _ = function(obj){
    this._wrapped = obj;
    return this;
}
console.log(new _([1,2]));

make_ = function(obj){
    return new _(obj);
}
console.log(make_([2,3]));

实际上,make_函数名称也能跟构造函数同名。同名后,根据this的指向来判断,函数时,this指向window对象。所以,用

this instanceof _

来判断,是当作构造函数使用,还是函数使用。所以,

var _ = function(obj){
    //当函数使用时,this指向的是window
    if(!(this instanceof _)){
        return new _(obj);
    }
    this._wrapped = obj;
    return this;
}
console.log( _([1,2]));

另外,对参数判断,如果obj已经是_对象时,就直接返回,不用包装了。

var _ = function(obj){
    if(obj instanceof _ ){
        return obj;
    }
    //当函数使用时,this指向的是window
    if(!(this instanceof _)){
        return new _(obj);
    }
    this._wrapped = obj;
    return this;
}
console.log( _([1,2]));
console.log(_(new _([1,2]))); //这种即是针对情况1

所以,对构造函数中的技巧,大概有如上三点。但是很简洁。

链式调用

这是一种常见的编程套路,比如jquery中也有,像php中,sql方面,也有。链式调用的关键是,对象方法调用后,依然返回该对象,那么调用起来,就非常方便。

相关代码:

_.chain = function(obj) {
  var instance = _(obj);
  instance._chain = true;
  return instance;
};

通过_(obj)来生成对象。并为该对象标记为_chain = true。我们可以简单的看作。该方法等同于一个工厂方法。

再来看,chainResult,大概相当于判断是否继续封装结果

// OOP
// ---------------
// If Underscore is called as a function, it returns a wrapped object that
// can be used OO-style. This wrapper holds altered versions of all the
// underscore functions. Wrapped objects may be chained.

// Helper function to continue chaining intermediate results.
var chainResult = function(instance, obj) {
  return instance._chain ? _(obj).chain() : obj;
};

其实,上面的方法,好像也能这样写:

//理论上,下面应该是等效的。
return instance._chain ? _.chain(obj) : obj;

关键mixin方法,相当于将之前所有定义的函数,全部改写了一遍。注意,在原型上,绑定了一个新函数,而此函数将存储在_wrapped属性上的obj,拿出来,跟arguments拼成数组,然后执行原来的方法func。并用chainResult来返回结果。

// Add your own custom functions to the Underscore object.
_.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 chainResult(this, func.apply(_, args));
    };
  });
  return _;
};

// Add all of the Underscore functions to the wrapper object.
_.mixin(_);

取结果,用value来终止操作。

// Extracts the result from a wrapped and chained object.
_.prototype.value = function() {
  return this._wrapped;
};

通过以上的方式,完成了链式调用。_.mixin(_)将原有的方法升级,在调用的时候,进行装饰,并chainResult方法,对每此的结果,进行封装。使其能链式调用。

但是,上面有个细节,chain方法也被改写了,即

_.prototype[name] = function() {
  var args = [this._wrapped];
  push.apply(args, arguments);
  return chainResult(this, func.apply(_, args));
};
//替换成chain
_.chain = function() {
  var args = [this._wrapped];
  push.apply(args, arguments);
  return chainResult(this, chain.apply(_, args));
};
//会造成死循环吗?  显然不会。
//但是,chain.apply(_, args) 本身就生成一个underscope实例。
//而chainResult中的方法, _(obj).chain()试图,再次封装,
//但是,由于前面的_方法已做判断,会直接返回该实例。

接下来,我们应该在浏览器中,直接console.log(_),来看其对象上、原型上的方法的绑定情况。结果输出的只是其构造函数。

函数原型

使用构造函数new出一个新对象,其原型上的方法,能直接获取到。

注意b.constructor.hello();b.test();调用区别。

(function(){
    var root = this;
    //构造函数、也是普通函数。
    var _ = function(obj){
        if(obj instanceof _ ){
            return obj;
        }
        //当函数使用时,this指向的是window
        if(!(this instanceof _)){
            return new _(obj);
        }
        this._wrapped = obj;
        return this;
    }

    _.hello = function(){
        console.log('hello world');
    };
    var b = _('some');
    _.prototype['test'] = function(){console.log('test');};
    console.log(b);
    // b.hello(); //b的原型上没有该方法,hello方法只绑定在构造方法上。
    b.constructor.hello();
    b.test(); //该方法能直接调用,因为绑定在原型上。_的方法只是该对象独享

}());

类的定义

ES6有class关键字来定义类。而underscope是如下来实现定义类的:

先看下面的一个空函数(Ctor可以看成是Constructor的缩写):

// Naked function reference for surrogate-prototype-swapping.
//一个空函数引用,用来做原型交换。
var Ctor = function(){};

再看构造函数的使用场景:

var nativeCreate = Object.create;

// An internal function for creating a new object that inherits from another.
var baseCreate = function(prototype) {
  if (!_.isObject(prototype)) return {};
  if (nativeCreate) return nativeCreate(prototype); //直接使用Object.create
  Ctor.prototype = prototype;
  var result = new Ctor;
  Ctor.prototype = null;
  return result;
};

来看_.create方法

// Creates an object that inherits from the given prototype object.
// If additional properties are provided then they will be added to the
// created object.
_.create = function(prototype, props) {
  var result = baseCreate(prototype);
  if (props) _.extendOwn(result, props);
  return result;
};

使用方法:

var moe = _.create(Stooge.prototype, {name: "Moe"});

其实也见过Ext源码,自己也实现了一套自己定义的方式。

引入其他的库noConflict

原理,首先从全局对象中获取到_变量,然后存储在一个变量中。然后在需要的时候,执行noConflict交还之前的变量。

// Establish the root object, `window` (`self`) in the browser, `global`
  // on the server, or `this` in some virtual machines. We use `self`
  // instead of `window` for `WebWorker` support.
  var root = typeof self == 'object' && self.self === self && self ||
            typeof global == 'object' && global.global === global && global ||
            this ||
            {};
  // Save the previous value of the `_` variable.
  
  var previousUnderscore = root._;
  
  
  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
  // previous owner. Returns a reference to the Underscore object.
  _.noConflict = function() {
    root._ = previousUnderscore;
    return this;
  };