内容简介:underscore 是一款成熟可靠的第三方开源库,正如 jQuery 统一了不同浏览器之间的 DOM 操作的差异,让我们可以简单地对 DOM 进行操作,underscore 则提供了一套完善的函数式编程的接口,让我们更方便地在 JavaScript 中实现函数式编程。jQuery 在加载时,会把自身绑定到唯一的全局变量在搭建 underscore 之前,让我们先来了解一下什么是 “立即执行函数(IIFE)”.
underscore 是一款成熟可靠的第三方开源库,正如 jQuery 统一了不同浏览器之间的 DOM 操作的差异,让我们可以简单地对 DOM 进行操作,underscore 则提供了一套完善的函数式编程的接口,让我们更方便地在 JavaScript 中实现函数式编程。
jQuery 在加载时,会把自身绑定到唯一的全局变量 $
上,underscore 与其类似,会把自身绑定到唯一的全局变量 _
上,这也是为啥它的名字叫 underscore 的原因。
在搭建 underscore 之前,让我们先来了解一下什么是 “立即执行函数(IIFE)”.
2. 立即执行函数(IIFE)
立即执行函数,顾名思义,就是定义好的匿名函数立即执行,写法如下:
(function(name) { console.log(name); })('suporka'); 复制代码
其作用是: 通过定义一个匿名函数,创建了一个新的函数作用域,相当于创建了一个“私有”的命名空间,该命名空间的变量和方法,不会破坏污染全局的命名空间。
// 函数外部拿不到内部的变量,因此不会造成变量污染,内部的变量在内部使用即可 (function() { var name = 'suporka'; })(); console.log(name); // name is undefinded 复制代码
3. 全局变量 _
的挂载
当我们在浏览器中使用 _.map([1,2,3], function(item){console.log(item)})
时, _
是挂载在 Window
对象上的,如果我们想在 node 环境中使用呢 ?
(function() { // root 为挂载对象,为 self 或 global 或 this 或 {} var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global) || this || {}; // _ 应该是一个对象,对象内有属性函数 var _ = {}; root._ = _; _.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧 })(); 复制代码
4. 函数式风格 && 面向对象风格的双重实现
首先我们实现一个倒装字符串的方法
(function() { // root 为挂载对象,为 self 或 global 或 this 或 {} var root = (typeof self == 'object' && self.self === self && self) || (typeof global == 'object' && global.global === global && global) || this || {}; // _ 应该是一个对象,对象内有属性函数 var _ = {}; root._ = _; _.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧 /** * 字符串倒装 */ _.reverse = function(string) { return string .split('') .reverse() .join(''); }; })(); _.reverse('suporka'); // akropus 复制代码
不错,很快实现,但是这种是函数式写法,调用一个函数去实现,如果我们要实现面向对象写法呢?如 _('suporka').reverse()
! underscore 是支持这种写法的,仔细观察 _('suporka')
, 你会发现, _
是一个函数啊,和我们前面定义的 var _ = {};
不一致,那么该怎么实现呢?
实例原型
我们先测试一下:如果 _
为函数,我们需要保存其传进来的参数 obj . new _() 生成一个实例原型对象
function _(obj) { this._wrapped = obj; } _.reverse = function(string) { return string .split('') .reverse() .join(''); }; _.reverse('suporka'); // "akropus", 函数式调用没问题 new _('suporka'); 复制代码
从图中我们可以看出,实例原型对象的 __proto__
(原型)的 constructor 构造函数指回了原来的 _(obj) 函数,要调用其 reverse() 方法只能 new _('suporka').constructor.reverse()
多了一个层级,不符合我们原本的期望。那我们不如在 _proto_
属性下增加一个和 reverse 一样的函数,这样不就可以直接调用了吗?
let us try it !
function _(obj) { this._wrapped = obj; } _.reverse = function(string) { return string .split('') .reverse() .join(''); }; _.reverse('suporka'); // "akropus", 函数式调用没问题 _.prototype.reverse = function() { return this._wrapped .split('') .reverse() .join(''); }; new _('suporka').reverse(); // "akropus", 面向对象式调用没问题 复制代码
5. 改造 _() function
new _('suporka').reverse()
有点累赘,去掉 new
, 重写 function _()
var _ = function(obj) { // 如果传入的是实例后对象,返回它 if (obj instanceof _) return obj; // 如果还没有实例化,new _(obj) if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; _('suporka').reverse(); // "akropus", 面向对象式调用没问题 复制代码
6. 写一个迭代函数 map()
/** * 数组或对象遍历方法,并返回修改后的对象或数组 * @param iteratee 回调函数 * @param context 回调函数中this的指向 */ _.map = function(obj, iteratee, context) { var length = obj.length, results = Array(length); for (var index = 0; index < length; index++) { results[index] = iteratee.call(context, obj[index], index, obj); } return results; }; _.prototype.map = function(iteratee, context) { var length = this._wrapped.length, results = Array(length); for (var index = 0; index < length; index++) { results[index] = iteratee.call( context, this._wrapped[index], index, this._wrapped ); } return results; }; _([1, 2, 3]).map( function(item) { console.log(item + this.value); }, { value: 1 } ); // 2,3,4 _.map( [1, 2, 3], function(item) { console.log(item + this.value); }, { value: 1 } ); // 2,3,4 复制代码
嗯嗯,真好,完美实现。到这里你会发现一个问题,每次我新增一个方法,都得在 prototype
上同时写多一次这个相似函数,你会发现两者之间只是 obj
换成了 this._wrapped
.有没有办法让它自动生成呢?答案肯定是有!
7. 自动创建原型方法
在这之前,我们需要先实现一个遍历方法 each(),如下:
// 最大数值 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; // 判断是否为数组 var isArrayLike = function(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++) { // 这里隐式的调用了一次 callback.call(obj[i], obj[i], i); 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; }; 复制代码
用 each() 来遍历 _
上挂载的所有方法函数,并给 prototype 创建相应的方法函数。那么,在此之前,我们需要知道 _
上挂载了哪些方法名,来写个 functions() 实现它
/** * 判断是否为 function */ _.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(); }; 复制代码
用 each()实现它:
var ArrayProto = Array.prototype; var push = ArrayProto.push; _.each(_.functions(_), function(name) { var func = _[name]; _.prototype[name] = function() { var args = [this._wrapped]; // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现 push.apply(args, arguments); return func.apply(_, args); }; }); 复制代码
7. 当前最终代码
(function() { // root 为挂载对象,为 self 或 global 或 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; // 如果还没有实例化,new _(obj) if (!(this instanceof _)) return new _(obj); this._wrapped = obj; }; // 最大数值 var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1; var ArrayProto = Array.prototype; var push = ArrayProto.push; // 判断是否为数组 var isArrayLike = function(collection) { var length = collection.length; return ( typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX ); }; root._ = _; _.VERSION = '1.9.1'; // 给我们的 underscore 一个版本号吧 /** * 字符串倒装 */ _.reverse = function(string) { return string .split('') .reverse() .join(''); }; /** * 判断是否为 function */ _.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(); }; /** * 数组或对象遍历方法,并返回修改后的对象或数组 * @param iteratee 回调函数 * @param context 回调函数中this的指向 */ _.map = function(obj, iteratee, context) { var length = obj.length, results = Array(length); for (var index = 0; index < length; index++) { results[index] = iteratee.call(context, obj[index], index, obj); } return results; }; /** * 数组或对象遍历方法 */ _.each = function(obj, callback) { var length, i = 0; if (isArrayLike(obj)) { // 数组 length = obj.length; for (; i < length; i++) { // 这里隐式的调用了一次 callback.call(obj[i], obj[i], i); 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; }; _.each(_.functions(_), function(name) { var func = _[name]; _.prototype[name] = function() { var args = [this._wrapped]; // args = [this._wrapped, arguments[0], arguments[1]...], 相当于用 this._wrapped 代替 obj 实现 push.apply(args, arguments); return func.apply(_, args); }; }); })(); 复制代码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。