常用的一些函数垫片,意在加深js基础概念,同时对js也是个很好的总结。以下案例为个人实践,考虑主流程完整,但有些边界问题未考虑,不推荐在工程项目中使用。正式项目推荐使用。
call/apply
问题
var foo = { value: 1 }function bar() { console.log(this.value) }bar.call(foo) // 期待打印:1bar.apply(foo) // 期待打印:1复制代码
思路
call/apply立即执行函数,同时函数中的this改为指向context。类似等价于以下
var foo = { value: 1, fn: function bar() { console.log(this.value) }}复制代码
Function.prototype.call = function(context, ...args) { context = context || window context.fn = this // 这里的this代表函数 context.fn(...args) // 给context添加属性fn,所以执行fn方法时,里面的this代表context delete context.fn}Function.prototype.apply = function(context, ...args) { context = context || window context.fn = this context.fn(args) // apply传递数组 delete context.fn}复制代码
bind
问题
var foo = { value: 1 }function bar() { console.log(this.value) }let barBind = bar.bind(foo)barBind() // 期待打印:1复制代码
思路
通过apply改变this,并且返回一个函数
Function.prototype.bind = function (context, ...args) { var fn = this return function() { return fn.apply(context, args) }}复制代码
curry
问题
let addFun = function(a, b, c) { return a + b + c }let curryFun = curry(addFun)curryFun(1)(2)(3) === 6 // true复制代码
思路
递归,当执行的参数个数等于原本函数的个数,执行函数
var curry = function(fn) { var limit = fn.length // fn函数参数个数 return function judgeCurry (...args) { if (args.length >= limit) { return fn.apply(null, args) } else { return function(...args2) { return judgeCurry.apply(null, args.concat(args2)) } } }}// or es6var curry = function(fn, ...args) { if (args.length >= fn.length) { return fn(...args) } return function (...args2) { return curry(fn, ...args, ...args2) }}复制代码
pipe/compose
pipe
- pipe(fn1,fn2,fn3,fn4)(args)等价于fn4(fn3(fn2(fn1(args)))
- 第一个函数的结果,作为第二个函数的参数,以此类推...
compose
- compose(fn1,fn2,fn3,fn4)(args)等价于fn1(fn2(fn3(fn4(args)))
- 与pipe相反,先计算倒数第一个结果,作为倒数第二的参数,以此类推...
let loopItem = (prevFn, nextFn) => (...args) => prevFn(nextFn(...args))const compose = (...fns) => fns.reduce(loopItem);const pipe = (...fns) => fns.reduceRight(loopItem)const example = pipe( (x, y) => x * y, x => x + 1);console.log(example(3, 4)) // 13复制代码
flatten
深度为1的展平
// before:[1, 2, [3, 4, [5, 6]]]// after flat: [1, 2, 3, 4, [5, 6]]// 思路:使用reduce或mapfunction flatSingle(arr) { return arr.reduce((pre, val) => pre.concat(val), [])}// orlet flatSingle = arr => [].concat(...arr)复制代码
深度无限的展平
// before: [1,2,3,[1,2,3,4, [2,3,4]]]// after flatDeep: [1, 2, 3, 1, 2, 3, 4, 2, 3, 4]// 思路:深度优先递归,使用reduce连接起来// 深度优先算法 - 递归function flatDeep(arr) { return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val) : val), [])}// 深度优先算法 - 堆栈function flatDeep(arr) { const stack = [...arr] const res = [] while (stack.length) { const val = stack.pop() // 从尾部开始 Array.isArray(val) ? stack.push(val) : res.push(val) } return res.reverse()}// 取巧,利用Array.toString()function flatDeep(arr) { return arr.toString().split(',')}复制代码
指定深度的展平
深度的含义是指每一项展平的次数
// before: [1,2,3,[1, [2]], [1, [2, [3]]]]// after: [ 1, 2, 3, 1, 2, 1, 2, [ 3 ] ]function flatDeep(arr, depth = 1) { if (depth === 1) return arr.reduce((pre, val) => pre.concat(val), []) return arr.reduce((pre, val) => pre.concat(Array.isArray(val) ? flatDeep(val, depth - 1) : val), [])}复制代码
去重
数组去除重复
// before: [2, 1, 3, 2]// after: [2, 1, 3]function removeRepeat(arr) { return arr.filter((item, index) => arr.indexOf(item) === index)}// or es6let removeRepeat = arr => Array.from(new Set(arr))let removeRepeat = arr => [...new Set(arr)]复制代码
浅拷贝/深拷贝
// 浅拷贝function clone(source) { var target = {} for (var i in source) { source.hasOwnProperty(i) && target[i] = source[i] } return target}// or es6const clone = source => Object.assign({}, source)const clone = source => { ...source }复制代码
// 深拷贝// 思路:递归赋值const deepClone = source => { if (!source || typeof source !== 'object') { throw new Error('error arguments', 'shallowClone') } // 区分array和object对象 let target = source instanceof Array ? [] : {} for (let key in source) { if (source.hasOwnProperty(key)) { target[key] = typeof source[key] === 'object' ? deepClone(source[key]) : source[key] } } return target}// or 取巧方法// 注意这种取巧方法是有限制的// 1. 只能解析Number、String、Array等能够被json表示的数据结构// 2. 不能处理循环引用const deepClone = source => JSON.parse(JSON.stringify(source))复制代码
防抖/节流
- 防抖:在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时。适合多次事件一次响应。
- 节流:规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。适合大量事件按时间做平均分配触发。
// 防抖案例:窗口resize停止才执行最终的函数function debounce(fn, wait, ...args) { var that = this fn.tId && clearTimeout(fn.tId) fn.tId = setTimeout(function() { fn.apply(that, args) }, wait)}function handle(e) { console.log('resize end event') console.log(e) // Event{}}// 缺点:handle不能写成匿名函数,因为把tId存储在handle函数对象上。所以间接导致传递参数e较为复杂window.onresize = function(e) { debounce(handle, 1000, e) }// 改进版// 思路: 用闭包把tId存储起来function debounce(fn, wait) { var tId return function() { var that = this var args = arguments tId && clearTimeout(tId) tId = setTimeout(function() { fn.apply(that, args) }, wait) }}function handle(e) { console.log('resize end event') console.log(e) // Event{}}window.onresize = debounce(handle, 1000)复制代码
// 节流案例: 不停scroll时,滚动条每隔100ms固定频率执行函数function throttle(fn, wait) { var cur = new Date() fn.last = fn.last || 0 if (cur - fn.last > wait) { fn.call() fn.last = cur }}function handle() { console.log('scroll event')}window.onscroll = function() { throttle(handle, 100) }复制代码
参考文章
- MDN