this(二)JavaScript 的 `this` 全面解析:从规则到反常识,彻底搞懂它!

1. 调用位置:this 的起点是函数如何被调用

关键原则this 的值由 函数调用时的上下文 决定,而非函数定义的位置。找到函数的 调用位置(Call-site) 是理解 this 的第一步。

javascript 复制代码
function sayHello() {
  console.log("Hello, " + this.name);
}

const user = { 
  name: "Lucy",
  greet: sayHello 
};

user.greet(); // 调用位置在 user.greet(),this 指向 user → "Hello, Lucy"
sayHello();   // 直接调用,非严格模式下 this 指向全局对象(浏览器中是 window)→ "Hello, undefined"

2. 四大绑定规则:决定 this 的四大天王

(1) 默认绑定:没有修饰的独立调用

  • 非严格模式下,this 指向全局对象(浏览器中是 window)。
  • 严格模式下('use strict'),thisundefined
javascript 复制代码
function showThis() {
  console.log(this);
}

showThis(); // 浏览器中输出 window(非严格模式)或 undefined(严格模式)

(2) 隐式绑定:通过对象调用方法

  • this 指向调用该方法的对象。
javascript 复制代码
const cat = {
  name: "Mimi",
  meow: function() {
    console.log(this.name + " says meow!");
  }
};

cat.meow(); // "Mimi says meow!" → this 指向 cat

因为调用函数时 this 被绑定到 cat ,所以 this.name 等于 cat.name

隐式丢失陷阱

将方法赋值给变量后调用,this 会丢失绑定!

javascript 复制代码
const meowLater = cat.meow;
meowLater(); // "undefined says meow!" → this 指向全局对象

这里就等于直接引用函数本身,那就是应用了默认绑定,this 就指向全局作用域了。

(3) 显式绑定:用 call/apply/bind 强制指定 this

  • 直接控制 this 的指向。
javascript 复制代码
function introduce(lang) {
  console.log(`I speak ${lang}. My name is ${this.name}`);
}

const person = { name: "Bob" };

// call 立即调用
introduce.call(person, "Chinese"); // "I speak Chinese. My name is Bob"

// bind 返回绑定后的函数
const boundIntro = introduce.bind(person, "English");
boundIntro(); // "I speak English. My name is Bob"

精确控制 this 指向

显式绑定是通过 call()apply()bind() 方法强制指定函数调用时的 this 值。

1. call() 方法

call() 立即调用函数,并显式设置 this 值:

javascript 复制代码
function greet(greeting, punctuation) {
  console.log(greeting + ", " + this.name + punctuation);
}

const person = { name: 'Alice' };

// 第一个参数设置 this,后续参数作为函数参数
greet.call(person, 'Hello', '!'); // 输出: "Hello, Alice!"

2. apply() 方法

apply()call() 类似,但接受参数数组:

javascript 复制代码
const args = ['Hi', '!!'];
greet.apply(person, args); // 输出: "Hi, Alice!!"

3. bind() 方法(硬绑定)

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

javascript 复制代码
const boundGreet = greet.bind(person);
boundGreet('Hey', '...'); // 输出: "Hey, Alice..."

硬绑定的本质与特性

硬绑定是指通过 bind() 方法创建一个新函数,该函数的 this 值被永久绑定到指定对象,无法通过其他方式修改。

关键特点:

  1. 永久性绑定:一旦绑定就无法更改

    javascript 复制代码
    const boundFn = greet.bind(person);
    boundFn.call({name: 'Bob'}, 'Hi', '!'); // 仍然输出: "Hi, Alice!"
  2. 参数预设:可以预先设置部分参数

    javascript 复制代码
    const greetAlice = greet.bind(person, 'Hello');
    greetAlice('!!!'); // 输出: "Hello, Alice!!!"
  3. new 操作符例外:当绑定函数被用作构造函数时,绑定会被忽略

    javascript 复制代码
    function Person(name) {
      this.name = name;
    }
    const BoundPerson = Person.bind({name: 'Ignored'});
    const p = new BoundPerson('Actual');
    console.log(p.name); // 输出: "Actual"
  4. 无 prototype 属性:绑定函数没有 prototype 属性

    javascript 复制代码
    console.log(boundGreet.prototype); // 输出: undefined

(4) new 绑定:构造函数中的 this

  • this 指向新创建的对象。
javascript 复制代码
function Dog(name) {
  this.name = name;
  this.bark = function() {
    console.log(this.name + " barks!");
  };
}

const myDog = new Dog("Buddy");
myDog.bark(); // "Buddy barks!"

3. 优先级:四大规则的较量

