本文深入解析 JavaScript 中 this 的四种绑定规则:默认绑定、隐式绑定、new 绑定与显示绑定,并详细阐述它们的优先级顺序。通过代码示例说明回调函数、严格模式及箭头函数对 this 的影响,帮助开发者准确判断运行时的 this 指向。
前言
首先观察一个示例。定义一个函数,通过三种不同的方式调用,会产生三种不同的结果。
function foo(){
console.log(this);
}
foo() //window
let obj ={
name:'sss',
foo:foo
}
obj.foo()//{name:'sss',foo:function}
foo.call("abc") // 生成包装类型对象,String {'abc'}同一个函数因调用方式不同,this 的指向也会随之变化。为何会出现这种看似复杂的现象?原因在于 JavaScript 中函数的 this 是动态绑定的(箭头函数除外)。经过对 this 机制的研究,总结出以下核心结论:
- 函数在调用时,JavaScript 引擎会默认给 this 绑定一个值。
- this 的绑定结果与代码编写的位置无关。
- this 的绑定取决于调用的方式以及调用的位置。
- this 的绑定发生在运行时,而非定义时。
既然 this 的绑定与调用方式和调用位置相关,我们该如何根据这两点来确定 this 的具体指向呢?
近期阅读《你不知道的 JavaScript》一书,让我对 JS 有了更深入的理解,书中也详细探讨了 this 绑定问题。有兴趣的读者推荐阅读该书,此处不再赘述。下面重点讨论如何确定 this 指向,这是结合该书及网络资料总结出的个人理解,旨在帮助彻底理清 this 指向的逻辑。
this 的四大绑定规则
一、默认绑定
独立函数调用属于默认绑定。简单来说,当函数没有绑定到某个特定对象上进行调用时,this 默认指向全局对象 window(注意:在严格模式下,this 指向 undefined)。
举例说明:
function foo(){
console.log(this);
}
foo() // 独立函数调用
let obj ={
name:'sss',
bar:function(){
console.log(this);
}
}
let ff = obj.bar
ff() // 函数在对象中,但是独立函数独立函数调用
// 高阶函数
function test(fn){
fn()
}
test(obj.bar) //相当于fn=obj.bar fn() 所以还是独立函数调用
上述代码的输出结果均为 window。

二、隐式绑定
在调用位置中,通过对象属性发起的调用,this 会指向调用该方法的对象本身。
举例说明:
let obj ={
name:'sss',
bar:function(){
console.log(this);
}
}
obj.bar() // 通过对象调用,结果是 obj
存在一种更微妙但更常见的情况,即函数作为回调函数传入时:
function foo(){
console.log(this);
}
setTimeout(foo,1000) //Window
let btn = document.querySelector('button')
btn.addEventListener('click',foo) //button
let ar = [1,2,3,4]
ar.forEach(item=>console.log(this)) //Window
在 setTimeout 内部绑定的 this 是 window,btn 事件监听器中的 this 指向按钮元素,而 forEach 方法中的 this 指向 Window。
三、new 绑定
JavaScript 中的函数也可以作为构造函数使用,即通过 new 关键字进行调用。
回顾一下 new 关键字执行的操作:
- 创建一个全新的对象。
- 将该新对象与构造函数的原型 [[prototype]] 进行连接。
- 将这个新对象绑定到 this 上。
- 如果构造函数没有显式返回其他对象,则默认返回这个新创建的对象。
代码示例:
function person(name) {
this.name =name
console.log(this);
}
new person('sss') // person{name:'sss'}
四、显示绑定
所谓显示绑定,即通过 call、bind、apply 等方法强制指定 this 的指向。
显示绑定即通过特定方法强制绑定,此时 this 指向被强制指定的对象。代码示例如下:
// 显示绑定
function foo(){
console.log(this);
}
let obj ={
name:'sss'
}
// 此时我们希望this指向obj 如果是正常情况下该这么指向呢
// 1
obj.foo = foo
obj.foo() // obj
// 但是我们希望直接绑定 就可以通过call 来进行显示绑定
foo.call(obj) //obj
foo.call("123") //"123"
foo.call(123) //123
foo.call(undefine) // 特例:window
call、apply、bind 方法简介:
相同点:
- 三者均用于改变 this 的指向。
- 第一个参数均指定 this 要指向的对象。
- 三者均可利用后续参数传递其他参数。
区别:
- call 和 bind 的参数是依次传递,一一对应。
- apply 仅接受两个参数,第二个参数必须为数组。
- call 和 apply 会直接调用函数,而 bind 方法返回的仍是一个新的函数。
绑定规则的优先级
了解了 this 的四种绑定规则后,关键在于如何应用它们。
如同运算符存在优先级,this 的四种绑定规则也有明确的优先级顺序,下面介绍其判断逻辑。
直接给出结论,判断 this 指向的顺序如下:
- 函数是否在 new 中调用(new 绑定)?如果是,this 绑定的是新创建的对象。
- 函数是否通过 call、apply、bind 调用(显示绑定)?如果是,this 绑定的是指定的对象。
- 函数是否在某个上下文对象中调用(隐式绑定)?如果是,this 绑定的是那个上下文对象。
- 如果以上都不是,则使用默认绑定,this 绑定到全局对象(严格模式下绑定到 undefined)。
代码示例:
//优先级
function foo(){
console.log(this);
}
let obj = {
name:'ss',
foo:foo
}
obj.foo.call('nn') // 结果是 包装对象nn
// 显然显示大于隐形绑定
new obj.foo() // 结果是foo实例对象
// new绑定大于隐式绑定
// new 和显示绑定绑定不能一起使用
let f = foo.bind(obj)
new f() // 结果依然是foo实例对象 new绑定>显示绑定
let f2 = foo.bind(obj)
f2.call('ss') // 结果是obj对象 bind>call
输出结果:

