Close
升级到 Vue 3 | Vue 2 EOL

添加实例属性

基本示例

您可能希望在许多组件中使用一些数据/实用程序,但您不希望污染全局范围。 在这些情况下,您可以通过在原型上定义它们,使它们对每个 Vue 实例可用

Vue.prototype.$appName = 'My App'

现在 $appName 在所有 Vue 实例上都可用,甚至在创建之前。 如果我们运行

new Vue({
beforeCreate: function() {
console.log(this.$appName)
}
})

那么 "My App" 将被记录到控制台!

实例属性作用域的重要性

您可能想知道

“为什么 appName$ 开头? 这很重要吗? 它做了什么?

这里没有魔法。 $ 是 Vue 用于所有实例可用的属性的约定。 这避免了与任何定义的数据、计算属性或方法发生冲突。

“冲突? 你是什么意思?”

另一个好问题! 如果您设置

Vue.prototype.appName = 'My App'

那么您期望下面记录什么?

new Vue({
data: {
// Uh oh - appName is *also* the name of the
// instance property we defined!
appName: 'The name of some other app'
},
beforeCreate: function() {
console.log(this.appName)
},
created: function() {
console.log(this.appName)
}
})

它将是 "My App",然后是 "The name of some other app",因为 this.appNamedata 覆盖(有点像)当实例被创建时。 我们使用 $ 对实例属性进行作用域以避免这种情况。 如果您愿意,您甚至可以使用自己的约定,例如 $_appNameΩappName,以防止与插件或未来功能发生冲突。

现实世界示例:用 Axios 替换 Vue 资源

假设您正在替换现在已退役的 Vue 资源。 您非常喜欢通过 this.$http 访问请求方法,并且您希望用 Axios 代替它。

您所要做的就是将 axios 包含在您的项目中

<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.15.2/axios.js"></script>

<div id="app">
<ul>
<li v-for="user in users">{{ user.name }}</li>
</ul>
</div>

axios 别名为 Vue.prototype.$http

Vue.prototype.$http = axios

然后您将能够在任何 Vue 实例中使用诸如 this.$http.get 之类的方法

new Vue({
el: '#app',
data: {
users: []
},
created() {
var vm = this
this.$http
.get('https://jsonplaceholder.typicode.com/users')
.then(function(response) {
vm.users = response.data
})
}
})

原型方法的上下文

如果您不知道,添加到 JavaScript 原型中的方法将获得实例的上下文。 这意味着它们可以使用 this 访问实例上定义的数据、计算属性、方法或任何其他内容。

让我们在 $reverseText 方法中利用这一点

Vue.prototype.$reverseText = function(propertyName) {
this[propertyName] = this[propertyName]
.split('')
.reverse()
.join('')
}

new Vue({
data: {
message: 'Hello'
},
created: function() {
console.log(this.message) // => "Hello"
this.$reverseText('message')
console.log(this.message) // => "olleH"
}
})

请注意,如果您使用 ES6/2015 箭头函数,上下文绑定将不会起作用,因为它们隐式绑定到其父作用域。 这意味着箭头函数版本

Vue.prototype.$reverseText = propertyName => {
this[propertyName] = this[propertyName]
.split('')
.reverse()
.join('')
}

将抛出错误

Uncaught TypeError: Cannot read property 'split' of undefined

何时避免这种模式

只要您在对原型属性进行作用域方面保持警惕,使用这种模式是相当安全的 - 也就是说,不太可能产生错误。

但是,它有时会导致与其他开发人员的混淆。 例如,他们可能会看到 this.$http,然后想,“哦,我不知道 Vue 有这个功能!” 然后他们转到另一个项目,当 this.$http 未定义时感到困惑。 或者,也许他们想谷歌如何做某事,但找不到结果,因为他们没有意识到他们实际上是在使用 Axios 的别名。

便利性是以明确性为代价的。 当查看组件时,无法判断 $http 来自哪里。 Vue 本身? 插件? 同事?

那么有哪些替代方案呢?

替代模式

不使用模块系统时

没有模块系统(例如通过 Webpack 或 Browserify)的应用程序中,有一个模式经常与任何 JavaScript 增强的前端一起使用:全局 App 对象。

如果您要添加的内容与 Vue 本身无关,这可能是一个不错的选择。 以下是一个示例

var App = Object.freeze({
name: 'My App',
version: '2.1.4',
helpers: {
// This is a purely functional version of
// the $reverseText method we saw earlier
reverseText: function(text) {
return text
.split('')
.reverse()
.join('')
}
}
})

如果您对 Object.freeze 皱眉,它的作用是防止将来更改该对象。 这实质上使它的所有属性都成为常量,保护您免受未来的状态错误。

现在这些共享属性的来源更加明显:应用程序中某个地方定义了一个 App 对象。 要找到它,开发人员可以执行项目范围的搜索。

另一个优点是 App 现在可以在您的代码中的任何地方使用,无论它是否与 Vue 相关。 这包括将值直接附加到实例选项,而不是必须进入函数以访问 this 上的属性

new Vue({
data: {
appVersion: App.version
},
methods: {
reverseText: App.helpers.reverseText
}
})

使用模块系统时

当您可以使用模块系统时,您可以轻松地将共享代码组织成模块,然后在需要的地方 require/import 这些模块。 这是明确性的典范,因为在每个文件中,您都会获得一个依赖项列表。 您确切地知道每个依赖项来自哪里。

虽然肯定更冗长,但这种方法绝对是最易于维护的,尤其是在与其他开发人员合作或构建大型应用程序时。