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;
};