Lodash 是一个一致性、模块化、高性能的 JavaScript 实用工具库。处理复杂数组,对比等能够直接采用该库,也方便快捷。javascript
官方网站html
https://www.lodashjs.com/前端
.throttle是lodash中的节流函数,.debounce是lodash中的防抖函数。
具体做用能够直接看官方文档。vue
import _ from 'lodash' export default{ methods:{ click:_.throttle(function(){ console.log('test') console.log(this) },1000) } } import _ from 'lodash' export default{ methods:{ onUpdateNote: _.debounce(function() { this.updateNote({ noteId: this.curNote.id, title: this.curNote.title, content: this.curNote.content }) .then(data => { this.statusText = '已保存' }).catch(data => { this.statusText = '保存出错' }) }, 300) }
在lodash的throttle,debounce方法中,能够直接使用function,并且无需从新指向this,在函数内部中已经作了apply,因此这里的this指向的就是vue实例,对已有函数的外面包一层.throttle/.debounce就能够了。java
cnpm i lodash -S
import _ from 'lodash' Vue.prototype._ = _
this._.debounce(this.handleClick,1000,false)
let _ = require('lodash')
_.debounce(this.handleClick,1000,false)
里面分别有我本身写的debounce函数和lodash的debounce函数,效果同样!react
<template> <div> <el-button @click="myDebounce">个人debounce</el-button> <el-button @click="_debounce">lodash的debounce</el-button> </div> </template> <script> import { debounce } from '@/utils/util' let _ = require('lodash') export default { methods: { handleClick1() { console.log(`真正执行的函数,次数比较少:handleClick1.....`) }, handleClick2() { console.log(`真正执行的函数,次数比较少:handleClick2.....`) }, myDebounce() { console.log(`myDebounce.....`) this.DB() }, _debounce() { console.log(`_debounce.....`) this._DB() } }, created() { this.DB = debounce(this.handleClick1, 1000, false) this._DB = this._.debounce(this.handleClick2,1000,false) } } </script>
注意:之前我是在data选项里面定义,而后再methods里面初始化函数,可是须要判断‘若是有了就不赋函数,若是为空就赋’,发现比较麻烦;后来直接在created钩子里面定义,就很方便了!或者挂载以后。es6
有多年开发经验的工程师,每每都会有本身的一套工具库,称为utils、helpers等等,这套库一方面是本身的技术积累,另外一方面也是对某项技术的扩展,领先于技术规范的制订和实现。算法
Lodash就是这样的一套工具库,它内部封装了诸多对字符串、数组、对象等常见数据类型的处理函数,其中部分是目前ECMAScript还没有制订的规范,但同时被业界所承认的辅助函数。莫倩天天使用npm安装Lodash的数量在百万级以上,这在必定程度上证实了其代码的健壮性,值得咱们在项目中一试。shell
Lodash听得辅助函数主要分为如下几类,函数列表和用法实力请查看Lodash的官方文档:npm
在React + Webpack + Babel(ES6)的开发环境中,使用Lodash须要安装插件babel-plugin-lodash并更新Babel配置文件:
npm install --save lodash npm install --save-dev babel-plugin-lodash
更新Bable的配置文件 .babelrc:
{ "presets":[ "react", "es2015", "stage-0" ], "plugins":[ "lodash" ] }
使用方式:
import _ from 'lodash'; import { add } from 'lodash/fp'; const addOne = add(1); _.map([1, 2, 3], addOne);
在 Filip Zawada的文章《How to Speed Up Lo-Dash ×100? Introducing Lazy Evaluation》中提到了Lodash提升执行速度的思路,主要有三点: Lazy Evaluation、Pipelining和Deferred Execution。下面两张图来自Filip的博客:
假设有如上图所示的问题: 从若干个求中取出三个面值小于10的球。第一步是从全部的求中取出全部面值小于10的球,第二部是从上一步的结果中去三个球。
上图是另外一个解决方案,若是一个球可以经过第一步,那么就继续执行第二步,直至结束而后测试下一个球。。。当咱们取到三个球以后就中断整个循环。Filip称这是Lazy Evaluation Algorithm, 就我的理解这并不全面,他后续提到的Pipelining(管道计算),再加上一个中断循环执行的算法应该更符合这里的图示。
此外,使用Lodash的链式调用时,只有现实或隐式调用 .value 方法才会对链式调用的整个操做进行取值,这种不在声明时当即求值,而在使用时进行求职的方式,是Lazy Evaluation最大的特色。
收益于Lodash的普及程度,使用它能够提升不少人开发时于都代码的效率,减小彼此之间的误解(Loss of Consciousness)。在《Lodash: 10 Javascript Utility Functions That You Should Probably Stop Rewriting》一文中,做者列举了多个经常使用的Lodash函数,实例演示了使用Lodash的技巧。
// 1. Basic for loop. for(var i = 0; i < 5; i++){ //... } // 2. Using Array's join and split methods Array.apply(null, Array(5)).forEach(function(){ //... }); // Lodash _.times(5, function(){ //... });
for 语句是执行虚幻的不二选择,Array.apply也能够模拟循环,但在上面代码的使用场景下,_.tiems()的解决方法更加简洁和易于理解。
// Fetch the name of the first pet from each owner var ownerArr = [{ "owner": "Colin", "pets": [{"name": "dog1"}, {"name": "dog2"}] }, { "owner": "John", "pets": [{"name": "dog3"}, {"name": "dog4"}] }]; // Array's map method. ownerArr.map(function(owner){ return owner.pets[0].name; }); // Lodash _.map(ownerArr, "pets[0].name");
_.map 方法是对原生 map 方法的改进,其中使用 pets[0].name 字符串对嵌套数据取值的方式简化了不少冗余的代码,很是相似使用jQuery选择DOM节点 ul>li>a , 对于前端开发者来讲有种久违的亲切感。
// Array's map method. Array.apply(null, Array(6)).map(function(item, index){ return "ball_" + index; }); // Lodash _.times(6, _.uniqueId.bind(null, 'ball_')); // Lodash _.times(6, _.partial(_.uniqueId, 'ball_')); // eg. [ball_0, ball_1, ball_2, ball_3, ball_4, ball_6]
在上面的代码中,咱们要建立一个初始值不一样、长度为6的数组,其中 .uniqueId 方法用于生成独一无二的标示符(递增的数字,在程序运行期间保持独一无二), .partial 方法是对 bind 的封装。
var objA = { "name": "colin" } // 经常使用的方法通常会比较长,循环对象等 // http://stackoverflow.com/questions/4459928/how-to-deep-clone-in-javascript // Lodash var objB = _.cloneDeep(objA); objB === objA // false
JavaScript 没有直接提供深拷贝的函数,可是咱们能够用其余杉树来模拟,好比 JSON.parse(JSON.stringify(objectToClone)), 但这种方法要求对象中的属性值不能是函数。Lodash 中的 _.cloneDeep 函数封装了深拷贝的逻辑,用起来更加简洁。
// Native utility method function getRandomNumber(min, max){ return Math.floor(Math.random() * (max - min + 1)) + min; } getRandomNumber(15, 20); // Lodash _.random(15, 20);
Lodash 的随机数生成函数更贴近实际开发,ECMAScript 的随机数生成函数式底层必备的接口,二者都不可获取。此外,使用 _.random(15, 20, true) 还能够在15到20之间生成随机的浮点数。
// Adding extend function to Object.prototype Object.prototype.extend = function(obj) { for (var i in obj) { if (obj.hasOwnProperty(i)) { this[i] = obj[i]; } } }; var objA = {"name": "colin", "car": "suzuki"}; var objB = {"name": "james", "age": 17}; objA.extend(objB); objA; // {"name": "james", "age": 17, "car": "suzuki"}; // Lodash _.assign(objA, ojbB);
_.assign 是浅拷贝, 和ES6新增的 Object.assign 函数功能一致(建议优先使用Object.assign)。
// Native method: Remove an array of keys from object Object.prototype.remove = function(arr) { var that = this; arr.forEach(function(key){ delete(this[key]); }); }; var objA = {"name": "colin", "car": "suzuki", "age": 17}; objA.remove(['car', 'age']); objA; // {"name": "colin"} // Lodash objA = _.omit(objA, ['car', 'age']); // => {"name": "colin"} objA = _.omit(objA, "car"); // => {"name": "colin", "age": 17} objA = _.omit(objA, _.isNumber); // => {"name": "colin", "car": "suzuki"};
大多数状况下,Lodash所提供的辅助函数都会比原声的函数更贴近开发需求。在上面的代码中,开发者可使用数组、字符串以及函数的方式筛选对象的属性,而且最终会返回一个新的对象,中间执行筛选时不会对旧对象产生影响。
// Native method: Returning a new object with selected properties Object.prototype.pick = function(arr) { var _this = this; var obj = {}; arr.forEach(function(){ obj[key] = _this[key]; }); return obj; }; var objA = {"name": "colin", "car": "suzuki", "age": 17}; var objB = objA.pick(['car', 'age']); // => {"car": "suzuki", "age": 17} // Lodash var objB = _.pick(objA, ['car', 'age']); // => {"car": "suzuki", "age":17}
.pick 是 .omit 的相反操做,用于从其余对象中挑选属性生成新的对象。
var luckDraw = ["Colin", "John", "James", "Lily", "Mary"]; function pickRandomPerson(luckyDraw){ var index = Math.floor(Math.random() * (luckyDraw.length - 1)); return luckyDraw[index]; } pickRandomPerson(luckyDraw); //John // Lodash _.sample(luckyDraw); // Colin // Lodash - Getting 2 random item _.sample(luckyDraw, 2); // ['John', 'Lily']
_.sample 支持随机挑选多个元素并返回新的数组。
// Using try-catch to handle the JSON.parse error function parse(str){ try { return JSON.parse(str); } catch(e) { return false; } } // With Lodash function parseLodash(str){ return _.attempt(JSON.parse.bind(null, str)); } parse('a'); // => false parseLodash('a'); // => Return an error object parse('{"name": "colin"}'); // => Return {"name": "colin"} parseLodash('{"name": "colin"}'); // => Return {"name": "colin"}
若是你在使用 JSON.parse 时没有预置错误处理,那么它颇有可能会成为一个定时炸弹,咱们不该该默认接收的JSON对象都是有效的。 try-catch 是常见的错误处理方式,若是项目中使用Lodash,那么可使用 _.attmpt 替代 try-catch 的方式,当解析JSON出错时,该方法会返回一个 Error 对象。
随着ES6的普及,Lodash的功能或多或少会被原生功能所替代,因此使用时还须要进一步甄别,建议优先使用原生函数,有关ES6替代Lodash的部分,请参考文章《10 个可用 ES6 替代的 Lodash 特性》。
其中有两处分厂值得一看:
// 使用箭头函数建立可复用的路径 const object = { 'a': [{ 'b': { 'c': 3 } }, 4] }; [ obj => obj.a[0].b.c, obj => ojb.a[1] ].map(path => path(object)); // 使用箭头函数编写链式调用 const pipe = function => data => { return functions.reduce( (value, func) => func(value), data ); }; const pipeline = pipe([ x => x * 2, x => x / 3, x => x > 5, b => !b ]); pipeline(5); // true pipeline(20); // false
在ES6中,若是一个函数只接收一个形参且函数提示一个 return 语句, 就可使用箭头函数简化为:
const func = p => v; // 相似于(不彻底相同) const func = function(p) { return v; }
当有多重嵌套时,能够简化为:
const func = a => b => c => a + b + c; func(1)(2)(3); // => 6 // 相似于 const func = function (a) { return function (b) { return function (c) { return a + b + c; } } }
平常开发中,一般会对数据,特别是数组和对象进行各类读写等操做:好比去重,拷贝,合并,过滤,求交集,求和等等。根据平时开发中对数据的操做,我对Lodash常见的用法作了如下总结,方便从此的学习和整理。
建立一个数组,元素为0, 1, 2, ... , 23
_.range([start=0], end, [step=1])
let arr = _.range(24) console.log(arr) // [0, 1, 2, 3, ... , 23]
建立一个数组,元素为100, 100, 100, 100, 100
_.fill(array, value, [start=0], [end=array.length])
let arr = _.fill(Array(5), 100) console.log(arr) // [100, 100, 100, 100, 100]
获取数组中最后一个元素
_.last(array)
let arr = [1, 2, 3, 4, 5] let lastElement = _.last(arr) console.log(lastElement) // 5
获取数组中倒数第二个元素
_.nth(array, [n=0])
let arr = [1, 2, 3, 4, 5] let lastSecondElement = _.nth(-2) console.log(lastSecondElement) // 4
获取对象数组中某一同名属性的属性值集合
*.map(collection, [iteratee=*.identity])
let users = [{ id: 12, name: 'Adam', hobbies: [ {name: 'running', index: 100}, {name: 'cycling', index: 95} ] },{ id: 14, name: 'Bob', hobbies: [ {name: 'movie', index: 98}, {name: 'music', index: 85} ] },{ id: 16, name: 'Charlie', hobbies: [ {name: 'travelling', index: 90}, {name: 'fishing', index: 88} ] },{ id: 18, name: 'David', hobbies: [ {name: 'walking', index: 99}, {name: 'football', index: 85} ] } ] let userIds = _.map(users, 'id') let mostFavouriteHobbies = _.map(users, 'hobbies[0].name') console.log(userIds) // [12, 14, 16, 18] console.log(mostFavouriteHobbies) // ["running", "movie", "travelling", "walking"]
获取对象数组中某一属性值最大的对象
*.maxBy(array, [iteratee=*.identity])
let arr = [{a:1, b: 2, c: {d:4}}, {a:3, b: 4, c: {d:6}}] let maxBObj = _.maxBy(arr, 'b') console.log(maxBObj) // {a: 3, b: 4, c: {d: 6}}
找出两个数组中元素值相同的元素
_.intersection([arrays])
let arr1 = [2, 1, {a: 1, b: 2}] let arr2 = [2, 3, {a: 1, b: 2}] let intersection = _.intersection(arr1, arr2) console.log(intersection) // [2]
求数值数组中元素值的平均数
_.mean(array)
let numbers = [1, 2, 3, 4, 5] let average = _.mean(numbers) console.log(average) // 3
求对象数组中某个属性值的平均数
*.meanBy(array, [iteratee=*.identity])
let objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }] let average = _.meanBy(objects, 'n') console.log(average) // 5
获取数组中前n个元素,不改变原数组
_.take(array, [n=1])
let arr = [1, 2, 3, 4, 5] let part1Arr = _.take(arr, 4) let part2Arr = _.take(arr, 6) let part3Arr = _.take([], 5) console.log(part1Arr) // [1, 2, 3, 4] console.log(part2Arr) // [1, 2, 3, 4, 5] console.log(part3Arr) // []
删除数组中值为falsy的元素
_.compact(array)
let arr = [0, 1, false, 2, '', 3, null, undefined, NaN] let truthyArr = _.compact(arr) console.log(truthyArr) // [1, 2, 3]
去重。
_.uniq(array)
let arr = [2, 1, 2, '2', true] let uniqArr = _.uniq(arr) console.log(uniqArr) // [2, 1, '2', true]
排序。对象数组,根据对象中的某个属性的值,升序或降序排序
*.orderBy(collection, [iteratees=[*.identity]], [orders])
let users = [ {user: 'Tom', age: 25}, {user: 'Amy', age: 23}, {user: 'Perter', age: 22}, {user: 'Ben', age: 29} ] let sortedUsers = _.orderBy(users, 'age', 'desc') console.log(sortedUsers) // [{user: "Ben", age: 29}, {user: "Tom", age: 25}, {user: "Amy", age: 23}, {user: "Perter", age: 22}]
分割数组[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]为 [1, 2, 3, 4, 5] 和 [6, 7, 8, 9, 10]
_.chunk(array, [size=1])
let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] let [left, right] = _.chunk(arr, 5) console.log(left) // [1, 2, 3, 4, 5] console.log(right) // [6, 7, 8, 9, 10]
脱掉一层[]
_.flatten(array)
let address = { '江苏省': ['南京市', '苏州市'], '浙江省': ['杭州市', '绍兴市'] } let cities = _.flatten(_.values(address)) console.log(cities) // ["南京市", "苏州市", "杭州市", "绍兴市"]
将多维数组转为一维数组
_.flattenDeep(array)
let flattenedArr = _.flattenDeep([1, [2, [3, [4]], 5]]); console.log(flattenedArr) // [1, 2, 3, 4, 5]
经过数组["x", "y"] 和 数组[10, 10] 建立对象 {x: 10, y: 10}
_.zipObject([props=[]], [values=[]])
let keys = ["x", "y"] let values = [10, 10] let obj = _.zipObject(keys, values) console.log(obj) // {x: 10, y: 10}
合并对象
_.assign(object, [sources])
let desObj = {name: '', gender: 'male', job: 'developer'} let sourceObj = {name: 'Tom', job: ''} let mergedObj = _.assign(desObj, sourceObj) console.log(mergedObj) // {name: "Tom", gender: "male", job: ""}
深拷贝对象
_.cloneDeep(value)
let sourceObj = {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14]}} let desObj = _.cloneDeep(sourceObj) desObj.permissions.store.push(15, 16) console.log(desObj) // {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14, 15, 16]}} console.log(sourceObj) // {department_id: 1, permissions: {management: [1, 2, 3, 4], store: [11, 12, 13, 14]}}
合并多个对象中key值相同的键值对
_.merge(object, [sources])
let obj1 = {'9': {name: '乐购超市'}} let obj2 = {'9': {storeToken: 'xxx'}} let obj3 = {'9': {storePosition: 'Hangzhou'}} let mergedObj = _.merge(obj1, obj2, obj3) console.log(mergedObj) // 9: {name: "乐购超市", storeToken: "xxx", storePosition: "Hangzhou"}
判断对象中是否有某个属性
_.has(object, path)
let obj = {a: [{b: {c: 3}}]} let hasC = _.has(obj, 'a[0].b.c') console.log(hasC) // true
获取对象中的某个属性的值
_.get(object, path, [defaultValue])
let obj = {a: [{b: {c: 3}}]} let c = _.get(obj, 'a[0].b.c') console.log(c) // 3
设置对象中的某个属性的值
_.set(object, path, value)
let obj = {a: [{b: {c: 3}}]} let newObj = _.set(obj, 'a[0].b.c', 4); console.log(obj.a[0].b.c); // 4
对多个对象相同属性的属性值求和。
let customers = { new_customer: {0: 33, 1: 5, ... , 23: 0}, old_customer: {0: 22, 1: 7, ... , 24: 0} } let customer = {} let keys = _.keys(customers.new_customer) let values = _.values(customers) _.map(keys, key => { customer[key] = _.sumBy(values, key) }) customers.customer = customer console.log(customers) // console { customer: {0: 55, 1: 12, ... , 23: 0} new_customer: {0: 33, 1: 5, ... , 23: 0} old_customer: {0: 22, 1: 7, ... , 23: 0} }
生成一个随机数,范围n~m
_.random([lower=0], [upper=1], [floating])
let random1 = _.random(2, 5) let random2 = _.random(5) console.log(random1) // 2, 3, 4, 5 console.log(random2) // 0, 1, 2, 3, 4, 5
判断数据类型
_.isNumber(value)
_.isInteger(value)
...
_.isPlainObject(value)
let variable = 'hello'; // Number console.log(_.isNumber(variable)); // Integer console.log(_.isInteger(variable)); // Boolean console.log(_.isBoolean(variable)); // String console.log(_.isString(variable)); // Null console.log(_.isNull(variable)); // Undefined console.log(_.isUndefined(variable)); // Array console.log(_.isArray(variable)); // Function console.log(_.isFunction(variable)); // Object console.log(_.isPlainObject(variable)); // Date console.log(_.isDate(variable)); // DOM element console.log(_.isElement(variable));
数据类型转换
_.toArray
_.toArray('abc') // ["a", "b", "c"]
_.toInteger
_.toInteger(3.2); // 3 _.toInteger('3.2'); // 3
_.toNumber
_.toNumber('3.2') // 3.2
_.toString
_.toString(1); // "1" _.toString([1, 2, 3]); // "1,2,3"
重复屡次某个元素
*.times(n, [iteratee=*.identity])
const dateParams = _.times(2, () => '2018-08-27'); console.log(dateParams) // ["2018-08-27", "2018-08-27"]