我把对象的方法直接传给 setTimeout 当回调,运行到一半就报 this 是 undefined,我对着 JavaScript 的 this 指向丢失排查了大半天的复盘

用原生 JavaScript 写小组件,有个类的方法负责更新状态,我把它当回调传给了 setTimeout 和按钮事件监听,读起来天经地义。一运行控制台就炸:Cannot read properties of undefined,报错在方法内部 this.state 那行。懵了:方法明明是这对象的,this 不就该是它吗?排查大半天才理解 JS 里让无数人栽跟头的概念:this 的指向不取决于方法定义在哪,而取决于它怎么被调用。c.increment() 调用时 this 是 c(点号左边),但 setTimeout(this.increment) 只是把函数本身取出来传走、丢了"它属于谁"的信息,1 秒后被裸调用时(无 对象.方法() 形式),严格模式下 this 就是 undefined,于是 this.state 成了 undefined.state 崩了。这篇从 this 的指向规则(调用时决定/方法调用/裸调用/new/call/bind/箭头词法this)、构造函数bind/箭头包一层/类字段箭头函数锁定this的正解、箭头vs普通函数this的根本区别(对象方法本身别用箭头、方法内回调该用箭头)、this指向速查、其他丢this场景、决策图与铁律,到附上一组亲眼验证this随调用方式变化的实验、以及延伸:为何React Hooks/Vue Composition API绕开了this。核心领悟:跨语言学习最危险的是"看起来一样语义却不同"的概念,别把旧语言心智模型想当然迁移过来;遇到强大但易错的特性,除了学会正确用它,更要想有没有从根上绕开它的新范式。

我把对象的方法直接传给 setTimeout 当回调,运行到一半就报 this 是 undefined,我对着 JavaScript 的 this 指向丢失排查了大半天的复盘

那是我用原生 JavaScript 写的一个小组件。我有个类,里面有个方法负责更新状态,我把这个方法当回调,传给了 setTimeout、还有按钮的事件监听。代码读起来天经地义:"定时器到了,就调用我这个方法呗"。可一运行,控制台就炸了:Cannot read properties of undefined (reading 'state'),报错就在方法内部那行 this.state.xxx 上。我懵了:这方法明明是这个对象的,里面的 this 不就该是这个对象吗?怎么会是 undefined?我盯着代码看了又看,逻辑没有任何问题。排查了大半天,我才真正理解了 JavaScript 里那个让无数人栽过跟头的概念:this 的指向,不取决于方法定义在哪,而取决于它怎么被调用。这篇就把这场"this 凭空丢失"的事故,从头复盘一遍。

故障现场:方法是对象的,this 却是 undefined

先看现场。问题就藏在我那个"把方法当回调传出去"的写法里:

class Counter {
  constructor() {
    this.count = 0;
    this.state = { value: "ready" };
  }

  increment() {
    // 方法内部用了 this
    console.log(this.state.value);   // ← 报错就在这: this 是 undefined!
    this.count++;
  }

  start() {
    // ✗ 我把方法直接当回调传给 setTimeout
    setTimeout(this.increment, 1000);
    //          ^^^^^^^^^^^^^^ 只是把"函数本身"传过去了,
    //                         丢失了"它属于哪个对象"的信息!
  }
}

const c = new Counter();
c.start();
// 1秒后报错:
//   TypeError: Cannot read properties of undefined (reading 'value')
//   at increment

// 同样的坑, 在事件监听里:
button.addEventListener("click", c.increment);  // ✗ 点击时 this 也不对!

// 现象拼图:
//   - this 的值, 是在【函数被调用时】才确定的, 取决于"它是怎么被调用的"。
//   - c.increment()  这样调用: this = c (调用时 c 在点号左边, this 就是 c)。
//   - 但 setTimeout(this.increment, 1000): 我只是把 increment 这个【函数本身】
//     取出来、传给了 setTimeout。等 1 秒后 setTimeout 内部调用它时,
//     是"裸调用"(没有 对象.方法() 的形式), this 不再是 c!
//   - 严格模式(class 内部默认严格)下, 裸调用的 this 是 undefined。
//     于是 this.state 就成了 undefined.state → 爆 TypeError。
//   - ★ 我以为 this 跟着"方法定义在哪个类"走, 实际它跟着"怎么被调用"走。

