初始化数据
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=this
vm.$options=options
// 初始化状态
init_state(vm)
}
}
在state.js文件下加入如下代码
export function init_state(vm){
// 获取实例上挂载的选项
const opts=vm.$options;
// 选项上如果存在data初始化data
if(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初始化data
if(opts.data){
init_data(vm);
}
}
function init_data(vm){
let data=vm.$options.data;
// 判断data是对象还是函数并且重新赋值
data=typeof data==='function'?data.call(vm):data;
// 将data劫持重新定义data
observe(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;
// 实例上挂载_data
vm._data=data
// 将data劫持重新定义data
observe(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;
// 实例上挂载_data
vm._data=data
// 将data劫持重新定义data
observe(data)
// 将vm._data代理给vm
for(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
}
}