实现 Vue2.0 数据劫持
import Vue from './vue.js';
const vm = new Vue({
el: '#app',
data() {
return {
a: 1,
b: 2,
studentList: [3, 4, 5, 6, 7],
info: {
a: {
b: 1,
},
},
obj: [
{
id: 8,
name: 'Tom',
age: 28,
},
{
id: 9,
name: 'Jack',
age: 29,
},
{
id: 10,
name: 'Jack',
age: 30,
},
],
};
},
});
vue.js
import { initState } from './initState.js';
/**
* Vue构造函数
* @param {*} options 配置项
*/
function Vue(options) {
// 判断当前配置项的类型,如果类型不匹配则返回
if (typeof options !== 'object' || options === null) return;
// Vue初始化函数
this._init(options);
}
Vue.prototype._init = function (options) {
// 实例化对象
const vm = this;
// options配置项挂载到实例对象中
this.$options = options;
// 初始化实例对象的状态
initState(vm);
};
export default Vue;
initState.js
// 数据劫持绑定
import proxyData from './proxyData.js';
// 数据观察
import observe from './observe.js';
function initState(vm) {
// 获取实例对象上的$options属性
const option = vm.$options;
// 判断$options属性中是否存在data属性
if (option.data) {
// 如果存在data属性,执行初始化data方法
initData(vm);
}
}
function initData(vm) {
// 获取实例对象的data属性
let data = vm.$options.data;
// 在实例对象挂载_data属性,是data属性执行后的返回值;
vm._data = data = typeof data === 'function' ? data.call(vm) : data || {};
// 通过数据劫持的方式处理vm.a、vm.b可以直接获取值的需求;
for (const key in data) {
// 设置代理数据的方法
proxyData(vm, '_data', key);
}
// 进行完数据绑定后,需要观察data数据的更新
observe(data);
}
export { initState };
proxyData.js
/**
* @param {*} vm 实例化对象
* @param {*} target 目标对象属性
* @param {*} key 对象键名
*/
function proxyData(vm, target, key) {
// 数据劫持绑定
Object.defineProperty(vm, key, {
get() {
// vm.a ---> vm['_data']['a'];
return vm[target][key];
},
set(newValue) {
// vm.a = 3 --> vm['_data']['a'] = 3;
if (newValue === vm[target][key]) return;
vm[target][key] = newValue;
},
});
}
export default proxyData;
observe.js
// 引入观察者
import Observer from './observer.js';
/**
* @param {*} data vm._data属性
*/
function observe(data) {
// 判断vm.data属性类型
if (typeof data !== 'object' || data === null) return;
// 设置观察者实例对象
return new Observer(data);
}
export default observe;
observer.js
// 引入数据响应式的方法
import { defineReactiveData } from './reactive.js';
// 引入数组处理方法
import { arrMethods } from './array';
import observeArr from './observeArr';
/**
* @param {*} data vm._data
*/
function Observer(data) {
// 判断vm._data属性类型是数组还是对象;
if (Array.isArray(data)) {
data.__proto__ = arrMethods;
observeArr(data);
} else {
// 是对象,执行walk方法;
this.walk(data);
}
}
Observer.prototype.walk = function (data) {
// 获取vm._data对象的可枚举键名 keys --> ['a', 'b', 'students', 'obj']
const keys = Object.keys(data);
// 循环遍历keys数组
for (let i = 0; i < keys.length; i++) {
// 获取vm._data对象的键名和键值;
const key = keys[i];
const value = data[key];
// 执行数据响应式的方法
defineReactiveData(data, key, value);
}
};
export default Observer;
reactive.js
function defineReactiveData(data, key, value) {
// 存在的问题:此时value可能还是一个对象,或者是数组
// 递归观察
observe(value);
// 数据劫持
Object.defineProperty(data, key, {
get() {
console.log('响应式数据: 获取', value);
return value;
},
set(newValue) {
console.log('响应式设置: 设置', newValue);
if (newValue === value) return;
// 递归观察
observe(newValue);
value = newValue;
},
});
}
export { defineReactiveData };
array.js
// 引入更改原数组方法的配置对象
import { ARR_METHODS } from './config';
// 引入观察数组的方法
import observeArr from './observeArr.js';
const originArrMethods = Array.prototype;
const arrMethods = Object.create(originArrMethods);
ARR_METHODS.forEach(function (m) {
// 原数组的基础上包裹一层函数,目的是执行原有函数后还能够执行其他逻辑;
arrMethods[m] = function () {
const args = Array.prototype.slice.call(arguments);
// 执行原有的数组方法
const rt = originArrMethods[m].apply(this, args);
// 执行其他逻辑
let newArr;
switch (m) {
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
const ARR_METHODS = ['push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'];
observeArr.js
// 引入observe观察的方法
import observe from './observe';
function observeArr(arr) {
for (let i = 0; i < arr.length; i++) {
observe(arr[i]);
}
}
export default observeArr;