编辑
2025-08-18
前端
00

目录

参考资料
this 是什么
调用位置
this的绑定规则
默认绑定
隐式绑定
隐式丢失
显式绑定
new 绑定
箭头函数
如何判断函数是否被new调用了?
手写 call、apply、bind
call/apply
bind

深入理解 this 关键字,以及其背后的相关概念

参考资料

  • 《你不知道的JavaScript》- this全面解析

this 是什么

  • this 是一个代词,代指一个对象
  • this 提供了一种更优雅的方式来隐式的传递一个对象引用,可以让代码更加简洁易于复用

调用位置

调用位置就是函数在代码中被调用的位置(而不是声明的位置)。调用位置决定了 this 的绑定

比如下面代码:

js
function baz() { // 当前调用栈是:baz // 因此,当前调用位置是全局作用域 console.log("baz"); bar(); // <-- bar 的调用位置 } function bar() { // 当前调用栈是 baz -> bar // 因此,当前调用位置在 baz 中 console.log("bar"); foo(); // <-- foo 的调用位置 } function foo() { // 当前调用栈是 baz -> bar -> foo // 因此,当前调用位置在 bar 中 console.log("foo"); } baz(); // <-- baz 的调用位置

可以使用浏览器的开发工具查看调用栈




this的绑定规则

this的指向有以下四条特性/规则

  • 默认绑定
  • 隐式绑定
  • 显式绑定
  • new 绑定

默认绑定

当函数被独立调用时,函数的 this 指向 window

独立调用就是像这样:foo() 的调用

比如

js
function foo() { console.log(this.a); } var a = 2; foo(); // 2

如果使用严格模式(strict mode),那么全局对象将无法使用默认绑定,因此 this 会绑定到 undefined

隐式绑定

当函数引用有上下文对象 且被该对象调用时,隐式绑定规则会把函数调用中的 this 绑定到这个上下文对象

js
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo }; obj.foo(); // 2 // 这里就是隐式绑定,foo函数的this绑定到了obj上

需要注意的是,对象属性引用链中只有最顶层或者说最后一层会影响调用位置

比如

js
function foo() { console.log(this.a); } var obj2 = { a: 42, foo: foo, }; var obj1 = { a: 2, obj2: obj2, }; obj1.obj2.foo(); // 42 // 相当于 obj2.foo(),因为只有最后一层会影响调用位置

隐式丢失

隐式绑定的函数可能会丢失绑定对象,也就是说它会应用默认绑定

隐式丢失的几种情况:

  1. 函数别名
js
function foo() { console.log(this.a); } var obj = { a: 2, foo: foo, }; // 注意这里的函数别名,会导致隐式绑定丢失,导致foo函数的this指向全局 var bar = obj.foo; // 函数别名! var a = "oops, global"; // a 是全局对象的属性 bar(); // "oops, global"
  1. 函数作为参数传入,并调用时
