JavaScript this 关键字详解

简介

this 是 JavaScript 中最容易被误解的关键字之一。与其它语言不同,JavaScript 的 this 不是在定义时决定的,而是在调用时决定的

核心规则this 的值取决于函数的调用方式,而不是函数的定义位置。

function showThis() {
  console.log(this);
}

// 同样的 showThis 函数,不同的调用方式,this 完全不同!

一、默认绑定(Default Binding)

独立函数调用时,this 使用默认绑定

非严格模式

// 浏览器环境
function foo() {
  console.log(this);      // window(浏览器全局对象)
  console.log(this === window);  // true
}
foo();

// Node.js 环境
function bar() {
  console.log(this);      // global(Node.js 全局对象)
  console.log(this === global);  // true
}
bar();

严格模式(推荐)

'use strict';

function foo() {
  console.log(this);  // undefined(严格模式下默认绑定为 undefined)
}
foo();

// 在严格模式下,全局函数中 this 不再是全局对象

重要区别

模式 独立函数中的 this 说明
非严格模式 全局对象(window / global) 历史遗留行为
严格模式 undefined 现代 JavaScript 推荐
// 非严格模式
function test() {
  console.log(this);  // window 或 global
}
test();

// 严格模式
'use strict';
function testStrict() {
  console.log(this);  // undefined
}
testStrict();

二、隐式绑定(Implicit Binding)

当函数作为对象的方法调用时,this 指向该对象

基本示例

let person = {
  name: 'Alice',
  greet: function() {
    console.log('Hello, ' + this.name);
    console.log(this);  // person 对象
  }
};

person.greet();  // this 指向 person → "Hello, Alice"

只有最后一层影响 this

let obj = {
  name: 'Alice',
  inner: {
    name: 'Bob',
    greet: function() {
      console.log(this.name);
    }
  }
};

obj.inner.greet();  // this 指向 inner → "Bob"(只看最后一层)

隐式丢失(常见陷阱!)

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

// 正常调用
person.greet();  // "Hello, Alice"(this = person)

// 陷阱:赋值给变量
let greetFn = person.greet;  // 只是把函数本身赋值了
greetFn();  // "Hello, "(this = window 或 undefined!)

// 原因:greetFn 是独立调用的,使用默认绑定

另一个隐式丢失的例子

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

// 将方法作为参数传递(常见在回调中)
setTimeout(person.greet, 100);  // "Hello, "(this 丢失!)

// 原因:相当于 setTimeout(function() { ... }, 100)
// person.greet 只是传递了函数引用,调用时与 person 无关

链式调用中的 this

let calculator = {
  value: 0,
  add(n) {
    this.value += n;
    return this;  // 返回 this 实现链式调用
  },
  subtract(n) {
    this.value -= n;
    return this;
  },
  getResult() {
    return this.value;
  }
};

let result = calculator.add(5).subtract(2).add(10).getResult();
console.log(result);  // 13

三、显式绑定(Explicit Binding)

使用 call()apply()bind() 手动指定 this 的值。

call() — 逐个传参

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

let person = { name: 'Alice' };

// call 的第一个参数是 this,后面是函数的参数(逐个传)
greet.call(person, 'Hello', '!');   // "Hello, Alice!"
greet.call(person, 'Hi', '.');       // "Hi, Alice."

apply() — 数组传参

function add(a, b) {
  console.log(this.prefix + (a + b));
}

let obj = { prefix: '结果: ' };

// apply 的第一个参数是 this,第二个是参数数组
add.apply(obj, [3, 5]);  // "结果: 8"

call 和 apply 的区别

方法 参数传递方式 适用场景
call(this, arg1, arg2, ...) 逐个传递参数 参数已知
apply(this, [arg1, arg2, ...]) 数组传递参数 参数在数组中,或参数数量不定
function sum() {
  let args = Array.from(arguments);
  let total = args.reduce((a, b) => a + b, 0);
  console.log(this.prefix + total);
}

let data = { prefix: '总和: ' };

// call:需要知道参数数量
sum.call(data, 1, 2, 3);  // "总和: 6"

// apply:适合参数数组
sum.apply(data, [1, 2, 3, 4]);  // "总和: 10"

bind() — 永久绑定 this

bind() 返回一个新函数,其 this 被永久绑定到指定值。

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

let person = { name: 'Alice' };
let greetAlice = greet.bind(person);  // 创建一个 this 永久绑定到 person 的新函数

greetAlice();  // "Hello, Alice"

// 即使尝试用 call/apply 改变 this,也不生效(硬绑定)
greetAlice.call({ name: 'Bob' });  // 仍然是 "Hello, Alice"(已被 bind 锁定)

硬绑定(Hard Binding)

function bind(fn, obj) {
  return function() {
    return fn.apply(obj, arguments);  // 强制 this 指向 obj
  };
}

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

