前言
在JavaScript中,函数绑定是一项重要的技能。掌握了 bind、call 和 apply,你将能够更加灵活地操作函数的上下文和参数。这不仅是深入理解JavaScript语言本质的一部分,也是成为一位优秀开发者的必备技能之一。
在这个系列中,我们将深入剖析这些函数绑定。通过手写 bind、模拟实现 call 和 apply,我们将揭开它们神秘的面纱,逐步领略它们背后的原理。
什么是函数绑定
在js中,除了ES6之后出现的箭头函数之外,每个函数都有自己的this,函数绑定就是可以改变函数中的this指向,使this的值是开发过程中程序员所预期的值。函数绑定有三种方法,分别是 bind、call 和 apply。
bind
bind方法会创建一个新的函数,该函数的this会被绑定到指定的对象,并且该函数可以接收参数,并传递给原来的函数。bind也可以接受参数,且这些参数会插入到调用新函数时传入的参数的前面。
function sayHello(x, y, z) {
console.log("函数中的", this);
console.log("你好:", this.name);
console.log(x + y + z);
}
let person = {
name:'dante'
}
let bindFn = sayHello.bind(person,1,2)
bindFn(3)
根据这个例子,可以发现,手写bind需要以下需求
- 绑定函数的this的上下文
- 收集参数, bind时收集 ...args 执行时 ...args1 需要注意的是,args在前 [...args,...args1]
- 闭包返回一个函数
手写bind
Function.prototype.myBind = function (context, ...args) {
console.log(this);
// 如果myBind被赋给一个变量 则报错
if (typeof this !== "function") {
throw new TypeError("Error");
}
context = context || window; // 位于闭包之中
let that = this;
return function fn(...innerArgs) {
// 将原先的函数执行 this手动指定为context
if (this instanceof fn) { // new的方式运行
return new that(...args, ...innerArgs);
}
return that.apply(context, [...args, ...innerArgs]);
};
};
以下是一些测试的用例
function sayHello(x, y, z) {
console.log("函数中的", this);
console.log("你好:", this.name);
console.log(x + y + z);
}
let person = {
name: "dante",
};
const arrowFn = () => {
}
// 此时就会报错 arrowFn.myBind是一个函数体
// const f2 = arrowFn.myBind
// f2()
let myBindFn = sayHello.myBind(person, 1, 2);
myBindFn(3);
call
call以给定的 this 值和逐个提供的参数调用该函数。这是MDN上对call的解释,不难理解,call方法就是可以改变this的指向,以及将收集的参数给调用它的函数并且执行这个函数。
let person = {
name:'dante'
}
function sayName(x,y){
console.log(this.name,x,y)
}
sayName.call(person,'hello','world') // dante hello world
手写call
Function.prototype.myCall = function(context,...argments){
if(typeof this !== 'function'){
throw new TypeError('Error')
}
context = context || window
context.fn = this
let args = [...argments]
return context.fn(...args)
}
let person = {
name:'dante'
}
function sayName(x,y){
console.log(this.name,x,y)
}
sayName.myCall(person,'hello','world')
apply
apply方法与call方法相似,但是apply接收的参数是数组或类数组。
let person = {
name:'dante'
}
function sayName(x,y){
console.log(this.name,x,y)
}
sayName.apply(person, ['hello','world'])
手写apply
Function.prototype.myApply = function(context,args = []){
if(typeof this!== 'function'){
throw new TypeError('Error')
}
context = context || window
context.fn = this
return context.fn(...args)
}
let person = {
name:'dante'
}
function sayName(x,y){
console.log(this.name,x,y)
}
const set = new Set(['hello','world'])
sayName.myApply(person, ['hello','world'])
sayName.myApply(person, set)
bind、call、apply的区别
- bind会创一个新的函数,bind接收的参数会插入到新函数接受的参数前面,调用新函数,会将参数传递给原函数并执行
- call方法不创建新的函数,接收多个参数,并传递给原函数执行
- apply方法不创建新的函数,接收的参数为数组或类数组,传递给原函数执行