现象
在有 Vue Router 的项目开发过程中,在 Object.prototype 上挂自定义方法,会发现它的函数体内容会被拼接到 url 参数里。(下面以 Object.prototype.log 为例)
import Vue from 'vue'
import App from './App'
import router from './router'
// 在 Object.prototype 上挂了一个方法
Object.prototype.log = function() {
console.log(this)
}
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
原因
1. 定位 Vue Router 上的 for-in
初步怀疑是某个操作遍历到了 Object.prototype.log;和 url 有关那么很可能在 Vue Router 中。在这两点猜测下,定位到 Vue Router 的 query.js 的一个 resolveQuery 函数:
export function resolveQuery (
query: ?string,
extraQuery: Dictionary<string> = {},
_parseQuery: ?Function
): Dictionary<string> {
const parse = _parseQuery || parseQuery
let parsedQuery
try {
parsedQuery = parse(query || '')
} catch (e) {
process.env.NODE_ENV !== 'production' && warn(false, e.message)
parsedQuery = {}
}
// 问题出在 for-in 的遍历中,遍历到了 log 函数
for (const key in extraQuery) {
parsedQuery[key] = extraQuery[key]
}
return parsedQuery
}
然后在 router.js 中经过 createRoute 函数中的 stringifyQuery 操作,拼接到最终的 url 上。
2. for-in 遍历方式
Object.prototype 上有 toString 等方法,为什么它不会被遍历到呢?for-in 的遍历方式如下:
for…in语句以任意顺序遍历一个对象自有的、继承的、可枚举的、非Symbol的属性。对于每个不同的属性,语句都会被执行。
通过 Object.prototype.propertyIsEnumerable 方法可以判断出 log 方法是一个可枚举的属性,所以会被 for-in 遍历到。
解决方法
把定义的方法变成不可枚举就行,需要借助 Object.defineProperty,可以解决上述问题
import Vue from 'vue'
import App from './App'
import router from './router'
/*// 在 Object.prototype 上挂了一个方法
Object.prototype.log = function() {
console.log(this)
}*/
// 改成 Object.defineProperty 的方法
Object.defineProperty(Object.prototype, 'log', {
// enumerable: false, // 设置为不可枚举,默认值已为 false
// configurable、writable 等根据需要设置
value: function() {
console.log(this)
}
})
new Vue({
el: '#app',
router,
components: { App },
template: '<App/>'
})
更多
尽量不要改任何 builtin 对象的 prototype,尤其是修改 Object.prototype。因为即使不用 Vue Router,即使不是在 vue 项目中,在任何地方,只要对任何对象执行了 for-in 操作,都会可能引入不想要的内容。
其实不应该通过 prototype 来增加方法,最好是通过外置公共方法,例如上面应该改成 export function log(obj) { console.log(obj) }