let person = { name: 'Alice' };
let boundGreet = bind(greet, person);

boundGreet();  // "Hello, Alice"
boundGreet.call({ name: 'Bob' });  // 仍然是 "Hello, Alice"(硬绑定)

四、new 绑定(New Binding)

使用 new 调用函数时,该函数成为构造函数this 指向新创建的对象

new 操作符的执行过程

function Person(name) {
  // 1. 创建一个新空对象
  // 2. 将新对象的原型指向 Person.prototype
  // 3. 将 this 绑定到新对象(这一步!)
  // 4. 执行函数体
  // 5. 如果没返回其他对象,自动返回 this

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

  // 没有 return,隐式返回 this
}

let alice = new Person('Alice');
alice.greet();  // "Hello, Alice"
console.log(alice.name);  // "Alice"

// alice 是新对象,this 在构造函数中指向它

构造函数返回值的特殊情况

function Person(name) {
  this.name = name;

  // 如果返回的是对象(非原始值),会覆盖 new 创建的 this
  // return { name: 'Bob' };  // 取消注释后,alice.name 会变成 "Bob"
}

let alice = new Person('Alice');
console.log(alice.name);  // "Alice"(如果没有返回对象)

如果没有 new 会怎样?

function Person(name) {
  this.name = name;
}

// 正确使用 new
let p1 = new Person('Alice');
console.log(p1.name);  // "Alice"

// 忘记使用 new(非严格模式)
let p2 = Person('Bob');  // 没有 new!
console.log(p2);          // undefined(构造函数没返回)
console.log(name);          // "Bob"(this 是 window,污染了全局!)

// 严格模式下会更安全
'use strict';
function PersonStrict(name) {
  // this 是 undefined,赋值会报错
  // this.name = name;  // TypeError: Cannot set property 'name' of undefined
}
PersonStrict('Charlie');

五、箭头函数(Arrow Functions)

箭头函数没有自己的 this。它继承外层词法作用域中的 this

基本特性

let person = {
  name: 'Alice',
  // 普通函数:this 动态绑定
  greetRegular: function() {
    console.log('Regular: ' + this.name);
  },
  // 箭头函数:this 继承外层
  greetArrow: () => {
    console.log('Arrow: ' + this.name);
  }
};

person.greetRegular();  // "Regular: Alice"(this = person)

// 问题:箭头函数的 this 继承自外层(这里是全局或 undefined)
person.greetArrow();  // "Arrow: " 或报错(this 不是 person!)

箭头函数适合的场景

let counter = {
  count: 0,
  start() {
    // 箭头函数继承 start() 的 this(即 counter)
    setInterval(() => {
      this.count++;
      console.log(this.count);
    }, 1000);
  }
};

counter.start();  // 每秒输出 1, 2, 3...(this 正确指向 counter)

箭头函数不适合的场景

// 不适合作为对象方法(无法获取对象 this)
let person = {
  name: 'Alice',
  // ❌ 箭头函数作为方法时,this 不是 person
  greet: () => {
    console.log('Hello, ' + this.name);  // this.name 可能不是 "Alice"
  }
};

// ✅ 应该使用普通函数
let person2 = {
  name: 'Bob',
  greet() {
    console.log('Hello, ' + this.name);  // this = person2
  }
};

对比表

特性 普通函数 箭头函数
有自己的 this ✅ 是 ❌ 否(继承外层)
this 绑定时机 调用时决定 定义时决定(继承外层)
适合作为方法 ✅ 适合 ❌ 不适合
适合作为回调 需手动绑定 this ✅ 自动继承外层 this
arguments ✅ 是 ❌ 否
可用 new ✅ 可以 ❌ 不可以(没有 [[Construct]])

六、事件监听器中的 this

DOM 事件(浏览器环境)

在 DOM 事件监听器中,this 通常指向触发事件的元素

// 浏览器环境
let button = document.getElementById('my-btn');

// 普通函数:this 指向触发事件的元素
button.addEventListener('click', function() {
  console.log(this);        // button 元素
  console.log(this.tagName);  // "BUTTON"
});

// 箭头函数:this 继承外层(可能不是 button!)
button.addEventListener('click', () => {
  console.log(this);  // 继承自外层作用域(可能是 window 或 undefined)
});

Node.js 事件(EventEmitter)

const EventEmitter = require('events');
let emitter = new EventEmitter();

emitter.on('message', function(data) {
  console.log(this);  // emitter 对象(EventEmitter)
  console.log(data);
});

emitter.emit('message', 'Hello');

七、定时器中的 this

setTimeout / setInterval

setTimeoutsetInterval 中,回调函数中的 this 默认指向全局对象(非严格)或 undefined(严格)

