class MyVue{
constructor(options){ //options为对象里面的配置项
//接收所有的配置项
this.$options=options;
//接收配置项里面的值
this.$data=options.data;
//接收挂载点
this.$el=document.getElementById(options.el);
//数据劫持
this.observer(this.$data);
//进行编译
new Compile(this.$el,this);
}
observer(data){
//这里我们要对传进来的data值进行判断,只有是对象形式才走下面的代码。
if(!data || typeof data!='object') return;
//获取到data身上的所有的key值进行遍历
Object.keys(data).forEach((key)=>{
//给data身上所有的属性添加getter和setter方法
this.defineRective(data,key,data[key]);
//将data身上的所有的属性到实例身上
this.ProxyData(key);
})
}
ProxyData(key){
Object.defineProperty(this,key,{
get(){
//访问
return this.$data[key];
},
set(newvalue){
this.$data[key]=newvalue;
}
})
}
defineRective(data,key,value){
//递归 检测data的属性的值是否还是一个对象,如果是在进行遍历
this.observer(value);
var dep=new Dep();
//添加getter和setter方法
Object.defineProperty(data,key,{
get(){
//收集依赖
Dep.target && dep.addDep(Dep.target)
//访问
return value;
},
set(newvalue){
//设置
if(newvalue===value) return;
value=newvalue;
//当设置的时候我们只需要做一次更新即可
dep.message();
}
})
}
}
class Compile {
constructor(el, vm) {
this.$el = el;
this.$vm = vm;
if (this.$el) {
//1、获取app下面的所有节点,this.$Fragment为获取到的挂载点下的所有节点
this.$Fragment = this.getNode(this.$el);
//2、进行编译
this.compile(this.$Fragment)
//3、将编译好的节点插入到挂载点中
this.$el.appendChild(this.$Fragment)
}
}
getNode(root) {
//创建文档碎片
var frag = document.createDocumentFragment();
var child;
while (child = root.firstChild) {
//将节点保存到了JS内存当中,这样页面上就不会有这个节点了
frag.appendChild(child)
}
return frag;
}
compile(fragment) {
//遍历所有的子节点
Array.from(fragment.childNodes).forEach((node) => {
//判断当前节点是文本节点还是元素节点
//判断是否是文本节点,并且文本节点中必须要有{{内容}}
if (node.nodeType === 3 && /\{\{(.+)\}\}/.test(node.textContent)) {
//操作文本节点方法
this.compileText(node)
} else if (node.nodeType === 1) { //判断是否是元素节点
//获取当前节点的所有属性
var attrs = node.attributes;
Array.from(attrs).forEach(attr => {
var key = attr.name; //拿到元素身上的属性 如v-text @
var value = attr.value; //拿到元素身上属性的值 如sex handleclick
//判断是否是指令
if (key.indexOf('v-') === 0) {
//判断是否是v-text指令,如果是,则把属性上对应的值给节点内容
if (key.substring(2) === 'text') {
node.textContent = value
}
}
//判断是否是事件
else if (key.indexOf('@') === 0) {
//事件处理
//拿到vm实例上的方法取名fn
var fn = this.$vm.$options.methods[value];
//拿到方法的类型,点击或者输入类型
var dir = key.substr(1);
//将事件绑定到该节点上
node.addEventListener(dir, fn.bind(this.$vm));
}
})
}
//如果子节点下面还有子节点,那么就进行递归,遍历所有的子节点
if (node.childNodes && node.childNodes.length > 0) {
this.compile(node)
}
})
}
compileText(node) {
this.update(node, this.$vm, RegExp.$1);
}
update(node, vm, exp) {
node.textContent = vm[exp];
new Watcher(node,vm,exp,(value)=>{
node.textContent = vm[exp];
})
}
}
class Dep{
constructor(){
//存放所有的依赖
this.deps=[];
}
addDep(rely){
this.deps.push(rely);
}
message(){
//通知状态
this.deps.forEach((item)=>{
item.update()
})
}
}
class Watcher{
constructor(node,vm,exp,cb){
this.$vm=vm;
this.$exp=exp;
this.cb=cb;
Dep.target=this;
//这一步是在做get的触发
this.$vm[this.$exp];
Dep.target=null;
}
update(){
this.cb.call(this.$vm,this.$vm[this.$exp])
}
}
var vm=new MyVue({
el:'app',
data:{
username:'健哥',
age:'18',
sex:'男'
},
methods: {
handlclick(){
this.username='张三'
}
},
})
<div id="app">
<div>{{username}}</div>
<div>{{age}}</div>
<div v-text="sex"></div>
<button @click="handlclick">快点我!</button>
</div>
<!-- 主要实现了三个功能 -->
<!-- 1:数据劫持
2:编译
3:收集依赖和watcher监听 -->