MVVM 双向数据绑定
- 脏值检查
AngularJS 实现方式
- 数据劫持+发布订阅模式
VUE 的实现方式(不兼容低版本 Object.defineProperty)
Object.defineProperty()
let obj = {};
Object.defineProperty(obj, "school", {
configurable: true,
// writable:true,
enumerable: true,
// value:'hhhh',
get() {
//获取obj.school值时会调用get
return "hhhh";
},
set(val) {
//给obj赋值时会调用set
console.log(val);
}
});
- 发布-订阅模式 订阅[fn1,fn2,fn3]
//发布订阅
function Dep(){
this.subs=[];
}
Dep.prototype.addSub=function(sub){//订阅
this.subs.push(sub);
};
Dep.prototype.notify=function(){
this.subs.forEach(sub=>sub.update());
};
function Watcher(fn){//Watcher是个类,通过这个类创建的实例都拥有update方法
this.fn=fn;
}
Watcher.prototype.update=function () {
this.fn();
};
let watcher=new Watcher(function () { //监听函数
console.log(1)
});
let dep =new Dep();
dep.addSub(watcher);//将watcher放到了数组中[watcher.update]
console.log(dep.subs);
dep.notify();//数组关系
- 代码实现
<div id="app">
<p>a的值{{a.a}}</p>
<div>b的值{{b}}</div>
<input type="text" v-model="b" />
<!-- computed可以缓存 只是把数据挂在vm上 -->
{{hello}}
</div>
function Vue(options = {}) {
this.$options = opthins; //将所有属性挂载在了$options
// this._data
var data = (this._data = this.$options.data);
observe(data);
//this代理了this._data
for (let key in data) {
Object.defineProperty(this, key, {
enumerable: true,
get() {
return this._data[key]; //this.a={a:1}
},
set(newVal) {
this._data[key] = newVal;
}
});
}
initComputed.call(this);
new Compile(options.el, this);
}
function initComputed() {//具有缓存功能的
let vm=this;
let computed=this.$options.computed;//Object.keys {name:1,age:2}=>[name,age]
Object.keys(computed).forEach(function (key) {
Object.defineProperty(vm,key,{//computed[key]
get:typeof computed[key]==='function'?computed[key]:computed[key].get,
set(){
}
})
})
}
function Compile(el, vm) {
// el表示替换的范围
vm.$el = document.querySelector(el);
let fragment = document.createDocumentFragment();
while ((child = vm.$el.firstChild)) {
//将app中的内容移入到内存中
fragment.appendChild(child);
}
replace(fragment);
function replace(fragment) {
Array.from(fragment.childNodes).forEach(function(node) {
//循环每一层
let text = node.textContent;
let reg = /\{\{(.*)\}\}/;
if (node.nodeType === 3 && reg.test(text)) {
console.log(RegExp.$1);//vm.a.a vm.b
let arr=RegExp.$1.split('.');//[a,a]
let val=vm;
arr.forEach(function (k) {//取this.a.a this.b
val=val[k];
});
// 替换的逻辑
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;//获取当前dom节点的属性
Array.from(node.attributes).forEach(function (attr) {
let name=attr.name;//type="text"
let exp=attr.value;//v-model="b"
if(name.indexOf('v-')==0){//v-model
node.value=vm[exp];
}
new Watcher(vm,exp,function (newVal) {
node.value=newVal;//当watcher触发时会自动将内容放到输入框内
});
node.addEventListener('input',function (e) {
let target=e.target.value;
vm[exp]=newVal;
})
})
}
if (node.childNodes) {
replace(node);
}
});
}
vm.$el.appendChild(fragment);
}
// vm.$options
//观察对象给对象增加Object.defineProperty
function Observe(data) {
//主要逻辑
let dep=new Dep();
for (let key in data) {
let val = data[key];
observe(val);
//把data属性通过object.defineProperty的方式定义属性
Object.defineProperty(data, key, {
enumerable: true,
get() {
Dep.target&&dep.addSub(Dep.target);//[watcher]
return val;
},
set(newValue) {
//更改值的时候
if (newValue === val) {
return;
}
val = newValue; //如果以后在获取值的时候将刚才设置的值在丢回去
observe(newValue);
dep.notify();//让所有的watch的update方法执行
}
});
}
}
function observe(data) {
if (typeof data != "object") return;
return new Observe(data);
}
//vue 不能新增不存在的属性,因为不存在的属性没有 get 和 set
// 深度响应 因为每次赋予一个新对象时会给这个对象增加数据劫持
//发布订阅
function Dep(){
this.subs=[];
}
Dep.prototype.addSub=function(sub){//订阅
this.subs.push(sub);
};
Dep.prototype.notify=function(){
this.subs.forEach(sub=>sub.update());
};
// watcher
function Watcher(vm,exp,fn){//Watcher是个类,通过这个类创建的实例都拥有update方法
this.fn=fn;
this.vm=vm;
this.exp=exp;//添加到订阅中
Dep.target=this;
let val=vm;
let arr=exp.split('.');
arr.forEach(function (k) {//this.a.a
val=val[k];
})
Dep.target=null
}
Watcher.prototype.update=function () {
let val=this.vm;
let arr=this.exp.split('.');
arr.forEach(function (k) {//this.a.a
val=val[k];
})
this.fn(val);//newVal
};
let vue = new Vue({
el: "#app",
//data: { a: 1 };
//data: { a: { a: 1 } }
data: { a: { a: "a" }, b: "b" },
computed: {
hello(){
return this.b+this.c
}
},
});