看清真相后,我恍然大悟。问题的根源,是我对 this 的理解从根上就错了:我以为 this 跟着"方法定义在哪个类/对象里"走,可实际上,this 的值是在"函数被调用的那一刻"才确定的,取决于"它是怎么被调用的"c.increment() 这样调用时,thisc(因为调用时 c 在点号左边);setTimeout(this.increment, 1000),我只是把 increment 这个"函数本身"取了出来、传给 setTimeout,丢掉了"它属于 c"这个信息。等 1 秒后 setTimeout 内部"裸调用"它时(没有 对象.方法() 的形式),this 就不再是 c——在严格模式(class 内部默认严格)下,裸调用的 thisundefined,于是 this.state 就成了 undefined.state,爆了 TypeError

第一件事:搞懂 this 的指向到底由什么决定

要解决它,得先彻底搞懂 JavaScript 里 this 的指向规则——这是无数 bug 的根源。

JavaScript 中 this 的指向规则

# 核心原则: this 不是"定义时"绑定的, 而是"调用时"决定的!
#   (箭头函数除外, 见下)同一个函数, 用不同方式调用, this 完全不同。

# 普通函数的 this, 看"调用时的形式":
#   1. 方法调用 obj.fn()    → this = obj (点号左边那个对象)
#   2. 裸调用 fn()          → this = undefined(严格模式) / window(非严格)
#   3. new Fn()             → this = 新创建的实例
#   4. fn.call(x)/apply(x)  → this = x (显式指定)
#   5. fn.bind(x) 后调用    → this = x (永久绑定为 x)

# 关键: "方法被当成值取出来、再单独调用", 就丢失了原来的 this!
#   const f = obj.method;  f();   // f 里的 this 不是 obj 了!
#   setTimeout(obj.method, 100);  // 同理, 传出去后是裸调用
#   arr.forEach(obj.method);      // 同理
#   → 这就是"this 指向丢失"。

# 箭头函数: 没有自己的 this!
#   - 箭头函数不绑定自己的 this, 它"捕获定义时所在作用域的 this"(词法 this)。
#   - 一旦定义, this 就固定了, 不随调用方式改变。
#   - 所以箭头函数常用来"锁定" this(见正解)。

# 一句话总结指向判断:
#   看函数"被调用的那一刻"长什么样:
#     有 obj. 在前面 → this 是 obj
#     光秃秃地调用   → this 是 undefined(严格)
#     箭头函数       → this 是"定义它时"外层的 this(和调用方式无关)

# 核心: this 取决于"函数怎么被调用"而非"定义在哪"; 方法被取出单独调用就丢this;
#   箭头函数无自己的this, 捕获定义时外层的this, 故能"锁定"this。

原来,this 的指向有一套清晰(但反直觉)的规则。核心原则:this 不是"定义时"绑定的,而是"调用时"决定的(箭头函数除外)。普通函数的 this,看"调用时的形式":方法调用 obj.fn()thisobj;裸调用 fn()thisundefined(严格模式);new Fn() → 新实例;call/apply/bind → 显式指定的对象关键的坑就在于:"方法被当成值取出来、再单独调用",就丢失了原来的 this——const f = obj.method; f()setTimeout(obj.method, 100)arr.forEach(obj.method) 全是这个坑。箭头函数没有自己的 this——它捕获定义时所在作用域的 this(词法 this),一旦定义就固定、不随调用方式改变,所以常用来"锁定" this一句话判断:看函数被调用那一刻——前面有 obj. 就是 obj、光秃秃调用就是 undefined、箭头函数则是定义时外层的 this

