一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
this的四种绑定规则
function(){}在运行时(被调用的时候)会基于绑定规则动态进行 this 绑定。
有两点强调:
- this 是在运行时绑定的,不是定义时。
- 要看 this 的指向,关键在于看函数调用的位置和方式。
根据函数调用位置和方式不同,有四种绑定策略。
默认绑定
如果直接调用函数,则将 this 绑定到全局对象,在 Node 中是 global,在浏览器中是 window。
function foo() {
console.log(this)
}
// 运行时绑定
foo()
隐式绑定
如果有上下文对象调用函数,则将 this 绑定到上下文对象。
const obj = {
foo: function() {
console.log(this)
},
}
// 运行时绑定
obj.foo()
显式绑定
通过调用函数的 call,apply,bind 方法进行绑定。
function foo() {
console.log(this)
}
const obj = { a: 1 }
// 运行时绑定
foo.call(obj)
foo.apply(obj)
foo.bind(obj)()
new绑定
new 的过程也叫函数的“构造调用”,经历了以下几个步骤:
- 创建一个空对象 obj
- 将对象 obj 的
__proto__
关联到构造函数的prototype
- 将对象绑定到构造函数的 this,执行构造函数
- 如果构造函数没有返回对象,则返回对象 obj
function Foo() {
console.log(this)
}
// 运行时绑定
foo = new Foo()
call和apply实现
显示绑定中的call和apply可以指定this对象进行函数执行,下面来模拟实现一下。
call和apply的区别在于传参,call传递可变参数,apply传递参数数组,其它都一样。所以我们重点实现call,apply就调用call就行了。
fn.call(obj, 'zhangsan', 18)
fn.apply(obj, ['zhangsan', 18])
apply其实传递的是类数组对象,比如fn.apply(obj, { 0: 'zhangsan', 1: 18, length: 2 })这种也行。
思路很简单,我们看上面四种绑定中,只有隐式绑定能派上用场,所以显示绑定就要依赖隐式绑定来进行。
Function.prototype.myCall = function (thisArg, ...argArray) {
const context = thisArg || window; // 防御代码
context.fn = this;
const result = context.fn(...argArray); // 核心就是用隐式绑定的方式调用函数
delete context.fn;
return result;
};
Function.prototype.myApply = function (thisArg, argArray) {
argArray = argArray || [];
if (!Array.isArray(argArray)) throw 'argArray is not Array';
return this.myCall(thisArg, ...argArray); // apply直接调用call就行了
};
bind实现
bind会将函数和指定this绑定,然后返回一个新的函数。既然返回一个新的函数,那自然就和闭包
联系在一起,bind的核心就是返回一个闭包,通过闭包引用自由变量的方式替换this。
Function.prototype.myBind = function (thisArg, ...argArray) {
const fn = this;
return function () { // 核心就是返回一个闭包
return fn.myCall(thisArg, ...argArray); // 将thisArg和原函数fn作为自由变量进行引用
};
};