上述判断顺序几乎适用于所有情况,但需注意一些例外:
1. 显示绑定传入 null 和 undefined
function foo(){
console.log(this);
}
foo.apply("ss") //"ss"
foo.apply(null) //Window
foo.apply(undefined) //Window
当传入 null 或 undefined 时,会忽略显示绑定,转而使用默认规则。
另外,在严格模式下,传入 null 或 undefined 时,this 会保持为基本数据类型(null 或 undefined)。
"use strict"
function foo(){
console.log(this);
}
foo.apply("ss") //"ss"
foo.apply(null) //null
foo.apply(undefined) //undefine
2. 创建函数的间接引用,这种情况同样使用默认绑定规则。
function foo(){
console.log(this);
}
let obj = {
foo:foo
}
(a = obj.foo)() // Window
例如 a = obj.foo 赋值后调用,相当于独立函数调用,因此使用默认规则。
3. 箭头函数
箭头函数内部没有自己的 this,它会沿着作用域链向上查找外层作用域的 this 并使用。
面试题
现在你已经掌握了如何判断 this 指向,通过几道面试题来检验一下吧。
题 1
let name = 'Window';
let Person = {
name:'person',
say:function(){
console.log(this);
}
};
let ss = Person.say;
ss(); // Window
Person.say(); // Person
(Person.say)(); // Person
(b = Person.say)(); // Window
题 2
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 () {
// console.log(this) // 第一个表达式this -> person1
// console.log(this) // 第二个表达式this -> person2
// console.log(this) // 第三个表达式this -> person1
return () => {
console.log(this.name)
}
}
}
var person2 = { name: 'person2' }
// 开始题目:
person1.foo1(); // 隐式绑定: person1
person1.foo1.call(person2); // 显式绑定: person2
person1.foo2(); // 上层作用域: window
person1.foo2.call(person2); // 上层作用域: window
person1.foo3()(); // 默认绑定: window
person1.foo3.call(person2)(); // 默认绑定: window
person1.foo3().call(person2); // 显式绑定: person2
person1.foo4()(); // person1
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1
题 3
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
this.foo1 = function () {
console.log(this.name)
},
this.foo2 = () => console.log(this.name),
this.foo3 = function () {
return function () {
console.log(this.name)
}
},
this.foo4 = function () {
return () => {
console.log(this.name)
}
}
}
// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')
// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2
person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: person1
person1.foo3()() // 默认绑定: window
person1.foo3.call(person2)() // 默认绑定: window
person1.foo3().call(person2) // 显式绑定: person2
person1.foo4()() // 上层作用域查找: person1(隐式绑定)
person1.foo4.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.foo4().call(person2) // 上层作用域查找: person1(隐式绑定)
题 4
var name = 'window'
/*
1.创建一个空的对象
2.将这个空的对象赋值给this
3.执行函数体中代码
4.将这个新的对象默认返回
*/
function Person(name) {
this.name = name
this.obj = {
name: 'obj',
foo1: function () {
return function () {
console.log(this.name)
}
},
foo2: function () {
return () => {
console.log(this.name)
}
}
}
}
var person1 = new Person('person1')
var person2 = new Person('person2')
person1.obj.foo1()() // 默认绑定: window
person1.obj.foo1.call(person2)() // 默认绑定: window
person1.obj.foo1().call(person2) // 显式绑定: person2
person1.obj.foo2()() // 上层作用域查找: obj(隐式绑定)
person1.obj.foo2.call(person2)() // 上层作用域查找: person2(显式绑定)
person1.obj.foo2().call(person2) // 上层作用域查找: obj(隐式绑定)以上是一文搞懂 JavaScript 中 this 绑定规则的详细内容,更多关于 JavaScript this 绑定规则的资料请关注本站其它相关文章!