彻底搞懂 JavaScript 中的 this 绑定规则与优先级

作者:拾光

时间:2026-10-26

浏览:501

本文深入解析 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 绑定规则的资料请关注本站其它相关文章!

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章