优先级排序
new 绑定 > 显式绑定(bind/call/apply) > 隐式绑定(对象调用) > 默认绑定

示例:new 如何碾压 bind

javascript 复制代码
function Phone(brand) {
  this.brand = brand;
}

const obj = {};
const BoundPhone = Phone.bind(obj); // 硬绑定到 obj

const iphone = new BoundPhone("Apple");
console.log(obj.brand);    // undefined → new 覆盖了 bind 的 this
console.log(iphone.brand); // "Apple"   → this 指向新对象

4. 使用 nullundefined 传入 call?小心全局对象!

  • 非严格模式call(null)call(undefined) 时,this 会指向全局对象。
  • 严格模式this 严格为 nullundefined
javascript 复制代码
function logThis() {
  console.log(this);
}

logThis.call(null);       // 非严格模式:输出 window
logThis.call(undefined);  // 非严格模式:输出 window

"use strict";
logThis.call(null);       // 输出 null

总是使用 null 来忽略 this 的绑定,可能会产生一些副作用。如果某个函数确实使用了 this (比如第三方库中的一个函数),那默认绑定规则会把this绑定到全局作用域(在浏览器中这个对象是 window),这将导致不可预计的后果(比如修改全局对象)。

5. 间接引用:隐式绑定的天敌

将对象方法赋值给变量或作为参数传递时,会触发间接引用,导致 this 丢失。

javascript 复制代码
const car = {
  brand: "Tesla",
  start: function() { console.log(this.brand + " starts!"); }
};

const startCar = car.start;
startCar(); // "undefined starts!" → this 指向全局对象

// 事件监听中的经典问题
button.addEventListener("click", car.start); // this 指向 button,而非 car!

6. 软绑定:更灵活的 this 控制

硬绑定(bind)的缺点是永久性绑定,无法覆盖。软绑定(Soft Binding) 允许默认绑定一个对象,但允许显式修改。

实现软绑定(参考《你不知道的JavaScript》):

javascript 复制代码
Function.prototype.softBind = function(obj) {
  const fn = this;
  return function(...args) {
    // 如果 this 是全局对象或 undefined,则使用 obj
    const boundThis = (!this || this === window) ? obj : this;
    return fn.apply(boundThis, args);
  };
};

function showBrand() {
  console.log(this.brand);
}

const defaultBrand = { brand: "Default" };
const softShow = showBrand.softBind(defaultBrand);

const car1 = { brand: "BMW" };
softShow.call(car1);  // "BMW" → 显式绑定优先
softShow();           // "Default" → 默认使用 defaultBrand

7. 箭头函数:this 的词法捕获

箭头函数没有自己的 this,而是 继承外层作用域的 this ,且无法通过 call/bind 修改。

javascript 复制代码
const counter = {
  count: 0,
  increment: function() {
    // 传统函数:this 由调用方式决定
    setTimeout(function() {
      this.count++; // 错误!this 指向 window
    }, 100);
    
    // 箭头函数:继承外层 this
    setTimeout(() => {
      this.count++; // 正确!this 指向 counter
    }, 100);
  }
};

总结

  • 调用位置决定一切:时刻关注函数如何被调用。
  • 优先级是核心new > 显式 > 隐式 > 默认。
  • 箭头函数是规则破坏者 :它直接继承外层 this
  • 软绑定提供灵活性:在需要默认值但允许覆盖时使用。

理解 this 的规则后,你会发现它不再是玄学,而是一套逻辑清晰的机制。下次遇到 this 的"诡异行为"时,冷静分析调用位置,一切

相关推荐
LaughingZhu1 分钟前
PH热榜 | 2025-04-09
前端·数据库·人工智能·mysql·开源
枫super13 分钟前
Day-03 前端 Web-Vue & Axios 基础
前端·javascript·vue.js
程序猿chen1 小时前
Vue.js组件安全工程化演进:从防御体系构建到安全性能融合
前端·vue.js·安全·面试·前端框架·跳槽·安全架构
你也来冲浪吗1 小时前
MD编辑器用法讲解
前端
小小小小宇1 小时前
十万字总结所有React hooks(含简单原理)
前端
MariaH1 小时前
MySQL数据库DQL
前端
Enjoy10241 小时前
v8垃圾回收机制
前端
zhangbao90s1 小时前
Tauri 与 Electron 对比:性能、包大小及实际权衡
javascript·node.js
Georgewu1 小时前
【HarmonyOS 5】敏感信息本地存储详解
前端·harmonyos
_Le_1 小时前
css 小师系列:一种新的影响样式优先级的方式😍
前端·css