认识 data 选项

Vue中,data必须是一个函数:

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8" />
  5. <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  6. <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  7. <title>Document</title>
  8. </head>
  9. <body>
  10. <div id="app"></div>
  11. <!-- 使用 Vue3 的 CDN -->
  12. <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  13. <script src="./main.js"></script>
  14. </body>
  15. </html>
  1. const { createApp } = window.Vue;
  2. const app = Vue.createApp({
  3. template: `
  4. <h1>{{ title }}</h1>
  5. `,
  6. // 将 data 赋值为一个对象
  7. data:{
  8. title: "this is title"
  9. }
  10. });
  11. const vm = app.mount("#app");

image.png

这是因为**Vue**在创建实例的过程中会「执行」**data**函数,然后返回数据对象。并通过响应式进行包装data存储到「实例对象」的$data属性中。

  1. const { createApp } = window.Vue;
  2. const app = Vue.createApp({
  3. template: `
  4. <h1>{{ title }}</h1>
  5. `,
  6. data(){
  7. return {
  8. title: "this is title"
  9. }
  10. }
  11. });
  12. const vm = app.mount("#app");

image.png

我们可以通过实例直接访问到data中的数据,而不需要访问$data

  1. const vm = app.mount("#app");
  2. console.log(vm.title); // "this is title"
  3. console.log(vm.$data.title); // "this is title"

这是因为**vm.title****vm.$data.title**指向的是同一个数据引用,如果你直接给实例对象新增一个属性的话,这个属性并不会新增到**$data**对象中,**$data**对象是响应式数据只在初始化的时候对数据进行定义拦截。

  1. const { createApp } = window.Vue;
  2. const app = Vue.createApp({
  3. template: `
  4. <h1>{{ title }}</h1>
  5. `,
  6. data() {
  7. return {
  8. title: "this is title",
  9. };
  10. },
  11. });
  12. const vm = app.mount("#app");
  13. // 新增一个 author 属性
  14. vm.author = "xiechen";
  15. console.log(vm);

image.png :::info 在vm实例化对象中,以$_开头的属性都是Vue内置的属性或api,开发者应尽量的避免用这些前缀命名自己的变量或方法!!! :::

模拟 Vue 把 data 数据挂载到实例上

实现的思路其实也非常的简单,上面我们说了vm.titlevm.$data.title是同一个数据的引用,所以我们按照这个思路来实现。

创建一个Vue的构造函数,这样才能返回一个实例化对象。

  1. function VueTest(options){
  2. // ...
  3. }
  4. // 进行实例化
  5. let vm = new VueTest({
  6. data(){
  7. return {
  8. a: 1,
  9. b: 2
  10. }
  11. }
  12. })

然后我们专心写构造函数内部的逻辑。

  1. function VueTest(options){
  2. // 因为 data 是个函数,所以我们需要执行后才能得到数据
  3. // 需要把 $data 挂载到实例对象上
  4. this.$data = options.data();
  5. }
  6. let vm = new VueTest({
  7. data(){
  8. return {
  9. a: 1,
  10. b: 2
  11. }
  12. }
  13. })
  14. console.log(vm);

image.png

既然$data已经挂载到实例上了,下面我们还需要把data中的属性也挂载到实例上。

  1. function VueTest(options){
  2. this.$data = options.data();
  3. for (const key in this.$data) {
  4. // 把 key 都定义到 this 对象上,也就是当前实例对象
  5. Object.defineProperty(this, key, {
  6. get: function () {
  7. return this.$data[key];
  8. },
  9. set: function (newValue) {
  10. this.$data[key] = newValue;
  11. }
  12. })
  13. }
  14. };

我们利用Object.defineProperty对属性进行拦截,当访问vm.a的时候实际上访问的是vm.$data.a

最后来测试一下:

  1. function VueTest(options) {
  2. this.$data = options.data();
  3. for (const key in this.$data) {
  4. Object.defineProperty(this, key, {
  5. get: function () {
  6. return this.$data[key];
  7. },
  8. set: function (newValue) {
  9. this.$data[key] = newValue;
  10. },
  11. });
  12. }
  13. }
  14. var vm = new VueTest({
  15. data() {
  16. return {
  17. a: 1,
  18. b: 2,
  19. };
  20. },
  21. });
  22. console.log(vm.a);
  23. vm.b = 3;
  24. console.log(vm);

image.png
可以看到vm可以直接访问a属性,a属性会被拦截,实际访问的仍然是$data.a

data 为什么必须要是一个函数?

这是一个老生常谈的问题了,大家都知道JS中的对象是引用类型,如果把一个对象赋值给另外一个对象,则新对象赋值的其实是原对象的堆内存地址。

  1. var obj1 = { a:1 };
  2. var obj2 = obj1;
  3. obj2.b = 2;
  4. console.log(obj1); // {a: 1, b: 2}
  5. console.log(obj2); // {a: 1, b: 2}

如果data是一个对象就会出现上面的问题,当同时实例化两个Vue应用(或者在Vue页面上多次使用同一个组件)就会造成以上的引用数据的问题。

  1. function VueTest(options) {
  2. // 不用执行 data ,而是直接赋值
  3. this.$data = options.data;
  4. for (const key in this.$data) {
  5. Object.defineProperty(this, key, {
  6. get: function () {
  7. return this.$data[key];
  8. },
  9. set: function (newValue) {
  10. this.$data[key] = newValue;
  11. },
  12. });
  13. }
  14. }
  1. var data = {
  2. a: 1,
  3. b: 2,
  4. };
  5. var vm1 = new VueTest({
  6. data: data,
  7. });
  8. var vm2 = new VueTest({
  9. data: data,
  10. });
  11. vm1.b = 3;
  12. console.log(vm1);
  13. console.log(vm2);

image.png :::info Vue中的data之所以要求是一个函数,就是为了确保每次执行的时候能返回一个新的对象,确保每个实例数据的引用都是独一无二的。 :::