最近在学习Node.js下REST API的编写。数据库选用了MongoDB,因为不用关心数据类型转换等杂七杂八的问题,并且可以直接使用JSON查询,特别方便。

环境

node.js v14.5.0

问题

[code lang="javascript" show_lang=true]app.route('/tags/:key/:value')
.get((req,res)=>{
const {key,value}= req.params
const query = Object.defineProperty({},key,{value})
console.log(query)
Tag.getTag(query)
.then((docs)=>{
res.send(docs)
},(reason)=>{
res.send(`Reject with:${reason}`)
})
})
[/code]

这段代码是路由处理,从HTTP请求中获取key与value两个参数,并用它们组建一个Object传递给MongoDB查询。

然而,这段代码每次查询都会返回数据库中所有的tag。排查发现这里的console.log()在控制台中会输出{},在Chrome Developer Tool 中是{name: "神楽めあ"}。吊诡的是,就算是在getTag()函数内设置断点监视输入的query变量也会得到一样的结果(其实和console.log()收到的query的值不一样是一个道理)

也就是说,MongoDB收到的查询参数是{},这使它返回所有的文档。

考虑到Node.js和Chrome的V8不一定是一个版本的,对Javascript的语法支持可能有所不同,调整了一下tsconfig.json里的target,从原来的ES2019改到了ES6,但是编译后的Javascript没有变化。

我又试着改动了query的赋值方式:

[code lang="javascript"] var query = {}
query = Object.defineProperty(query,key,{value})[/code]

结果没有变化。

我猜测应该是在Node.js中,Object.defineProperty()出于未知原因无法工作。

咨询谷歌后得知:

One of the properties of property descriptors is enumerable, which has the default value false. If a property is non-enumerable, Node.js chooses not to display the property, thats it.

You can change that bit and try this:[code lang="javascript"]let name = {}; Object.defineProperty(name, 'last', { value: 'Doe', enumerable: true }); console.log(name);[/code]

Object.defineProperty in Node.js - Stackflow

也就是说,Object.defineProperty()默认会把属性定义为不可枚举的,而Node.js会选择不显示不可枚举的属性。

查询MDN后发现确实:

enumerable当且仅当该属性的 enumerable 键值为 true 时,该属性才会出现在对象的枚举属性中。
默认为 false

Object.defineProperty() - MDN

也就是说,Node.js的console和Mongoose都一同把这个不能枚举的name属性给忽略了,导致MongoDB收到了空的query。

随后发现JSON.stringify()也会忽略无法枚举的属性,而这个函数应该是Nodejs侧的MongoDB驱动与MongoDB沟通中的关键函数。

所以究竟为什么defineProperty()要默认把属性定义成不可枚举的啊?????

解决方案

......

于是我一转Object.fromEntries()