初始化数据
vue中最核心的我们都知道那就是响应式数据,数据的变化视图自动更新。那么我们来new一个我们自己的vue
在index.html文件下加入如下代码,这也是vue最常见的基本结构。data已经有了下面我们来获取data的数据
<script src="./vue.js"></script><script>const vm=new MVue({data(){return{name:"zhangsan",age:"16"}}})</script>
首先我们在src/main.js文件下加入如下代码
// 创建MVue构造函数
function MVue(options) { // options就是我们接受到用户的选项,选项式api}// 将构造函数导出
export default MVue
为了更能的扩展功能和方便维护,我们需要一个专门做初始化的方法。所以我们在src下创建init.js文件加入如下代码
export function init(MVue){// 给MVue原型对象上挂载_init()方法MVue.prototype._init = function(options){}
}
在main.js文件下如果这个方法,并且调用
import { init } from "./init"// 创建MVue构造函数
function MVue(options) { // options就是我们接受到用户的选项,选项式api// 当vue被new的时候调用_init()方法this._init(options)
}init(MVue)// 将构造函数导出
export default MVue
下面我们就可以写我们的初始化方法了,init.js文件下加入如下代码。可能你会好奇这个init_state()方法是那里的。由于用户选项有很多比如data,watch,computed…所以我们最好创建一个文件统一管理。在src文件下创建state.js文件
import { init_state } from "./state"export function init(MVue){// 给MVue原型对象上挂载_init()方法MVue.prototype._init = function(options){// 在用户选项挂载到实例上const vm=thisvm.$options=options// 初始化状态init_state(vm)}
}
在state.js文件下加入如下代码
export function init_state(vm){// 获取实例上挂载的选项const opts=vm.$options;// 选项上如果存在data初始化dataif(opts.data){init_data(vm);}
}
function init_data(vm){let data=vm.$options.data;// 判断data是对象还是函数并且重新赋值data=typeof data==='function'?data.call(vm):data;console.log(data);
}
到这里,输入打包命令打开index.html如果在游览器下看到如下图所示,我们就成功获取到了data里面的数据
pnpm run build
实现对象的响应式原理
下面我们首先来实现只有一层对象的数据劫持。state.js文件下加入如下代码
import { observe } from "./observe/index";export function init_state(vm){// 获取实例上挂载的选项const opts=vm.$options;// 选项上如果存在data初始化dataif(opts.data){init_data(vm);}
}
function init_data(vm){let data=vm.$options.data;// 判断data是对象还是函数并且重新赋值data=typeof data==='function'?data.call(vm):data;// 将data劫持重新定义dataobserve(data)
}
src文件下创建observe/index.js文件并且加入如下代码
class Observer {constructor(data) {/* 这里注意,Object.defineProperty只能劫持已经存在的属性,新增或删除的无法劫持这也是为什么vue2中新增了,$set和$delete方法这类方法的原因*/this.walk(data)}walk(data) {// 循环对象keys依次劫持,重新定义属性Object.keys(data).forEach(key => {define_reactive(data, key, data[key])})// 这里重新定义data后我们打印看下data是否都加入了get和set方法console.log(data);}}// 这里会存在一个闭包,get和set方法存放了函数外部的变量value
export function define_reactive(data, key, value) {Object.defineProperty(data, key, {get() {return value},set(newValue){if(value!=newValue){value=newValue}}})
}export function observe(data) {// 对对象劫持if (typeof data !== 'object' || data === null) {return;}// 如果一个对象被劫持过就不需要再次劫持,我们可以通过一个观察类判断是否被劫持过return new Observer(data)
}
如果你的游览器打印了如下图所示,那么恭喜你只有一层对象的data你重新定义完成了
说起data,vue2中一直有个属性_data属性。这上面挂载了我们所有在data中定义的属性。下面我们来实现它
在state.js文件下修改如下代码。没错就是这么简单要加入vm._data=data这行代码就完成了。由于这是引用值。所有对data的修改也会影响到_data
function init_data(vm){let data=vm.$options.data;// 判断data是对象还是函数并且重新赋值data=typeof data==='function'?data.call(vm):data;// 实例上挂载_datavm._data=data// 将data劫持重新定义dataobserve(data)
}
下面我们在index.html下打印vm看看是否有我们的_data属性
到这里,其实我们的_data属性并不完美,你通过vm获取属性的时候发现每次都要加上_data,
vm._data.name。但是vue2中并不是这样的而是直接vm.name就可以获取并且修改。下面我们来解决这个问题
state.js文件下加入如下代码。我们将将vm._data代理给vm就可以解决这个问题
function proxy(vm,source,key){Object.defineProperty(vm,key,{get(){return vm[source][key]},set(newValue){if(vm[source][key]!=newValue){vm[source][key]=newValue;}}})
}function init_data(vm){let data=vm.$options.data;// 判断data是对象还是函数并且重新赋值data=typeof data==='function'?data.call(vm):data;// 实例上挂载_datavm._data=data// 将data劫持重新定义dataobserve(data)// 将vm._data代理给vmfor(let key in data){proxy(vm,'_data',key)}
}
下面我们在看我们的vm实例会发现多了name和age属性。并且可以通过vm访问到和修改
到这里不知你们还记得我们最早说过的,这只是一个只有一层对象的数据劫持。如果多层就代理不到了
我们修改index.html代码如下
const vm=new MVue({data(){return{name:"zhangsan",age:"16",address:{city:"地球村 "}}}})console.log(vm);
在游览器上。我们会看到address并且没有get和set方法。下面我们来解决这个问题
在observe/index.js文件下修改如下代码
// 这里会存在一个闭包,get和set方法存放了函数外部的变量value
export function define_reactive(data, key, value) {observe(value) // 递归调用我们的observe方法Object.defineProperty(data, key, {get() {return value},set(newValue){if(value!=newValue){value=newValue}}})
}
再次看我们的address就会发现get和set方法已经出现了。这说明我们的多级对象代理已经完成了
用户可能会存在这样的赋值,现在我们是没办法办法代理到的
const vm = new MVue({data() {return {name: "zhangsan",age: "16",}}
})
vm.address = {city: "地球村"
}
所以我们在observe/index.js文件下define_reactive方法中的set修改如下。如果直接通过vm赋值对象,给这些属性同样代理
set(newValue){if(value!=newValue){observe(newValue)value=newValue}
}