轩辕李的博客 轩辕李的博客
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

轩辕李

勇猛精进,星辰大海
首页
  • Java
  • Spring
  • 其他语言
  • 工具
  • HTML&CSS
  • JavaScript
  • 分布式
  • 代码质量管理
  • 基础
  • 操作系统
  • 计算机网络
  • 编程范式
  • 安全
  • 中间件
  • 心得
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • JavaScript

    • 核心

      • 关于this关键字的魔幻现实
        • 一、完整的函数调用
        • 二、对象中的 this
        • 三、对象方法中嵌套函数的 this
        • 四、原型与 this
        • 五、箭头函数与 this
        • 六、bind、call 和 apply
          • 1、和 apply
          • 2、bind
        • 七、严格模式下的 this
        • 八、this 绑定优先级
        • 九、总结
  • HTML & CSS

  • 前端
  • JavaScript
  • 核心
轩辕李
2020-08-25
目录

关于this关键字的魔幻现实

不得不承认 JavaScript 中存在一些"历史遗产",比如基于全局变量的编程模型。
如果写一个函数:

function test(name){
    var namex = name
    console.log(this.namex)
}
test('jack')

方法调用之后,this.namex 等于 undefined,这感觉很奇怪,为什么会等于 undefined?因为此处的 this 是全局对象 window,而 namex 是函数内的局部变量,并非 window 的属性。
不过别着急,慢慢往下看,关于 this 还有很多魔幻的现实,我们来一一解锁。

# 一、完整的函数调用

还是上面的代码,其实它的完整调用写法是这样的:

function test(name){
    var namex = name
    console.log(this.namex)
}
test.call(undefined, 'jack')

test 是一个函数对象——即 Function 对象,Function.prototype 有 call 方法。call() 的第一个参数是 this 上下文对象,后续参数是函数的入参列表。
如果 call() 传入的 this 上下文是 undefined 或 null,那么在非严格模式下,window 对象将成为默认的 this 上下文(严格模式下 this 保持为 undefined)。这也就解释了开头例子中 this 为什么是 window 的原因了。

# 二、对象中的 this

const obj = {
    name: 'Jack',
    greet: function() {
        console.log(this.name)
    }
}
obj.greet()  // 简写调用,输出 'Jack'
obj.greet.call(obj) // 完整调用,输出 'Jack'

obj.greet() 中的 this 指向 obj 对象,因为函数作为对象的方法被调用时,this 自动绑定到该对象。

# 三、对象方法中嵌套函数的 this

对第 2 节中的代码进行修改:

const obj = {
    name: 'Jack',
    greet: function() {
        return function(){
            console.log(this.name)
        }
    }
}
obj.greet()()  // 输出 undefined

需要注意的是嵌套函数中的 this 依然是 window,为什么呢?可以拆分来看:

var greet = obj.greet()
greet()  // 等价于 greet.call(undefined)

嵌套函数被调用时,真实的调用者上下文是 undefined,在非严格模式下会被转换为 window。

# 四、原型与 this

function Clt() {
}

Clt.prototype.x = 10
Clt.prototype.test = function () {
    console.log(this)  // 输出 Clt 实例对象
    this.y = this.x + 1
}
let bean = new Clt()
bean.test()
console.log(bean.y)  // 输出 11

test 方法中输出的 this 是一个 Clt 实例对象。
这里需要引入一个新的概念:构造器函数。
使用 new 关键字调用函数时,它的作用是:

一旦函数被 new 来调用,就会创建一个链接到该函数的 prototype 属性的新对象,同时 this 会被绑定到那个新对象上

理解了构造器函数,我们就理解了原型与 this 的关系了,因为 new 会重新指定 this 上下文。

# 五、箭头函数与 this

ECMAScript 6 引入了箭头函数,关于箭头函数中的 this,需要先记住一句话:

箭头函数没有自己的 this,它会捕获其所在上下文的 this 值,作为自己的 this 值。

看代码(引自阮一峰老师的教程 (opens new window)):

function foo() {
  setTimeout(() => {
    console.log('id:', this.id);
  }, 100);
}

var id = 21;

foo.call({ id: 42 });
// id: 42

此处的 this 指向了 foo 的 this 上下文,即箭头函数定义时所在的词法作用域。

箭头函数与普通函数的 this 区别:

