认识 data 选项
在Vue中,data必须是一个函数:
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8" /><meta http-equiv="X-UA-Compatible" content="IE=edge" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><title>Document</title></head><body><div id="app"></div><!-- 使用 Vue3 的 CDN --><script src="https://unpkg.com/vue@3/dist/vue.global.js"></script><script src="./main.js"></script></body></html>
const { createApp } = window.Vue;const app = Vue.createApp({template: `<h1>{{ title }}</h1>`,// 将 data 赋值为一个对象data:{title: "this is title"}});const vm = app.mount("#app");

这是因为**Vue**在创建实例的过程中会「执行」**data**函数,然后返回数据对象。并通过响应式进行包装data存储到「实例对象」的$data属性中。
const { createApp } = window.Vue;const app = Vue.createApp({template: `<h1>{{ title }}</h1>`,data(){return {title: "this is title"}}});const vm = app.mount("#app");

我们可以通过实例直接访问到data中的数据,而不需要访问$data。
const vm = app.mount("#app");console.log(vm.title); // "this is title"console.log(vm.$data.title); // "this is title"
这是因为**vm.title**和**vm.$data.title**指向的是同一个数据引用,如果你直接给实例对象新增一个属性的话,这个属性并不会新增到**$data**对象中,**$data**对象是响应式数据只在初始化的时候对数据进行定义拦截。
const { createApp } = window.Vue;const app = Vue.createApp({template: `<h1>{{ title }}</h1>`,data() {return {title: "this is title",};},});const vm = app.mount("#app");// 新增一个 author 属性vm.author = "xiechen";console.log(vm);
:::info
在vm实例化对象中,以$、_开头的属性都是Vue内置的属性或api,开发者应尽量的避免用这些前缀命名自己的变量或方法!!!
:::
模拟 Vue 把 data 数据挂载到实例上
实现的思路其实也非常的简单,上面我们说了vm.title和vm.$data.title是同一个数据的引用,所以我们按照这个思路来实现。
创建一个Vue的构造函数,这样才能返回一个实例化对象。
function VueTest(options){// ...}// 进行实例化let vm = new VueTest({data(){return {a: 1,b: 2}}})
然后我们专心写构造函数内部的逻辑。
function VueTest(options){// 因为 data 是个函数,所以我们需要执行后才能得到数据// 需要把 $data 挂载到实例对象上this.$data = options.data();}let vm = new VueTest({data(){return {a: 1,b: 2}}})console.log(vm);

既然$data已经挂载到实例上了,下面我们还需要把data中的属性也挂载到实例上。
function VueTest(options){this.$data = options.data();for (const key in this.$data) {// 把 key 都定义到 this 对象上,也就是当前实例对象Object.defineProperty(this, key, {get: function () {return this.$data[key];},set: function (newValue) {this.$data[key] = newValue;}})}};
我们利用Object.defineProperty对属性进行拦截,当访问vm.a的时候实际上访问的是vm.$data.a。
最后来测试一下:
function VueTest(options) {this.$data = options.data();for (const key in this.$data) {Object.defineProperty(this, key, {get: function () {return this.$data[key];},set: function (newValue) {this.$data[key] = newValue;},});}}var vm = new VueTest({data() {return {a: 1,b: 2,};},});console.log(vm.a);vm.b = 3;console.log(vm);

可以看到vm可以直接访问a属性,a属性会被拦截,实际访问的仍然是$data.a。
data 为什么必须要是一个函数?
这是一个老生常谈的问题了,大家都知道JS中的对象是引用类型,如果把一个对象赋值给另外一个对象,则新对象赋值的其实是原对象的堆内存地址。
var obj1 = { a:1 };var obj2 = obj1;obj2.b = 2;console.log(obj1); // {a: 1, b: 2}console.log(obj2); // {a: 1, b: 2}
如果data是一个对象就会出现上面的问题,当同时实例化两个Vue应用(或者在Vue页面上多次使用同一个组件)就会造成以上的引用数据的问题。
function VueTest(options) {// 不用执行 data ,而是直接赋值this.$data = options.data;for (const key in this.$data) {Object.defineProperty(this, key, {get: function () {return this.$data[key];},set: function (newValue) {this.$data[key] = newValue;},});}}
var data = {a: 1,b: 2,};var vm1 = new VueTest({data: data,});var vm2 = new VueTest({data: data,});vm1.b = 3;console.log(vm1);console.log(vm2);
:::info
Vue中的data之所以要求是一个函数,就是为了确保每次执行的时候能返回一个新的对象,确保每个实例数据的引用都是独一无二的。
:::
