深入解析JavaScript中this的四种绑定规则
本文详细解析JavaScript中this的四种绑定规则:默认绑定、隐式绑定、显示绑定(call/apply/bind)以及new绑定。通过调用栈分析确定调用位置,结合箭头函数的词法作用特性,深入探讨this指向的动态变化,并解析常见前端面试题中的this指向逻辑。
执行上下文
执行上下文主要分为两类:全局上下文和函数上下文。全局上下文仅存在一个,而函数上下文则在函数被调用时动态创建。
每个执行上下文都包含三个核心属性:
- 变量对象
- 作用域链
- this
this到底是什么呢
this的绑定发生在运行时,而非代码编写阶段。其指向取决于函数被调用时的具体条件,与函数声明的位置无关,仅由调用方式决定。
调用位置
理解this绑定的关键在于确定“调用位置”,即函数实际被调用的位置,而非其声明位置。为此,我们需要分析调用栈(执行上下文栈)。
function baz() {
console.log("baz")
bar()
}
function bar() {
console.log("bar")
foo()
}
function foo() {
console.log("foo")
}
baz()当代码执行到foo()时,进入foo函数体,此时当前的调用栈为:
ECStack = [
fooContext, // foo
barContext, // bar
bazContext, // baz
globalContext, // 全局
]通过调用栈可以清晰地定位函数的调用位置。例如,baz在全局环境中被调用,bar在baz内部被调用,而foo又在bar内部被调用。
那么,函数在执行时究竟如何确定this的绑定对象呢?
绑定规则
this的绑定对象由特定的绑定规则决定。
默认绑定
最常见的调用类型是独立函数调用。
function foo(){
console.log(this.a) // 2
}
var a = 2;
foo()当函数被调用时,若应用了默认绑定规则,this将指向全局对象。
如何判断应用了默认绑定?通过分析调用位置即可。如果foo()是直接使用不带任何修饰的函数引用进行调用,则只能应用默认绑定,无法应用其他规则。
因此,在全局环境中调用一个函数,函数内部的this指向全局变量window。
隐式绑定
function foo() {
console.log( this.a );
}
var obj = {
a: 2,
foo: foo
};
obj.foo(); // 2在上述代码中,foo的声明位置虽在全局,但它被作为引用属性添加到了obj对象中。调用位置使用obj上下文引用该函数。当foo被调用时,它被obj对象所包含,落脚点指向obj对象。当函数引用拥有上下文对象时,隐式绑定规则会将函数调用中的this绑定到这个上下文对象。
通过一个对象调用其内部的方法,该方法的执行上下文中的this指向对象本身。
我们看个特殊的例子
function foo() {
console.log(this.a)
}
var obj = {
a: 2,
foo
}
var bar = obj.foo
var a = "mick"
bar() // mickbar是obj.foo的一个引用,但它实际引用的是foo函数本身。因此,此时bar()是一个不带任何修饰的函数调用,应用了默认绑定规则。
我们再看另一种情况
function foo() {
console.log(this.a)
}
function doFoo(fn) {
fn()
}
var obj = {
a: 2,
foo
}
var a = "mick"
doFoo(obj.foo) // mick
嵌套函数中的this不会从外层函数中继承。this永远指向最后调用它的那个对象。
显示绑定
可以使用call、apply或bind方法。如果对这三个方法的实现原理感兴趣,可以参考相关手写实现文章。
function foo() {
console.log(this.a)
}
var obj = {
a: 2
}
foo.call(obj) // 2通过call方法,可以在调用foo时,强制将它的this绑定到obj上。
new绑定
这里我们先说明使用new调用函数时会发生哪些事情:
- 创建一个全新的对象
- 这个新对象会被执行[[原型]]链接
- 这个新对象会绑定到函数调用的this
- 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象。
function foo(a){
this.a = a
}
var bar = new foo(2)
console.log(bar.a)使用new来调用foo时,我们会构造一个新对象,并将其绑定到foo调用中的this上。
特例
function foo() {
console.log(this.a)
}
var a = 2
var o = { a: 3, foo: foo }
var p = { a: 4 }
o.foo() // 3
;(p.foo = o.foo)() // 2赋值表达式p.foo = o.foo的返回值是目标函数的引用,因此调用位置实际上是foo(),而不是p.foo()或o.foo()。所以这里应用的是默认绑定规则。
面试题
下面我们看个面试题吧
var name = 'window'
var person1 = {
name: 'person1',
foo1: function () {
console.log(this.name)
},
foo2: () => console.log(this.name),
foo3: function () {
return function () {
console.log(this.name)
}
},
foo4: function () {
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
person1.foo1()
person1.foo1.call(person2)
person1.foo2()
person1.foo2.call(person2)
person1.foo3()()
person1.foo3.call(person2)()
person1.foo3().call(person2)
person1.foo4()()
person1.foo4.call(person2)()
person1.foo4().call(person2)
我们一个个来解析一下。
person.foo1()这个属于隐式绑定,foo1的this绑定到了person,所以打印person1person1.foo1.call(person2)显示绑定,foo1的this通过call改变了this,指向了person,所以打印person2person.foo2()因为foo2是箭头函数,所以它的this指向是它上一层this的指向,也就是windowperson1.foo2.call(person2)和上面一条同样的道理,也是windowperson1.foo3()()内部返回了一个函数,其实是一个函数的引用,此时this应该指向window,所以打印windowperson1.foo3.call(person2)()通过call只是改变了foo3的this指向,和返回的函数没有什么关系,所以this还是指向window,打印windowperson1.foo3().call(person2)通过call改变了foo3内部返回函数的this指向,所以打印person2person1.foo4()()返回的是一个箭头函数,箭头函数的this是它上一层函数内部this的指向,所以也就是foo4this的指向。由于foo4被person1包含并调用,所以this指向person1,打印person1person1.foo4.call(person2)(),此时foo4内部的this通过call改变成了person2,所以打印personperson1.foo4().call(person2)这个和person1.foo4()是一样的道理,打印person1
简单的谈了谈this的绑定,欢迎留言你的问题,大家一起学习一起进步!!!
到此这篇关于JavaScript中this的绑定你知道几种?的文章就介绍到这了,更多相关JavaScript this绑定内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
