深入理解 JavaScript 中的 this 绑定机制:从原理到实战

为什么要读这篇文章

在日常开发中,你是否遇到过这些困惑:

  • 为什么同一个函数,在不同地方调用,this 指向完全不同?
  • 箭头函数的 this 为什么"不听话"?
  • 面试官问"this 的绑定规则优先级"时,如何系统回答?

this 是 JavaScript 中最容易被误解的概念之一。它不像其他语言那样简单地指向"当前对象",而是具有动态绑定的特性。掌握 this 的核心规则,不仅能让你写出更优雅的代码,还能在排查 bug 时快速定位问题。

本文收益

  • 掌握 this 的 4 种绑定规则及其优先级
  • 理解常见场景下的 this 指向(事件监听、定时器、数组方法等)
  • 学会手写 call/apply/bind 实现
  • 建立完整的 this 知识体系,应对各种边界情况

一、this 的本质:动态绑定的执行上下文

1.1 什么是 this

this 是函数执行时指向"当前执行上下文"的对象引用

这句话包含三个关键信息:

  1. 执行时确定:this 的值在函数被调用时才确定,而非定义时
  2. 执行上下文:每次函数调用都会创建一个函数执行上下文(FEC),this 是其中的一个属性
  3. 动态绑定:同一个函数在不同调用方式下,this 可能指向不同对象

1.2 为什么需要 this

在面向对象编程中,Java、C++ 等语言的 this 通常只出现在类的实例方法中,指向当前实例。但 JavaScript 的 this 更加灵活,这种灵活性既是优势也是挑战。

使用 this 的核心价值

javascript 复制代码
// 不使用 this:代码耦合度高
var obj = {
  name: "小吴",
  eating: function() {
    console.log(obj.name + "在吃东西");
  },
  running: function() {
    console.log(obj.name + "在跑步");
  }
}

// 使用 this:代码可复用性强
var obj = {
  name: "小吴",
  eating: function() {
    console.log(this.name + "在吃东西");
  },
  running: function() {
    console.log(this.name + "在跑步");
  }
}

obj.eating()  // 小吴在吃东西
obj.running() // 小吴在跑步

对比分析

