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'),this 为 undefined。
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. 使用 null 或 undefined 传入 call?小心全局对象!

  • 非严格模式 :call(null) 或 call(undefined) 时,this 会指向全局对象。
  • 严格模式 :this 严格为 null 或 undefined。
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 的"诡异行为"时,冷静分析调用位置,一切

相关推荐
前端双越老师8 分钟前
程序员 1v1 访谈系列第二期:很焦虑但又不知道干啥
前端·ai编程·全栈
广州华水科技22 分钟前
北斗形变监测传感器在大坝安全监测中的应用与优势分析
前端
孔令飞29 分钟前
如何学习Go
前端
HUIBUR科技33 分钟前
AI 中枢,面向企业场景构建的 AI 智能体
前端·数据库·人工智能
怕浪猫40 分钟前
给AI配一台电脑,开源版Muse来了,OpenMuse赶紧去二次开发吧
javascript·面试·github
随便写个软件1 小时前
【新版本】EasyExplorer资源管理器 3.12.0——地址栏重要更新
运维·服务器·前端·everything·资源管理器
Jack N1 小时前
【Strapi 系列之二】Collection / Single / Component 怎么选?Strapi 字段大全一篇讲透
前端·javascript·strapi
hiahiahia1231 小时前
Agent Tool 执行失败怎么办
服务器·前端
Java后端的Ai之路1 小时前
09-React组件生命周期
前端·react.js·前端框架
IT_陈寒1 小时前
SpringBoot开发实战:从入门到精通
前端·人工智能·后端