第二件事:正解——把 this 牢牢绑定住

搞懂了原理,正解就清晰了:把方法的 this 锁定到对象上——用箭头函数、bind、或类字段箭头函数,别让它被裸调用时丢失

class Counter {
  constructor() {
    this.count = 0;
    this.state = { value: "ready" };
    // ====== 正解一: 在构造函数里 bind, 永久绑定 this ======
    this.increment = this.increment.bind(this);
    //   → bind 返回一个"this 永久锁定为当前实例"的新函数, 赋回去。
    //     之后无论怎么传递/裸调用, this 都是这个实例。
  }
  increment() { console.log(this.state.value); this.count++; }

  start() {
    setTimeout(this.increment, 1000);   // ✓ 现在 this 不会丢了
  }
}

// ====== 正解二: 用箭头函数包一层(最常用)======
class Counter2 {
  state = { value: "ready" };
  increment() { console.log(this.state.value); }
  start() {
    // 箭头函数捕获 start 的 this(就是实例), 在里面用 obj.method() 形式调用
    setTimeout(() => this.increment(), 1000);   // ✓ this 正确
    //          ^^^^^^^^^^^^^^^^^^^^ 箭头函数锁定this, 内部正常方法调用
  }
}

// ====== 正解三: 类字段 + 箭头函数(定义即绑定, 最简洁)======
class Counter3 {
  state = { value: "ready" };
  // 用类字段把方法定义成箭头函数 → this 自动绑定到实例, 永不丢失
  increment = () => {
    console.log(this.state.value);   // ✓ this 永远是实例
  };
  start() {
    setTimeout(this.increment, 1000);    // ✓ 直接传也没问题
    button.addEventListener("click", this.increment);  // ✓ 事件监听也对
  }
}

// ====== 正解四: 事件监听里, 别忘了 removeEventListener 要同一个引用 ======
// ✗ 错误: bind/箭头每次都生成新函数, remove 时对不上, 移除不掉
button.addEventListener("click", this.handler.bind(this));  // 加的是新函数
button.removeEventListener("click", this.handler.bind(this)); // 又是新函数, remove失败!
// ✓ 正确: 用类字段箭头函数(同一引用), 或先存起来
this.boundHandler = this.handler.bind(this);  // 存一份
button.addEventListener("click", this.boundHandler);
button.removeEventListener("click", this.boundHandler);  // 同一引用, 移除成功

# 核心: 锁定this的三招 —— 构造函数里bind、调用处用箭头函数包一层、
#   类字段箭头函数(定义即绑定, 最推荐); 事件监听要removeEventListener时务必用同一引用。

修复的核心,是"把方法的 this 牢牢锁定到对象上,不让它在被裸调用时丢失"正解一:构造函数里 bind——this.increment = this.increment.bind(this),bind 返回一个"this 永久锁定为当前实例"的新函数,之后怎么传都不丢。正解二:用箭头函数包一层(最常用)——setTimeout(() => this.increment(), 1000),箭头函数捕获外层的 this,内部再用 obj.method() 形式正常调用。正解三:类字段 + 箭头函数(最简洁、最推荐)——increment = () => {...},定义即绑定到实例,this 永不丢失,直接传给 setTimeout 或事件监听都对。还有一个高频附带坑:正解四:事件监听要 removeEventListener 时,必须用同一个函数引用——bind/箭头每次都生成新函数,临时 bind 后 remove 会因引用对不上而失败;应该用类字段箭头函数(同一引用)或先把绑定后的函数存起来。归根结底:锁定 this 三招——构造函数 bind、调用处箭头函数包一层、类字段箭头函数(最推荐);要 remove 监听务必用同一引用。

第三件事:箭头函数 vs 普通函数,this 行为的根本区别

排查时我把箭头函数和普通函数在 this 上的区别,系统辨析了一遍。这是用对它们的关键。

