vue2
是options API
的,在vue3
中 保留了
options api
无法tree-shaking
vue2
是一个构造函数
不是类(class),通过原型的方式
给vue
实现功能:vue.prototype = xxx
.babelrc
文件不用引入 默认会执行,preset
预设插件集合
package.json
1 | "main": "rollup.config.js", //指定配置文件= |
1、vue初始化
当用户 new Vue的时候 就调用init方法进行vue的初始化
2、vm.$options
代表 用户传入的所有属性
3、响应式数据变化,数据代理 (Object.defineProperty
)
3.1、初始化状态initState(vm)
将所有数据都定义在vm属性上,并且后续更改都需要触发视图更新
3.1.1、数据的初始化initData
3.1.1.1、数据劫持
vue
实例的data
有两种情况 :要么是函数
要么是对象
,对data
类型进行判断 如果是 函数就获取函数返回值获取函数执行结果的时候 用
data.call(vm)
保证 data函数中的this
指向vue
实例 其实和返回值无关
3.1.1.2、观测这个数据observe
(对象类型拦截)
props
初始化在data
之前
只对
对象类型
进行观测非对象类型
无法观测直接return
通过
Observe类
来实现对数据的观测,类方便扩展,类会产生实例以作为唯一标识
1 | export function observe(data){ |
3.1.1.2.1、将对象中的所有key
重新用defineProperty
定义为响应式的
1 | export function defineReactive(data,key,value){ |
3.1.1.2.2、在initData中给实例增加一个_data
属性指向劫持后的数据,用户可以通过vm._data
获取劫持后的数据
3.1.1.2.3、在initData中自己写一个代理(proxy)将_data
的数据全部代理到vm上(即通过vm.属性名
就可以获取),原理还是Object.defineProperty
实现 将
vm.nam
转化为vm._data.name
1 | function proxy(vm,source,key){ |
两个
Object.defineProperty
,一个是为了将数据直接代理到vue实例上
,一个是对每一个数据进行取值和设置代理
3.1.1.2.4、再次调用observe对数据进行递归拦截
递归的时候会再次判断数据类型只对对象类型进行观测 非对象类型无法观测直接return
所以
vue2中
数据不要嵌套过深,不然递归浪费性能太多代码见
3.1.1.2.1
3.1.1.2.5、如果给vm设置了一个新的对象类型数据,应该在Object.defineProperty 的set中再次劫持变成响应式的
1 | //设置了一个新的对象类型数据 |
3.1.1.3、观测数据observe
(数组类型拦截)
按照
3.1.1.2
的逻辑,现在的情况是,给数组的每一项都进行了数据劫持,这样是有性能问题的,而且数组的属性可能是各种各样的,比如: 函数、length而且我们去直接更改数组的某一项用到的也不多
数组不用Object.defineProperty
代理 性能太差,通过监听(改写)数组自身的方法来实现,比如:push、shift、reverse、sort
3.1.1.3.1、在监测数据的时候 对数组和对象分类处理,是对象的话就按照上边的处理
要重写数组自身的方法,增加更新逻辑来监听数组变化,这也是为啥不调用数组的方法直接arr[0]=xx
不能被监控的原因
不能直接改写数组的方法,只有
被vue控制
的数组才需要改写
1 | let arrayMethods = Object.create(Array.prototype); // 让arrayMethods 继承于Array.prototype,arrayMethods找不到去Array.prototype找 |
改写方法时使用 AOP切片编程,做一些操作以后 还是要调用数组原来的方法
比如新增,我们需要只需要在对应方法呗调用的时候去,将新增的数据放到一个数组中(也就是arguements),然后再调用observeArray就好了
1 | let methods = [ |
3.1.1.3.2、当是数组时 通过value.__proto__ = arrayMethods
挂载重写后的方法
value.__proto__ = arrayMethods
等同于Object.setPrototypeOf(value,arrayMethods)
向数组里push
对象,或者数组里边直接放对象
是监控不到
的
vue中数组中的对象发生了变化也要更新视图(就是数据劫持变为响应式的):观测数组中的每一项observeArray
调用上边的
observe
只对对象进行观测观测的节点有两个:一个
是数组中原有的对象
,一个是用户新增的数据是对象
数组中原有的对象,就是通过observeArray循环调用observe
不用判断每一项的数据类型,因为上边的
observe
函数中已经做了判断
1 | observeArray(value){ |
用户新增的数据是对象,只对会增加的方法进行处理
新增有两种:一种是
push、unshift
这种参数只有增加项的方法,一种是splice
这种参数有好项的增加方式因此需要获取到加入的数据,并通过
observeArray
拦截对数据进行拦截的时候是先赋值再通过
observeArray
拦截
在别的地方拿到实例上的observeArray
我们在
Observe
的constructor
中给观测的数据
加上自定义属性__ob__
指向实例本身(就是this)
,根据原型链实例身上带有observeArray
value.__ob__ = this;
(暂时的,这个写法有问题,下边是原因和解决办法)
因为walk方法
循环了对象的所有属性并对对象属性循环调用Observe
,并且value.__ob__ = this;
中__ob__
肯定是对象所以会造成死循环
解决办法是通过Object.defineProperty
方法给数据设置__ob__
属性并且将__ob__
属性设置为不可枚举(表示不能被循环),同时设置为不能删除,这样 walk
方法就不能循环到__ob__
属性了
1 | // value.__ob__ = this; 因为`walk方法` 循环了对象的所有属性并对对象属性循环调用Observe,并且`value.__ob__ = this;`中`__ob__`肯定是对象所以会造成死循环 |
最终数组方法改写的代码
1 | let oldArrayPrototypeMethods = Array.prototype; |
如果数据有__ob__
属性,证明已经观测过了,不应再次观测
代码 见
3.1.1.2