简易版
vm.$data.xxx 与 vm.xxx 的关系
- Vue 在创建实例的过程中调用了 data 函数
- data 函数返回数据对象
- Vue 将其包装成响应式对象后保存到
$data
中
- 而且实现了跨过
$data
还能访问属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| const App = Vue.createApp({ data() { return { title: 'This is my TITLE', } }, template: ` <h1>{{ title }}</h1> ` });
const vm = App.mount('#app'); console.log(vm); console.log(vm.$data.title); console.log(vm.title);
vm.$data.title = 'This is your TITLE'; console.log(vm.title);
|
$data 是响应式数据对象
后续在 vm 上添加 author ,不会出现在 vm.$data 上:
在 $data 上添加属性,不会出现在 vm 上:
1
| vm.$data.author = 'Lance';
|
两种添加方式都不能渲染到页面上:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| const App = Vue.createApp({ data() { return { title: 'This is my TITLE', } }, template: ` <div> <h1>{{ title }}</h1> <h1>{{ author }}</h1> </div> ` }); const vm = App.mount('#app');
vm.$data.author = 'Lance';
|
手写简易 Vue 读写 data
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| var vm = new Vue({ data() { return { a: 1, b: 2 } } });
console.log(vm.a); vm.b = 233; console.log(vm.b);
function Vue(options) { this.$data = options.data(); var _this = this; for (var key in this.$data) { (function(k) { Object.defineProperty(_this, k, { get: function() { return _this.$data[k]; }, set: function(newVal) { _this.$data[k] = newVal; } }); })(key); } }
|
defineGetter__、__defineSetter
Object.defineProperty 在 IE8 下只支持DOM,可以使用 defineGetter__、__defineSetter 替换方案:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| var vm = new Vue({ data() { return { a: 1, b: 2 } } });
console.log(vm.a); vm.b = 233; console.log(vm.b);
function Vue(options) { this.$data = options.data(); var _this = this; for (var key in this.$data) { (function(k) { _this.__defineGetter__(k, function() { return _this.$data[k]; }); _this.__defineSetter__(k, function(newVal) { _this.$data[k] = newVal; }); })(key); } }
|
data为什么必须得是个函数
- 如果 data 不是个函数,则有可能出现不同实例、组件修改的是同一份 data 引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var data = { a: 1, b: 2 } var vm = new Vue({ data: data }); var vm2 = new Vue({ data: data });
vm.a = 233; console.log(vm, vm2);
function Vue(options) { this.$data = options.data; var _this = this; for (var key in this.$data) { (function(k) { Object.defineProperty(_this, k, { get: function() { return _this.$data[k]; }, set: function(newVal) { _this.$data[k] = newVal; } }); })(key); } }
|
当然分别给两个新对象也没问题,但Vue为了避免出现上边情况,所以强制你得是个function,返回一个新对象,这样在Vue内部执行它时,每次就是新对象了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| var vm = new Vue({ data: { a: 1, b: 2 } }); var vm2 = new Vue({ data: { a: 1, b: 2 } });
vm.a = 233; console.log(vm, vm2);
function Vue(options) { this.$data = options.data; var _this = this; for (var key in this.$data) { (function(k) { Object.defineProperty(_this, k, { get: function() { return _this.$data[k]; }, set: function(newVal) { _this.$data[k] = newVal; } }); })(key); } }
|
Object.defineProperty 无法监听能修改原数组的数组方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
const vm = { data: { a: 1, b: 2, list: [1, 2, 3, 4, 5] } };
for (var key in vm.data) { (function(key) { Object.defineProperty(vm, key, { get() { console.log('数据获取'); return vm.data[key]; }, set(newValue) { console.log('数据设置'); vm.data[key] = newValue; } }); })(key); }
console.log(vm);
vm.a = 233; console.log(vm);
vm.list = vm.list.map(item => item * 2); console.log(vm);
console.log(vm.list);
|
Vue2 数组结构
1 2 3 4 5 6 7 8 9 10 11
| const vm = new Vue({ el: '#app', template: `<div></div>`, data() { return { list: [1, 2, 3, 4, 5] } } });
console.log(vm.list);
|
针对修改原数组,执行方法后不返回新数组的方法进行了封装
Vue类似做了这个操作:
- Vue先调用自己封装的 push 等方法
- 再在里边调用原生的
- 再进行一些更新视图操作
复杂版
思路
- 用
_data
去接收 options.data()
函数返回的对象,保存到 vm 实例中
- 为了能直接
vm.title
的形式操作数据(跳过 vm._data.title
开发更方便),我们遍历 _data
,通过 Object.defineProperty
在 vm
实例上挂载了所有 _data
下的属性
- 有了
_data
后,我们就需要对 _data
本身的改变进行拦截观察了(为了更新值时做页面更新等操作)
- 观察者模式,
new Observer(data)
- 递归观察,因为
_data
下的属性也有可能是个对象,属性的属性也有可能是个对象…
- 写
Observer
观察者构造函数
- 观察者接收的 data 有可能是个数组也有可能是个对象,所以得分情况处理
是[]
- 给数组的原型链上加中间桥梁
-
[].__proto__ = arrMethods
、arrMethods.__proto__ = Array.prototype
arrMethods
下挂载了 7 个属性,分别对应能原地改变数组的 7 个方法
- 每个方法在执行的时候,先用原生方法执行一次,再执行自己的逻辑
push、unshift、splice
三个方法有可能新增数组成员,而新增的成员也有可能是数组或对象,所以还得用一个 newArr
保存下新数组成员,然后对它们进行遍历观察
- 获取原生方法执行后的结果,返回出去
是{}
- 遍历对象属性并用 Object.defineProperty
进行数据劫持
- 每个对象属性有可能还是个对象或数组,记得再次调用
new Observer(value)
观察它
文件夹结构
- index.js
function Vue(options)
_init(options)
初始化Vue
initState(this)
初始化状态(data、computed …)
- init.js
- 包含
initState(vm)
方法,用来初始化各种状态
initData(vm)
初始化 data
- 用临时变量
vm._data
保存 options.data()
返回的 data
- 把
vm._data
挂载到 vm
下
- 目的:使
vm.title
能访问 vm._data.title
- 原理:利用
Object.defineProperty
- 方式:遍历
vm._data
调取 proxyData(vm, '_data', key)
- 对
vm._data
进行劫持,监听 vm._data
的变化
- 方式:
observe(vm._data)
observe
在 observe.js
下
- 梳理上述两个过程
- 开发者对
vm.title
进行更改
- 导致
vm._data.title
的更改
- 而我们要监听的,是
vm._data.title
的变化
- proxy.js
proxyData(vm, target, key)
方法
- 给
vm
下挂载 _data
下的各个属性(利用 Object.defineProperty
)
- observe.js
observe(data)
方法
- 如果
vm.data
不是个对象,则直接 return 不做任何操作
- 如果是对象,则调用
new Observer(data)
对 vm._data
进行观察
Observer
方法在 observer.js
中
- observer.js
Observer(data)
方法
- 判断传入的
data
是否为数组
- 是
data.__proto__ = arrMethods
(来自下方 array.js)
- 由于
data
这个数组的成员也有可能是对象或者数组,所以还得递归观察,所以得调用 observeArr(data)
(在 observeArr.js
中)
- 否
- 调用
Observer.prototype.walk(data)
方法
- 遍历 data,调用
defineReactiveData(data, key, value)
观察data下每个属性
defineReactiveData
在 reactive.js
下
- array.js
- 创建一个继承自Array原型的对象
const originArrMethods = Array.prototype
const arrMethods = Object.create(originArrMethods)
- 用一个数组存 7 个原地更新数组的方法
- 遍历它们,把它们挂载到
arrMethods
下
[ARR_METHODS].forEach(m => { arrMethods[m] = functino() {} })
- 保存传入的参数为数组
const args = [].slice.call(arguments)
- 先用原生方法执行一遍,改变数组 (别忘了保存返回值,最后得返回出去)
const ret = originArrMethods[m].apply(this, args)
push、unshift、splice
有可能给数组新增成员,所以用 newArr
保存新增数据
push、unshift
时 newArr
就是 args
unshift
时从第三个参数开始才是新增的 newArr = args.slice(2)
- 如果最后发现
newArr
有值,则需要调用 observeArr.js
遍历并监听它们
- 导出
arrMethods
- config.js
- 保存着
ARR_METHODS
,是个数组,保存着 7 个原地更新数组的方法名称
- observeArr.js
observeArr(arr)
- 遍历
arr
,然后对每个数组成员调用 observe(arr[i])
- reactive.js
defineReactiveData(data, key, value)
- 有可能 value 本身也是个对象,所以先得
observe(value)
- 调用
Object.defineProperty
- get
- set
if (newValue === value) return
value = newValue
- 有可能 value 又是个对象,则再对
value
观察:observe(newValue)
代码
array.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| import { ARR_METHODS } from './config'; import observeArr from './observeArr';
var originArrMethods = Array.prototype, arrMethods = Object.create(originArrMethods);
ARR_METHODS.map(method => { arrMethods[method] = function() { var args = Array.prototype.slice.call(arguments), rt = originArrMethods[method].apply(this, args); var newArr;
switch (method) { case 'push': case 'unshift': newArr = args; break; case 'splice': newArr = args.slice(2); break; default: break; }
newArr && observeArr(newArr); return rt; } });
export { arrMethods }
|
config.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| var ARR_METHODS = [ 'push', 'unshift', 'pop', 'shift', 'splice', 'sort', 'reverse' ];
export { ARR_METHODS };
|
index.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| import { initState } from "./init";
function Vue(options) { this._init(options); }
Vue.prototype._init = function(options) { var vm = this; vm.$options = options; initState(vm); }
export default Vue;
|
init.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import proxyData from "./proxy"; import observe from './observe';
function initState(vm) { var options = vm.$options;
if (options.data) { initData(vm); } }
function initData(vm) { var data = vm.$options.data; vm._data = data = typeof data === 'function' ? data.call(vm) : data || {}; for (var key in data) { proxyData(vm, '_data', key); }
observe(vm._data); console.log(vm); }
export { initState }
|
observe.js
1 2 3 4 5 6 7 8 9 10
| import Observer from './observer';
function observe(data) { if (data == null || typeof data !== 'object') return; return new Observer(data); }
export default observe;
|
observeArr.js
1 2 3 4 5 6 7 8 9
| import observe from "./observe";
function observeArr(arr) { for (var i = 0; i < arr.length; i++) { observe(arr[i]); } }
export default observeArr;
|
observer.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import defineReactiveData from "./reactive"; import { arrMethods } from './array'; import observeArr from './observeArr';
function Observer(data) { if (Array.isArray(data)) { data.__proto__ = arrMethods;
observeArr(data); } else { this.walk(data); } }
Observer.prototype.walk = function(data) { var keys = Object.keys(data); console.log(keys); for (var i = 0; i < keys.length; i++) { var key = keys[i], value = data[key];
defineReactiveData(data, key, value); } }
export default Observer;
|
proxy.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function proxyData(vm, target, key) { Object.defineProperty(vm, key, { get() { console.log('proxyData获取' + key); return vm[target][key]; }, set(newValue) { console.log('proxyData设置' + key); vm[target][key] = newValue; } }); }
export default proxyData;
|
reactive.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import observe from "./observe";
function defineReactiveData(data, key, value) { observe(value); Object.defineProperty(data, key, { get: function() { console.log('defineReactiveData获取', key); return value; }, set: function(newValue) { if (newValue === value) return; console.log('defineReactiveData设置', key); value = newValue; observe(newValue); } }); }
export default defineReactiveData;
|
注意区别
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| var obj = { name: "Lance", age: 28 }
function defineReactiveData(data, key, value) { Object.defineProperty(data, key, { get() { return value; }, set(newVal) { value = newVal; } }) }
for (const key in obj) { defineReactiveData(obj, key, obj[key]); }
console.log(obj.name);
|