首页 > 打造属于自己的underscore系列 ( 一 )

打造属于自己的underscore系列 ( 一 )

underscore作为开发中比较常用的一个javascript工具库,提供了一套丰富的函数式编程功能,该库并没有拓展原有的javascript原生对象,而是在自定义的_对象上,提供了100多个方法函数。在这个系列中,将从uderscore源码角度, 打造一个自己的underscore框架

一,框架设计

1.1 自执行函数

现代js 库的框架设计,一般都是以自执行函数的形式,自执行函数一般有两种形式

(function(){// 形式一
}())
(function(){// 形式二
})()

我们知道,函数声明的形式会挂载到window对象作为方法存在,而函数表达式的形式则会挂载在window对象作为属性存在,这都会造成变量污染,而自执行函数的好处在于可以防止变量的污染,函数执行完后便立刻销毁掉。

1.2 使用风格

underscore有两种风格形式可以使用,一种是面向对象类型,另一种是函数类型。

// 例子
_.map([1, 2, 3], function(n){ return n * 2; });
_([1, 2, 3]).map(function(n){ return n * 2; });

因此,在定义underscore类的时候需要考虑对象和函数两种场景。当以函数的形式调用时需要把 _ 当作一个构造函数并返回他的实例化。代码如下

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

1.3 使用环境

现在前端开发重视模块化,以node服务端而论, 我们有commonjs规范,以客户端而论,我们有AMD 和 CMD规范,对应的模块加载器为 requirejs 和 seajs。目前通行的javascript模块规范主要集中在commonjs 和 AMD,因此为了让定义的underscore库能够适用于各种规范。在框架的定义时需检测使用环境并兼容各种规范。

  • 服务端:commonjs规范,检测module.exports 是否存在,满足时通过 module.exports = {} 将 underscore暴露出来,不满足则 通过window对象暴露出来。
  • 客户端: AMD 规范, 检测 define.amd 是否存在,满足时通过 define('**', [], function(){ return '***' })暴露模块
