一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
前言
手写apply、call、bind是面试的经典题,在项目中也会经常遇到。所以这篇文章通过自己实现的方式加深对方法的理解和使用。
call()
-
call函数
-
接收三个参数,需要执行的函数、函数运行时this指向的对象、函数运行时参数(可以是多个)
-
功能的实现:如何去改变this的指向然后指向函数呢?并不容易但是可以变相实现效果:
- 为
obj
对象添加临时方法obj.temp = Fn
此时obj
身上有fn
一样的方法,就可以不需要调用fn
而调用obj
身上的方法,达到调用fn
一样的效果,并且temp
方法在执行时this
指向是obj
的,所以就变相实现了this指向obj的效果 - 调用
temp
方法let result = obj.temp(...args)
这里还需要把参数放到temp
中 - 此时我们的
obj
身上还要temp
这个临时方法,需要做一个复原。删除temp
方法,通过delete
关键字delete obj.temp
- 返回执行结果
return result
- 观察一下
js
中的call
方法,我们需要和它更相似,就需要判断一下是否指向全局。如果obj为undefine
或null
,this
指向全局对象。注意点:这里的全局对象指向谁呢?window
,但是在node
中就不是window
了而是global
。此时的解决方法是使用globalThis
,这是es11
新推出来的新特性,用它指向全局对象。if(obj===undefined || obj===null){obj=globalThis}
- 为
-
call函数测试
目的:执行
add
,并且改变运行时add
的this
指向为obj
- 新建
html
文件,引入call.js
,添加script标签 - 声明一个函数,
add(a,b){console.log(this);return a+b+this.c;}
- 声明一个对象,
obj={c:2}
- 添加全局属性,
window.c=3
- 指向call函数。
call(add,obj,10,20)
// 结果为 30+2=32
- 新建
call.js
function call(Fn, obj, ...args) { // 判断是否指向全局 if (obj === undefined || obj === null) { obj = globalThis } // 为obj添加临时方法 obj.temp = Fn // 调用obj临时方法 let result = obj.temp(...args) // 删除临时方法 delete obj.temp // 返回执行结果 return result }
call.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <script src="./call.js"></script> </head> <body> <script> // 声明一个函数 function add(a, b) { console.log(this) return a + b + this.c } // 声明一个对象 let obj = { c: 2 } // 添加全局属性 window.c = 3 // 执行call函数 console.log(call(add, obj, 10, 20)) // 32 此时函数add中this的指向是obj console.log(call(add, null, 10, 20)) // 33 此时函数add中this的指向是window </script> </body> </html>
-
2 apply()
与call函数一样。区别:函数运行时的参数,applay是以数组形式传递参数
实现:
-
接收参数,第一个函数、第二个为this指向的对象、第三个函数运行的实参(不需要...args了)
-
功能实现:
- 为obj添加临时方法
- 执行方法。参数需要调整,通过...args,因为传过来是数组,所以需要扩展运算符展开
- 删除临时属性
- 返回结果
- 判断是否指向全局和 call一样
-
总结:几乎和call一样,除了接收的参数不同
测试:
- 测试方法和call一样,就是传入的时候是一个数组
apply.js
function apply(Fn, obj, args) {
// 判断是否指向全局
if (obj === undefined || obj === null) {
obj = globalThis
}
// 创建临时方法
obj.temp = Fn
// 调用临时方法
let result = obj.temp(...args)
// 删除临时方法
delete obj.temp
// 返回执行结果
return result
}
apply.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./apply.js"></script>
</head>
<body>
<script>
function add(a, b) {
console.log(this)
return a + b + this.c
}
let obj = {
c: 2
}
window.c = 3
console.log(apply(add, obj, [10, 20]))
console.log(apply(add, null, [10, 20]))
</script>
</body>
</html>
3 bind()
与call很像,call会执行函数,bind不会执行函数。接收参数都是一样的
实现
-
结构:第一参数函数、第二参数对象、第三...args
-
功能实现:需要实现两个形式:一种和call类似、一种执行函数时传递参数
- 返回一个新的函数。这个函数的作用是调用目标函数,并且改变this的指向。这里的效果其实就是与call一样的,可以直接指向call函数。return function(){return call(Fn,obj,...args)},这里需要引入call文件。到这里只能实现第一种形式。如需实现第二种形式需要继续传参
- 返回一个新的函数时,传递...args2参数。在调用call函数时,将...arg2拼接到最后面,因为在实际使用bind时,调用函数传递新参数都会再最后面拼接。return function(){return call(Fn,obj,...args,...args2)}
测试
说明:执行
add
,并且改变运行时add
的this
指向为obj
,这里有两种实现方式。
-
新建
html
文件,引入call.js
,添加script标签 -
声明一个函数,
add(a,b){console.log(this);return a+b+this.c;}
-
声明一个对象,
obj={c:2}
-
添加全局属性,
window.c=3
-
执行函数:
let fn = bind(add,obj,10,20); console.log(fn());
let fn2 = bind(add,obj); console.log(fn2(10,20));
let fn3 = bind(add,obj,10,20); console.log(fn(30,40));
bind.js
function bind(Fn, obj, ...args) {
// 返回一个新的函数
return function (...args2) {
// 执行call函数
return call(Fn, obj, ...args, ...args2)
}
}
bind.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
<script src="./call.js"></script>
<script src="./bind.js"></script>
</head>
<body>
<script>
// 声明一个函数
function add(a, b) {
console.log(this)
return a + b + this.c
}
// 声明一个对象
let obj = {
c: 2
}
// 添加全局属性
window.c = 3
// 定义bind函数
let fn1 = bind(add, obj, 10, 20)
let fn2 = bind(add, obj)
let fn3 = add.bind(obj, 10, 20)
// 执行bind函数
console.log(fn1()) // 32
console.log(fn2(30, 40)) // 72
console.log(fn3(50, 60)) // 32 此时运算时只是跟前面的实参有关系与这里的无关。
</script>
</body>
</html>