摘要:值得注意的是,如果值在前面也就是值小于值,那么值域会被认为是零长度,而不是负增长。
underscore.js源码加注释一共1500多行,它提供了一整套函数式编程实用的功能,一共一百多个函数,几乎每一个函数都可以作为参考典范。初读的时候,真是一脸懵圈,各种函数闭包、迭代和嵌套的使用,让我一时很难消化。
在这里,我来记录一下我学习underscore.js的一些发现,以及几个我认为比较经典的函数使用。
首先我们可以看到,underscore.js中所有的函数和方法都在一个闭包里:(function() {...}.call(this));这么做的目的是为了避免污染全局变量。
为了压缩代码,underscore中用到将原型赋值给变量保存的方法:
// Save bytes in the minified (but not gzipped) version: // 原型赋值,便于压缩代码,这里的压缩指压缩到min.js而不是gzip压缩 var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; // Create quick reference variables for speed access to core prototypes. // 将内置对象原型中的常用方法赋值给引用变量,减少在原型链中的查找次数,从而提高代码效率 var push = ArrayProto.push, slice = ArrayProto.slice, toString = ObjProto.toString, hasOwnProperty = ObjProto.hasOwnProperty;
我们在处理代码时,不能直接将Array.prototype等直接压缩,因为压缩过后,浏览器是无法识别这些压缩字段的。压缩过后,我们在使用obj.prototype方法时,直接使用其相对应的变量就可以了。如果在我们的代码中会多次用到某个方法,用上面的方法就进行处理,使用起来就方便多了。
接着创建了一个"_"对象,之后将underscore中的相关方法添加到"_"原型中,那么创建的"_"对象也就具备了underscore方法。
// 创建一个"_"对象 var _ = function(obj) { if (obj instanceof _) return obj; //如果obj是"—"的实例,则直接返回obj if (!(this instanceof _)) return new _(obj); //如果不是,则调用new运算符,返回实例化的对象 this._wrapped = obj; //将underscore对象存放在_.wrapped属性中 };
上面用到了instanceof运算符,JavaScript中instanceof运算符是返回一个 Boolean 值,指出对象是否是特定类的一个实例。
使用方法:result = object instanceof class
其中,result是必选项,表任意变量;object是必选项,表任意对象表达式;class是必选项,表任意已定义的对象类。如果 object 是 class 的一个实例,则 instanceof 运算符返回 true。如果 object 不是指定类的一个实例,或者 object 是 null,则返回 false。
接下来我就列举几个underscore中的函数。
1、_.each
_.each = _.forEach = function(obj, iteratee, context) { iteratee = optimizeCb(iteratee, context); // 根据 context 确定不同的迭代函数 var i, length; if (isArrayLike(obj)) { // 如果是类数组 (默认不会传入类似 {length: 10} 这样的数据) for (i = 0, length = obj.length; i < length; i++) { //遍历 iteratee(obj[i], i, obj); } } else { // 如果 obj 是对象 var keys = _.keys(obj); // 获取对象的所有 key 值 for (i = 0, length = keys.length; i < length; i++) { //如果是对象,则遍历处理 values 值 iteratee(obj[keys[i]], keys[i], obj); } } return obj; //返回 obj 参数,供链式调用(Returns the list for chaining) }; _.each = _.forEach = function(obj, iteratee, context)中,一共有三个参数: 第一个参数为数组(包括类数组)或者对象;第二个参数为迭代方法,对数组或者对象每个元素都执行 该方法,该方法又能传入三个参数,分别为 (item, index, array)((value, key, obj) for object); 第三个参数(可省略)确定第二个参数 iteratee 函数中的(可能有的)this 指向, 即 iteratee 中出现的(如果有)所有 this 都指向 context。
2、_.contains
_.contains = _.includes = _.include = function(obj, item, fromIndex, guard) { if (!isArrayLike(obj)) obj = _.values(obj); // // 如果是对象,返回 values 组成的数组 //fromIndex 表示查询起始位置,如果没有指定该参数,则默认从头找起 if (typeof fromIndex != "number" || guard) fromIndex = 0; //_.indexOf 是数组的扩展方法(Array Functions) //数组中寻找某一元素 return _.indexOf(obj, item, fromIndex) >= 0; };
判断数组或者对象中(value 值)是否有指定元素,如果是 object,则忽略 key 值,只需要查找 value 值即可,如果obj 中是否有指定的 value 值,则返回布尔值。
3、 _.uniq
_.uniq = _.unique = function(array, isSorted, iteratee, context) { if (!_.isBoolean(isSorted)) { // 没有传入 isSorted 参数 context = iteratee; iteratee = isSorted; isSorted = false; // 转为 _.unique(array, false, undefined, iteratee) } // 如果有迭代函数,则根据 this 指向二次返回新的迭代函数 if (iteratee != null) iteratee = cb(iteratee, context); var result = []; // 结果数组,是 array 的子集 var seen = []; //// 已经出现过的元素(或者经过迭代过的值),用来过滤重复值 for (var i = 0, length = getLength(array); i < length; i++) { var value = array[i], //如果指定了迭代函数,则对数组每一个元素进行迭代 //迭代函数传入的三个参数通常是 value, index, array 形式 computed = iteratee ? iteratee(value, i, array) : value; //如果是有序数组,则当前元素只需跟上一个元素对比即可,并用 seen 变量保存上一个元素 if (isSorted) { //如果 i === 0,是第一个元素,则直接 push,否则比较当前元素是否和前一个元素相等 if (!i || seen !== computed) result.push(value); seen = computed; // seen 保存当前元素,供下一次对比 } else if (iteratee) { if (!_.contains(seen, computed)) { //// 如果 seen[] 中没有 computed 这个元素值 seen.push(computed); result.push(value); } } else if (!_.contains(result, value)) { // 如果不用经过迭代函数计算,也就不用 seen[] 变量了 result.push(value); } } return result; };
这是一个数组去重函数,如果函数参数中第二个参数 isSorted 为 true,则说明事先已经知道数组有序,程序会跑一个更快的算法;如果有第三个参数 iteratee,则对数组每个元素迭代,对迭代之后的结果进行去重,然后返回去重后的数组(array 的子数组);另外,暴露的 API 中没 context 参数。
4、_.range
// 返回某一个范围内的数组成的数组 _.range = function(start, stop, step) { if (stop == null) { stop = start || 0; start = 0; } step = step || 1; // 返回数组的长度 var length = Math.max(Math.ceil((stop - start) / step), 0); var range = Array(length); // 返回的数组 for (var idx = 0; idx < length; idx++, start += step) { range[idx] = start; } return range; };
_.range([start], stop, [step]) 是一个用来创建整数灵活编号的列表的函数,便于each 和 map循环。如果省略start则默认为 0;step 默认为 1.返回一个从start 到stop的整数的列表,用step来增加 (或减少)独占。值得注意的是,如果stop值在start前面(也就是stop值小于start值),那么值域会被认为是零长度,而不是负增长。-如果要一个负数的值域 ,则使用负数step. (参考http://www.css88.com/doc/unde...)
5、_.bind
_.bind = function(func, context) { if (nativeBind && func.bind === nativeBind) // 如果浏览器支持 ES5 bind 方法,并且 func 上的 bind 方法没有被重写,则优先使用原生的 bind 方法 return nativeBind.apply(func, slice.call(arguments, 1)); if (!_.isFunction(func)) //如果传入的参数 func 不是方法,则抛出错误 throw new TypeError("Bind must be called on a function"); //经典闭包,函数返回函数 var args = slice.call(arguments, 2); // args 获取优先使用的参数 var bound = function() { //最终函数的实际调用参数由两部分组成 //一部分是传入 _.bind 的参数(会被优先调用),另一部分是传入 bound(_.bind 所返回方法)的参数 return executeBound(func, bound, context, this, args.concat(slice.call(arguments))); }; return bound; };
该方法是ES5 bind 方法的扩展, 将 func 中的 this 指向 context(对象),用法为_.bind(function, object, *arguments),其中arguments 参数可选,它会被当作 func 的参数传入,func 在调用时,会优先用 arguments 参数,然后使用 _.bind 返回方法所传入的参数。
6、 _.memoize
_.memoize = function(func, hasher) { var memoize = function(key) { // 储存变量,方便使用 var cache = memoize.cache; //求 key //如果传入了 hasher,则用 hasher 函数来计算 key,否则用 参数 key(即 memoize 方法传入的第一个参数)当 key var address = "" + (hasher ? hasher.apply(this, arguments) : key); //如果这个 key 还没被 hash 过(还没求过值) if (!_.has(cache, address)) cache[address] = func.apply(this, arguments); return cache[address]; }; memoize.cache = {}; // cache 对象被当做 key-value 键值对缓存中间运算结果 return memoize; // 返回一个函数(经典闭包) };
Memoizes方法可以缓存某函数的计算结果,用法:_.memoize(function, [hashFunction])
如果传递了 hashFunction 参数,就用 hashFunction 的返回值作为key存储函数的计算结果。hashFunction 默认使用function的第一个参数作为key。memoized值的缓存可作为返回函数的cache属性。
7、_.throttle
_.throttle = function(func, wait, options) { var context, args, result; var timeout = null; //标记时间戳,上一次执行回调的时间戳 var previous = 0; if (!options) //如果没有传入 options 参数 options = {}; // 则将 options 参数置为空对象 var later = function() { //如果 options.leading === false,则每次触发回调后将 previous 置为 0,否则置为当前时间戳 previous = options.leading === false ? 0 : _.now(); timeout = null; result = func.apply(context, args); if (!timeout) context = args = null; }; // _.throttle 方法返回的函数 return function() { var now = _.now(); // 记录当前时间戳 //第一次执行回调(此时 previous 为 0,之后 previous 值为上一次时间戳) //并且如果程序设定第一个回调不是立即执行的(options.leading === false),则将 previous 值(表示上次执行的时间戳)设为 now 的时间戳(第一次触发时),表示刚执行过,这次就不用执行了 if (!previous && options.leading === false) previous = now; // 距离下次触发 func 还需要等待的时间 var remaining = wait - (now - previous); context = this; args = arguments; if (remaining <= 0 || remaining > wait) { if (timeout) { clearTimeout(timeout); // 解除引用,防止内存泄露 timeout = null; } // 重置前一次触发的时间戳 previous = now; // 触发方法,result 为该方法返回值 result = func.apply(context, args); if (!timeout) context = args = null; //引用置为空,防止内存泄露 } else if (!timeout && options.trailing !== false) {// 最后一次需要触发的情况 //如果已经存在一个定时器,则不会进入该 if 分支 // 如果 {trailing: false},即最后一次不需要触发了,也不会进入这个分支 timeout = setTimeout(later, remaining); // 间隔 remaining milliseconds 后触发 later 方法 } return result; // 回调返回值 }; };
函数节流(如果有连续事件响应,则每间隔一定时间段触发),每间隔 wait(Number) milliseconds 触发一次 func 方法,如果 options 参数传入 {leading: false},不会马上触发(等待 wait milliseconds 后第一次触发 func),如果 options 参数传入 {trailing: false},那么最后一次回调不会被触发。options 不能同时设置 leading 和 trailing 为 false。
8、_.debounce
_.debounce = function(func, wait, immediate) { var timeout, args, context, timestamp, result; var later = function() { //定时器设置的回调 later 方法的触发时间,和连续事件触发的最后一次时间戳的间隔 var last = _.now() - timestamp; //如果间隔为 wait(或者刚好大于 wait),则触发事件 if (last < wait && last >= 0) { //时间间隔 last 在 [0, wait) 中,还没到触发的点,则继续设置定时器 timeout = setTimeout(later, wait - last); } else { //到了可以触发的时间点 timeout = null; // 如果不是立即执行,随即执行 func 方法 if (!immediate) { result = func.apply(context, args); // 执行 func 函数 if (!timeout) context = args = null; } } }; return function() { context = this; args = arguments; //每次触发函数,更新时间戳 timestamp = _.now(); // 立即触发需要满足两个条件 // immediate 参数为 true,并且 timeout 还没设置 var callNow = immediate && !timeout; // 设置 wait seconds 后触发 later 方法 // 在某一段的连续触发中,只会在第一次触发时进入这个 if 分支中 if (!timeout) // 设置了 timeout,所以以后不会进入这个 if 分支了 timeout = setTimeout(later, wait); // 如果是立即触发 if (callNow) { result = func.apply(context, args); context = args = null; // 解除引用 } return result; }; };
函数去抖(连续事件触发结束后只触发一次),如_.debounce(function(){}, 1000)表示连续事件结束后的 1000ms 后触发。
9、 _.pick
_.pick = function(object, oiteratee, context) { var result = {}, // result 为返回的对象副本 obj = object, iteratee, keys; if (obj == null) return result; // 如果第二个参数是函数 if (_.isFunction(oiteratee)) { keys = _.allKeys(obj); iteratee = optimizeCb(oiteratee, context); } else { // 如果第二个参数不是函数 // 则后面的 keys 可能是数组 // 也可能是连续的几个并列的参数 // 用 flatten 将它们展开 keys = flatten(arguments, false, false, 1); //也转为 predicate 函数判断形式,将指定 key 转化为 predicate 函数 iteratee = function(value, key, obj) { return key in obj; }; obj = Object(obj); } for (var i = 0, length = keys.length; i < length; i++) { var key = keys[i]; var value = obj[key]; if (iteratee(value, key, obj)) result[key] = value; } return result; }; _.pick(object, *keys)根据一定的需求(key 值,或者通过 predicate 函数返回真假),返回拥有一定键值对的对象副本。第二个参数可以是一个 predicate 函数,也可以是0个或多个key。
10、_.noConflict
_.noConflict = function() { root._ = previousUnderscore; return this; };
如果全局环境中已经使用了 _ 变量,可以用该方法返回其他变量,继续使用 underscore 中的方法。
11、_.template
_.template = function(text, settings, oldSettings) { // 兼容旧版本 if (!settings && oldSettings) settings = oldSettings; settings = _.defaults({}, settings, _.templateSettings); // Combine delimiters into one regular expression via alternation. // // 正则表达式 pattern,用于正则匹配 text 字符串中的模板字符串 var matcher = RegExp([ (settings.escape || noMatch).source, (settings.interpolate || noMatch).source, (settings.evaluate || noMatch).source ].join("|") + "|$", "g"); // Compile the template source, escaping string literals appropriately. // 编译模板字符串,将原始的模板字符串替换成函数字符串 // 用拼接成的函数字符串生成函数(new Function(...)) var index = 0; // source 变量拼接的字符串用来生成函数,用于当做 new Function 生成函数时的函数字符串变量 var source = "__p+=""; // replace 函数不需要为返回值赋值,主要是为了在函数内对 source 变量赋值 // // 将 text 变量中的模板提取出来 text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { // escape/interpolate/evaluate 为匹配的子表达式(如果没有匹配成功则为 undefined) // offset 为字符匹配(match)的起始位置(偏移量) source += text.slice(index, offset).replace(escaper, escapeChar); index = offset + match.length; // 改变 index 值,为了下次的 slice if (escape) { // 需要对变量进行编码(=> HTML 实体编码) // 避免 XSS 攻击 source += ""+ ((__t=(" + escape + "))==null?"":_.escape(__t))+ ""; } else if (interpolate) { // 单纯的插入变量 source += ""+ ((__t=(" + interpolate + "))==null?"":__t)+ ""; } else if (evaluate) { source += ""; " + evaluate + " __p+=""; } // Adobe VMs need the match returned to produce the correct offest. // return 的作用是将匹配到的内容原样返回(Adobe VMs 需要返回 match 来使得 offset 值正常) return match; }); source += ""; "; // If a variable is not specified, place data values in local scope. // 如果设置了 settings.variable,能显著提升模板的渲染速度,否则,默认用 with 语句指定作用域 if (!settings.variable) source = "with(obj||{}){ " + source + "} "; // 增加 print 功能 source = "var __t,__p="",__j=Array.prototype.join," + "print=function(){__p+=__j.call(arguments,"");}; " + source + "return __p; "; // __p 为返回的字符串 try { // render 方法,前两个参数为 render 方法的参数 // obj 为传入的 JSON 对象,传入 _ 参数使得函数内部能用 Underscore 的函数 var render = new Function(settings.variable || "obj", "_", source); } catch (e) { e.source = source; throw e; } // 返回的函数 //data 一般是 JSON 数据,用来渲染模板 var template = function(data) { // render 为模板渲染函数 return render.call(this, data, _); // 传入参数 _ ,使得模板里 <% %> 里的代码能用 underscore 的方法 }; // Provide the compiled source as a convenience for precompilation. var argument = settings.variable || "obj"; template.source = "function(" + argument + "){ " + source + "}"; return template; }; _.template(templateString, [settings]) // 将 JavaScript 模板编译为可以用于页面呈现的函数,setting 参数可以用来自定义字符串模板,是一个哈希表包含任何可以覆盖的设置。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/80858.html
摘要:它通过数据模型进行键值绑定及事件处理,通过模型集合器提供一套丰富的用于枚举功能,通过视图来进行事件处理及与现有的通过接口进行交互。 本人兼职前端付费技术顾问,如需帮助请加本人微信hawx1993或QQ345823102,非诚勿扰 1.为初学前端而不知道怎么做项目的你指导 2.指导并扎实你的JavaScript基础 3.帮你准备面试并提供相关指导性意见 4.为你的前端之路提供极具建设性的...
摘要:所以经常会在一个源码中看到写法吧立即执行函数创建变量,保存全局根变量。 // ================立即执行函数================ // 使用(function(){}())立即执行函数,减少全局变量 // ----????----函数声明 function (){} 与函数表达式 var funName = function(){}----????---- /...
摘要:所以,刚开始,我从源码比较短的包含注释只有行开始学习起。一般,在客户端浏览器环境中,即为,暴露在全局中。学习以后判断直接使用看起来也优雅一点滑稽脸。在的函数视线中,的作用执行一个传入函数次,并返回由每次执行结果组成的数组。 前言 最近在社区浏览文章的时候,看到了一位大四学长在寻求前端工作中的面经,看完不得不佩服,掌握知识点真是全面,无论是前端后台还是其他,都有涉猎。 在他写的文章中,有...
摘要:今天要讲的是,如何在数组中寻找元素,对应中的,,,以及方法。如果往一个有序数组中插入元素,使得数组继续保持有序,那么这个插入位置是这就是这个方法的作用,有序,很显然用二分查找即可。 Why underscore (觉得这部分眼熟的可以直接跳到下一段了...) 最近开始看 underscore.js 源码,并将 underscore.js 源码解读 放在了我的 2016 计划中。 阅读一...
摘要:加载的模块会以参数形式传入该函数,从而在回调函数内部就可以使用这些模块。异步加载,和,浏览器不会失去响应它指定的回调函数,只有前面的模块都加载成功后,才会运行,解决了依赖性的问题。插件,可以让回调函数在页面结构加载完成后再运行。 这次主要是对《高性能JavaScript》一书的读书笔记,记录下自己之前没有注意到或者需要引起重视的地方 第一章 加载和执行 js代码在执行过程中会阻塞浏览...
阅读 3013·2021-11-25 09:43
阅读 994·2021-11-24 10:22
阅读 1305·2021-09-22 15:26
阅读 647·2019-08-30 15:44
阅读 2412·2019-08-29 16:33
阅读 3556·2019-08-26 18:42
阅读 833·2019-08-23 18:07
阅读 1793·2019-08-23 17:55