(function (root) {var _ = function (obj) {if (!(this instanceof _)) {return new _(obj)}}// commonjs 规范 检测 module.exports 是否存在if ((typeof module !== 'undefined' && module.exports)) {module.exports = {_: _}} else {root._ = _;// window 对象暴露方法}// amd 规范,检测 define.amd 是否存在if (typeof define == 'function' && define.amd) {define('underscore', [], function () {return _;});}}(this))
1.3.1 服务端使用
// commonjs
const _ = require('./underscore.js')
console.log(_)
1.3.2 客户端使用
// AMD
require(['underscore'], function (underscore) {console.log(underscore)
})

1.4 方法定义

underscore的调用,既可以通过_.unique(),也可以通过 _().unique(),两种方法效果相同却需要在框架设计时定义两套方法,一套是定义 _ 对象的静态方法,另一套是扩展 _对象原型链上的方法。

_.uniqe = function() {}_.prototype.unique = function() {}

为了避免冗余代码,可以将定义好的静态方法复制一份成为原型链上的方法

(function(root){···_.mixins = function() {// 复制静态方法到原型上}_.mixins() // 执行方法
}(this))

mixins 方法的实现,需要遍历 underscore 对象上所有的静态方法,因此需要先完成对 遍历方法 _.each 的实现

1.41 _.each

_.each(list, iteratee, [context]) Alias: forEach

遍历list中的所有元素,按顺序用每个元素当做参数调用 iteratee 函数。如果传递了context参数,则把iteratee绑定到context对象上。每次调用iteratee都会传递三个参数:(element, index, list)。如果list是个JavaScript对象,iteratee的参数是 (value, key, list))。返回list以方便链式调用。

each 的第一个参数按照文档可以支持 数组,类数组,对象三种类型,数组类数组和对象在遍历时的处理方式不同。前者回调函数处理的是 值和下标,后者处理的是 值和属性。

// 判断数组,类数组方法
(function(root) {···_.each = function (list, callback, context) {// context 存在会改变callback 中this 的指向var i = 0;var key;if (isArrayLikeLike(list)) { //  数组,类数组for (var i = 0; i < list.length; i++) {context ? callback.call(context, list[i], i, list) : callback(list[i], i, list)}} else { // 对象for (key in list) {context ? callback.call(context, list[key], key) : callback(list[key], key)}}}var isArrayLike = function (collection) {// 返回参数 collection 的 length 属性值var length = collection.length;// length是数值,非负,且小于等于MAX_ARRAY_INDEX// MAX_ARRAY_INDEX = Math.pow(2, 53) - 1return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;}
}(this))
1.4.2 _.mixin

mixin方法的设计,目的是为了在underscore原型对象上扩展更多的方法,它既可以用来扩展用户自定义的方法,比如
_.mixin({capitalize: function(string) {return string.charAt(0).toUpperCase() + string.substring(1).toLowerCase();}
});
_("fabio").capitalize();
=> "Fabio"

当然也可以用来内部拷贝静态方法到原型链的方法上。

(function(root){···var push = Array.prototype.pushvar _ = function (obj) {if (!(this instanceof _)) {return new _(obj)}this.wrap = obj // 存储实例对象传过来的参数}_.mixins = function (obj) {_.each(obj, function (value, key) {_.prototype[key] = function () {var args = [this.wrap]push.apply(args, arguments)return value.apply(this, args)}})}_.mixins(_)
}(this))

其中关注点在arguments 的处理上,静态方法需要传递目标源作为方法的参数 即_.unique(目标源, 回调函数),而实例方法的目标源存储在构造对象的属性中 ,即_(目标源).unique(回调函数),因此定义实例方法时需要合并属性和回调函数。即Array.prorotype.push.apply([this.wrap], arguments),之后将他作为参数传递给静态方法并返回处理结果。

将类数组转成数组的方法

  • Array.prototype.slice.call(类数组)
  • var a = []; Array.prototype.push.apply(a, 类数组); console.log(a);
  • var a = []; Array.prototype.concat.apply(a, 类数组); console.log(a);
  • ES6方法 Array.from(类数组)
  • ES6扩展运算符 var args = [...类数组]

1.5 链式调用

1.5.1 _.chain()

返回一个封装的对象. 在封装的对象上调用方法会返回封装的对象本身, 直道 value 方法调用为止。

underscore中方法的调用返回的是处理后的值,因此无法支持方法的链式调用。如果需要链式调用,需要使用chain()方法,chain的使用会使每次调用方法后返回underscore的实例对象,直到 调用value方法才停止返回。

(function(root){···// chain方法会返回 _ 实例,并且标注该实例是否允许链式调用的_.chain = function(obj) {var instance = _(obj);instance.chain = true; return instance}// 增加是否支持链式调用的判断,如果支持,则返回该实例,不支持则直接返回结果,var chainResult = function (instance, obj) {return instance.chain ? _(obj).chain() : obj}_.mixins = function (obj) {_.each(obj, function (value, key) {_.prototype[key] = function () {var args = [this.wrap]push.apply(args, arguments)return chainResult(this, value.apply(this, args)) // 修改实例方法的返回值,返回值通过chainResult 包装,根据chainResult的判断结果改变返回值}})}
}(this))
1.5.2 value()

因为链式调用会使underscore的方法返回他的实例对象,所以当需要结束这一调用行为时,需要使用value()。 value()方法会返回调用的结果。

(function(root){···_.value = function(instance) {return instance.wrap}
}(this))
未完待续。。。

转载于:https://www.cnblogs.com/kidflash/p/10077643.html

更多相关:

  • 来源:公众号|计算机视觉工坊(系投稿)作者:仲夏夜之星「3D视觉工坊」技术交流群已经成立,目前大约有12000人,方向主要涉及3D视觉、CV&深度学习、SLAM、三维重建、点云后处理、自动驾驶、CV入门、三维测量、VR/AR、3D人脸识别、医疗影像、缺陷检测、行人重识别、目标跟踪、视觉产品落地、视觉竞赛、车牌识别、硬件选型、学术交流、...

  • 点云PCL免费知识星球,点云论文速读。文章:Real-Time LIDAR-Based Urban Road and Sidewalk Detection for Autonomous Vehicles作者:Ern˝o Horváth  , Claudiu Pozna ,and Miklós Unger编译:点云PCL代码:http...

  • 文章:Semantic Histogram Based Graph Matching for Real-Time Multi-Robot Global Localization in Large Scale Environment作者:Xiyue Guo, Junjie Hu, Junfeng Chen, Fuqin Deng, T...

  • 点云PCL免费知识星球,点云论文速读。文章:Robust Place Recognition using an Imaging Lidar作者:Tixiao Shan, Brendan Englot, Fabio Duarte, Carlo Ratti, and Daniela Rus编译:点云PCL(ICRA 2021)开源代码:...

  • 文章:A Survey of Calibration Methods for Optical See-Through Head-Mounted Displays作者:Jens Grubert , Yuta Itoh, Kenneth Moser编译:点云PCL本文仅做学术分享,如有侵权,请联系删除。欢迎各位加入免费知识星球,获取PD...

  •   /*禁止缩放safari浏览器*/ var scale = {disabledSafari: function () {/* 阻止双击放大*/var lastTouchEnd = 0;document.addEventListener("touchstart", function (event) {if (event.touch...

  •   $g.$utils = {/**舒工Ajax-lite 1.0 -- 最精简的ajax自定义访问方法*/ajax: function (o) {var p = o.post, g = o.get, d = p.data, a = p.async, J = 'json', j = p[J], s = g.success, e =...

  •   Sg.js框架核心概念: 1)所有变量、方法、类对象全部都是从属于$g主树,由$g分支出很多$g.变量名、$g.方法、$g.对象id、$g.类;2)获取控件内部属性必须使用公开的get方法获取,禁止直接用访问内部变量方式来获取控件内部变量、属性值;3)修改控件内部属性、绑定方法等都必须使用公开的set方法来操作,禁止直接用访问...

  •  一、ios header导航栏被推起解决方法 1 设置弹出软键盘时自动改变webview的高度 plus.webview.currentWebview().setStyle({ softinputMode: "adjustResize" // 弹出软键盘时自动改变webview的高度 }); 2 增加样式 html...

  • 前端发送Ajax请求到服务器,服务器返回数据这一过程,因原因不同耗时长短也有差别,且这段时间内页面显示空白。如何优化这段时间内的交互体验,以及长时间内服务器仍未返回数据这一问题,是我们开发中不容忽视的重点。 常见的做法是: 1、设置超时时间,一旦时间超过设定值,便终止请求;2、页面内容加载之前,手动增加一个 loading 层。 代码...

  • 点云PCL免费知识星球,点云论文速读。文章:DSP-SLAM: Object Oriented SLAM with Deep Shape Priors作者:Jingwen Wang Martin Runz Lourdes Agapito编译:点云PCL代码:https://github.com/JingwenWang95/DSP-S...

  • RAM缓存 新RAM缓存算法(CLFUS) 新的RAM缓存使用的创意来自许多缓存替换策略和算法,包括LRU,LFU,CLOCK,GDFS及2Q,它被命名为时钟周期内最小频繁使用大小算法CLFUS(Clocked Least Frequently Used by Size)。它避开了任何专利算法,具有如下特性: 均衡最近性(Rec...

  • MP4 |视频:AVC,1280×720 30 fps |音频:AAC,48 KHz,2 Ch |时长:2h 12m 语言:英语+中英文字幕(根据原英文字幕机译更准确)|大小解压后:560M C4D是一个有抱负的运动图形艺术家和设计师的重要工具。借助C4D,您可以使用3D对象、动态效果和动画来增强运动图形、模型和可视化效果。本课...

  • 文章目录先说问题:再说解决尝试1:尝试2(该尝试建议先在自己环境搭配对应业务测试通过后再现场尝试): 感谢 学无止境996同学的陪伴和vigourtyy美丽女友的支持,直到这个解决问题的深夜 先说问题: ceph 12.2.1生产环境:3副本 tier + 3副本data 机房在拥有业务的情况下重启集群交换机,产生如下场景...

  • 这周主要学习了java中的类和对象的知识点,发现和C++中的类和对象极为相似,对于类和对象的概念理解起来也简单。同时在自学的过程中也把类的知识重新复习巩固了一下(如类的三大特征:继承,封装和多态,构造,成员对象的访问权限,构造,无参有参函数的调用等),同时也了解到一些新的概念,比如类对象创建和引用占据堆内存和栈内存,输出对象时默认调...