test.html
<!--
* @Description:
* @Autor: wangDuJuan
* @Date: 2020-08-11 06:38:09
-->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{name}}</p>
<p k-text="name"></p>
<p>{{age}}</p>
<p>
{{doubleAge}}
</p>
<input type="text" k-model="name">
<button @click="changeName">呵呵</button>
<div k-html="html"></div>
</div>
<script src="./vueyyg.js"></script>
<script src='./compile.js'></script>
<script>
const kaikeba = new Vue({
el: '#app',
data: {
name: "I am test.",
age: 12,
html: '<button>这是一个按钮</button>'
},
created() {
console.log('开始啦')
setTimeout(() => {
this.name = '我是测试'
}, 1500)
},
methods: {
changeName() {
this.name = '哈喽'
this.age = 1
}
}
})
</script>
</body>
</html>
vueyyg.js
/*
* @Author: yang
* @Date: 2020-08-10 14:47:28
* @LastEditors: yang
* @LastEditTime: 2020-08-18 11:22:06
* @FilePath: \demo3\vueyyg.js
*/
class Vue{
constructor(options){
this.$options = options
this.$data = options.data
this.observe(this.$data)
// new Watcher()
new Compile(options.el,this)
if(options.created){
options.created.call(this)
}
}
observe(data){
if(!data || typeof data != 'object'){
return
}
Object.keys(data).forEach(key=>{
this.defineRective(key,data[key],data)
//代理到vm上
this.proxyData(key)
})
}
proxyData(key){
// let dep = new Dep()
Object.defineProperty(this,key,{
get(){
return this.$data[key]
},
set(newValue){
this.$data[key] = newValue
}
})
}
defineRective(key,value,obj){
let dep = new Dep()
Object.defineProperty(obj,key,{
get(){
Dep.target&&dep.addDep(Dep.target)
return value
},
set(newValue){
if(newValue!==value){
value = newValue
// console.log(`${key}更新了${newValue}`)
// 每一个依赖,一定有一个单独的dep
dep.notify()
}
}
})
//递归
this.observe(value)
}
//数据更改的 依赖的视图对应去做更新 Dep数组里面有n个watcher,对应对数据依赖的视图
}
class Dep{
constructor(){
this.deps = []
}
addDep(dep){
this.deps.push(dep)
}
notify(){
this.deps.forEach(dep=>dep.update())
}
}
class Watcher{
constructor(vm,key,cb){
this.vm = vm
this.key = key
this.cb = cb
Dep.target = this
this.vm[this.key];
Dep.target = null
}
update(){
// console.log('数据更新')
this.cb.call(this.vm,this.vm[this.key])
}
}
compile.js
/*
* @Author: yang
* @Date: 2020-08-13 13:51:08
* @LastEditors: yang
* @LastEditTime: 2020-08-21 09:23:05
* @FilePath: \demo3\compile.js
*/
class Compile {
constructor(el,vm){
this.$el = document.querySelector(el)
this.$vm = vm
if(this.$el){
// 提取宿主中模板的内容到fragment标签,dom操作会提高效率
this.$fragment = this.node2Fragment(this.$el)
this.compile(this.$fragment)
this.$el.appendChild(this.$fragment)
}
}
node2Fragment(el){
let fragment = document.createDocumentFragment()
let child;
while(child = el.firstChild){
fragment.appendChild(child)
}
return fragment
}
compile(el){
var childNodes = el.childNodes
Array.from(childNodes).forEach(node=>{
if(node.nodeType === 1){
this.compileElement(node)
console.log('编译节点',node.nodeName)
} else if(this.isInterpolation(node)){
//插值表达式
this.compileText(node)
console.log('插值表达式',node.textContent)
}
// 遍历子节点
if(node.childNodes&&node.childNodes.length>0){
this.compile(node)
}
})
}
compileText(node){
node.textContent = this.$vm[RegExp.$1]
this.update(node,this.$vm,RegExp.$1,'text')
}
compileElement(node){
var nodeAttributes = node.attributes
Array.from(nodeAttributes).forEach(attr=>{
const attrName = attr.name
const exp = attr.value
if(this.isDirective(attrName)){
const dir = attrName.substring(2)
this[dir]&&this[dir](node,this.$vm,exp)
}
if(this.isEvent(attrName)){
let dir = attrName.substring(1)
this.eventHandler(node,this.$vm,exp,dir)
}
})
}
isDirective(name){
return name.indexOf('k-') === 0
}
isEvent(name){
return name.indexOf('@') === 0
}
update(node,vm,exp,dir){
let updateFn = this[dir + 'Updator']
updateFn&&updateFn(node,vm[exp])
//收集依赖
new Watcher(vm,exp,function(val){
updateFn&&updateFn(node,val)
})
}
text(node,vm,exp){
this.update(node,vm,exp,'text')
}
html(node,vm,exp){
this.update(node,vm,exp,'html')
}
model(node,vm,exp){
// data -> view
this.update(node,vm,exp,'model')
// view -> data
node.addEventListener('input',e=>{
vm[exp] = e.target.value
})
}
htmlUpdater(node,value){
node.innerHTML = value
}
modelUpdater(node,value){
node.value = value
}
textUpdator(node,val){
node.textContent = val
}
isInterpolation(node){
console.log(RegExp.$1)
return node.nodeType ==3&&/\{\{(.*)\}\}/.test(node.textContent)
}
eventHandler(node,vm,exp,dir){
let fn = vm.$options.methods&&vm.$options.methods[exp]
if(dir&&fn){
node.addEventListener(dir,fn.bind(vm))
}
}
}