引言:初期学习 JS 的时候,通常会对以下的问题存在疑惑
1:写 JS 时,this 为啥时而指向 window,时而指向当前对象?
2:箭头函数的 this 为啥 "不听话"?和普通函数到底差在哪?
3:call/apply/bind 改 this 指向,该怎么选才不踩坑?
一、this 是什么
首先,我们要明白一点,this 不是 "指向函数自身"
this 是函数执行时的 "上下文对象"
在实际应用时, this 具体代表的时谁,看的是 this 被谁调用
this 是一个代词,用在不同的地方代表不同的值
1.如果 this 被用在全局,在浏览器环境下,this 指向的其实是 window
js
function fn() { console.log(this); }
fn(); // 浏览器环境下 this 指向 window
2.如果 this 在被函数调用时,涉及 this 的绑定规则
二、this的绑定规则
1. 默认绑定
当函数被独立调用时,函数中的 this 指向 window
例如
js
function fn() { console.log(this); }
fn(); // 浏览器环境下 this 指向 window
又如
js
var a = 1
function foo() {
console.log(this.a); // 1
}
再如
js
var a = 1
function foo() {
console.log(this.a);
}
function bar () {
var a = 2
foo()
}
bar() // 浏览器为 1, node 为 undefined
注意:这里全局作用域下的 var a = 1 ,其实等效于 window.a , 而 Node.js 中模块内 var 声明的变量不挂载到 global
2.隐式绑定
当函数引用有上下文对象且被该对象调用时,函数中的 this 会绑定 到这个上下文对象上
例如
js
const foo = {
a: 1,
bar: function() {
console.log(this.a);
}
}
foo.bar() // 1
只有这种写法,函数作为属性值被调用,才叫被函数调用
3.隐式丢失
当一个函数被多层对象调用时,函数的 this 指向最近的那个对象
例如
js
function foo() {
console.log(this.a);
}
var obj = {
a: 1,
foo: foo
}
var obj2 = {
a: 2,
foo: obj
}
obj2.foo.foo() // 1
有点像英语里的就近原则
4. 显示绑定
- fn.call(obj,x,y) 显示的将 fn 里面的 this 绑定到 obj 这个对象上, call 负责帮 fn 接受参数
- fn.apply(obj,[x,y])
- fn.bind(obj,x,y)()
常见的就是这几种,相当于是强行掰弯 this 到别人身上
现在来介绍他们的写法
1.call
js
// 带参数的函数
const fn = function(b, c) {
console.log(this.a + b + c);
};
// 最简调用:this + 零散参数
fn.call({a: 2}, 3, 4); // 输出 9(2+3+4)
2.bind
返回的是一个函数,需要人为调用
js
const fn1 = function(b, c) {
console.log(this.a + b + c);
};
// 写法1:先绑定(this+预设参数),后传剩余参数
const boundFn = fn.bind({a: 2}, 3);
boundFn(4); // 输出 9(2+3+4)
// 写法2:极致简洁(绑定+调用一行完成)
fn1.bind({a: 2}, 3, 4)(); // 输出 9
3.apply
延迟执行,属于异步
apply 只传 "必须的"------ 绑定的 this + 一维数组参数,数组能字面量就不临时变量
js
const fn2 = function(b, c) {
console.log(this.a + b + c);
};
// 场景1:参数是现成数组(最简)
const params = [3, 4];
fn2.apply({a: 2}, params); // 输出 9(2+3+4)
// 场景2:参数是临时数组(字面量写法,一行完成)
fn2.apply({a: 2}, [3, 4]); // 输出 9
汇总而言就是:
-
call:参数逐个传递,调用后立即执行
-
apply:参数以数组传递,调用后立即执行(适配参数不确定场景)
-
bind:参数逐个传递,返回新函数(延迟执行,适配定时器 / 事件)
-
一句话区分:call/apply 立即执行,bind 延迟执行;call 散传参数,apply 传数组
三、 new 绑定
就像前面文章
[JS 原型与原型链"为什么构造函数 new 出来的实例,都能用同一个方法?" 这背后就是 "原型 + 原型链" 的复用逻 - 掘金](https://juejin.cn/post/7605051978872045587 "https://juejin.cn/post/7605051978872045587")
所介绍的,详细讲述了 new 内部的 this 工作原理,简单来说就是
1.new 的原理会导致函数的 this 指向实例对象
2.当构造函数中存在 return ,并且 return 的是一个引用类型的数据,则 new 的返回失效
四、箭头函数的 this
简单来说,就两句话:
箭头函数中没有 this 这个概念,写在了箭头函数中的 this ,也是它外层那个非箭头函数的 this
箭头函数继承的外层 this 无法修改