bind,call,apply手写作为一个经典的前端面试题,很多人都会被问到,通常面试者回答的话一般都会回答以下三点:
- call传递的第一个参数是this指向的对象,后面以参数队列传递参数
- apply传递的第一个参数是this指向的对象,后面以数组传递参数
- bind只有一个参数(绑定的对象),返回的是一个可执行的函数,函数调用后完成this指向的改变
但其中的原理还需要一一道来
首先分别来手写call,apply,bind
call实现:
Function.prototype.myCall = function (context) {
// 判断调用myCall的是否是函数
if (typeof this !== 'function') {
throw new Error('not function')
}
// 关键点 需要挂载的对象,如果第一个参数为null,undefined等其它会自动转换为false的对象时,挂载的对象为window
context = context || window
// 这里改变this的指向,将this(执行call的函数的执行环境)绑定到了context上
context.fn = this
// 取参数
const arg = [...arguments].slice(1)
const result = context.fn(...arg) // 执行
delete context.fn // 用完删掉
return result
}
apply实现:
Function.prototype.myApply = function (context) {
// 判断调用myCall的是否是函数
if (typeof this !== 'function') {
throw new Error('not function')
}
if (!Array.isArray(arguments[1])) {
throw new Error('arg not a array')
}
// 关键点 需要挂载的对象,如果第一个参数为null,undefined等其它会自动转换为false的对象时,挂载的对象为window
context = context || window
// 这里改变this的指向,将this(执行call的函数的执行环境)绑定到了context上
context.fn = this
const result = context.fn(...arguments[1]) // 执行
delete context.fn // 用完删掉
return result
}
由上面可以看出,改变this指向实质上是将函数的this通过对象绑定的形式绑定到第一个参数上,同时执行。 bind实现:
Function.prototype.mybind = function(thisArg) {
if (typeof this !== 'function') {
throw TypeError("Bind must be called on a function")
}
const args = Array.prototype.slice.call(arguments, 1)
const self = this
// 构建一个干净的函数,用于保存原函数的原型
const nop = function() {}
// 绑定的函数
const bound = function() {
// this instanceof nop, 判断是否使用 new 来调用 bound
// 如果是 new 来调用的话,this的指向就是其实例,
// 如果不是 new 调用的话,就改变 this 指向到指定的对象thisArg
return self.apply(
this instanceof nop ? this : thisArg,
args.concat(Array.prototype.slice.call(arguments))
);
};
// 箭头函数没有 prototype,箭头函数this永远指向它所在的作用域
if (this.prototype) {
nop.prototype = this.prototype;
}
// 关键点 如果有this 将通过原型链继承this 确保在返回的函数仍然可以使用初始调用bind函数的方法
// 同时如果通过new 创建对象 对象的原型链会指向nop, 如果直接返回bound,bound只有prototype指向nop
bound.prototype = new nop();
return bound;
}
为了更好理解bind的过程,再熟悉一下new的过程
- 创建一个新对象
- 新对象的__proto__指向构造函数的prototype
- 将构造函数的this指向这个新对象
- 执行构造函数 如果执行构造函数返回值为对象则直接返回对象,如果不为对象则返回创建的新对象
本质上就时new创建了一个新的对象,构造了一个原型链关系,同时将this指向了这个对象
bind内部借助new判断了是否bind过后为new调用,同时内部通过apply或者call改变this指向,将执行环境绑定到指定的对象上。这便是bind的核心点。
如果觉得有用,谢谢大家点赞,六冰感激不尽