1. VueJs 原理 Vue 是数据驱动的,数据变化,视图自动更新 也就是所谓的 “响应式”,它承载的就是数据与视图的处理,让开发者更加关注逻辑的处理;
Vue 是一个 MVVM 框架,它包含三个部分:Model、View、ViewModel:
Model:数据层,负责处理数据,包括数据的增删改查;
View:视图层,负责数据的展示;
ViewModel:负责数据的处理,包括数据的绑定、监听、过滤等;
Vue 的响应式遵循发布订阅模式
2.实现响应式原理 5343 2.1 初始化数据 Vue 接受一个对象 options,当我们去 new Vue
时,Vue 会调用 init 方法初始化初始数据
1 2 3 4 5 6 7 8 9 const vm = new Vue ({1 data : { name : "张三" , age : 12 , person : { names : "里斯" , }, }, });
初始化 data 数据,data 可以是函数,也可以是对象,但最终返回的是一个对象
1 2 3 4 5 6 7 8 9 10 import initMixin from "./init" ;function Vue (options ) { this ._init (options); }initMixin (Vue );export default Vue ;
initMixin 用来给 vue 添加各种内置方法属性,_init 方法就是在其中添加的:
1 2 3 4 5 6 7 8 9 10 11 12 import initState from "./state" ;export default function initMixin (Vue ) { Vue .prototype ._init = function (options ) { this .$options = options; const vm = this ; initState (vm); }; }
initState 用来初始化 data 数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { def } from "./def" ;import observer from "./oberver" ;export default function initState (vm ) { let data = vm.$options .data ; data = typeof data === "function" ? data.call (vm) : data; vm._data = data; for (let key in data) { def (vm, key, data[key]); } observer (vm._data ); }
def 函数是将 data 中的属性遍历一个个都挂载到 vm 上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 export function def (target, key, value ) { Object .defineProperty (target, key, { get ( ) { console .log ("设置值" ); return value; }, set (newValue ) { console .log ("获取值" ); if (newValue === value) return ; value = newValue; }, }); }
2.2 响应式实现 接下来是 Vue 的核心,响应式的实现
1. 数据对象劫持 遍历 data 数据,对于对象,我们遍历对象属性,劫持每个属性的 访问器描述符
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 class Observer { constructor (data ) { this .walk (data); } walk (data ) { Object .keys (data).forEach ((key ) => { defineReactive (data, key, data[key]); }); } }function defineReactive (data, key, value ) { if (arguments .length === 2 ) { value = data[key]; } if (typeof value === "object" ) { observer (value); } Object .defineProperty (data, key, { get ( ) { console .log ("获取值" ); return value; }, set (newV ) { console .log ("设置值" ); if (value === newV) return ; value = newV; }, }); }export default function observer (data ) { const oberver = new Observer (data); return oberver; }
到这步,实际上我们劫持两遍 data 中的数据,一个是 _data 中的属性,一个是 我们将 _data 属性值代理到 vm 上时,又劫持了一遍,这里会造成性能的浪费,也就未 vue3 的优化埋下了伏笔
2.数组劫持 判断值是数组,则进行数组的劫持,我们重写数组的 push、pop、shift、unshift、splice、sort、reverse 方法,因为这些方法会改变原数组值,也就涉及到视图的更新。
我们在重写的方法中执行原有的方法,这样原有的方法功能不会丢失,我们又可以在新的方法中做些事情例如视图的更新:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Observer { constructor (data ) { if (Array .isArray (data)) { data.__proto__ = newArrayProto; this .arrayWalk (data); } else { this .walk (data); } } arrayWalk (array ) { array.forEach ((item ) => { observer (item); }); } }
数组方法重写:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 const oldArrayMethods = Array .prototype ;const newArrayMethods = Object .create (oldArrayMethods);const menthods = ["push" , "pop" , "shift" , "unshift" , "splice" ]; menthods.forEach ((method ) => { newArrayMethods[method] = function (...args ) { console .log ("捕获到 数组数据更改" ); const length = oldArrayMethods[method].apply (this , args); let insertValue; switch (method) { case "push" : insertValue = args; break ; case "unshift" : insertValue = args; break ; case "splice" : insertValue = args.slice (2 ); break ; default : break ; } this .__ob__ .arrayWalk (insertValue); return length; }; });export default newArrayMethods;
上面的代码我们重写了数组相应的方法,对于数组新增的值(数组),我们使用原来实例的方法arrayWalk
重新判断是否需要继续劫持,那么我们如何再这里访问到 Observer 的实例的呢?
这里注意 this 的值指向的是当前数组,我们可以为在数组中添加一个ob 属性,将值赋值为 Observer 实例, 这样就可以使用实例中的方法:
1 2 3 4 5 6 7 8 9 10 11 class Observer { constructor (data ) { Object .defineProperty (data, "__ob__" , { value : this , enumerable : false , }); ...... } }
这里对象或者数组都会创建一个 ob 属性,当有这个属性也说明里面的属性都被劫持过了,也就不用再次劫持:
1 2 3 4 5 6 export default function observer (data ) { if (typeof data != "object" ) return ; if (data.__ob__ ) return ; const oberver = new Observer (data); return oberver; }
ob 属性枚举设置为 false 是为了避免死循环,如果为 true,当我们去遍历一个对象的时候,也会去遍历 Ob 属性, ob 属性里有原型,原型又有它自己的原型,就会陷入无限循环
下面是完整代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 import newArrayProto from "./array" ;class Observer { constructor (data ) { Object .defineProperty (data, "__ob__" , { value : this , enumerable : false , }); if (Array .isArray (data)) { data.__proto__ = newArrayProto; this .arrayWalk (data); } else { this .walk (data); } } walk (data ) { Object .keys (data).forEach ((key ) => { defineReactive (data, key, data[key]); }); } arrayWalk (array ) { array.forEach ((item ) => { observer (item); }); } }function defineReactive (data, key, value ) { if (arguments .length === 2 ) { value = data[key]; } if (typeof value === "object" ) { observer (value); } Object .defineProperty (data, key, { get ( ) { console .log ("获取值" ); return value; }, set (newV ) { console .log ("设置值" ); if (value === newV) return ; value = newV; }, }); }export default function observer (data ) { if (typeof data != "object" ) return ; if (data.__ob__ ) return ; const oberver = new Observer (data); return oberver; }const oldArrayMethods = Array .prototype ;const newArrayMethods = Object .create (oldArrayMethods);const menthods = ["push" , "pop" , "shift" , "unshift" , "splice" ]; menthods.forEach ((method ) => { newArrayMethods[method] = function (...args ) { console .log ("捕获到 数组数据更该" , this ); const length = oldArrayMethods[method].apply (this , args); let insertValue; switch (method) { case "push" : insertValue = args; break ; case "unshift" : insertValue = args; break ; case "splice" : insertValue = args.slice (2 ); break ; default : break ; } this .__ob__ .arrayWalk (insertValue); return length; }; });export default newArrayMethods;
至此,data 中的所有属性改变时我们就都可以监听到了
注意: data 本身也是一个对象
概览图: