数据代理
数据代理是浅代理,将data中的数据读写的权限交给了vm
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
//数据转绑(数据代理 它是浅代理)
//并没有进行存值操作 也没有进行拷贝 所以是浅代理
var vm = new MVVM({
el:"#app",
data:{
msg:"hello vue",
a:{
b:{
c:{
name:"i am damu"
}
}
}
}
})
console.log(vm.msg);//msg只是一个空壳,数据代理
console.log(vm.a.b.c.name);//a是代理
</script>
</html>
mvvm.js:
function MVVM(options) {
//外面配置项
this.$options = options;
//data : 外面配置项中data数据
var data = this._data = this.$options.data;
//this劫持(因为下面的this会丢) me是一个鸡肋闭包
var me = this;
// Object.keys(data): 返回一个数组 数组中存放data所有属性名
Object.keys(data).forEach(function(key) {
//内部本身的this指向window
//key : data中每一个key(属性名)
me._proxy(key);
});
//数据劫持
observe(data, this);
//模板解析
this.$compile = new Compile(options.el || document.body, this)
}
MVVM.prototype = {
$watch: function(key, cb, options) {
new Watcher(this, key, cb);
},
//key : data中每一个key(属性名)
_proxy: function(key) {
//this劫持 me是一个鸡肋闭包
//this:MVVM实例对象;vm
var me = this;
//给me 也就是MVVM实例对象 vm 新增属性
Object.defineProperty(me, key, {
configurable: false,
enumerable: true,
get: function proxyGetter() {
return me._data[key];//me和key都是闭包
},
set: function proxySetter(newVal) {
me._data[key] = newVal;
}
});
}
};
数据劫持
数据劫持是深度的,将data中的每一个属性都拿出来进行重新定义,让他具备get和set方法
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app"></div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
//数据转绑(数据代理 它是浅代理)
/*
dep的分配
msg damu damuName damuAge wife wifeName wifeAge son sonName sonAge
改变属性的本质(数据描述符->访问描述符)
数据劫持顺序:
msg damuName damuAge wifeName wifeAge sonName sonAge son wife damu
*/
var vm = new MVVM({
el:"#app",
data:{
msg:"hello world", //数据描述符
damu:{
damuName:"达姆",
damuAge:18,
wife:{
wifeName:"冬雨",
wifeAge:18,
son:{
sonName:"小乐",
sonAge:8
}
}
}
}
})
console.log(vm.damu.wife.son.sonName)
</script>
</html>
observer.js:
//value:外部的data配置(data数据)
//vm:MVVM的实例对象
function observe(value, vm) {
// 外部的data配置有值而且必须得是对象 才不会被咬住
if (!value || typeof value !== 'object') {return;}
//value:外部的data配置(data数据)
return new Observer(value);
};
//data:data数据
function Observer(data) {
//this:Observer的实例对象
this.data = data;
this.walk(data);
}
Observer.prototype = {
walk: function(data) {
//me:Observer的实例对象
var me = this;
Object.keys(data).forEach(function(key) {
//key : data中一个个可枚举属性的名称
me.convert(key, data[key]);
});
},
//key, val : data中一组组键值对
convert: function(key, val) {
//this:Observer的实例对象
this.defineReactive(this.data, key, val);
},
//data:data数据
//key, val : data中一组组键值对
defineReactive: function(data, key, val) {
//分配dep
var dep = new Dep();
//递归!!!!!
var childObj = observe(val);//是对象,结束递归,进入下面
// 改变属性的本质(数据描述符->访问描述符)
Object.defineProperty(data, key, {
enumerable: true, // 可枚举
configurable: false, // 不能再define
get: function() {
//Dep.target:正解析的指令对应的watcher实例对象
if (Dep.target) {
//dep : 真正访问的data中的数据所对应的dep
dep.depend();
}
return val;
},
set: function(newVal) {
if (newVal === val) {
return;
}
val = newVal;
childObj = observe(newVal);
//dep唤醒所有watcher!!!
dep.notify();
}
});
}
};
var uid = 0;
function Dep() {
this.id = uid++;
this.subs = [];
}
Dep.prototype = {
addSub: function(sub) {
this.subs.push(sub);
},
depend: function() {
//this : 真正访问的data中的数据所对应的dep
//Dep.target : 正解析的指令对应的watcher实例对象
Dep.target.addDep(this);
},
removeSub: function(sub) {
var index = this.subs.indexOf(sub);
if (index != -1) {
this.subs.splice(index, 1);
}
},
notify: function() {
this.subs.forEach(function(sub) {
sub.update();
});
}
};
Dep.target = null;
模板解析(插值表达式)&&(指令)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
{{msg}}
</div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
var vm = new MVVM({
el:"#app",
data:{
msg:"hello world"
}
})
console.log(app.childNodes);
console.log(app.children);
</script>
</html>
compile.js:
//el: el配置
//vm: MVVM的实例对象
function Compile(el, vm) {
//this:Compile实例对象
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
//将模板拆入到文档碎片中
this.$fragment = this.node2Fragment(this.$el);
//真正的模板解析
this.init();
//解析完模板之后的挂载过程
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
//将指定节点中子元素全部剪切到文档碎片中
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(),
child ;
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
//模板解析的入口方法
init: function() {
//this.$fragment : 存放模板的文档碎片
this.compileElement(this.$fragment);
},
//el : 存放模板的文档碎片
compileElement: function(el) {
//childNodes是会获取到文本节点的
var childNodes = el.childNodes,
me = this;
// [].slice.call(childNodes) : 将childNodes转为真数组
[].slice.call(childNodes).forEach(function(node) {
//node : 模板中的每一个节点
var text = node.textContent;
//正则表达式 {{.*}} ===> "{{}}" " {{msg}} "
var reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
//vue只解析 包含插值表达式的文本节点
//RegExp.$1 : 拿到的是第一个分组匹配到的内容!!! 就是插值表达式中内容!!
//表达式!!!
me.compileText(node, RegExp.$1);
}
//对嵌套的节点进行递归解析
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
//插值表达式的解析 node:当前正在被解析的节点 exp:表达式
compileText: function(node, exp) {
compileUtil.text(node, this.$vm, exp);
},
//指令的解析
compile: function(node) {
//拿到元素节点的所有属性
var nodeAttrs = node.attributes,
me = this;
//将元素节点的所有属性遍历一遍 如果满足是vue的指令的话 则要解析
[].slice.call(nodeAttrs).forEach(function(attr) {
//当前解析的属性的名称
var attrName = attr.name;
//判断解析的属性是否为vue指令
if (me.isDirective(attrName)) {
//拿到指令对应的表达式
var exp = attr.value;
//将指令名称去掉v-
var dir = attrName.substring(2);
//区分普通指令 和 事件指令
if (me.isEventDirective(dir)) {
//事件指令的解析规则
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
//普通指令的解析规则
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
//将vue指令从节点上移除!
node.removeAttribute(attrName);
}
});
},
//判断属性是否为vue指令
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
//判断属性是否为事件指令
isEventDirective: function(dir) {
return dir.indexOf('on') === 0;
},
//判断node是否是一个元素节点
isElementNode: function(node) {
return node.nodeType == 1;
},
//判断node是否是一个文本节点
isTextNode: function(node) {
return node.nodeType == 3;
}
};
// 指令处理的工具类 ({{}} v-text v-html v-model v-class v-on)
var compileUtil = {
//node:当前正在被解析的节点
//vm : vm实例对象
//exp:表达式
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
html: function(node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model: function(node, vm, exp) {
this.bind(node, vm, exp, 'model');
//实现数据双向绑定
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class: function(node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
eventHandler: function(node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
},
//找对应指令的更新器
//根据exp 找到exp对应的值
bind: function(node, vm, exp, dir) {
//找更新器
var updaterFn = updater[dir + 'Updater'];
//确认更新器是否存在 如果存在则调用!!!
//this._getVMVal(vm, exp):根据表达式exp 去vm.data中找exp对应的值
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
//每一个指令都对应着一个watcher(拿着指令最重要的两个内容:更新器 和 节点)
new Watcher(vm, exp, function(value, oldValue) {
updaterFn && updaterFn(node, value, oldValue);
});
},
//根据表达式去vm实例对象中 找表达式对应的值!
_getVMVal: function(vm, exp) {
//val : data配置!!!
var val = vm._data;
//[obj,msg,text]
exp = exp.split('.');
exp.forEach(function(k) {
// k:obj / msg / text
val = val[k];
});
return val;
},
//数据双向绑定有关 exp:obj.msg.text2
_setVMVal: function(vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
// damu.age 0 1
exp.forEach(function(k, i) {
if (i < exp.length - 1) {
val = val[k];
} else {
//调用k对应的set方法
val[k] = value;
}
});
}
};
//更新器集合
var updater = {
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function(node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function(node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<span v-text="obj.msg.text" damu="damu"></span>
{{obj.msg.text2}}
</div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
var vm = new MVVM({
el:"#app",
data:{
obj:{
msg:{
text:"hello mvvm",
text2:"hello mvvm2"
}
}
}
})
</script>
</html>
数据响应式
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<span v-html="obj.msg.text" damu="damu"></span> <br>
{{obj.msg.text2}}
</div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
/*
data中的每一个key都对应着一个dep
模板上的每一个指令都对应着一个watcher
watcher 和 dep之间应该构建什么样的关系?
一个指令有没有可能引用多个data中的数据? 一个watcher对应多个dep
一个data中的数据有没有可能出现在多个指令内? 一个dep对应多个watcher
总结:
dep和watcher之间是多对多的关系!!!
*/
/*
v-html.watcher.depIds={
1:obj的dep,
2:msg的dep,
3:text的dep
}
{{}}.watcher.depIds={
1:obj的dep,
2:msg的dep,
4:text2的dep
}
obj.dep.subs=[v-html.watcher,{{}}.watcher]
msg.dep.subs=[v-html.watcher,{{}}.watcher]
text.dep.subs=[v-html.watcher]
text2.dep.subs=[{{}}.watcher]
*/
var vm = new MVVM({
el:"#app",
data:{
obj:{
msg:{
text:"hello mvvm",
text2:"hello mvvm2"
}
}
}
})
setTimeout(()=>{
vm.obj.msg.text = "xxx"
},2000)
</script>
</html>
watcher.js:
//vm : MVVM的实例对象
//exp : 表达式
//cb : 回调函数(两个闭包,updaterFn,node)
function Watcher(vm, exp, cb) {
this.cb = cb;
this.vm = vm;
this.exp = exp;
//this: Watcher的实例对象
this.depIds = {};
this.value = this.get(); //拿老值!!!
}
Watcher.prototype = {
update: function() {
this.run();
},
run: function() {
var value = this.get(); //拿新值
var oldVal = this.value;
if (value !== oldVal) {
this.value = value;
this.cb.call(this.vm, value, oldVal);
}
},
addDep: function(dep) {
if (!this.depIds.hasOwnProperty(dep.id)) {
//dep:真正访问的data中的数据所对应的dep
dep.addSub(this);
//this : Dep.target(watcher) 正解析的指令对应的watcher实例对象
this.depIds[dep.id] = dep;
}
},
get: function() {
Dep.target = this; //真正解析的指令对应的watcher实例对象
var value = this.getVMVal();
Dep.target = null;
return value;
},
//根据表达式exp 去 data中找exp对应的值
//exp : obj.msg.text (调三次get方法)
getVMVal: function() {
var exp = this.exp.split('.');
var val = this.vm._data;
exp.forEach(function(k) {
val = val[k];
});
return val;
}
};
数据双向绑定
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<div id="app">
<input type="text" v-model="obj.msg.text2">
{{obj.msg.text2}}
</div>
</body>
<script src="./MVVM/mvvm.js"></script>
<script src="./MVVM/observer.js"></script>
<script src="./MVVM/compile.js"></script>
<script src="./MVVM/watcher.js"></script>
<script>
var vm = new MVVM({
el:"#app",
data:{
obj:{
msg:{
text:"hello mvvm",
text2:"hello mvvm2"
}
}
}
})
</script>
</html>
compile.js:
//el: el配置
//vm: MVVM的实例对象
function Compile(el, vm) {
//this:Compile实例对象
this.$vm = vm;
this.$el = this.isElementNode(el) ? el : document.querySelector(el);
if (this.$el) {
//将模板拆入到文档碎片中
this.$fragment = this.node2Fragment(this.$el);
//真正的模板解析
this.init();
//解析完模板之后的挂载过程
this.$el.appendChild(this.$fragment);
}
}
Compile.prototype = {
//将指定节点中子元素全部剪切到文档碎片中
node2Fragment: function(el) {
var fragment = document.createDocumentFragment(),
child ;
while (child = el.firstChild) {
fragment.appendChild(child);
}
return fragment;
},
//模板解析的入口方法
init: function() {
//this.$fragment : 存放模板的文档碎片
this.compileElement(this.$fragment);
},
//el : 存放模板的文档碎片
compileElement: function(el) {
//childNodes是会获取到文本节点的
var childNodes = el.childNodes,
me = this;
// [].slice.call(childNodes) : 将childNodes转为真数组
[].slice.call(childNodes).forEach(function(node) {
//node : 模板中的每一个节点
var text = node.textContent;
//正则表达式 {{.*}} ===> "{{}}" " {{msg}} "
var reg = /\{\{(.*)\}\}/;
if (me.isElementNode(node)) {
me.compile(node);
} else if (me.isTextNode(node) && reg.test(text)) {
//vue只解析 包含插值表达式的文本节点
//RegExp.$1 : 拿到的是第一个分组匹配到的内容!!! 就是插值表达式中内容!!
//表达式!!!
me.compileText(node, RegExp.$1);
}
//对嵌套的节点进行递归解析
if (node.childNodes && node.childNodes.length) {
me.compileElement(node);
}
});
},
//插值表达式的解析 node:当前正在被解析的节点 exp:表达式
compileText: function(node, exp) {
compileUtil.text(node, this.$vm, exp);
},
//指令的解析
compile: function(node) {
//拿到元素节点的所有属性
var nodeAttrs = node.attributes,
me = this;
//将元素节点的所有属性遍历一遍 如果满足是vue的指令的话 则要解析
[].slice.call(nodeAttrs).forEach(function(attr) {
//当前解析的属性的名称
var attrName = attr.name;
//判断解析的属性是否为vue指令
if (me.isDirective(attrName)) {
//拿到指令对应的表达式
var exp = attr.value;
//将指令名称去掉v-
var dir = attrName.substring(2);
//区分普通指令 和 事件指令
if (me.isEventDirective(dir)) {
//事件指令的解析规则
compileUtil.eventHandler(node, me.$vm, exp, dir);
} else {
//普通指令的解析规则
compileUtil[dir] && compileUtil[dir](node, me.$vm, exp);
}
//将vue指令从节点上移除!
node.removeAttribute(attrName);
}
});
},
//判断属性是否为vue指令
isDirective: function(attr) {
return attr.indexOf('v-') == 0;
},
//判断属性是否为事件指令
isEventDirective: function(dir) {
return dir.indexOf('on') === 0;
},
//判断node是否是一个元素节点
isElementNode: function(node) {
return node.nodeType == 1;
},
//判断node是否是一个文本节点
isTextNode: function(node) {
return node.nodeType == 3;
}
};
// 指令处理的工具类 ({{}} v-text v-html v-model v-class v-on)
var compileUtil = {
//node:当前正在被解析的节点
//vm : vm实例对象
//exp:表达式
text: function(node, vm, exp) {
this.bind(node, vm, exp, 'text');
},
html: function(node, vm, exp) {
this.bind(node, vm, exp, 'html');
},
model: function(node, vm, exp) {
this.bind(node, vm, exp, 'model');
//实现数据双向绑定
var me = this,
val = this._getVMVal(vm, exp);
node.addEventListener('input', function(e) {
var newValue = e.target.value;
if (val === newValue) {
return;
}
me._setVMVal(vm, exp, newValue);
val = newValue;
});
},
class: function(node, vm, exp) {
this.bind(node, vm, exp, 'class');
},
eventHandler: function(node, vm, exp, dir) {
var eventType = dir.split(':')[1],
fn = vm.$options.methods && vm.$options.methods[exp];
if (eventType && fn) {
node.addEventListener(eventType, fn.bind(vm), false);
}
},
//找对应指令的更新器
//根据exp 找到exp对应的值
bind: function(node, vm, exp, dir) {
//找更新器
var updaterFn = updater[dir + 'Updater'];
//确认更新器是否存在 如果存在则调用!!!
//this._getVMVal(vm, exp):根据表达式exp 去vm.data中找exp对应的值
updaterFn && updaterFn(node, this._getVMVal(vm, exp));
//每一个指令都对应着一个watcher(拿着指令最重要的两个内容:更新器 和 节点)
new Watcher(vm, exp, function(value, oldValue) {
updaterFn && updaterFn(node, value, oldValue);
});
},
//根据表达式去vm实例对象中 找表达式对应的值!
_getVMVal: function(vm, exp) {
//val : data配置!!!
var val = vm._data;
//[obj,msg,text]
exp = exp.split('.');
exp.forEach(function(k) {
// k:obj / msg / text
val = val[k];
});
return val;
},
//数据双向绑定有关 exp:obj.msg.text2
_setVMVal: function(vm, exp, value) {
var val = vm._data;
exp = exp.split('.');
// damu.age 0 1
exp.forEach(function(k, i) {
if (i < exp.length - 1) {
val = val[k];
} else {
//调用k对应的set方法
val[k] = value;
}
});
}
};
//更新器集合
var updater = {
textUpdater: function(node, value) {
node.textContent = typeof value == 'undefined' ? '' : value;
},
htmlUpdater: function(node, value) {
node.innerHTML = typeof value == 'undefined' ? '' : value;
},
classUpdater: function(node, value, oldValue) {
var className = node.className;
className = className.replace(oldValue, '').replace(/\s$/, '');
var space = className && String(value) ? ' ' : '';
node.className = className + space + value;
},
modelUpdater: function(node, value, oldValue) {
node.value = typeof value == 'undefined' ? '' : value;
}
};