前言

underscore虽然有点过时,这些年要慢慢被Lodash给淘汰或合并。

但通过看它的源码,还是能学到一个库的封装和扩展方式。

第一步,不污染全局环境。

ES5中的JS作用域是函数作用域。

函数内部可以直接读取全局变量,当然函数外部无法读取函数内的局部变量。

所以,我们在匿名函数里啪啪啪写代码,妈妈再也不会担心修改到全局变量。

(funtion(){

    var _ = function(obj) {
return new wrapper(obj);
}; var wrapper = function(obj) {
this._wrapped = obj;
}; window._ = _;
})()

第二步,扩展实例方法

首先,我们要知道,

声明在_.prototype的方法是专门给_实例用。

声明在wrapper.prototype的方法是给wrapper方法实例用。

underscore的_方法是一个工厂方法,_方法返回的是私有wrapper方法实例。

那么如何把_的静态方法赋予给wrapper方法实例?且看以下代码。

(function(){

    var _ = function(obj) {
return new wrapper(obj);
}; var wrapper = function(obj) {
this._wrapped = obj;
}; var result = function(obj) {
return obj;
}; var ArrayProto = Array.prototype,
forEach = ArrayProto.forEach,
push = ArrayProto.push;
_.each = forEach; _.type = function(obj){
return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
} _.isFunction = function(fn){
return (_.type(fn) == "function");
} _.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
} _.mixin = function(obj) {
forEach.call(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result( func.apply(_, args));
};
});
}; // _.prototype 指向 wrapper.prototype
_.prototype = wrapper.prototype;
// 修复_实例的原型链
_.prototype.constructor = _;
// 这里通过mixin方法把_的静态方法,赋值给wrapper实例
_.mixin(_); window._ = _;
})();

测试代码

var str = _("str");
str.type(); //"String"
str instanceof _; //true

第三步,支持链式调用。

链式调用使得操作同一个对象时非常方便。

实现的思路是,重新包装调用的函数,缓存函数调用结果,使其返回的值是wrapper方法实例。

(function(){
var _ = function(obj) {
return new wrapper(obj);
}; var wrapper = function(obj) {
this._wrapped = obj;
}; // 链式包装函数
var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
}; // 触发可链式函数
wrapper.prototype.chain = function() {
// this._chain用来标示当前对象是否使用链式操作
this._chain = true;
return this;
}; // 当触发可链式后,用这个来取结果值
wrapper.prototype.value = function() {
return this._wrapped;
};
var ArrayProto = Array.prototype,
forEach = ArrayProto.forEach; // 这些数组方法需要包装以下才可以链式调用
forEach.call(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
// 调用Array对应的方法并返回结果
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) {
delete wrapped[0];
}
return result(wrapped, this._chain);
};
}); // 这些数组方法本身可链式调用
forEach.call(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
}); window._ = _; })();

测试代码

var a =_([1, 2]).chain().push(3).push(4).push(5);
a.value(); // [1, 2, 3, 4, 5]
[1,2].push(3).push(4).push(5); // Uncaught TypeError: [1,2].push(...).push is not a function(…)

第四步,模块化支持。

ES6 Modules之前,UMD很盛行,我们要支持。

(function(){
var _ = function(obj) {
return new wrapper(obj);
}; var wrapper = function(obj) {
this._wrapped = obj;
}; if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
} else if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
window['_'] = _;
}
)();

第五步,综合代码

(function() {

    var _ = function(obj) {
return new wrapper(obj);
}; if (typeof define === 'function' && define.amd) {
define('underscore', [], function() {
return _;
});
} else if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = _;
}
exports._ = _;
} else {
window['_'] = _;
} var wrapper = function(obj) {
this._wrapped = obj;
}; var result = function(obj, chain) {
return chain ? _(obj).chain() : obj;
}; wrapper.prototype.chain = function() {
this._chain = true;
return this;
}; wrapper.prototype.value = function() {
return this._wrapped;
}; var ArrayProto = Array.prototype,
forEach = ArrayProto.forEach,
push = ArrayProto.push;
_.each = forEach; _.type = function(obj){
return Object.prototype.toString.call(obj).slice(8,-1).toLowerCase();
} _.isFunction = function(fn){
return (_.type(fn) == "function");
} _.functions = function(obj) {
var names = [];
for (var key in obj) {
if (_.isFunction(obj[key])) names.push(key);
}
return names.sort();
} _.mixin = function(obj) {
forEach.call(_.functions(obj), function(name) {
var func = _[name] = obj[name];
_.prototype[name] = function() {
var args = [this._wrapped];
push.apply(args, arguments);
return result( func.apply(_, args),this._chain);
};
});
}; _.prototype = wrapper.prototype;
_.prototype.constructor = _;
_.mixin(_); forEach.call(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { var method = ArrayProto[name];
wrapper.prototype[name] = function() {
var wrapped = this._wrapped;
method.apply(wrapped, arguments);
var length = wrapped.length;
if ((name == 'shift' || name == 'splice') && length === 0) {
delete wrapped[0];
}
return result(wrapped, this._chain);
};
}); forEach.call(['concat', 'join', 'slice'], function(name) {
var method = ArrayProto[name];
wrapper.prototype[name] = function() {
return result(method.apply(this._wrapped, arguments), this._chain);
};
}); })();

