这是我参与更文挑战的第22天,活动详情查看: 更文挑战
前言
我们面试的如果遇到需要手写代码,防抖节流是比较高频的一个(链接点击可以点这里,之前实现过),手写实现apply
,call
,bind
也是问的比较多的,今天咋们就好好来学习一下,来看看怎么手写实现。
用法
在手写实现apply
,call
,bind
之前,我们得先看看它们的用法。先了解用法,再看看怎么实现。
apply
改变函数运行时函数体内this的指向。
语法:
func.apply(thisArg, [argsArray])
thisArg:函数运行时this指向的对象,必填
argsArray:函数运行时传递的参数,数组形式,选填
例子:
function fn (a,b,c) {
console.log(this.name)
console.log(a, b, c)
}
let obj = {name: '答案cp3'}
fn.apply(obj, [1,2,3])
// 输出 答案cp3 1 2 3
复制代码
可以看到,fn执行时指向了obj
,并且把传入的数组拆开,当作函数传入的参数。
call
和apply一样,也是改变函数运行时函数体内this的指向。
区别在于:apply传入的参数必须是数组形式,call没有这个限制。
语法:
func.call(thisArg[, args1, args2, args3,….])
thisArg:函数运行时this指向的对象,必填
args1等:函数运行时传递的参数,选填
例子:
function fn (a,b,c) {
console.log(this.name)
console.log(a, b, c)
}
let obj = {name: '答案cp3'}
fn.call(obj, 1, 2, 3)
// 输出 答案cp3 1 2 3
复制代码
可以看到,fn执行时指向了obj
,并且收集所有的参数,当作函数传入的参数。
bind
和apply
, call
不同的是,bind不会执行函数,它返回一个新函数,然后给这个新函数绑定this的指向。
语法:
func.bind(thisArg[, args1, args2, args3,….])
例子:
function fn (a, b, c, d) {
console.log(this.name)
console.log(a, b, c, d)
}
let obj = {name: '答案cp3'}
let bindFn = fn.bind(obj, 1, 2, 3)
bindFn('bind') // 输出 答案cp3 1 2 3 'bind'
复制代码
由例子可以看出 bind
返回一个新函数,执行新函数的的时候this
指向obj
,然后把bind
时传入的参数和调用新函数时传入的参数合并,一起传给新函数。
手写实现
通过上面的apply
,call
,bind
用法可以得知:
apply
,call
,bind
都是可以改变this
的指向apply
,call
会执行调用的函数,bind
返回一个新函数。apply
第二个参数要求是数组,call
,bind
则没有限制数据类型,它会把剩余的参数一起传给函数,bind
还会把新函数调用时传入的参数一起合并,传给新函数。- 他们都是绑定在
Function
的prototype
上。
下面来看看怎么手写实现。
因为它们都是绑定在Function
的prototype
上,我们也一样,只不过我们换个名字_apply
, _call
, _bind
apply
Function.prototype._apply = function (context, args) {
// 不传默认是全局,window
context = context || window
// args不传时默认是空数组,防止下面用spread操作符时报错
args = args ? args : []
// 把this存到context.fn,这里的this是调用的函数
context.fn = this
// 执行调用的函数,this指向context,参数用spread操作符扩展
const res = context.fn(...args)
// 删除,不污染context
delete context.fn
// 返回res
return res
}
复制代码
用上面例子验证一下:
function fn (a,b,c) {
console.log(this.name)
console.log(a, b, c)
}
let obj = {name: '答案cp3'}
fn._apply(obj, [1,2,3])
// 输出 答案cp3 1 2 3
复制代码
通过~
call
call和apply一样,主要是参数和apply不一样,小改一下就行;
代码如下:
Function.prototype._call = function (context, ...args) {
// 不传默认是全局,window
context = context || window
// args不传时默认是空数组,防止下面用spread操作符时报错
args = args ? args : []
// 把this存到context.fn,这里的this是调用的函数
context.fn = this
// 执行调用的函数,this指向context,参数用spread操作符扩展
const res = context.fn(...args)
// 删除,不污染context
delete context.fn
// 返回res
return res
}
复制代码
主要是在_call
函数第二个参数,获取args
时,使用了spread操作符
,这样可以把剩余参数都获取到args中,其它的都一样。
用上面例子验证一下:
function fn (a,b,c) {
console.log(this.name)
console.log(a, b, c)
}
let obj = {name: '答案cp3'}
fn._call(obj, 1, 2, 3)
// 输出 答案cp3 1 2 3
复制代码
也通过~
bind
bind是不一样的,它要返回一个新函数,这个新函数可以被调用,也可以被当作构造函数,使用new
操作符,所以这里要做区分。
Function.prototype._bind = function (context, ...args) {
// 不传默认是全局,window
context = context || window
// 把this存到fn,这里的this是调用的函数
let fn = this
return function newFn (...fnArgs) {
let res
// 要考虑新函数是不是会当作构造函数
if (this instanceof newFn) {
// 如果是构造函数则调用new 并且合并参数args,fnArgs
res = new fn(...args, ...fnArgs)
} else {
// 当作普通函数调用 也可以用上面定义的_call
res = fn.call(context, ...args, ...fnArgs)
}
return res
}
}
复制代码
用上面例子验证一下:
function fn (a, b, c, d) {
console.log(this.name)
console.log(a, b, c, d)
}
let obj = {name: '答案cp3'}
let bindFn = fn._bind(obj, 1, 2, 3)
bindFn('bind') // 输出 答案cp3 1 2 3 'bind'
复制代码
let bindFn = fn._bind(obj, 1, 2, 3)
let instance = new bindFn()
instance.constructor === fn // true
复制代码
也通过~
总结
以上就是总结的手写实现apply
,call
,bind
,如果不理解或者写的有问题,欢迎评论沟通~