compile.js
// 编译
class Compile{
constructor(el,vm){
this.$el = el
this.$vm = vm
if(this.$el){
// 1.获取到app下面所有的节点
this.$Fragment = this.getNodeFragment(this.$el)
// 2.进行编译
this.compile(this.$Fragment)
// 3.将编译好的节点插入app
this.$el.appendChild(this.$Fragment)
}
}
getNodeFragment(root){
// 创建文档碎片
var frag = document.createDocumentFragment()
var child
// firstChild返回所有子节点
while(child = root.firstChild){
// 将节点保存在了js的内存当中 页面上就不会有这个节点了
frag.appendChild(child)
}
return frag
}
compile(fragment){
var childNodes = fragment.childNodes
// 遍历所有的子节点
Array.from(childNodes).forEach((node)=>{
// 判断文本节点
if(this.isText(node)){
// 文本节点的编译
this.compileText(node)
}
// 判断元素节点
if(this.isElement(node)){
var attrs = node.attributes
Array.from(attrs).forEach((attr)=>{
// v-text sex
// @click handle
var key = attr.name
var value = attr.value
// 判断是否是指令
if(this.isDirective(key)){
// text
// 获取指令名称
var dir = key.substr(2)
// 调用指令对应的函数
this[dir+"update"] && this[dir+"update"](node,this.$vm[value])
}
// 判断是否是事件
if(this.isEvent(key)){
var dir = key.substr(1)
this.handleEvent(node,this.$vm,value,dir)
}
})
}
// 如果子节点下面还有子节点那么就进行递归
if(node.childNodes && node.childNodes.length>0){
this.compile(node)
}
})
}
isText(node){
// 判断文本节点 并且文本节点中必须要有{{内容}}
return node.nodeType === 3 && /\{\{(.+)\}\}/.test(node.textContent)
}
isElement(node){
return node.nodeType === 1
}
compileText(node){
// 分别代表元素 vue的实例 {{属性}} 标识
this.update(node,this.$vm,RegExp.$1,'text')
}
// 更新
update(node,vm,exp,dir){
var updateFn = this[dir+"update"]
updateFn && updateFn(node,vm[exp])
new Watcher(node,vm,exp,(value)=>{
updateFn && updateFn(node,vm[exp])
})
}
textupdate(node,value){
node.textContent = value
}
// 判断指令
isDirective(attr){
return attr.indexOf("v-") === 0
}
// 判断事件
isEvent(attr){
return attr.indexOf("@") === 0
}
// 事件处理
handleEvent(node,vm,callback,type){
// 判断methods是否存在以及callback函数是否在methods中 如果存在则进行绑定
var fn = vm.$options.methods && vm.$options.methods[callback]
node.addEventListener(type,fn.bind(vm))
}
}
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<div id="app">
<div>{{username}}</div>
<div>{{age}}</div>
<div v-text="sex"></div>
<button @click="handle">点击</button>
</div>
</body>
</html>
<script src="./index.js"></script>
<script src="./compile.js"></script>
<script>
var vm = new LiuyangVue({
el:"#app",//挂载点
data:{
username:"liuyang",
age:18,
sex:"女"
},
methods:{
handle(){
this.username = "哈哈"
}
}
})
console.log(vm);
</script>
index.js
class LiuyangVue{
// 配置项
constructor(options){
this.$options = options
// 数据
// 给data上所有的属性进行遍历 给每个属性加上getter和setter方法
this.$data = options.data
// 挂载点
this.$el = document.querySelector(options.el)
// 数据劫持 作用:给data上的每个属性添加getter和setter方法
this.observer(this.$data)
// 进行编译 vue初始化的时候
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.defineReactive(data,key,data[key])
// 对象 key 配置项
// Object.defineProperty("对象","key",{})
// 将data身上所有的属性复制一份到vm的实例身上
this.proxyData(key)
})
}
proxyData(key){
Object.defineProperty(this,key,{
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal
}
})
}
defineReactive(data,key,val){
// 递归(对子属性对象的属性进行递归) 检测data属性的值是否还是一个对象 如果是则再进行遍历
this.observer(val)
var dep = new Dep()
// 添加getter和setter方法
Object.defineProperty(data,key,{
get(){
// 依赖收集
Dep.target && dep.addDep(Dep.target)
// 访问
return val
},
set(newVal){
// 设置
if(newVal == val)
return
val = newVal
// 当设置的时候我们只要做一次通知更新即可
dep.notify()
}
})
}
}
class Dep{
constructor(){
// 存储所有的依赖
this.deps=[]
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach((dep)=>{
dep.update()
})
}
}
class Watcher{
constructor(node,vm,exp,cb){
this.$vm = vm
this.$exp = exp
this.cb = cb
Dep.target = this
this.$vm[this.$exp]//这一步是在做getter的触发 vm.username 做访问
Dep.target = null
}
update(){
this.cb.call(this.$vm,this.$vm[this.$exp])
}
}