不使用 this 的问题:

  • 方法内部硬编码了对象名称(obj.name
  • 无法复用方法到其他对象
  • 对象重命名时需要修改所有方法内部代码

使用 this 的优势:

  • 方法与具体对象解耦,提高可维护性
  • 同一套方法可以被多个对象共享
  • 符合面向对象的封装原则

1.3 全局作用域中的 this

在深入绑定规则前,先了解全局 this 的特殊性:

  • 浏览器环境:this 指向 window 对象
  • Node.js 环境:this 指向空对象 {}
javascript 复制代码
// 浏览器环境
console.log(this === window); // true

// Node.js 环境
console.log(this); // {}
console.log(this === module.exports); // true
console.log(this === global); // false

Node.js 中的特殊机制

Node.js 将每个文件视为一个模块,执行时会包装成如下形式:

javascript 复制代码
(function(exports, require, module, __filename, __dirname) {
  // 你的模块代码
  // 顶层 this 被绑定到 module.exports
});

这就是为什么 Node.js 模块顶层的 this 指向 module.exports(初始为空对象),而非 global 对象。

函数内部的 this

javascript 复制代码
function foo() {
  console.log(this);
}

foo.apply("小吴"); // [String: '小吴']

文件被 Node 执行时,会调用 foo.apply({}),将空对象传入作为 this。

1.4 同一函数,不同 this

这是理解 this 的关键案例:

javascript 复制代码
function foo() {
  console.log(this);
}

// 1. 直接调用
foo() // window(浏览器)或 global(Node.js 非严格模式)

// 2. 对象方法调用
var obj = {
  name: "小吴",
  foo: foo
}
obj.foo() // { name: '小吴', foo: [Function: foo] }

// 3. 显式绑定
foo.apply("XiaoWu") // [String: 'XiaoWu']

** 图8-3 函数的三种调用方式效果**

核心结论

  1. this 的绑定与函数定义位置无关
  2. this 的绑定与函数调用方式和调用位置有关
  3. this 是在运行时动态绑定的

执行上下文中的 this

** 图8-4 函数调用内存图**

在函数执行上下文(FEC)中,除了作用域链、变量对象(AO)等,还包含 this 绑定。

二、this 的四种绑定规则

掌握 this 的核心在于理解这四种绑定规则。只有显式绑定可以人为改变 this 指向,其他三种规则的 this 指向是固定的。

2.1 规则一:默认绑定

适用场景:独立函数调用(函数没有被绑定到任何对象上)

绑定结果

  • 非严格模式:指向全局对象(浏览器为 window,Node.js 为 global)
  • 严格模式:指向 undefined

案例 1:最基础的独立调用

javascript 复制代码
function foo() {
  console.log(this);
}

foo() // window(浏览器)

案例 2:函数调用链中的独立调用

javascript 复制代码
function foo1() {
  console.log("foo1", this);
}

function foo2() {
  console.log("foo2", this);
  foo1()
}

function foo3() {
  console.log("foo3", this);
  foo2()
}

foo3()
// 输出:
// foo3 window
// foo2 window
// foo1 window

** 图8-5 案例2代码结果**

虽然函数之间有调用关系,但每个函数都是独立调用的,因此 this 都指向 window。

案例 3:对象方法赋值后的独立调用

javascript 复制代码
var obj = {
  name: "小吴",
  foo: function() {
    console.log(this);
  }
}

var fn = obj.foo
fn() // window

关键理解:this 指向与函数定义位置无关,只与调用方式有关。虽然 foo 定义在 obj 中,但 fn() 是独立调用,因此 this 指向 window。

案例 4:函数引用的独立调用

javascript 复制代码
function foo() {
  console.log(this);
}

var obj = {
  name: "小吴",
  foo: foo
}

var bar = obj.foo
bar() // window

与案例 3 本质相同,bar 获取的是函数引用,调用时是独立调用。

案例 5:闭包中的独立调用

javascript 复制代码
function foo() {
  function bar() {
    console.log(this);
  }
  return bar
}

var fn = foo()
fn() // window

// 改变调用方式后
var obj = {
  name: "why",
  age: fn
}

obj.age() // { name: 'why', age: [Function: bar] }

闭包函数的 this 不是固定指向 window,而是取决于调用方式。这打破了"闭包必定指向 window"的误解。

小结

  • 默认绑定的判断标准:函数是否独立调用(没有通过对象调用,没有使用 call/apply/bind,没有使用 new)
  • 独立调用的 this 指向全局对象(非严格模式)或 undefined(严格模式)
  • 函数定义位置不影响 this,只有调用方式才影响

2.2 规则二:隐式绑定

适用场景:通过对象调用方法(obj.method())

绑定结果:this 指向调用该方法的对象

核心原则:哪个对象发起的方法调用,this 就指向谁。

案例 1:基础隐式绑定

javascript 复制代码
function foo() {
  console.log(this);
}

var obj = {
  name: "why",
  foo: foo
}

obj.foo() // { name: 'why', foo: [Function: foo] }

** 图8-6 隐式绑定案例1效果图**

JavaScript 引擎会将 obj 对象绑定到 foo 函数的 this 中。

案例 2:方法中使用 this

javascript 复制代码
var obj = {
  name: "小吴",
  eating: function() {
    console.log(this.name + "在吃东西");
  },
  running: function() {
    console.log(this.name + "在跑步");
  }
}

obj.eating()  // 小吴在吃东西
obj.running() // 小吴在跑步

// 解除绑定关系
var fn = obj.eating
fn() // undefined在吃东西(this.name 为 undefined)

** 图8-7 obj与eating绑定关系解除前后对比**

一旦解除对象与方法的绑定关系,this 指向就会改变。

案例 3:多层对象调用

javascript 复制代码
var obj1 = {
  name: "obj1",
  foo: function() {
    console.log(this);
  }
}

var obj2 = {
  name: "obj2",
  bar: obj1.foo
}

obj2.bar() // { name: 'obj2', bar: [Function: foo] }

** 图8-8 案例3控制台打印结果**

虽然 bar 引用的是 obj1.foo,但调用时是通过 obj2 发起的,因此 this 指向 obj2。

小结

  • 隐式绑定的判断标准:函数是否通过对象调用(obj.method())
  • this 指向最后调用该方法的对象
  • 赋值操作会丢失隐式绑定,转为默认绑定

2.3 规则三:显式绑定

适用场景:使用 call、apply、bind 方法主动指定 this

绑定结果:this 指向传入的第一个参数对象

隐式绑定是"被动"的,需要对象内部有函数引用才能绑定。显式绑定则是"主动"的,可以直接指定 this 指向。

2.3.1 call 和 apply 的使用

call 语法func.call(thisArg, arg1, arg2, ...) apply 语法func.apply(thisArg, [argsArray])

核心区别:参数传递方式不同

  • call:参数逐个传递
  • apply:参数以数组形式传递
javascript 复制代码
function sum(num1, num2) {
  console.log(num1 + num2, this)
}

sum.call("call", 20, 30)   // 50 [String: 'call']
sum.apply("apply", [20, 30]) // 50 [String: 'apply']

与直接调用的区别

javascript 复制代码
function foo() {
  console.log("函数被调用了", this);
}

var obj = {
  name: "why"
}

foo()              // window
foo.apply("小吴")  // [String: '小吴']
foo.call(obj)      // { name: 'why' }

** 图8-9 直接调用与apply、call调用的不同**

2.3.2 bind 的使用

当需要多次使用相同的 this 绑定时,bind 比 call/apply 更方便。

bind 语法func.bind(thisArg[, arg1[, arg2[, ...]]])

特点

  • 返回一个新函数,不会立即执行
  • 新函数的 this 被永久绑定到指定对象
  • 可以预设部分参数(柯里化)
javascript 复制代码
function foo() {
  console.log(this)
}

// 使用 call 需要重复传参
// foo.call("小吴")
// foo.call("小吴")
// foo.call("小吴")

// 使用 bind 只需绑定一次
var newFoo = foo.bind("小吴")
newFoo() // [String: '小吴']
newFoo() // [String: '小吴']

bind 的特殊性

javascript 复制代码
function foo() {
  console.log(this)
}

var newFoo = foo.bind("小吴")
var bar = foo

console.log(bar === foo)    // true
console.log(newFoo === foo) // false

bind 返回的是一个新函数,与原函数不是同一个引用。这证明 bind 不会修改原函数,而是创建一个新的绑定函数。

2.3.3 三者对比

方法 执行时机 参数形式 返回值 使用场景
call 立即执行 逐个传递 函数执行结果 一次性调用,参数较少
apply 立即执行 数组传递 函数执行结果 一次性调用,参数较多或动态参数
bind 不执行 逐个传递 新函数 需要多次调用或延迟执行

小结

  • 显式绑定可以主动改变 this 指向
  • call/apply 立即执行,bind 返回新函数
  • 显式绑定的优先级高于隐式绑定和默认绑定

2.4 规则四:new 绑定

适用场景:使用 new 关键字调用函数(构造函数)

绑定结果:this 指向新创建的对象

new 的执行过程

  1. 创建一个全新的对象
  2. 将这个对象的原型指向构造函数的 prototype
  3. 将 this 绑定到这个新对象
  4. 执行构造函数代码
  5. 如果构造函数没有返回对象,则返回这个新对象
javascript 复制代码
function Person(name, age) {
  this.name = name
  this.age = age
}

Person() // 普通调用,this 指向 window

var p1 = new Person("小吴", 20)
console.log(p1.name, p1.age) // 小吴 20

var p2 = new Person("why", 35)
console.log(p2.name, p2.age) // why 35

** 图8-10 正常调用与new调用区别**

使用 new 调用时,JavaScript 会创建一个新对象并将其绑定到函数的 this 上。

小结

  • new 绑定会创建新对象并绑定到 this
  • 构造函数只是使用 new 调用的普通函数
  • new 绑定的优先级高于隐式绑定

三、常见场景中的 this 分析

3.1 setTimeout 定时器

javascript 复制代码
// 普通函数
setTimeout(function() {
  console.log("普通函数的this", this); // window(浏览器)或 global(Node.js)
}, 1000)

// 箭头函数
setTimeout(() => {
  console.log("箭头函数的this", this); // 取决于外层作用域
}, 2000)

** 图8-11 node环境下的结果**

原理:setTimeout 内部不会绑定特定的 this,回调函数是独立调用,因此遵循默认绑定规则。

3.2 DOM 事件监听

javascript 复制代码
const boxDiv = document.querySelector(".box")

// 方式1:onclick(只能绑定一个)
boxDiv.onclick = function() {
  console.log(this); // boxDiv 元素对象
}

// 方式2:addEventListener(可以绑定多个)
boxDiv.addEventListener('click', function() {
  console.log(this); // boxDiv 元素对象
})

** 图8-12 监听的对象**

原理 :浏览器内部会使用 fn.call(boxDiv) 的方式调用回调函数,将 DOM 元素绑定到 this。

3.3 数组高阶函数

javascript 复制代码
var names = ["ABC", '小吴', 'why']

// 不传第二个参数
names.forEach(function(item) {
  console.log("item", this); // window(三次)
})

// 传入第二个参数绑定 this
names.forEach(function(item) {
  console.log("item", this); // [String: '小吴'](三次)
}, "小吴")

** 图8-13 forEach不加第二个参数**

** 图8-14 forEach加第二个参数**

常见数组方法的 this 绑定

javascript 复制代码
names.forEach(function() {
  console.log("forEach", this);
}, "小吴")

names.map(function() {
  console.log("map", this);
}, "小吴")

names.filter(function() {
  console.log("filter", this);
}, "小吴")

names.find(function() {
  console.log("find", this);
}, "小吴")

** 图8-16 forEach map filter find高阶函数对比情况**

** 图8-15 编辑器提供的语法提示**

实战建议

  • 大多数数组方法的最后一个参数用于绑定 this
  • 使用 TypeScript 或现代编辑器可以看到参数提示
  • 不需要死记硬背,看 API 文档或编辑器提示即可

四、this 绑定规则的优先级

当多个规则同时适用时,需要了解优先级来判断最终的 this 指向。

4.1 优先级排序

从高到低:new 绑定 > 显式绑定 > 隐式绑定> 默认绑定

4.2 优先级验证

1. 显式绑定 > 隐式绑定

javascript 复制代码
var obj = {
  name: "小吴",
  foo: function() {
    console.log(this);
  }
}

obj.foo() // { name: '小吴', foo: [Function: foo] }

// call/apply 优先级更高
obj.foo.call("我是why") // [String: '我是why']

// bind 优先级更高
var bar = obj.foo.bind("小吴666")
bar() // [String: '小吴666']

更明显的对比

javascript 复制代码
function foo() {
  console.log(this)
}

var obj1 = {
  name: "这是bind更明显的比较",
  foo: foo.bind("why")
}

obj1.foo() // [String: 'why']

虽然通过 obj1 调用(隐式绑定),但 foo 已经被 bind 绑定(显式绑定),最终 this 指向 "why"。

2. new 绑定 > 隐式绑定

javascript 复制代码
var obj = {
  name: "why的JS高级课程很不错,强烈推荐来看",
  foo: function() {
    console.log(this);
  }
}

var f = new obj.foo() // foo {}
obj.foo() // { name: '...', foo: [Function: foo] }

** 图8-17 new绑定优先级高于隐式绑定**

3. new 绑定 > 显式绑定(bind)

注意:new 不能与 call/apply 一起使用(都是立即调用函数),只能与 bind 比较。

javascript 复制代码
function foo() {
  console.log(this);
}

var bar = foo.bind("测试一下")
bar() // [String: '测试一下']

var obj = new bar() // foo {}

new 调用时会找到原函数(foo),将其作为构造函数,创建新对象并绑定到 this。

4.3 优先级总结表

绑定类型 描述 优先级 判断方式
new 绑定 使用 new 关键字调用 最高 new func()
显式绑定 call/apply/bind 中高 func.call(obj)
隐式绑定 对象方法调用 中低 obj.func()
默认绑定 独立函数调用 最低 func()

记忆技巧:越主动的绑定方式,优先级越高。

五、特殊情况与边界处理

5.1 忽略显式绑定

当 call/apply/bind 传入 null 或 undefined 时,会被忽略,应用默认绑定规则。

javascript 复制代码
function foo() {
  console.log(this);
}

foo()                // window
foo.apply(null)      // window
foo.apply(undefined) // window

使用场景

  • 不关心 this 指向,只想使用 apply 传递数组参数
  • 使用 bind 进行柯里化,不需要绑定 this

安全实践 :传入空对象 Object.create(null) 代替 null,避免意外修改全局对象。

5.2 间接函数引用

赋值表达式返回的是函数引用,调用时属于独立调用。

javascript 复制代码
var obj1 = {
  name: "obj1",
  foo: function() {
    console.log(this);
  }
}

var obj2 = {
  name: "obj2"
}

obj2.foo = obj1.foo
obj2.foo() // { name: 'obj2', foo: [Function: foo] }

// 间接引用
(obj2.foo = obj1.foo)() // window

(obj2.foo = obj1.foo) 返回函数引用,然后立即调用,属于独立调用。

代码规范提醒

javascript 复制代码
var obj2 = {
  name: "obj2"
}
(obj2.foo = obj1.foo)()
// 如果 obj2 后面没有分号,会被解析为:
// var obj2 = { name: "obj2" }(obj2.foo = obj1.foo)()
// 导致错误

解决方案:在对象字面量后加分号,或使用 ESLint 等工具强制规范。

5.3 经典测试题

javascript 复制代码
function foo(el) {
  console.log(el, this);
}

var obj = {
  id: "XiaoWu"
};

[1, 2, 3].forEach(foo, obj)
// 报错:Uncaught TypeError: Cannot read properties of undefined

问题原因 :JavaScript 解析器将 [1,2,3] 解释为访问 obj 的属性。

解决方案

javascript 复制代码
// 方案1:使用变量
var names = [1, 2, 3]
names.forEach(foo, obj)

// 方案2:在 obj 后加分号
var obj = {
  id: "XiaoWu"
};
[1, 2, 3].forEach(foo, obj)

这是 JavaScript 自动分号插入(ASI)机制的经典陷阱。

六、实战应用与最佳实践

6.1 判断 this 的决策树

在实际开发中,按以下顺序判断 this 指向:

javascript 复制代码
1. 函数是否使用 new 调用?
   → 是:this 指向新创建的对象

2. 函数是否通过 call/apply/bind 调用?
   → 是:this 指向传入的第一个参数(null/undefined 除外)

3. 函数是否通过对象调用(obj.method())?
   → 是:this 指向该对象

4. 以上都不是?
   → 默认绑定:非严格模式指向全局对象,严格模式为 undefined

6.2 常见陷阱与解决方案

陷阱 1:事件回调中丢失 this

javascript 复制代码
class Button {
  constructor(text) {
    this.text = text
  }

  handleClick() {
    console.log(this.text)
  }
}

const btn = new Button("点击我")
document.querySelector(".btn").addEventListener('click', btn.handleClick)
// 点击后输出 undefined,因为 this 指向 DOM 元素

解决方案

javascript 复制代码
// 方案1:使用 bind
document.querySelector(".btn").addEventListener('click', btn.handleClick.bind(btn))

// 方案2:使用箭头函数
document.querySelector(".btn").addEventListener('click', () => btn.handleClick())

// 方案3:在构造函数中绑定
class Button {
  constructor(text) {
    this.text = text
    this.handleClick = this.handleClick.bind(this)
  }

  handleClick() {
    console.log(this.text)
  }
}

陷阱 2:定时器中的 this

javascript 复制代码
var obj = {
  name: "小吴",
  delayLog: function() {
    setTimeout(function() {
      console.log(this.name) // undefined
    }, 1000)
  }
}

obj.delayLog()

解决方案

javascript 复制代码
// 方案1:保存 this 引用
delayLog: function() {
  var self = this
  setTimeout(function() {
    console.log(self.name) // 小吴
  }, 1000)
}

// 方案2:使用箭头函数(推荐)
delayLog: function() {
  setTimeout(() => {
    console.log(this.name) // 小吴
  }, 1000)
}

// 方案3:使用 bind
delayLog: function() {
  setTimeout(function() {
    console.log(this.name) // 小吴
  }.bind(this), 1000)
}

陷阱 3:数组方法中的 this

javascript 复制代码
var obj = {
  name: "小吴",
  friends: ["张三", "李四"],
  printFriends: function() {
    this.friends.forEach(function(friend) {
      console.log(this.name + "的朋友:" + friend)
      // undefined的朋友:张三
      // undefined的朋友:李四
    })
  }
}

解决方案

javascript 复制代码
// 方案1:传入 thisArg 参数
printFriends: function() {
  this.friends.forEach(function(friend) {
    console.log(this.name + "的朋友:" + friend)
  }, this)
}

// 方案2:使用箭头函数(推荐)
printFriends: function() {
  this.friends.forEach(friend => {
    console.log(this.name + "的朋友:" + friend)
  })
}

6.3 团队协作规范建议

1. 代码审查检查点

  • 事件监听器是否正确绑定 this
  • 定时器回调是否需要保持 this 上下文
  • 数组方法回调是否需要访问外层 this

2. 编码规范

  • 优先使用箭头函数处理回调中的 this 问题
  • 避免在构造函数外使用 bind,影响性能
  • 对象字面量后统一加分号,避免 ASI 陷阱

3. TypeScript 辅助

typescript 复制代码
class Component {
  name: string = "组件"

  // 使用箭头函数属性,自动绑定 this
  handleClick = () => {
    console.log(this.name)
  }
}

6.4 性能优化建议

bind 的性能开销

javascript 复制代码
// ❌ 不推荐:每次渲染都创建新函数
render() {
  return <button onClick={this.handleClick.bind(this)}>点击</button>
}

// ✅ 推荐:在构造函数中绑定一次
constructor() {
  this.handleClick = this.handleClick.bind(this)
}

// ✅ 推荐:使用箭头函数属性
handleClick = () => {
  // ...
}

call/apply 的选择

  • 参数少于 3 个:使用 call(性能略优)
  • 参数多或动态参数:使用 apply
  • 需要多次调用:使用 bind

七、总结与进阶路线

7.1 核心要点回顾

this 的本质

  • this 是函数执行时的上下文对象引用
  • 在函数调用时动态绑定,与定义位置无关
  • 不同调用方式决定不同的 this 指向

四种绑定规则

  1. 默认绑定:独立调用 → 全局对象或 undefined
  2. 隐式绑定:对象方法调用 → 调用对象
  3. 显式绑定:call/apply/bind → 指定对象
  4. new 绑定:构造函数调用 → 新创建的对象

优先级:new > 显式 > 隐式 > 默认

特殊情况

  • null/undefined 会被忽略,应用默认绑定
  • 间接引用会导致默认绑定
  • 箭头函数不遵循这些规则(继承外层作用域的 this)

7.2 团队落地建议

阶段一:知识普及(1-2 周)

  • 组织内部分享会,讲解 this 的四种规则
  • 整理常见陷阱案例库,供团队参考
  • 在代码审查中重点关注 this 相关问题

阶段二:规范制定(1 周)

  • 制定团队编码规范(箭头函数使用场景、bind 使用时机等)
  • 配置 ESLint 规则,自动检测潜在问题
  • 建立 this 相关的最佳实践文档

阶段三:工具支持(持续)

  • 引入 TypeScript,利用类型系统减少 this 错误
  • 使用现代框架(React Hooks、Vue 3 Composition API)减少 this 依赖
  • 建立单元测试覆盖 this 相关逻辑

阶段四:持续优化(持续)

  • 定期回顾 this 相关 bug,总结经验
  • 更新团队知识库,补充新的边界情况
  • 在新人培训中加入 this 专题

7.3 进阶学习路线

下一步学习内容

  1. 箭头函数深入

    • 箭头函数为什么没有自己的 this
    • 箭头函数的词法作用域绑定
    • 箭头函数的使用场景与限制
  2. 手写实现

    • 手写 call/apply/bind 方法
    • 理解 arguments 对象
    • 实现 new 操作符
  3. 原型与继承

    • 原型链中的 this
    • 继承模式中的 this 处理
    • ES6 class 中的 this
  4. 框架中的 this

    • React 中的 this 绑定策略
    • Vue 中的 this 代理机制
    • 现代框架如何减少 this 依赖

推荐资源

  • 《你不知道的 JavaScript(上卷)》第二部分
  • MDN Web Docs - this 关键字
  • JavaScript.info - 对象方法与 this

7.4 验证学习成果

自测题

  1. 以下代码输出什么?为什么?
javascript 复制代码
var name = "window"
var obj = {
  name: "obj",
  foo: function() {
    return function() {
      console.log(this.name)
    }
  }
}
obj.foo()()
  1. 如何让以下代码正确输出 "小吴"?
javascript 复制代码
var obj = {
  name: "小吴",
  getName: function() {
    setTimeout(function() {
      console.log(this.name)
    }, 1000)
  }
}
obj.getName()
  1. 以下代码的优先级判断是否正确?
javascript 复制代码
function foo() {
  console.log(this)
}
var obj = {
  foo: foo.bind("bind")
}
new obj.foo() // 输出什么?

答案与解析

  1. 输出 "window"。obj.foo() 返回一个函数,然后独立调用,应用默认绑定。
  2. 使用箭头函数:setTimeout(() => { console.log(this.name) }, 1000)
  3. 输出 foo {}。new 绑定优先级高于显式绑定。

八、写在最后

this 是 JavaScript 中最具争议的特性之一。它的灵活性带来了强大的表达能力,但也增加了理解成本。

关键心态

  • 不要死记硬背,理解背后的执行机制
  • 遇到问题时,按优先级逐一排查
  • 善用工具(TypeScript、ESLint)减少错误
  • 在现代开发中,考虑使用箭头函数或 Hooks 减少对 this 的依赖

实践建议

  • 在真实项目中刻意练习 this 的判断
  • 遇到 bug 时,先检查 this 指向是否正确
  • 代码审查时,关注 this 相关的潜在问题
  • 定期回顾本文,加深理解

掌握 this 不是终点,而是深入理解 JavaScript 执行机制的起点。接下来,我们将探讨箭头函数、手写实现 call/apply/bind,以及原型链等更深入的话题。

持续学习,保持好奇心,我们下期见!

相关推荐
Json_Lee2 小时前
2026 年了,多 Agent 编码该怎么选?agent-team vs Claude Agent Teams vs Claude Squad vs Met
前端·后端·vibecoding
Novlan12 小时前
Stepper 小数输入精度丢失 Bug 修复
前端
陈随易2 小时前
刚上市就断货?如此火爆的编程显示器到底有什么魔力
前端·后端·程序员
兆子龙2 小时前
前端哨兵模式(Sentinel Pattern):优雅实现无限滚动加载
前端·javascript·算法
豆苗学前端2 小时前
彻底讲透浏览器渲染原理,吊打面试官
前端·javascript·面试
踩着两条虫3 小时前
AI 驱动的 Vue3 应用开发平台 入门指南(五):创建 H5 移动应用
前端·vue.js·ai编程
ZengLiangYi3 小时前
用 AudioContext.suspend()/resume() 作为流式音视频的同步门控
前端·音视频开发
进击的尘埃3 小时前
可视化搭建引擎的撤销重做系统:Command 模式 + Immutable 快照实现操作历史树
javascript
踩着两条虫3 小时前
AI 驱动的 Vue3 应用开发平台 入门指南(二):快速入门
前端·vue.js·ai编程