function Timer() {
  this.s1 = 0;
  this.s2 = 0;
  // 箭头函数
  setInterval(() => this.s1++, 1000);
  // 普通函数
  setInterval(function () {
    this.s2++;
  }, 1000);
}

var timer = new Timer();

setTimeout(() => console.log('s1: ', timer.s1), 3100);
setTimeout(() => console.log('s2: ', timer.s2), 3100);
// s1: 3
// s2: 0

箭头函数的 this 指向了定义时所在对象的 this;普通函数的 this 指向了运行时的作用域,即全局域 window。

this 的指向固定化,解决了许多历史问题,带来了很大好处,比如事件处理的封装:

var handler = {
  id: '123456',

  init: function() {
    document.addEventListener('click',
      event => this.doSomething(event.type), false);
  },

  doSomething: function(type) {
    console.log('Handling ' + type  + ' for ' + this.id);
  }
};

一个嵌套的例子:

function foo() {
  return () => {
    return () => {
      return () => {
        console.log('id:', this.id);
      };
    };
  };
}

var f = foo.call({id: 1});

var t1 = f.call({id: 2})()(); // id: 1
var t2 = f().call({id: 3})(); // id: 1
var t3 = f()().call({id: 4}); // id: 1

最里层的 this 也绑定到了定义时所在对象——即 foo 的 this。注意,由于箭头函数没有自己的 this,所以 call、apply、bind 方法对箭头函数无效。

# 六、bind、call 和 apply

JavaScript 提供了三个方法来显式设置函数的 this 值:

# 1、和 apply

function greet(greeting, punctuation) {
    console.log(greeting + ', ' + this.name + punctuation);
}

const person = { name: 'Alice' };

// 使用 call
greet.call(person, 'Hello', '!');  // Hello, Alice!

// 使用 apply
greet.apply(person, ['Hi', '?']);   // Hi, Alice?

call 和 apply 的区别仅在于传参方式:

  • call 接受参数列表
  • apply 接受参数数组

# 2、bind

bind 创建一个新函数,永久绑定 this:

const person = { name: 'Bob' };
function greet() {
    console.log('Hello, ' + this.name);
}

const boundGreet = greet.bind(person);
boundGreet();  // Hello, Bob

// 即使作为对象方法调用,this 仍然是绑定的值
const obj = { 
    name: 'Charlie',
    sayHello: boundGreet 
};
obj.sayHello();  // Hello, Bob(不是 Charlie)

# 七、严格模式下的 this

在严格模式下,this 的行为有所不同:

'use strict';

function test() {
    console.log(this);  // undefined,而不是 window
}
test();

// 但作为对象方法调用时行为不变
const obj = {
    method: function() {
        'use strict';
        console.log(this);  // 仍然是 obj
    }
};
obj.method();

# 八、this 绑定优先级

当多种绑定规则同时存在时,优先级从高到低为:

  1. new 绑定:使用 new 调用
  2. 显式绑定:使用 call、apply 或 bind
  3. 隐式绑定:作为对象方法调用
  4. 默认绑定:独立函数调用
function foo() {
    console.log(this.a);
}

const obj1 = { a: 2, foo: foo };
const obj2 = { a: 3, foo: foo };

obj1.foo();  // 2(隐式绑定)
obj1.foo.call(obj2);  // 3(显式绑定优先)

const bar = obj1.foo.bind(obj2);
bar();  // 3(bind 绑定)

const instance = new bar();  // new 绑定优先于 bind

# 九、总结

JavaScript 中的 this 是一个动态的概念,其值取决于函数的调用方式而非定义位置(箭头函数除外)。理解 this 的各种绑定规则和优先级,对于编写健壮的 JavaScript 代码至关重要。

关键要点:

  • 普通函数的 this 由调用方式决定
  • 箭头函数没有自己的 this,继承外层作用域
  • new、call、apply、bind 可以显式控制 this
  • 严格模式改变了默认绑定的行为
  • 绑定规则有明确的优先级顺序

掌握这些规则,就能在 JavaScript 的"魔幻现实"中游刃有余。

编辑 (opens new window)
#JavaScript
上次更新: 2025/08/15
HTML基础全景图

HTML基础全景图→

最近更新
01
AI时代的编程心得
09-11
02
Claude Code与Codex的协同工作
09-01
03
Claude Code实战之供应商切换工具
08-18
更多文章>
Theme by Vdoing | Copyright © 2018-2025 京ICP备2021021832号-2 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式