Vue实例里面的data属性为什么用函数返回

最近在学习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 {} // 返回一个唯一的对象,不要和其他组件共用一个对象进行返回
  },
})

上面这个操作是一个简易操作,实际上,它首先需要创建一个组件构造器,然后注册组件。注册组件的本质其实就是建立一个组件构造器的引用。使用组件才是真正创建一个组件实例。所以,注册组件其实并不产生新的组件类,但会产生一个可以用来实例化的新方式。

posted @ 2018-01-20 16:29  Lawliet__zmz  阅读(618)  评论(0编辑  收藏  举报