call、apply、bind的使用方法及区别

作用:这三个函数的作用都是用来改变this的指向

call使用方法

fn.call(thisArg, arg1, arg2, arg3 ...)

function fn1() {
    console.log(this)
}
const obj = { a: 1 }
fn1.call(obj, 1, 2, 3, 4) // {a: 1}
复制代码
apply的用法

fn.apply(thisArg, [arg1, arg2, arg3...])

function fn1() {
    console.log(this)
}
const obj = { a: 1 }
fn1.apply(obj, [1, 2, 3, 4]) // {a: 1}
复制代码
bind的用法

fn.bind(thisArg, arg1, arg2, arg3)

function fn1() {
    console.log(this)
}
const obj = { a: 1 }
fn1.bind(obj, 1, 2, 3, 4) // 不会打印任何东西。
复制代码

乍一看,三者的用法几乎一致,需要注意的是bind函数,调用之后并没有执行fn1
先看call和apply, 它们仅有的区别是传参的不同,call接收的是参数逐一传入,apply接收的是一个参数组成的数组
接下来,来实现自己的call和apply函数,通过用法发现需要实现两点:

改变执行函数的this指向为第一个参数
执行原函数

第二点好实现,来看第一点,通常情况下,当函数作为对象的方法调用时,this就指向这个对象,可以通过这个特点来实现自己的call函数
实现call
// 因为需要所有函数都可以调用,所以需要写在Function的原型上

Function.prototype.myCall(context) {
	// 判断context是否存在,不存在设置为window
    context = context ? Object(context) : window
    // 处理参数
    const args = [...arguments].slice(1)
    // 要将this指向改为context,需要用context来调用
    context.fn = this // 这里的this是原函数
    const result = context.fn(...args) // 执行原函数,此时因为是context调用,因此函数中的this指向了context
    delete context.fn
    return result
}

复制代码
实现apply
实现自己的apply函数只需要修改传参方式即可
// 因为需要所有函数都可以调用,所以需要写在Function的原型上

Function.prototype.myCall(context) {
	// 判断context是否存在,不存在设置为window
    context = context ? Object(context) : window
    // 要将this指向改为context,需要用context来调用
    context.fn = this // 这里的this是原函数
    
    let result
    // 处理参数
   if (arguments[1]) {
   	result = context.fn(...arguments[1])
   } else {
   	result = context.fn()
   }
    delete context.fn
    return result
}

复制代码
实现bind
接下来实现bind函数,bind与前两者较大不同是它创建了一个新的函数,并且它第一个参数被指定为这个新函数的this

返回一个新函数
第一个参数被指定为新函数的this
其余参数被当作新函数的参数

// 第一版

Function.prototype.myBind(context) {
	const args = [...arguments].slice(1)
	const that = this
	return function() {
    	that.apply(context, args.concat(...arguments))
    }
}

复制代码
到这里已经实现了基本的bind功能,但是根据MDN对bind的介绍:

调用绑定函数时作为 this 参数传递给目标函数的值。 如果使用new运算符构造绑定函数,则忽略该值。当使用 bind 在 setTimeout 中创建一个函数(作为回调提供)时,作为 thisArg 传递的任何原始值都将转换为 object。如果 bind 函数的参数列表为空,或者thisArg是null或undefined,执行作用域的 this 将被视为新函数的 thisArg。

通过new运算符操作bind生成的函数时,bind绑定的this会失效,此时的this会指向new生成的实例
,接下来了解一下new运算符
new的用法

function Animal(type) {
	this.type = type
    this.age = age
}
const animal1 = new Animal('猫', 2)
const animal2 = new Animal('狗', 2)

复制代码
当执行new Animal()的时候,做了以下几件事情

创建一个空对象
改变this指向到这个空对象,并且让这个对象能访问到构造函数原型上的属性
执行构造函数,此时的this指向了新创建的对象

模拟生成new函数

function _new() {
	// 创建一个新对象
    let obj = {}
    // 获取传入的构造函数
    let constructor = [].shift.call(arguments)
    // 让这个对象能访问到构造函数原型上的属性
    obj.__proto__ = constructor.prototype
    // 改变this指向,并执行构造函数
    constructor.apply(obj, aruguments) // 这里的arguments已经经过截取处理
    // 返回创建的实例对象
    return obj

}
复制代码
回到上面的问题,我们来改造一下
// 第二版

Function.prototype.myBind(context) {
	const args = [...arguments].slice(1)
	const that = this
	return function resFn () {
    	// 判断这里面的this是否是构造函数的实例,如果是说明是用了new运算符
    	that.apply(this instanceof resFn ? this : context , args.concat(...arguments))
    }
}

复制代码
到这里解决了this绑定的问题,this指向了new出来的实例,但是发现这个实例和原函数已经没什么关系了,也就是访问不到原函数原型上的属性,继续改造
// 第三版


```bash
Function.prototype.myBind(context) {
	const args = [...arguments].slice(1)
	const that = this
    const Fn = new Function() // 为了避免直接修改到原型,采用中转函数
	function resFn () {
    	// 判断这里面的this是否是构造函数的实例,如果是说明是用了new运算符
    	that.apply(this instanceof resFn ? this : context , args.concat(...arguments))
    }
    Fn.prototype = this.prototype
    resFn.prototype = new Fn()
    return resFn
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值