这篇文章主要介绍“vue2.0双向数据绑定的方法是什么”,在日常操作中,相信很多人在vue2.0双向数据绑定的方法是什么问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”vue2.0双向数据绑定的方法是什么”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
直接上代码:一个简单的发布订阅模式,帮助大家更好的理解双向数据绑定原理
//发布订阅模式
function Dep() {
this.subs = []//收集依赖(也就是手机watcher实例),
}
Dep.prototype.addSub = function (sub) { //添加订阅者
this.subs.push(sub); //实际上添加的是watcher这个实例
}
Dep.prototype.notify = function (sub) { //发布,这个方法的作用是遍历数组,让每个订阅者的update方法去执行
this.subs.forEach((sub) => sub.update())
}
function Watcher(fn) {
this.fn = fn;
}
Watcher.prototype.update = function () { //添加一个update属性让每一个实例都可以继承这个方法
this.fn();
}
let watcher = new Watcher(function () {
alert(1)
});//订阅
let dep = new Dep();
dep.addSub(watcher);//添加依赖,添加订阅者
dep.notify();//发布,让每个订阅者的update方法执行
只是针对双向数据绑定做说明
<template>
<div id="app">
<div>obj.text的值:{{obj.text}}</div>
<p>word的值:{{word}}</p>
<input type="text" v-model="word">
</div>
</template>
<script>
new Vue({
el: "#app",
data: {
obj: {
text: "向上",
},
word: "学习"
},
methods:{
// ...
}
})
</script>
Vue 构造函数都干什么了?
function Vue(options = {}) {
this.$options = options;//接收参数
var data = this._data = this.$options.data;
observer(data);//对data中的数据进型循环递归绑定
for (let key in data) {
let val = data[key];
observer(val);
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key];
},
set(newVal) {
this._data[key] = newVal;
}
})
}
new Compile(options.el, this)
};
在 new Vue({…})构造函数时,首先获取参数 options ,然后把参数中的 data 数据赋值给当前实例的 _data 属性上(this._data = this.$options.data),重点来了,那下面的遍历是为什么呢?首先我们在操作数据的时候是 this.word 获取,而不是 this._data.word,所以是做了一个映射,在获取数据的时候 this.word,其实是获取的 this._data.word 的值,大家可以在自己项目中输出this查看一下
1.接下来看看 observer 方法干了什么
function observer(data) {
if (typeof data !== "object") return;
return new Observer(data);//返回一个实例
}
function Observer(data) {
let dep = new Dep();//创建一个dep实例
for (let key in data) {//对数据进行循环递归绑定
let val = data[key];
observer(val);
Object.defineProperty(data, key, {
enumerable: true,
get() {
Dep.target && dep.depend(Dep.target);//Dep.target就是Watcher的一个实例
return val;
},
set(newVal) {
if (newVal === val) {
return;
}
val = newVal;
observer(newVal);
dep.notify() //让所有方法执行
}
})
}
}
Observer 构造函数,首先 let dep=new Dep(),作为之后的触发数据劫持的 get 方法和 set 方法时,去收集依赖和发布时调用,主要的操作就是通过 Object.defineProperty 对 data 数据进行循环递归绑定,使用 getter/setter 修改其默认读写,用于收集依赖和发布更新。
2.再来看看 Compile 具体干了那些事情
function Compile(el, vm) {
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment(); //创建文档碎片,是object类型
while (child = vm.$el.firstChild) {
fragment.appendChild(child);
};//用while循环把所有节点都添加到文档碎片中,之后都是对文档碎片的操作,最后再把文档碎片添加到页面中,这里有一个很重要的特性是,如果使用appendChid方法将原dom树中的节点添加到fragment中时,会删除原来的节点。
replace(fragment);
function replace(fragment) {
Array.from(fragment.childNodes).forEach((node) => {//循环所有的节点
let text = node.textContent;
let reg = /\{\{(.*)\}\}/;
if (node.nodeType === 3 && reg.test(text)) {//判断当前节点是不是文本节点且符不符合{{obj.text}}的输出方式,如果满足条件说明它是双向的数据绑定,要添加订阅者(watcher)
console.log(RegExp.$1); //obj.text
let arr = RegExp.$1.split("."); //转换成数组的方式[obj,text],方便取值
let val = vm;
arr.forEach((key) => { //实现取值this.obj.text
val = val[key];
});
new Watcher(vm, RegExp.$1, function (newVal) {
node.textContent = text.replace(/\{\{(.*)\}\}/, newVal)
});
node.textContent = text.replace(/\{\{(.*)\}\}/, val); //对节点内容进行初始化的赋值
}
if (node.nodeType === 1) { //说明是元素节点
let nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach((item) => {
if (item.name.indexOf("v-") >= 0) {//判断是不是v-model这种指令
node.value = vm[item.value]//对节点赋值操作
}
//添加订阅者
new Watcher(vm, item.value, function (newVal) {
node.value = vm[item.value]
});
node.addEventListener("input", function (e) {
let newVal = e.target.value;
vm[item.value] = newVal;
})
})
}
if (node.childNodes) { //这个节点里还有子元素,再递归
replace(node);
}
})
}
//这是页面中的文档已经没有了,所以还要把文档碎片放到页面中
vm.$el.appendChild(fragment);
}
Compile(编译方法)首先解释一下 DocuemntFragment(文档碎片)它是一个 dom 节点收容器,当你创造了多个节点,当每个节点都插入到文档当中都会引发一次回流,也就是说浏览器要回流多次,十分耗性能,而使用文档碎片就是把多个节点都先放入到一个容器中,最后再把整个容器直接插入就可以了,浏览器只回流了1次。
Compile 方法首先遍历文档碎片的所有节点,
1.判断是否是文本节点且符不符合{{obj.text}}的双大括号的输出方式,如果满足条件说明它是双向的数据绑定,要添加订阅者(watcher),new Watcher(vm,动态绑定的变量,回调函数fn)
2.判断是否是元素节点且属性中是否含有 v-model 这种指令,如果满足条件说明它是双向的数据绑定,要添加订阅者(watcher),new Watcher(vm,动态绑定的变量,回调函数fn) ,直至遍历完成。最后别忘了把文档碎片放到页面中
3.Dep构造函数(怎么收集依赖的)
var uid=0;
//发布订阅
function Dep() {
this.id=uid++;
this.subs = [];
}
Dep.prototype.addSub = function (sub) { //订阅
this.subs.push(sub); //实际上添加的是watcher这个实例
}
Dep.prototype.depend = function () { // 订阅管理器
if(Dep.target){//只有Dep.target存在时采取添加
Dep.target.addDep(this);
}
}
Dep.prototype.notify = function (sub) { //发布,遍历数组让每个订阅者的update方法去执行
this.subs.forEach((sub) => sub.update())
}
Dep 构造函数内部有一个 id 和一个 subs,id=uid++ ,id用于作为 dep 对象的唯一标识,subs 就是保存 watcher 的数组。depend 方法就是一个订阅的管理器,会调用当前 watcher 的 addDep 方法添加订阅者,当触发数据劫持(Object.defineProperty)的 get 方法时会调用 Dep.target && dep.depend(Dep.target)添加订阅者,当数据改变时触发数据劫持(Object.defineProperty)的 set 方法时会调用 dep.notify 方法更新操作。
4.Watcher构造函数干了什么
function Watcher(vm, exp, fn) {
this.fn = fn;
this.vm = vm;
this.exp = exp //
this.newDeps = [];
this.depIds = new Set();
this.newDepIds = new Set();
Dep.target = this; //this是指向当前(Watcher)的一个实例
let val = vm;
let arr = exp.split(".");
arr.forEach((k) => { //取值this.obj.text
val = val[k] //取值this.obj.text,就会触发数据劫持的get方法,把当前的订阅者(watcher实例)添加到依赖中
});
Dep.target = null;
}
Watcher.prototype.addDep = function (dep) {
var id=dep.id;
if(!this.newDepIds.has(id)){
this.newDepIds.add(id);
this.newDeps.push(dep);
if(!this.depIds.has(id)){
dep.addSub(this);
}
}
}
Watcher.prototype.update = function () { //这就是每个绑定的方法都添加一个update属性
let val = this.vm;
let arr = this.exp.split(".");
arr.forEach((k) => {
val = val[k] //取值this.obj.text,传给fn更新操作
});
this.fn(val); //传一个新值
}
Watcher 构造函数干了什么
1.接收参数,定义了几个私有属性( this.newDep ,this.depIds,this.newDepIds)
2. Dep.target = this,通过参数进行 data 取值操作,这就会触发 Object.defineProperty 的 get 方法,它会通过订阅者管理器(dep.depend())添加订阅者,添加完之后再将 Dep.target=null 置为空;
3.原型上的 addDep 是通过id这个唯一标识,和几个私有属性的判断防止订阅者被多次重复添加
4.update 方法就是当数据更新时,dep.notify()执行,触发订阅者的 update 这个方法, 执行发布更新操作。
到此,关于“vue2.0双向数据绑定的方法是什么”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。