// 普通函数: this 是"动态"的, 调用时才定, 谁调用(点号左边)就是谁
const obj = {
  name: "obj",
  normal: function () { console.log(this.name); },
  arrow: () => { console.log(this.name); },
};
obj.normal();   // "obj"  —— 普通函数, obj.normal() 调用, this=obj
obj.arrow();    // undefined —— 箭头函数! this 是"定义时外层"的this(这里是模块顶层)
//                而不是 obj! 箭头函数无视"谁调用它"。

// ★ 所以: 【对象的方法, 别用箭头函数定义】(它捕获的是外层this, 不是obj):
const bad = {
  value: 42,
  getValue: () => this.value,   // ✗ this 不是 bad! 是外层this, 拿不到 value
};

// ★ 但是: 【对象方法内部的回调, 该用箭头函数】(捕获方法的this):
const good = {
  values: [1, 2, 3],
  factor: 10,
  scale() {
    // 这里的 this 是 good
    return this.values.map((v) => v * this.factor);
    //                     ^^^^^^^^^^^^^^^^^^^^^ 箭头函数捕获 scale 的 this(good)
    //   ✓ 所以能拿到 this.factor。若用普通function做map回调, this就丢了!
  },
};

// 总结口诀:
//   - 需要 this 动态绑定(如对象方法本身)→ 用普通函数。
//   - 需要 this 锁定为外层(如方法内的回调、组件方法)→ 用箭头函数。

# 核心: 普通函数this动态(调用时定、谁调是谁), 适合做对象方法本身;
#   箭头函数this静态(锁定定义时外层), 适合做方法内的回调/需固定this的场景。

把箭头函数和普通函数在 this 上的区别理清后,我才知道该在什么地方用哪个。普通函数的 this 是"动态"的——调用时才定、谁调用(点号左边)就是谁,所以 obj.normal()thisobj箭头函数的 this 是"静态"的——捕获定义时外层的 this、无视谁调用它,所以 obj.arrow()this 不是 obj 而是外层 this由此推出两条关键的"该不该用箭头函数"的规则:对象的方法本身,别用箭头函数定义(它捕获的是外层 this,拿不到对象自己);但对象方法内部的回调,该用箭头函数(它捕获方法的 this,比如 map((v) => v * this.factor) 能正确拿到 this.factor,换成普通 function 做回调 this 就丢了)。口诀:需要 this 动态绑定(对象方法本身)用普通函数;需要 this 锁定为外层(方法内回调、组件方法)用箭头函数。下面这张图,是这次 this 指向丢失的成因与解法:

第四件事:this 指向速查表

这次踩坑后,我把 this 在各种调用方式下的指向整理成一张速查表,以后一看调用形式就知道 this 是谁。

调用方式 this 指向 说明
obj.method() obj 点号左边的对象
fn() 裸调用 undefined(严格)/window 没有所属对象
const f=obj.m; f() undefined(严格) 取出后裸调用,this 丢失
new Fn() 新建的实例 构造调用
fn.call(x)/apply(x) x 显式指定,立即调用
fn.bind(x)() x 显式永久绑定
箭头函数 定义时外层的 this 词法绑定,与调用无关
setTimeout(obj.m) undefined/window 传出后是裸调用(本文坑)

这张表,把 this 在各种场景下的指向一网打尽了。记忆诀窍就一句:看"调用那一刻"函数前面有没有 对象.——有,this 就是那个对象;没有(裸调用、被取出来传走),this 就是 undefined;箭头函数则永远是定义时外层的 this它给我的启发是:JavaScript 的 this,本质上是一个"晚绑定"的、动态的概念——它故意不在定义时固定,而是留到调用时,根据"调用的上下文"来决定这种设计带来了极大的灵活性(同一个函数能服务于不同的对象,如 call/apply 借用方法),但也带来了"容易丢失、难以捉摸"的代价理解了"this 是动态绑定的"这个本质,所有关于 this 的谜题就都能迎刃而解了——因为你不再问"这个 this 应该是谁",而是问"这个函数此刻是被怎么调用的"。视角一变,迷雾尽散。

