作用:这三个函数的作用都是用来改变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
}