使用vue
举个非常简单的栗子
# html <div> {{msg}} </div> # script <script> new Vue({ el: '#app', data: { msg: 'hello' }, mounted() { setTimeout(() => { this.msg = 'hi' }, 1000); } }) </script>上面代码, new Vue进行创建vue对象, el属性是挂载的dom选择器,这里选择id为app的dom,data对象保存这所有数据响应的属性,当其中的某一属性值改变,就触发view渲染,从而实现了“数据->视图”的动态响应;
示例中msg初始值为hello,因此页面渲染时为hello,一秒之后,msg变为了hi,触发了view渲染,我们看到hello变为了li。那么接下来就从这简单的栗子来讲解vue的数据驱动把。
分析Object.defineProperty我们说vue是怎么实现双向数据绑定的?是Object.defineProperty实现了,那么我们就直接聚焦Object.defineProperty
以下是代码
function defineReactive ( obj, key, val, customSetter, shallow ) { // 创建派发器 var dep = new Dep(); var property = Object.getOwnPropertyDescriptor(obj, key); if (property && property.configurable === false) { return } // cater for pre-defined getter/setters var getter = property && property.get; var setter = property && property.set; if ((!getter || setter) && arguments.length === 2) { val = obj[key]; } var childOb = !shallow && observe(val); Object.defineProperty(obj, key, { enumerable: true, configurable: true, get: function reactiveGetter () { var value = getter ? getter.call(obj) : val; // 收集依赖对象 if (Dep.target) { dep.depend(); if (childOb) { childOb.dep.depend(); if (Array.isArray(value)) { dependArray(value); } } } return value }, set: function reactiveSetter (newVal) { var value = getter ? getter.call(obj) : val; /* eslint-disable no-self-compare */ if (newVal === value || (newVal !== newVal && value !== value)) { return } /* eslint-enable no-self-compare */ if ("development" !== 'production' && customSetter) { customSetter(); } if (setter) { setter.call(obj, newVal); } else { val = newVal; } childOb = !shallow && observe(newVal); dep.notify(); } }); }vue在给每一个data的属性执行defineReactive函数,来达到数据绑定的目的。从代码中可以看到几点:
每一个数据绑定,都会new一个Dep(暂且叫他派发器),派发器的功能是什么?依赖收集以事件分发;
在属性get中,除了获取当前属性的值,还做了dep.depend()操作;
dep.depend的目的是什么?看Dep部分代码,很简单,其实就是依赖收集,将Dep.target需要收集的依赖进行添加到自己的派发器里
在属性set时,就是给属性改变值时,除了改变值意外,还执行了dep.notify()操作;
dep.notify的目的又是什么?看代码,依旧很简单,将自己派发器的所有依赖触发update函数;
这一部分很容易了解,在data的属性get时,触发了派发器的依赖收集(dep.depend),在data的属性set时,触发了派发器的事件通知(dep.notify);
结合已知知识,Vue的数据绑定是上面这个函数带来的副作用,因此可以得出结论:
当我们改变某个属性值时,派发器Dep通知了view层去更新
Dep.target是派发器Dep收集的依赖,并在属性值改变时触发了update函数,view层的更新与Dep.target有必然的联系。换句话说:数据->视图的数据驱动就等于Dep.target.update()
简单的源码解析上一节已经确定,当更改属性值时,是Dep.target.update更新了view,因此带着这个目的,此小节做一个简单的源码解析
一切从头开始 function Vue (options) { this._init(options); } Vue.prototype._init = function (options) { var vm = this; callHook(vm, 'beforeCreate'); initState(vm); callHook(vm, 'created'); if (vm.$options.el) { vm.$mount(vm.$options.el); } }; function initState (vm) { vm._watchers = []; var opts = vm.$options; if (opts.data) { initData(vm); } else { observe(vm._data = {}, true /* asRootData */); } } function initData (vm) { var data = vm.$options.data; observe(data, true /* asRootData */); } function observe (value, asRootData) { if (!isObject(value) || value instanceof VNode) { return } var ob = new Observer(value);; return ob }从头开始,一步一步进入,发现最终我们new Vue传进来的data进入了new Observer中;
数据驱动部分-观察者 var Observer = function Observer (value) { this.value = value; this.dep = new Dep(); this.vmCount = 0; def(value, '__ob__', this); if (Array.isArray(value)) { var augment = hasProto ? protoAugment : copyAugment; augment(value, arrayMethods, arrayKeys); this.observeArray(value); } else { this.walk(value); } }; Observer.prototype.walk = function walk (obj) { var keys = Object.keys(obj); for (var i = 0; i < keys.length; i++) { defineReactive(obj, keys[i]); } };