第五件事:其他常见的 this 丢失场景

这次是 setTimeout,但 this 丢失的场景远不止这一个。我把常见的几个一并梳理了,免得在别处再栽。

场景 为什么丢 this 修法
setTimeout/setInterval 方法被取出后裸调用 箭头函数包/bind/类字段箭头
addEventListener 同上,回调被裸调用 类字段箭头函数(还便于remove)
数组方法回调 map/forEach 普通function回调this丢 用箭头函数做回调
Promise.then(obj.m) 方法被取出传入 .then(() => obj.m())
解构赋值方法 const {m}=obj 解构出来就脱离了obj m.bind(obj) 或别解构
对象方法用箭头定义 箭头捕获外层非obj 对象方法用普通function

这张表,把 this 可能丢失的"案发现场"都列了出来。它们的共同规律,其实只有一条:只要一个方法"脱离了它的对象"(被取出来当值传递、被解构、被当回调),它的 this 就会丢无论是 setTimeout、事件监听、数组回调、Promise.then,还是解构赋值,本质都是同一件事:obj.method 这个"整体"拆开了,只把 method 这个函数拿走,丢下了 obj它给我的最大启发是:在 JavaScript 里,"方法"和"它所属的对象"之间的联系,是松散的、易断的——不像有些语言里方法和对象"焊死"在一起,JS 的方法更像一个"恰好放在对象属性上的、独立的函数",一旦你把它取下来,它和对象的关系就断了理解了这种"松散绑定"的本质,我就能在任何"要把方法传出去"的地方,都条件反射地警觉一下:"这一传,this 还在吗?要不要锁一下?"——把"事后被报错教训",变成"事前主动设防"。

第六件事:要把方法传出去时,我现在的判断习惯

现在每当我要把一个方法当回调/值传出去,我都会先过一遍这张图,判断 this 会不会丢、怎么锁:

这张图的精髓,是"传方法前,先判断它用没用 this、再决定怎么锁"第一问是 "这方法内部用到 this 吗":没用就随便传;用了就警惕——裸调用会丢 this然后按场景锁定:类组件方法首选类字段箭头函数(定义即绑定,最推荐);临时回调用箭头函数包一层;需要固定 this 的独立函数用 bind如果还涉及 removeEventListener,务必用同一个引用(类字段箭头函数或存起来的 bind 结果)这套判断,让我传方法时,从"直接传出去再被报错教训"变成了"先想清楚 this 会不会丢"——核心始终是:方法一旦脱离对象被传递,就要主动锁定它的 this。

我立下的几条规矩

这场"this 凭空丢失"的事故,换来了我写 JavaScript 时,刻进骨子里的几条铁律:

  1. this 由"调用方式"决定,不由"定义位置"决定。看调用那一刻前面有没有 obj. 。
  2. 方法被取出来单独调用,this 就丢。setTimeout/事件监听/数组回调/解构都是。
  3. 类组件方法首选类字段箭头函数。increment = () => {} 定义即绑定,最省心。
  4. 临时回调用箭头函数包一层。setTimeout(() => this.m(), t),别直接传 this.m。
  5. 对象方法本身别用箭头函数定义。它捕获外层 this,拿不到对象自己。
  6. 方法内的回调该用箭头函数。捕获方法的 this,拿得到 this.xxx。
  7. removeEventListener 要用同一引用。临时 bind/箭头每次生成新函数,移除不掉。

附:一组亲手验证 this 指向的实验

口说无凭。下面这组代码,让你亲眼看见同一个方法,在不同调用方式下 this 怎么变,跑一遍胜过背十遍规则:

"use strict";  // 严格模式, 裸调用 this 是 undefined(class 内部默认严格)

