前言
- 文分【思路篇】和【实现篇】,本文为实现篇,建议看两个窗口同步阅读,或请先阅读-》vue2.0|思路篇|数据劫持
- 第一二阶段是框架搭建,代码见思路篇
第三阶段:实现初步数据劫持,监听用户对对象属性的赋值取值操作,触发同时执行自定义逻辑
定义observe方法,其参数是一个对象,非对象会被直接return;对象则会创建Observe实例,在构造中执行递归操作
observe/index.js
export function observe(data){
console.log(data);
if(typeof data !== 'object' && data !== null){
return;
}
return new Observer(data);
}
class Observer {
constructor(value){
this.walk(value);
}
walk(data){
let keys = Object.keys(data);
keys.forEach(key=>{
defineReactuve(data,key,data[key]);
})
}
}
function defineReactuve(data,key,value){
// 实现递归
observe(value)
Object.defineProperty(data,key,{
get(){
console.log('用户取值');
return value
},
set(newValue){
console.log('用户赋值');
if(value !== newValue){
// 在set中对新值也进行监听操作:对新赋值的对象进行观测
observe(newValue);
value = newValue
}
}
}
)
}
state.js
function initData(vm) {
let data = vm.$options.data;
vm._data = data = typeof data == 'function' ? data.call(vm) : data;
observe(data);
}
第四阶段 :数组数据劫持
不对数组的属性进行遍历,而是采用函数劫持的方式进行数组的劫持,即重写数组原型上会改写本身的七个方法,且遍历数组中是对象的元素进行深度观测,从而实现数组监听;
数组内对象的劫持
遍历数组中是对象的元素调用观测方法
observer/array.js
class Observer {
constructor(data){
if (Array.isArray(data)) {
// 新增属性,声明此属性已被观测
Object.defineProperty(data,"__ob__",{
enumerable:false,
configurable: false,
value:this
})
// 只能拦截数组的方法,但对数组中的每一项 无法监听 需要观测
data.__proto__ = arrayMethods;
this.observerArray(data)
// console.log(data,arrayMethods);
} else {
this.walk(data)
}
}
walk(data){
let keys = Object.keys(data);
keys.forEach(key=>{
defineReactuve(data,key,data[key]);
})
}
observerArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i])
}
}
}
function defineReactuve(data,key,value){
// 实现递归
observe(value)
Object.defineProperty(data,key,{
get(){
console.log('用户取值');
return value
},
set(newValue){
console.log('用户赋值');
if(value !== newValue){
// 对新赋值的对象进行观测
observe(newValue);
value = newValue
}
}
}
)
数组方法的劫持
获取数组原型,重写七个方法,将此对象导出
observer/array.js
import { observe } from "./index.js";
// 拦截用户调用的push shift unshift pop reverse sort splice
// 获取数组原型
let oldArrayProtoMethods = Array.prototype;
// 重写七个方法,将此新原型对象导出
export let arrayMethods = Object.create(oldArrayProtoMethods);
let methods = ['push','shift','unshift', 'pop', 'reverse', 'sort', 'splice'];
methods.forEach(method=>{
// console.log(arrayMethods,method);
arrayMethods[method] = function (...args){
let r = oldArrayProtoMethods[method].apply(this,args)
// todo
let inserted;
let ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
inserted = args;break;
case "splice":
inserted = args.slice(2)
default:
break;
}
console.log('数组更新方法 == 去渲染页面');
return r;
}
})
在Observer构造中判断,如果数据是数组特殊处理(observerArray),将数据的原型执行自定义对象
observer/index.js
class Observer {
constructor(data){
if (Array.isArray(data)) {
// 新增属性,声明此属性已被观测
Object.defineProperty(data,"__ob__",{
enumerable:false,
configurable: false,
value:this
})
// 只能拦截数组的方法,但对数组中的每一项 无法监听 需要观测
data.__proto__ = arrayMethods;
this.observerArray(data)
// console.log(data,arrayMethods);
} else {
this.walk(data)
}
}
walk(data){
let keys = Object.keys(data);
keys.forEach(key=>{
defineReactuve(data,key,data[key]);
})
}
observerArray(value) {
for (let i = 0; i < value.length; i++) {
observe(value[i])
}
}
}
function defineReactuve(data,key,value){
// 实现递归
observe(value)
Object.defineProperty(data,key,{
get(){
console.log('用户取值');
return value
},
set(newValue){
console.log('用户赋值');
if(value !== newValue){
// 对新赋值的对象进行观测
observe(newValue);
value = newValue
}
}
}
)
}
对有新增功能的方法进行处理,新增值进行观测处理
observer/array.js
methods.forEach(method=>{
arrayMethods[method] = function (...args){
let r = oldArrayProtoMethods[method].apply(this,args)
// 以数组形式保存新增元素
let inserted;
// __ob__在Observer类的构造中赋值,1. 声明对应属性已被观测 2. 使得Observer类上的observerArray方法可以在此处被获取到
let ob = this.__ob__;
switch (method) {
case 'push':
case 'unshift':
inserted = args;break;
case "splice":
inserted = args.slice(2)
default:
break;
}
// 如果新增元素数组存在 则进行数组观测
if(inserted) ob.observerArray(inserted)
console.log('数组更新方法 == 去渲染页面');
return r;
}
})
第五阶段 :小优化,代理实现用户直接从vm上处理数据
通过Object.defineProperty进行一层代理,遍历_data在vm上对所有key定义getset,这样当用户取值赋值时就是操作_data
state.js
function proxy(vm,data,key) {
Object.defineProperty(vm,key,{
get(){
return vm[data][key];
},
set(newValue){
vm[data][key] = newValue;
}
})
}
function initData(vm) {
let data = vm.$options.data;
vm._data = data = typeof data == 'function' ? data.call(vm) : data;
// 进行代理,实现直接从实例上处理数据
for (const key in data) {
proxy(vm,'_data',key)
}
observe(data);
}
最终实现
- 仓库地址:git@github.com:Sympath/blingSpace.git
- 直接访问地址:github.com/Sympath/bli…