-
发生时间:2022-08-22
-
问题描述:完成上位机客户端模块的开发后(代码片段如下所示),运行时报错:
TypeError: Cannot read properties of undefined (reading '_isDebugMode')class Client { _startRecvFrame() { port.on('data', this._recvFrame) } _recvFrame(frame) { if (this._isDebugMode) { console.log(xxx) } } } -
问题类别:软件开发
-
原因分析:
- 根据报错信息,可以推断:调用
_recvFrame时,函数内的this没有绑定到 Client 对象,因此其取值为undefined。 - 查阅 JS this 关键字 的相关资料,发现 JS
this的绑定一共有 4 条规则:
- 规则1:默认绑定(Default Binding)。即
this默认不绑定任何对象,其取值为undefined。 - 规则2:隐式绑定(Implicit Binding)。即如果函数被调用时含有上下文对象,就绑定到该对象。如
obj.func()的func里面的this绑定到obj。 - 规则3:显式绑定(Explicit Binding)。即如果函数是以 bind / call / apply 的方式被调用时,就绑定到传入的对象。如
method.bind(obj)的method里面的this绑定到obj。 - 规则4:new 绑定 (
newBinding)。即如果函数是以new的方式被调用时,就绑定到返回的对象。如var bar = new foo(2)的foo里面的this绑定到bar。
- 由于
_recvFrame是以回调函数的方式被调用的,在调用时已经失去原上下文对象 Client (参考Implicitly Lost),这里要应用规则1,导致出现问题。
- 根据报错信息,可以推断:调用
-
解决方案:
- 解决这个问题有两种方案:
- 方案一:改用箭头函数
- 方案二:改用显式绑定
// 方案一:改用箭头函数 class Client { _startRecvFrame() { port.on('data', (frame) => { this._recvFrame(frame) }) } } // 方案二:改用显式绑定 class Client { _startRecvFrame() { port.on('data', this._recvFrame.bind(this)(frame)) } }- 我采用了方案一.
-
实施结果:按照方案一修改代码后,运行程序不再报错。
-
经验总结:
- JS 的
this关键字是个容易踩坑的地方,特别是在有回调函数的时候。此时记住 JSthis绑定的 4 条规则 可以救命。 - You-Dont-Know-JS 是一份深入了解 JS 的好资料(第 2 版还没写完,可以先看第 1 版),JS 开发者应该学习一下。
- JS 的
《You Don’t Know JS: this & Object Prototypes》部分要点摘录
Call-site
- To understand
thisbinding, we have to understand the call-site: the location in code where a function is called (not where it’s declared). - Take care when analyzing code to find the actual call-site (from the call-stack), because it’s the only thing that matters for this binding.
Lexical this
- Instead of using the four standard this rules, arrow-functions adopt the
thisbinding from the enclosing (function or global) scope.
Review(TL;DR)
Determining the this binding for an executing function requires finding the direct call-site of that function. Once examined, four rules can be applied to the call-site, in this order of precedence:
- Called with
new? Use the newly constructed object. - Called with
callorapply(orbind)? Use the specified object. - Called with a context object owning the call? Use that context object.
- Default:
undefinedinstrict mode, global object otherwise.