const obj = {
  name: "obj",
  show() {
    // 打印 this 是谁
    console.log("this 是:", this === obj ? "obj ✓" : this);
  },
};

// ====== 实验1: 正常方法调用 ======
obj.show();                    // this 是: obj ✓   (点号左边是 obj)

// ====== 实验2: 取出来裸调用, this 丢失 ======
const f = obj.show;
try { f(); } catch (e) { console.log("裸调用报错:", e.message); }
//   严格模式下 this=undefined, this===obj 为 false; 若访问 this.name 会报错

// ====== 实验3: setTimeout 传方法(本文的坑)======
setTimeout(obj.show, 10);      // 10ms后: this 不是 obj!(传出去成了裸调用)

// ====== 实验4: bind 锁定 ======
const bound = obj.show.bind(obj);
bound();                       // this 是: obj ✓   (bind 永久锁定)
setTimeout(bound, 20);         // 20ms后依然: this 是 obj ✓

// ====== 实验5: 箭头函数包一层 ======
setTimeout(() => obj.show(), 30);  // 30ms后: this 是 obj ✓ (内部用 obj.show())

// ====== 实验6: call/apply 临时指定 ======
const other = { name: "other" };
obj.show.call(other);          // this 是: {name:"other"} (call 指定成 other)

// ====== 实验7: 对象方法用箭头定义的反例 ======
const bad = {
  name: "bad",
  show: () => console.log("箭头this:", typeof this),  // 捕获外层this, 不是bad
};
bad.show();                    // 箭头this: undefined/object (反正不是 bad)

/* 控制台输出顺序(同步先, 定时器后):
   this 是: obj ✓                 ← 实验1
   裸调用报错(或 this 非obj)       ← 实验2
   this 是: obj ✓                 ← 实验4 bound()
   this 是: {name:"other"}         ← 实验6 call
   箭头this: ...                   ← 实验7
   this 不是 obj!                  ← 实验3 setTimeout(obj.show)
   this 是: obj ✓                 ← 实验4 setTimeout(bound)
   this 是: obj ✓                 ← 实验5 setTimeout(箭头)
*/

// 核心: 同一个 show 方法, obj.show()是obj、裸调用丢失、bind/箭头锁定、call指定;
//   亲眼看 this 随"调用方式"变化, 比背规则深刻得多。

这组实验,把"this 由调用方式决定"这个抽象规则,变成了一行行可以亲眼验证的输出它的精妙,在于用同一个 show 方法,跑遍了所有调用方式:实验 1 正常调用 this 是 obj;实验 2/3 取出来裸调用、传给 setTimeout,this 就丢了(正是本文的坑);实验 4/5 用 bind 和箭头函数把 this 锁回来;实验 6 用 call 临时指定成别的对象;实验 7 展示了"对象方法用箭头定义"的反例跑一遍,你会清清楚楚看到:同一个方法,什么都没改,仅仅是"被调用的方式"不同,this 就在 obj、undefined、other 之间反复横跳这,正是我想用这组实验,留给每个被 this 折磨过的人的最后一课:对于像 this 这样"反直觉、规则多、口说无凭"的概念,最好的学习方式,就是写一组对照实验,把每条规则都"跑给自己看"当我亲眼见证 this 随调用方式跳来跳去,那句抽象的"this 由调用决定"就从一句需要背诵的口诀,变成了一个我亲眼见过的事实把抽象的语言规则,变成具体的、可对照的实验现象——这是我征服每一个"玄学"般的语言特性,最朴素也最可靠的办法语言的"玄学",往往只是因为我们没亲手把它"跑"明白;一旦跑明白了,玄学就成了常识。

延伸:为什么现代框架里 this 的坑少了很多