js
function foo() { console.log(this.a); } function doFoo(fn) { // fn 其实引用的是 foo fn(); // <-- 调用位置! } var obj = { a: 2, foo: foo, }; var a = "oops, global"; // a 是全局对象的属性 doFoo(obj.foo); // "oops, global"

参数传递其实就是一种隐式赋值,因此我们传入函数时也会被隐式赋值,所以结果和上一个例子一样

显式绑定

  • fn.call(obj, x, x, ...) 将fn函数的this指向obj,并调用,call的剩余参数是fn需要的参数
javascript
function foo(aram) { console.log("foo", param.a); // foo 1 function bar(x, y) { console.log("bar", this.a, x, y); // bar 1 2 3 } bar.call(param, 2, 3); } foo({ a: 1 });
  • fn.apply(obj, [x, x, ...]) 将fn函数的this指向obj,并调用,apply的第二个参数是一个数组
javascript
function foo(param) { console.log("foo", param.a); // foo 1 function bar(x, y) { console.log("bar", this.a, x, y); // bar 1 2 3 } bar.apply(param, [2, 3]); } foo({ a: 1 });
  • fn.bind(obj, x, x, ...)(x, x, ...) 将fn函数的this指向obj,bind会返回一个新的函数,新的函数也可以传递参数
javascript
function foo(param) { console.log("foo", param.a); // foo 1 function bar(x, y) { console.log("bar", this.a, x, y); // bar 1 2 3 } return bar.bind(param, 2); } const bar = foo({ a: 1 }); bar(3);

new 绑定

new 绑定 - this会绑定到新创建的对象上

javascript
function Person(name, age) { // this 指向新创建的对象 this.name = name; this.age = age; // 通过new调用构造函数时,this指向新创建的对象 // 直接调用构造函数时,应用默认绑定规则,this指向全局或undefined console.log(this); } // 使用 new 关键字调用构造函数 const person1 = new Person("张三", 25); console.log(person1.name); // "张三" console.log(person1.age); // 25 // 不使用 new 调用,this 会指向全局对象(非严格模式)或 undefined(严格模式) const person2 = Person("李四", 30); // this 不会指向新对象 console.log(person2); // undefined (因为没有显式返回)

箭头函数

  1. 箭头函数没有自己的 this 指向,它需要继承外层函数的this指向
  2. 箭头函数即使是new也无法改变this指向,因此箭头函数不能用于编写构造函数
js
var a = 1; function foo() { var obj1 = { a: 2, bar: function () { console.log("bar", this.a); var obj2 = { a: 3, baz: () => { console.log("baz", this.a); }, }; // 箭头函数不会创建自己的 this,它会捕获外层函数的 this obj2.baz(); // baz 2 }, }; console.log("foo", this.a); obj1.bar(); // bar 2 } foo(); // foo 1

如何判断函数是否被new调用了?

按照上述 new 调用规则

  • 构造函数的this指向新创建的对象
  • 正常调用时this指向上下文对象

而我们又知道,新对象是由构造函数实例化的,对象隐式原型的constuctor就是构造函数

或者简单说,新对象 instanceof 构造函数

也就是 this instaceof 构造函数

除了这种方法,ES6中引入了new.target也可以判断当前函数是否被new

JavaScript
function Fn() { if (new.target) { console.log("new new.target 触发"); } if (this instanceof Fn) { console.log("new instanceof 触发"); } else { console.log("正常执行"); } } Fn(); new Fn();

手写 call、apply、bind

改变this指向核心是使用隐式绑定规则,手写时主要思路是如何让函数调用时上下文为指定对象。

call/apply

这里只写call的实现,apply只是传递参数的方式区别

JavaScript
Function.prototype.myCall = function (obj, ...args) { // 指定上下文对象,如果没传的话,默认是全局对象 const ctx = obj || globalThis; // 将 函数绑定到 上下文对象上,这样就可以使用隐式绑定规则 // ctx.fn = this; // 或者可以使用 Symbol 生成一个独一无二的 key,防止干扰 原对象 const key = Symbol("fn"); ctx[key] = this; // 调用并记录返回值,后续需要删除上下文上的 fn // const res = ctx.fn(...args); const res = ctx[key](...args); // 删除 函数,防止干扰上下文对象 // delete ctx.fn; delete ctx[key]; return res; };

bind

bind返回一个新的函数,这个函数在以下特性上需要和原函数保持一致

  • new行为和原函数一致
    • 新函数 返回new实例化的对象
    • 实例化对象隐式原型需要保留
  • 正常调用行为一致
    • 返回结果和原函数一致
JavaScript
Function.prototype.myBind = function (obj, ...args1) { // 使用闭包特性保留 this,也就是函数 const fn = this; function wrap(...args2) { const isNew = this instanceof wrap; // 如果是new调用时,应该把this传给原函数,让原函数处理this,相当于构造函数中执行的赋值 const ctx = isNew ? this : obj; return fn.apply(ctx, [...args1, ...args2]); } // 保持原型链 if (fn.prototype) { wrap.prototype = Object.create(fn.prototype); wrap.prototype.constructor = wrap; } return wrap; };
如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:pepedd864

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!