call函数

先从改变this指向上简单实现一个方法添加到Function的原型链上:

1
2
3
4
5
Function.prototype.myCall = function (content) {
content.fn = this
const result = eval(`content.fn()`)
return result
}

这就实现了call函数核心部分,因为使用了字符串的形式,所以函数的参数部分还需要进行特殊处理:

1
2
3
4
5
6
7
8
9
10
11
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
}

基本可以了,但还有问题,临时属性的处理,最终优化结果:

1
2
3
4
5
6
7
8
9
10
11
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
}

写个例子测试下:

1
2
3
4
5
6
7
8
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

节流

先说节流,以滚动事件为例,节流即是滚动过程中低频率地触发事件。

1
2
3
window.onscroll = throttle(function () {
console.log('throttle'); // 持续滚动只会间隔1s有节奏地执行打印
}, 1000)

定时器 throttle 实现:

1
2
3
4
5
6
7
8
9
10
11
12
function throttle(fn, delay = 1000) {
let timer = null;
return function () {
if (timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, arguments);
timer = null; // 闭包,释放timer
}, delay)
}
}

时间戳 throttle 实现:

1
2
3
4
5
6
7
8
9
10
function throttle(fn, delayTime = 1000) {
let lastTime = 0
return function () {
const nowTime = new Date().getTime()
if (nowTime - lastTime > delayTime) {
fn.call(this, ...arguments)
lastTime = nowTime // 也是闭包,维护一个时间戳
}
}
}

防抖

以滚动事件为例,防抖即是滚动过程中只触发一次事件,如果持续滚动,则一直不会触发事件。

1
2
3
window.onscroll = debounce(function () {
console.log('debounce'); // 直到完全停止滚动后1s才执行输出
}, 1000)

定时器实现:

1
2
3
4
5
6
7
8
9
10
11
12
function debounce(fn, delay = 1000) {
let timer = null
return function () {
if (timer) {
clearTimeout(timer) // 与节流的主要区别,就是清除任务,只保留最后一次执行
}
timer = setTimeout(() => {
fn.call(this, ...arguments)
timer = null
}, delay);
}
}