// 非严格模式(浏览器)
let obj = {
  count: 0,
  start() {
    setTimeout(function() {
      this.count++;  // this 是 window,不是 obj!
      console.log(this.count);  // NaN(window.count 是 undefined)
    }, 100);
  }
};
obj.start();

// 严格模式
'use strict';
let obj2 = {
  count: 0,
  start() {
    setTimeout(function() {
      console.log(this);  // undefined(严格模式)
    }, 100);
  }
};
obj2.start();

解决方法 1:使用箭头函数(推荐)

let obj = {
  count: 0,
  start() {
    // 箭头函数继承 start() 的 this(即 obj)
    setTimeout(() => {
      this.count++;
      console.log(this.count);  // 1, 2, 3...(this = obj)
    }, 100);
  }
};
obj.start();

解决方法 2:保存 this 引用

let obj = {
  count: 0,
  start() {
    let self = this;  // 保存 this 引用
    setTimeout(function() {
      self.count++;  // 使用 self
      console.log(self.count);
    }, 100);
  }
};
obj.start();

解决方法 3:使用 bind

let obj = {
  count: 0,
  start() {
    setTimeout(function() {
      this.count++;
      console.log(this.count);
    }.bind(this), 100);  // 手动绑定 this
  }
};
obj.start();

八、优先级规则

当多种绑定规则同时适用时,按以下优先级决定 this

优先级顺序(从高到低)

1. new 绑定        → 最高优先级
   new Foo()  → this = 新创建的对象

2. 显式绑定      → call / apply / bind
   fn.call(obj)  → this = obj

3. 隐式绑定      → 作为对象方法调用
   obj.fn()  → this = obj

4. 默认绑定        → 独立函数调用
   fn()  → this = 全局对象 或 undefined

判断决策树

函数是如何调用的?
│
├─ 使用了 new 吗?
│   └─ 是 → this = 新对象(优先级最高)
│
├─ 使用了 call / apply / bind 吗?
│   └─ 是 → this = 指定的对象(优先级第二)
│
├─ 是作为对象的方法调用的吗?(obj.fn())
│   └─ 是 → this = 该对象(优先级第三)
│
└─ 都不是
    └─ this = 全局对象(非严格)或 undefined(严格)(优先级最低)

示例验证

function foo() {
  console.log(this);
}

let obj1 = { name: 'obj1' };
let obj2 = { name: 'obj2' };

// 默认绑定
foo();  // window 或 undefined

// 隐式绑定(优先级高于默认)
obj1.foo = foo;
obj1.foo();  // obj1(this = obj1)

// 显式绑定(优先级高于隐式)
foo.call(obj2);  // obj2(this = obj2,覆盖了隐式)

// new 绑定(优先级最高)
let instance = new foo();  // foo {}(this = 新对象,即使 foo 上有 call/apply)

九、特殊情况和陷阱

null / undefined 作为 call / apply 的第一个参数

function foo() {
  console.log(this);
}

// null 或 undefined 作为第一个参数时,被忽略,使用默认绑定
foo.call(null);    // window(非严格)或 undefined(严格)
foo.apply(undefined);  // window(非严格)或 undefined(严格)

// 严格模式下
'use strict';
function bar() {
  console.log(this);
}
bar.call(null);  // null(严格模式下,this 就是 null)

间接引用导致的 this 丢失

function foo() {
  console.log(this);
}

let obj1 = {
  name: 'obj1',
  foo: foo
};

let obj2 = {
  name: 'obj2'
};

// 正常
obj1.foo();  // obj1

// 间接引用:赋值表达式返回的是函数本身,不是方法调用
(obj2.foo = obj1.foo)();  // window 或 undefined(this 丢失!)

回调函数中的 this 丢失

let person = {
  name: 'Alice',
  friends: ['Bob', 'Charlie'],
  listFriends() {
    this.friends.forEach(function(friend) {
      // forEach 的回调中,this 是 window 或 undefined!
      console.log(this.name + ' 的朋友: ' + friend);  // " 的朋友: Bob"(this.name 丢失)
    });
  }
};

person.listFriends();  // 输出不正确

// 解决方法 1:保存 this
listFriends() {
  let self = this;
  this.friends.forEach(function(friend) {
    console.log(self.name + ' 的朋友: ' + friend);  // "Alice 的朋友: Bob"
  });
}

// 解决方法 2:forEach 的第二个参数(thisArg)
listFriends() {
  this.friends.forEach(function(friend) {
    console.log(this.name + ' 的朋友: ' + friend);  // "Alice 的朋友: Bob"
  }, this);  // 传入 thisArg
}

// 解决方法 3:箭头函数(推荐)
listFriends() {
  this.friends.forEach(friend => {
    console.log(this.name + ' 的朋友: ' + friend);  // "Alice 的朋友: Bob"
  });
}

嵌套函数中的 this 问题

