call函数
先从改变this指向上简单实现一个方法添加到Function的原型链上:
Function.prototype.myCall = function (content) {
content.fn = this
const result = eval(`content.fn()`)
return result
}
这就实现了call函数核心部分,因为使用了字符串的形式,所以函数的参数部分还需要进行特殊处理:
Function.prototype.myCall = function (content) {
content.fn = this
/** 处理参数 */
const args = []
for (let i = 1; i < arguments.length; i++) {
args.push(`arguments[${i}]`) // 直接push会导致强转字符串时出现:[]
}
/** */
const result = eval(`content.fn(${args})`)
return result
}
基本可以了,但还有问题,临时属性的处理,最终优化结果:
Function.prototype.myCall = function (content) {
const fn = `fn_${(Math.random()*999).toFixed()}` // 防止极端情况下属性名冲突
content[fn] = this
const args = []
for (let i = 1; i < arguments.length; i++) {
args.push(`arguments[${i}]`)
}
const result = eval(`content[fn](${args})`)
delete content[fn] // 使用后释放
return result
}
写个例子测试下:
const a = {
name: 'a',
say: function (t) { console.log(`${t}, ${this.name}`) }
}
const b = { name: 'b' }
a.say.call(b, 'hi') // hi, b
a.say.myCall(b, 'hello') // hello, b
防抖
以滚动事件为例,防抖即是滚动过程中低频率地触发事件
window.onscroll = debounce(function () {
console.log('debounce'); // 持续滚动只会间隔1s有节奏地执行打印
}, 1000)
定时器 debounce 实现:
function debounce(fn, delay = 1000) {
let timer = null;
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null; // 闭包,释放timer
}, delay)
}
}
时间戳 debounce 实现:
function debounce(fn, delayTime = 1000) {
let lastTime = 0
return function () {
const nowTime = new Date().getTime()
if (nowTime - lastTime > delayTime) {
fn.call(this, ...arguments)
lastTime = nowTime // 也是闭包,维护一个时间戳
}
}
}
节流
以滚动事件为例,节流即是滚动过程中只触发一次事件
window.onscroll = throttle(function () {
console.log('throttle'); // 直到完全停止滚动后1s才执行输出
}, 1000)
定时器实现:
function throttle(fn, delay = 1000) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer) // 与防抖的主要区别,前面的任务清除,只保留最后一次执行
}
timer = setTimeout(() => {
fn.call(this, ...arguments)
timer = null
}, delay);
}
}