一步一步学习underscore的封装和扩展方式的更多相关文章

  1. underscore的封装和扩展

    // 1. 不污染全局环境 (function() { // 2. 保留之前同名变量 var previousUnderscore = window._; var _ = function(obj) ...

  2. jQuery的封装和扩展方式

    <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...

  3. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper

    前言:之前学习过很多的Bootstrap组件,博主就在脑海里构思:是否可以封装一套自己Bootstrap组件库呢.再加上看到MVC的Razor语法里面直接通过后台方法输出前端控件的方式,于是打算仿照H ...

  4. 一步一步学习SignalR进行实时通信_5_Hub

    原文:一步一步学习SignalR进行实时通信_5_Hub 一步一步学习SignalR进行实时通信\_5_Hub SignalR 一步一步学习SignalR进行实时通信_5_Hub 前言 Hub命名规则 ...

  5. 一步一步学习Vue(十一)

    本篇继续学习vuex,还是以实例为主:我们以一步一步学Vue(四)中讲述的例子为基础,对其改造,基于vuex重构一遍,这是原始的代码: todolist.js ; (function () { var ...

  6. C#进阶系列——一步一步封装自己的HtmlHelper组件:BootstrapHelper(三:附源码)

    前言:之前的两篇封装了一些基础的表单组件,这篇继续来封装几个基于bootstrap的其他组件.和上篇不同的是,这篇的有几个组件需要某些js文件的支持. 本文原创地址:http://www.cnblog ...

  7. 12.Linux软件安装 (一步一步学习大数据系列之 Linux)

    1.如何上传安装包到服务器 有三种方式: 1.1使用图形化工具,如: filezilla 如何使用FileZilla上传和下载文件 1.2使用 sftp 工具: 在 windows下使用CRT 软件 ...

  8. (转) 一步一步学习ASP.NET 5 (四)- ASP.NET MVC 6四大特性

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助.原文:http://blog.csdn.net/kinfey/article/details/44459625 编者语 : 昨晚写好的文章居然csd ...

  9. (转) 一步一步学习ASP.NET 5 (二)- 通过命令行和sublime创建项目

    转发:微软MVP 卢建晖 的文章,希望对大家有帮助. 注:昨天转发之后很多朋友指出了vNext的命名问题,原文作者已经做出了修改,后面的标题都适用 asp.net 5这个名称. 编者语 : 昨天发了第 ...

随机推荐

  1. Algorithm | Tree traversal

    There are three types of depth-first traversal: pre-order,in-order, and post-order. For a binary tre ...

  2. 【iCore3双核心板】【4.3寸液晶驱动板爆照!】

     [源代码完全开源,过几天连同硬件一起发布] 花了好久的时间,我们的fpga工程师才完成这液晶模块的驱动代码,其核心价值如下: 1.完全基于fpga驱动,sdram当做缓存: 2.内建双缓冲机制:方便 ...

  3. asp.net identity 2.2.0 在WebForm下的角色启用和基本使用(一)

    基本环境:asp.net 4.5.2 仔细看了在Webform下,模板就已经启动了角色控制,已经不用再进行设置了.直接调用相关类就可以了.这和原来在网站根目录下配置Web.config完全不同了. 相 ...

  4. Eclipse创建java web工程配置Tomacat和JDK 【转】

    在学习AJAX过程中,还用Intellij就有点老旧了,这是后装个Eclipse时,发现这个配置也很头疼,现在就叫你如何创建一个web工程,同时叫你配置Eclipse. 一.创建一个web工程 1.打 ...

  5. EntityFramework SQLiteCodeFirst 自动创建数据库 关闭级联删除

    外键的级联删除: 如A表中有主键idA, B表中设置外键(ForeignKey)为A表中的主键idA, 当A表中的记录被删除时, B表中所有引用此条记录的记录(即所有外键为idA的记录)将自动被删除 ...

  6. mysql笔记(存储引擎)

    读写锁:. 表级锁:开销小,加锁快:不会出现死锁:锁定粒度大,发生锁冲突的概率最高,并发度最低. 行级锁:开销大,加锁慢:会出现死锁:锁定粒度最小,发生锁冲突的概率最低,并发度也最高. 页面锁:开销和 ...

  7. C/C++面试

    1. RTTL 运行时类别识别,在只有一个指向基类的指针或引用时确定一个对象的准确类型. 2.什么是拷贝构造函数 是单个形参的构造函数,实参是与该类同属一类的对象:在类定义中,如果未提供自己的拷贝构造 ...

  8. php命名、注释规范

    一.注释 1.文件头部模板 /** *这是一个什么文件 * *此文件程序用来做什么的(详细说明,可选.). * @author richard<e421083458@163.com> * ...

  9. autoreleasepool自动释放池

     示例: @autoreleasepool { ; i[largeNumber; i++) { (因识别问题,该行代码中尖括号改为方括号代替) Person *per = [[Person alloc ...

  10. 调用java rest ful 接口实例

    HttpWebRequest request = WebRequest.Create("http://192.168.0.99:8080/wzh-webservice/rest/login? ...