model - view - viewmodel
的缩写,说都能直接说出来 model
:模型,view
:视图,view-Model
:视图模型
var dom = document.getElementById('xxx') dom.value = xxx; // 直接修改值 dom.innerHtml = xxx; //改变开始 和 结束标签中的html
$('#name').text('Homer').css('color', 'red');
view
)和模型(model
)之间的关系再看看如今VUE框架中怎么作到这种视图和模型的联动javascript
//html <input v-model = 'val' placeholder = 'edit here'> //script export defaults{ data:function(){ return { val:'' } } }
很简单,很经常使用的v-model指令,那么在input值修改的时候,data中的val变量值也会改变,直接在js中改变val的值的时候,input中的value也会改变??咱们作了什么,咱们怎么将数据和视图联系起来的?自动会关联这两个东西css
可能,这就是VM吧~html
model
数据同步到view
显示,也同时把view
修改的数据同步到model
,咱们无需关心中间的逻辑,开发者更多的是直接操做数据,至于更新视图或者会写model,都是咱们写好的视图模型(viewModel
)帮咱们处理概念:视图模型层,是一个抽象化的逻辑模型,链接视图(view
)和模型(model
),负责:数据到视图的显示,视图到数据的回写前端
vue框架中双向绑定是最经常使用的一个实用功能。实现的方式也网上不少文章,vue2.x是Object.DefineProperty,vue3.x是Es6语法的proxy
代理语法vue
具体是怎么作到的java
ps:暂时先看vue2.xjquery
第一步:监听对象全部属性值变化(Observer
)程序员
var data = {test: '1'}; observe(data); data.test = '2'; // changed 1 --> 2 function observe(data) { if (!data || typeof data !== 'object') { return; } // 取出全部属性遍历 Object.keys(data).forEach(function(key) { defineReactive(data, key, data[key]); }); }; function defineReactive(data, key, val) { observe(val); // 监听子属性 Object.defineProperty(data, key, { enumerable: true, // 可枚举 configurable: false, // 防止重复定义或者冲突 get: function() { return val; }, set: function(newVal) { console.log('changed ', val, ' --> ', newVal); val = newVal; } }); }
这里是Observer做为一个察觉数据变化的发布者,发现数据变化时,触发全部订阅者(Watcher
)的更新update
事件,首先要拥有一个能存储全部订阅者队列,而且能通知全部订阅者的中间件(消息订阅器Dep
)后端
function Dep () { // 订阅者数组 this.subs = []; } Dep.prototype = { addSub: function(sub) { this.subs.push(sub); }, notify: function() { //通知全部订阅者 this.subs.forEach(function(sub) { sub.update(); }); } };
而且在观察者Observer
中修改当Object对象属性发生变化时,触发Dep
中的notify事件,全部订阅者能够接收到这个改变api
function defineReactive(data, key, val) { observe(val); var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: false, get: function() { return val; }, set: function(newVal) { //修改的在这里 if(newVal === val){ return } // 若是新值不等于旧值发生变化,触发全部订阅中间件的notice方法,全部订阅者发生变化 val = newVal console.log('changed ', val, ' --> ', newVal); dep.notify(); } }); }
可是有没有发现还有一个问题,Dep订阅中间件中的订阅者数组一直是空的,何时把订阅者添加进来咱们的订阅中间件中间,哪些订阅者须要添加到咱们的中间件数组中
function Watcher(vm, exp, cb) { this.cb = cb; // 构造函数中执行,只有可能在实例化的时候执行一遍 this.vm = vm; this.exp = exp; this.value = this.get(); // 将本身添加到订阅器的操做---HACK开始 // 在构造函数中调用了一个get方法 } Watcher.prototype = { update: function() { this.run(); }, run: function() { var value = this.vm.data[this.exp]; var oldVal = this.value; if (value !== oldVal) { this.value = value; this.cb.call(this.vm, value, oldVal); } }, get: function() { //get方法中首先缓存了本身自己到target属性 Dep.target = this; // 获取了一下Observer中的值,至关于调用了一下get方法 var value = this.vm.data[this.exp] // get 完成以后清除了本身的target属性??? Dep.target = null; return value; } //很明显,get方法只在实例化的时候调用了,知足了只有在Watcher实例化第一次的时候调用 //update方法接收了发布者的notice 发布消息,而且执行回调函数,这里的回调函数仍是经过外部定义(简化版) //可是,好像在get方法中有一个很神奇的操做,缓存本身,而后调用Observer的getter,而后清除本身 //这里实际上是一步巧妙地操做把本身添加到Dep订阅者数组中,固然Observer 的getter方法也要变化以下 }; //Observer.js function defineReactive(data, key, val) { observe(val); var dep = new Dep(); Object.defineProperty(data, key, { enumerable: true, configurable: true, get: function() { if (Dep.target) {. dep.addSub(Dep.target); // 关键的在这里,当第一次实例化时,调用Watcher的get方法,get方法内部会获取Object的属性,会触发这个get方法,在这里将Watcher 添加到Dep的订阅者数组中 } return val; }, set: function(newVal) { if (val === newVal) { return; } val = newVal; dep.notify(); } }); } Dep.target = null;
看似好像发布者订阅者模式实现了,数据劫持也实现了,在数据改变的时候,触发Object.setProperty中定义的set函数,set函数触发Dep订阅者中间件的notice方法,触发全部订阅者的update方法,而且订阅者在实例化的时候就加入到了Dep订阅者的数组内部,让咱们来看看怎么用
<body> <!-- 这里其实仍是会直接显示{{name}} --> <h1 id="name">{{name}}</h1> </body>
function SelfVue (data, el, exp) { //初始化data属性 this.data = data; //将其设置为观察者 observe(data); //手动设置初始值 el.innerHTML = this.data[exp]; //初始化watcher,添加到订阅者数组中,而且回调函数是从新渲染页面,触发update方法时经过回调函数重写html节点 new Watcher(this, exp, function (value) { el.innerHTML = value; }); return this; }
var ele = document.querySelector('#name'); var selfVue = new SelfVue({ name: 'hello world' }, ele, 'name'); //设定延时函数,直接修改数据值,看可否绑定到页面视图节点 window.setTimeout(function () { console.log('name值改变了'); selfVue.data.name = 'canfoo'; }, 2000);
到上面为止:基本实现了数据(model
)到视图(view
)层的单向数据绑定,只有v-model是使用到了双向绑定,不少vue的数据绑定的理解,和难点也就在上面的单向绑定
那么:model->view单向绑定彷佛已经成功了,那么view -> model呢?
var dom = document.getElementById('xx') dom.addEventListener('input',function(e){ selfVue.data.xxx = e.target.value })
很是感谢:下面的文章给了我不少的帮助,感谢各位前行者的辛苦付出,能够点击查阅更多信息