解决完这个问题,我也顺带想明白了一件事:为什么我用 React/Vue 这些现代框架时,this 的坑似乎少了很多?答案是:现代前端开发的演进方向,恰恰是在有意识地"绕开 this 的复杂性"最典型的就是 React Hooks:在 class 组件时代,你得写一堆 this.handleClick = this.handleClick.bind(this),稍不留神就踩 this 丢失的坑;而 函数组件 + Hooks 出现后,组件就是一个普通函数,状态用 useState、副作用用 useEffect,事件处理函数就是函数内定义的普通函数或箭头函数——整个过程里几乎不再需要 this。这背后是一个深刻的设计取舍:既然 this 的动态绑定如此容易出错,那不如从编程模型上,尽量减少对 this 的依赖类似地,Vue 3 的 Composition API(setup 函数 + ref/reactive),也是在淡化 Vue 2 选项式 API 里那个无处不在、有时也让人困惑的 this。这件事给我的启发,超出了 this 本身:当一个语言特性"强大但易错"时,工程界的应对,往往有两条路:一是"教大家如何正确使用它"(如本文讲的各种锁定 this 的技巧),二是"设计出尽量不需要用它的新范式"(如 Hooks)前者治标(在现有框架下你仍需懂 this),后者治本(从根上减少了犯错的可能)。而真正推动技术进步的,往往是后者——用更好的抽象和设计,把"容易犯的错"从源头上"设计掉"这也提醒我:遇到一个反复坑人的特性,除了学会"小心地正确使用它",也该想想"有没有一种方式,能让我根本不需要面对这个坑"不过,理解 this 依然是 JavaScript 的基本功——因为框架能帮你少用它,却不能让它消失;在框架的边界之外、在阅读底层代码时,你迟早还会与它相遇。懂得它的本质,你才能在它出现时从容应对,也才能真正读懂"那些新范式,究竟帮你绕开了什么"。

写在最后

回头看,这场由 this 指向丢失引发的、方法莫名报 undefined 的事故,真正教给我的,远不止"记得 bind"这一个技巧。它让我对 JavaScript 这门语言的一个核心设计哲学,有了更深的理解,也对一类普遍的认知误区,有了警觉。我栽跟头,是因为我把 this 想象成了一个"静态的、写死的"东西——以为"方法定义在哪个对象里,它的 this 就永远是那个对象"(这其实是很多其他语言的行为)。可 JavaScript 的 this,偏偏是一个"动态的、调用时才决定的"概念。我用对"静态 this"的直觉,去理解一个"动态 this"的语言,自然处处碰壁。这让我领悟到一个跨语言学习中极其重要、也极易被忽视的道理:当我们从一门语言转到另一门语言时,最危险的,不是那些"语法上的不同"(那些一眼能看出来),而是那些"看起来一样、语义却不同"的概念;我们会不自觉地把旧语言的心智模型,套用到新语言的相似概念上,而这种"想当然的迁移",正是 bug 的温床this 这个词,在很多语言里都有,看起来都"指当前对象",但 JavaScript 给了它完全不同的、动态绑定的灵魂。所以,学一门语言,不能只学它的"语法长什么样",更要学它每个概念"背后的语义和机制",尤其要警惕那些"熟悉的词,陌生的含义"真正掌握一门语言,是掌握它独特的思维方式,而不是用旧习惯去硬套它的新外衣。这,是我用一次"this 凭空丢失"的事故,换来的、关于 JavaScript、也关于"跨语言心智迁移陷阱"的、最朴素也最深刻的领悟。如果这篇复盘,能让你下次把方法传出去时,条件反射地想一下"this 还在吗",那我对着那个 undefined 熬的这大半天,就值了。

—— 别看了 · 2026
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理 邮箱1846861578@qq.com。
技术教程

我的 Python 函数返回的数据,第一次遍历好好的、第二次却空空如也,我对着生成器只能消费一次这个坑排查了大半天的复盘

2026-6-2 6:10:35

技术教程

我在 for 循环里起了一堆 goroutine 处理每个元素,结果它们全在处理同一个、还是最后一个,我对着循环变量被闭包捕获排查了大半天的复盘

2026-6-2 6:21:22

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索