函数的使用技巧和各类设计模式,主要是基于闭包、高阶函数(以函数为参数或以函数为返回值的函数),并结合 apply 等函数方法实现的。
- call 和 apply 本质上是函数调用的一种方式,可以指定函数调用的上下文,改变函数内部 this 的指向
- bind 用于绑定 this 指向
- call 和 bind 都是基于 apply 的
// example
function hasOwn (obj, key) {
return Object.prototype.hasOwnProperty.call(obj, key)
}
// 将类数组转化为数组
var newArray = Array.prototype.slice.call(arrayLikeObj)
// 类数组对象借用数组方法
Array.prototype.push.call(arguments, 'value')
Array.prototype.shift.call(arguments) // 获取第一个参数
(function () {
Array.prototype.forEach.call(arguments, function (val) {
console.log(val + 1)
})
})(1, 2, 3) // 输出2, 3, 4
// 数组借用 Math 方法
Math.max.apply(null, arr)
// 借用构造函数
function f (a) {
this.a = a
}
function foo () {
f.apply(this, arguments)
console.log(this.a)
}
new foo('a') // 输出a
// 倒计时
var second = 60
function countdown (btn) {
if (second == 0) {
btn.removeAttribute("disabled")
btn.value = "获取验证码"
second = 60
} else {
btn.setAttribute("disabled", true)
btn.value = second + "秒后重新获取"
second --
}
setTimeout(function() {
countdown(btn)
}, 1000)
}
// 菲波那切数列
function Fibonacci (n , a1 = 1 , a2 = 1) {
if ( n <= 1 ) { return a2 }
return Fibonacci (n - 1, a2, a1 + a2)
}
// 遍历节点树(深度优先遍历)
function walk (node, func) {
func(node)
node = node.firstChild
while (node) {
walk(node, func)
node = node.nextSibling
}
}
// 广度优先遍历
function walk (node, func) {
func(node)
node = node.nextSibling
while (node) {
walk(node, func)
node = node.firstChild
}
}
throttle:事件处理降频
应用场景:避免过于频繁的执行事件响应(例如 onscroll、onmousemove、onresize 的回调函数),限定在 t 时间段内仅执行一次
同类方案:requestAnimationFrame
_.throttle = function (func, wait) {
var context, args, result
var timeout = null
var previous = 0
var later = function() {
previous = _.now()
timeout = null
result = func.apply(context, args)
if (!timeout) context = args = null
}
return function () {
var now = _.now()
var remaining = wait - (now - previous)
context = this
args = arguments
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout)
timeout = null
previous = now
result = func.apply(context, args)
if (!timeout) context = args = null
} else if (!timeout) {
timeout = setTimeout(later, remaining)
}
return result
}
}
debounce:一系列事件,按时间间隔 t 分组,每组仅执行一次
应用场景:一系列事件,待停顿一段时间后再触发响应操作,如果事件一直不停的产生,时间间隔未达到阈值,则一直不予响应
应用案例:1、窗口 resize;2、输入框自动补全
_.debounce = function (func, wait, immediate) {
var timeout, args, context, timestamp, result
var later = function() {
var last = _.now() - timestamp
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last)
} else {
timeout = null
if (!immediate) {
result = func.apply(context, args)
if (!timeout) context = args = null
}
}
}
return function() {
context = this
args = arguments
timestamp = _.now()
var callNow = immediate && !timeout
if (!timeout) timeout = setTimeout(later, wait)
if (callNow) {
result = func.apply(context, args)
context = args = null
}
return result
}
}
- 柯里化就是将多参数函数转化为单参数函数:f(arg1, arg2) => f(arg1)(arg2)
- 柯里化又称部分求值。是指向函数传参后,函数不会立即求值,而是将参数保存在闭包中,等到真正需要求值的时候再求值
- 柯里化是因为 lambda 演算只有一个参数才被发明的,是函数式编程的一个自然结果
f(1) // 仅仅将参数保存起来,除此以外什么都不做
f(1)(2) // 同上
f(1)(2)(3) // 真正执行了
const curry = (fn, arity = fn.length, ...args) =>
arity <= args.length
? fn(...args)
: curry.bind(null, fn, arity, ...args)
一种使用匿名单参数函数来实现多参数函数的方法 uncurrying 的作用是将通过 apply/call 实现对象间互调方法的方式泛化为一种通用形式 Function.prototype.uncurrying = function () { var self = this return function () { var obj = Array.prototype.shift.call(arguments) return self.apply(obj, arguments) } }
通过缓存,避免重复的计算
// Create a cached version of a pure function
function cached (fn) {
var cache = Object.create(null)
return (function cachedFn (str) {
var hit = cache[str]
return hit || (cache[str] = fn(str))
})
}
惰性载入的基本思想是只在调用的时候创建,而不是在程序初始化的时候创建。
通过替换变量,仅在第一次调用时执行有关逻辑,以后再次调用无须重复执行有关逻辑。
const pipeFunctions = (...fns) => fns.reduce((f, g) => (...args) => g(f(...args)))