1、编写vue数据
创建一个vue的实例 vm.
<script>
const vm = new Vue({
data:{
name:"xxxx",
age:20,
other:{
school:"xxx"
}
}
})
</script>
2、导出一个vue构造函数
1)编写一个Vue构造函数并导出
function Vue(options){
}
export default Vue;
2)从options中获取传入的参数
import { initMixin } from "./init";
function Vue(options){
this._init(options);
}
initMixin(Vue);
export default Vue;
3)为Vue添加init 原型方法
//init.js
import initState from "./state";
//初始化vue数据
export function initMixin(Vue){
Vue.prototype._init = function(options){
const vm =this;
vm.$options = options; //将用户的选项挂载到实例上
//初始化状态
initState(vm);
}
}
4)初始化数据状态
export default function initState(vm){
// 从vm上获取所有的选项 如果选项上含有 data 属性
//则执行initData 方法初始化数据
const opts = vm.$options;
if(opts.data){
initData(vm);
}
}
5)initData 方法对数据进行劫持 此时从vm上获取到的data可能是个函数,也可能是个对象,如果是函数,则取函数的返回值,如果是对象则直接获取data 例如:
//data在根组件中的写法可以是 对象格式{name:xxx},也可以是函数形式
// data(){return {name:xxx}}
import { observe } from "./observe/index";
function initData(vm){
let data = vm.$options.data;
vm._data = data = typeof data === "function" ? data.call(this) : data;
// 用户使用 vm._data来获取有些麻烦, 我们希望可以通过vm.xxx -> vm._data.xxx
for(let key in data){
proxy(vm,'_data',key); // 循环代理属性, 为了用户使用的时候 直接可以通过vm.xxx
}
//对数据进行劫持 defineProperty
observe(data)
}
function proxy(target,key,property){ // vm.xxx -> vm._data.xxx
Object.defineProperty(target,property,{
get(){
return target[key][property]
},
set(newValue){
target[key][property] = newValue;
}
})
}
3、对数据进行劫持
1)只会对对象进行劫持,如果不是对象则直接返回
//./observe/index.js
export function observe(data){
// 对这个对象进行劫持 只对对象进行劫持
if(typeof data != 'object' || data == null){
return ;
}
//如果一个对象已经被劫持过了,那么就不需要再次劫持
// todo...
return new Observer(data)
}
2)定义一个类 Observe,获取到所有的属性,并且遍历现有属性重新定义成响应式的数据(通过数据劫持的方式Object.defineProperty),这样会使得data中的所有数据被重新定义,所有性能会变得差; 因为这个原因我们平时写的一些变量层级最好不要太深,如果某个变量不需要具备响应式,也没有必要定义在data中。
//./observe/index.js
class Observer{
constructor(data){
this.walk(data);
}
walk(target) {
Object.keys(target).forEach(key => {
defineReactive(target, key, target[key]);
})
}
};
3)定义响应式数据
在Js中,使用Object.defineProperty来侦测一个对象的变化, 进行数据劫持或数据代理。
这里vue在初始化数据时,对于不存在的属性,不会被劫持,不能被定义成响应式;
例如一个对象形式
const vm = new Vue({
name:xxx,
age:12,
other:{
school:"xxx"
}
})
在上面的vm中,通过 Object.defineProperty定义的属性,会给每个属性添加getter和setter 方法,形式会是
age: (...)
name: (...)
other: Object
get age: ƒ ()
set age: ƒ (newValue)
get name: ƒ ()
set name: ƒ (newValue)
get other: ƒ ()
set other: ƒ (newValue)
3.1)但是在vm.other中的school中并不会有get和set方法,如此如果通过vm.other.school="***"改变了school的值,并不会被监测到,为了解决这个问题,就需要对属性进行递归类型监测;(例如以下#111处)
3.2)如果在对vm.other整个属性重新赋值的时候(vm.other={kk:"xxx"}),改变了引用地址,里面的属性就需要再次被观测,重新定义成响应式 (例如以下#222处)
//./observe/index.js
function defineReactive(target, key, value) { // 定义响应式
// 不存在的属性不会被defineProperty
observe(value); // 递归对象类型检测 #111
Object.defineProperty(target, key, { // 将属性重新定在对象上,增加了get和set(性能差)
get() {
return value;
},
set(newValue) {
if (newValue === value) return
observe(newValue); // 设置的值如果是对象,那么就再次调用observe让对象变成响应式的 #222
value = newValue;
}
})
}
4、测试数据
定义一个实例,并且取值和设置值
<script>
const vm = new Vue({
data:{
name:"xxxx",
age:20,
other:{
school:"xxx"
}
}
})
vm.other={sex:"xx"}
console.log(vm)
</script>
查看输出结果
age: 12
name: "xxxx"
other: Object
sex: (...)
get sex: ƒ ()
set sex: ƒ (newValue)
get age: ƒ ()
set age: ƒ (n)
get name: ƒ ()
set name: ƒ (n)
get other: ƒ ()
set other: ƒ (n)
5、完成对象的响应式
thank you for your reading !!!