let obj = {
  name: 'Alice',
  outer() {
    console.log(this);  // obj(outer 作为方法调用)

    function inner() {
      console.log(this);  // window 或 undefined!(inner 是独立调用)
    }

    inner();
  }
};

obj.outer();

// 解决方法:箭头函数
let obj2 = {
  name: 'Bob',
  outer() {
    console.log(this);  // obj2

    let inner = () => {
      console.log(this);  // obj2(箭头函数继承 outer 的 this)
    };

    inner();
  }
};
obj2.outer();

十、综合示例

示例 1:方法借用(Method Borrowing)

let person1 = {
  name: 'Alice',
  greet() {
    console.log('Hello, ' + this.name);
  }
};

let person2 = {
  name: 'Bob'
};

// 借用 person1 的 greet 方法,但 this 指向 person2
person1.greet.call(person2);  // "Hello, Bob"

示例 2:柯里化和偏函数中的 this

function multiply(a, b) {
  return a * b;
}

// 使用 bind 固定第一个参数
let double = multiply.bind(null, 2);  // this 无关紧要,因为函数没用 this
console.log(double(5));  // 10(2 * 5)
console.log(double(10)); // 20(2 * 10)

// 如果函数中使用了 this
function greet(greeting) {
  return greeting + ', ' + this.name;
}

let alice = { name: 'Alice' };
let greetAlice = greet.bind(alice, 'Hello');
console.log(greetAlice());  // "Hello, Alice"

示例 3:安全地使用事件监听器

// 浏览器环境
let button = document.getElementById('my-btn');
let counter = {
  count: 0,
  handleClick() {
    this.count++;
    console.log('点击次数: ' + this.count);
  }
};

// ❌ 直接传递:this 丢失
// button.addEventListener('click', counter.handleClick);

// ✅ 方法 1:bind
button.addEventListener('click', counter.handleClick.bind(counter));

// ✅ 方法 2:箭头函数
button.addEventListener('click', () => counter.handleClick());

// ✅ 方法 3:包装函数
button.addEventListener('click', function() {
  counter.handleClick();  // 通过 counter 调用,this 正确
});

示例 4:修复定时器中的 this

let timer = {
  count: 0,
  start() {
    // ❌ 错误方式
    // setInterval(function() {
    //   this.count++;  // this 不是 timer!
    // }, 1000);

    // ✅ 方法 1:箭头函数(推荐)
    setInterval(() => {
      this.count++;
      console.log('计数: ' + this.count);
    }, 1000);
  },
  // ✅ 方法 2:bind
  start2() {
    setInterval(function() {
      this.count++;
      console.log('计数: ' + this.count);
    }.bind(this), 1000);
  }
};

timer.start();

示例 5:构造函数的安全模式

function Person(name) {
  // 安全模式:检查是否使用了 new
  if (!(this instanceof Person)) {
    return new Person(name);  // 没使用 new,自动补上
  }

  this.name = name;
}

let p1 = new Person('Alice');
console.log(p1.name);  // "Alice"

let p2 = Person('Bob');  // 忘记使用 new
console.log(p2.name);  // "Bob"(仍然正确,因为函数内部处理了)

总结:this 速查表

四种绑定规则

绑定类型 调用方式 this 指向 优先级
new 绑定 new Foo() 新创建的对象 1(最高)
显式绑定 fn.call(obj) / fn.apply(obj) / fn.bind(obj) obj 2
隐式绑定 obj.fn() obj 3
默认绑定 fn() 全局对象(非严格)或 undefined(严格) 4(最低)

箭头函数的 this

特性 说明
没有自己的 this 继承外层词法作用域的 this
定义时决定 this 在定义箭头函数时就确定了
无法改变 call / apply / bind 对箭头函数无效

this 判断清单

每次遇到 this 时,按顺序问自己:

  • 是否使用了 new?(是 → 新对象)
  • 是否使用了 call / apply / bind?(是 → 指定的对象)
  • 是否作为对象的方法调用?(是 → 该对象)
  • 是否是箭头函数?(是 → 外层作用域的 this
  • 以上都不是 → 全局对象(非严格)或 undefined(严格)

常见陷阱速查

陷阱 现象 解决方法
方法赋值给变量 this 变为默认绑定 使用 bind() 或箭头函数
回调函数传参 this 丢失 传入 thisArg 或用箭头函数
定时器回调 this 不是期望的对象 箭头函数或 bind()
forEach 回调 this 是全局或 undefined thisArg 或用箭头函数
嵌套函数 内层函数 this 丢失 保存 self 或用箭头函数
忘记 new 污染全局或报错 使用安全模式或 class

终极记忆:JavaScript 的 this 不关心函数定义在哪,只关心函数怎么被调用。箭头函数是个例外——它关心定义在哪(继承外层的 this)。