最近在学习Vue中在图灵社区买了一本电子书《Vue小书》,我感觉挺坑的,没有期待的那么好。
其中有的一下子就给一大串代码,但这一大串代码只是为了说明某一点,但是这片代码很多处都可以讲的,大概是因为篇幅限制原因吧。
其中我第一次看到的Vue实例中的data属性时,很好奇为什么是用function return 一个对象,而在之前某些视频中的写法又是直接写的对象,然后一查,又牵扯到JS原型链等一些问题。本质上是js语言的问题。
首先官方解释:
当一个组件被定义, data 必须声明为返回一个初始数据对象的函数,因为组件可能被用来创建多个实例。如果 data 仍然是一个纯粹的对象,则所有的实例将共享引用同一个数据对象!通过提供 data 函数,每次创建一个新实例后,我们能够调用 data 函数,从而返回初始数据的一个全新副本数据对象。
所以官方推荐的写法是:
data() {
return {
返回的数据
}
}
然后又举了一个例子:
假定存在组件ComponentA,里面定义为
module.exports = {
props: {
title: String
},
data: {
test: 1
}
}
在某个Page里面我们定义了如下的模板
<template>
<div>
<component-a title="1"></component-a>
<component-a title="2"></component-a>
</div>
</template>
那么上面两个ComponentA的实例中的data将同时为组件定义时data对应的对象,即相当于两个实例的data相互影响了。
也就是第一个实例的data和第二个我们并不想改变的实例的data也会发生改变。
再举个例子:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
</div>
<!--祖册一个组件-->
<template id='my_btn'>
<button @click="counter += 1">点击的次数{{counter}}</button>
</template>
<script src="js/vue.min.js"></script>
<script>
Vue.component('my-btn', {
template: '#my_btn',
data() {
return {
counter: 0
}
}
})
new Vue({
el: '#app',
})
</script>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
<my-btn></my-btn>
</div>
<!--祖册一个组件-->
<template id='my_btn'>
<button @click="counter += 1">点击的次数{{counter}}</button>
</template>
<script src="js/vue.min.js"></script>
<script>
let data = {
counter:0
}
Vue.component('my-btn', {
template: '#my_btn',
data() {
return data;
}
})
new Vue({
el: '#app',
})
</script>
</body>
</html>
运行上面两段代码会发现不同结果。
这个问题类比到引用数据类型。
如果不用function return 每个组件的data都是内存的同一个地址,那一个数据改变其他也改变了,这当然就不是我们想要的。
用function return 其实就相当于申明了新的变量,相互独立,自然就不会有这样的问题
这让我之前看到的一个JS指针问题,js里面有引用类型和基本类型,
//基本类型
var a = 1;
var b = a;
b = 2;
console.log(a) //1 值不随b的改变而改变
//引用类型
var a = {x: 1};
var b = a;
b.x = 2
console.log(a.x) //2 值随着b的改变而改变
具体原因,犀牛书里面好像讲过, 基本类型占用空间少,而引用类型占用大,js为了节约空间,就把引用类型的值用一个叫做指针的东西和变量连接起来, 也就是说改一个其他的都会跟着改变,公用的意思.
我们用JS原型链在举一个例子:
var MyComponent = function() {}
MyComponent.prototype.data = {
a: 1,
b: 2,
}
// 上面是一个虚拟的组件构造器,真实的组件构造器方法很多
var component1 = new MyComponent()
var component2 = new MyComponent()
// 上面实例化出来两个组件实例,也就是通过<my-component>调用,创建的两个实例
component1.data.a === component2.data.a // true
component1.data.b = 5
component2.data.b // 5
可以看到上面代码中最后三句测试代码,如果两个实例同时引用一个对象,那么当你修改其中一个属性的时候,另外一个实例也会跟着改。这怎么可以,两个实例应该有自己各自的域才对。所以,需要通过下面方法来进行处理:
var MyComponent = function() {
this.data = this.data()
}
MyComponent.prototype.data = function() {
return {
a: 1,
b: 2,
}
}
这个方法就是给实例添加一个data属性,它的值是原型方法data返回的一个新对象,下次用实例访问data的时候就会直接访问到这个对象,因为有了实例属性data了,就不会去查找原型上的data。
Vue.component('my-component', {
template: '<div>测试</div>',
data() {
return {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
},
})
上面这个操作是一个简易操作,实际上,它首先需要创建一个组件构造器,然后注册组件。注册组件的本质其实就是建立一个组件构造器的引用。使用组件才是真正创建一个组件实例。所以,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。