英文:
undefined this in object function method express routing
问题
我是新手使用Express。
在路由中,我创建了一个对象并将其导出。
// 导入用户控制器
import userController from "../controllers/userController.js"
// 获取所有用户
router.get("/", isAuth, isAdmin, userController.list)
问题是,userController 中的 this 未定义。
const userController = {
_statusCode: 200,
async list( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
或者
const userController = {
_statusCode: 200,
list: async function( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
或者
const userController = {
_statusCode: 200,
list: async ( req, res ) => {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
在这三种方式中,日志输出都是:
undefined
file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
^
TypeError: Cannot read properties of undefined (reading 'statusCode')
at list (file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18:31)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
我知道关于对象,我尝试在Chrome控制台中创建对象,它被定义了,但在Express路由中,它却未定义。
有人能解释为什么会发生这种情况吗?
<details>
<summary>英文:</summary>
I'm new to express.
for routing I create an object in a file and export the object
import userController from "../controllers/userController.js"
// get all users
router.get("/", isAuth, isAdmin, userController.list)
the problem is, ***this** is undefined* on **userController**
const userController = {
_statusCode: 200,
async list( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
OR
const userController = {
_statusCode: 200,
list: async function( req, res ) {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
OR
const userController = {
_statusCode: 200,
list: async ( req, res ) => {
console.log(this);
try {
const users = await User.find()
return res.status( this._statusCode ).json( respSC( users, this._statusCode ) )
} catch( err ) {
this.statusCode = 500
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
}
}
}
export default userController
in all three ways, the log is :
undefined
file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18
return res.status( this._statusCode ).json( respER( this._statusCode, err ) )
^
TypeError: Cannot read properties of undefined (reading '_statusCode')
at list (file:///C:/NodeJS/<project-folder>/app/controllers/userController.js:18:31)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
I know about objects and I try create object in chrome console and this is defined, but in routing of express, this is undefined.
can anybody explain why it's happen?
</details>
# 答案1
**得分**: 1
首先,在这里传递`userController.list`时:
```javascript
router.get("/", isAuth, isAdmin, userController.list)
JS 解释器会访问 userController
对象并获取到 list
方法的引用,所传递的仅仅是该方法的引用本身,与 userController
对象没有任何关联。因此,list()
方法将被 Express 作为普通函数调用(因为它甚至不知道 userController
对象的存在),并且(如果在 strict
模式下运行),则调用普通函数会将 this
值设置为 undefined
。
因此,这解释了为什么出现了你所观察到的情况。
那么,如何修复呢。 最简单的方法是使用 .bind()
:
router.get("/", isAuth, isAdmin, userController.list.bind(userController));
这会将一个动态生成的存根函数传递给 Express,当 Express 调用该存根函数时,存根函数将以正确的方式调用你的 list
方法,以将 this
值设置为 userController
。
还有其他方法可以做到这一点:
- 你可以更改方法声明的方式,使它们自动绑定到其父对象。
- 你可以编写自己的子函数,以
userController.list()
的方式调用list
,然后将自己的存根函数传递给 Express。 - 你可以在类中使用实用函数自动绑定方法。
- 甚至有一个新的 ECMAScript 语法提案,使用
::
绑定运算符。
以下是一个简单的代码示例,展示了你所看到的 undefined
以及解决方案。第一个示例 fn1
是你当前的代码所做的事情。第二个示例 fn2
是我建议的解决方案。
<!-- 开始代码示例: js 隐藏: false 控制台: true babel: false -->
<!-- 语言: lang-js -->
"use strict";
class MyObj {
constructor() {
this.greeting = "Hi";
}
getGreeting() { console.log(this); }
};
const obj = new MyObj();
// 获取一个未绑定的方法并调用它
const fn1 = obj.getGreeting;
fn1();
// 获取一个绑定的方法并调用它
const fn2 = obj.getGreeting.bind(obj);
fn2();
<!-- 结束代码示例 -->
希望这有所帮助。
英文:
First, when you pass userController.list
in this:
router.get("/", isAuth, isAdmin, userController.list)
The JS interpreter reaches into the userController
object and gets a reference to the list
method and what it passes is just a reference to that method all by itself and will have no connection at all to the userController
object at all. Thus, the list()
method will just be called as a regular function by Express (because it doesn't even know about the userController
object and (if operating in strict
mode), then calling a regular function leaves the this
value as undefined
.
So, that explains why what you observe is happening.
Now, how to fix that. The simplest way would be use .bind()
:
router.get("/", isAuth, isAdmin, userController.list.bind(userController));
This passes a dynamically generated stub function to Express and when Express calls that stub function and the stub function then calls your list
method in the right way to set the this
value inside it to userController
.
There are other ways to do this too:
- You could change the way the methods are declared so they will automatically bind to their parent object.
- You could write your own sub function that calls
list
asuserController.list()
and you pass your own stub function to Express. - You can use utility functions in your class that auto-bind methods.
- There's even a new ECMAScript syntax proposal for a binding operator with
::
.
Here's a simple code example that shows both the undefined
you're seeing and the solution. The first example fn1
is what your current code is doing. The second example fn2
is my suggested solution.
<!-- begin snippet: js hide: false console: true babel: false -->
<!-- language: lang-js -->
"use strict";
class MyObj {
constructor() {
this.greeting = "Hi";
}
getGreeting() { console.log(this); }
};
const obj = new MyObj();
// get a non-bound method and call it
const fn1 = obj.getGreeting;
fn1();
// get a bound method and call it
const fn2 = obj.getGreeting.bind(obj);
fn2();
<!-- end snippet -->
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论