不使用任何vue的文件
目前做到的程度是,
胡子语法的数据绑定
k-text指令的数据绑定
created的生命周期函数
k-model的双向绑定
k-html的显示
@click指令的执行
index.html
初始化KVue实例,传入对象
<!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">
<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='./compile.js'></script>
<script src='./kvue.js'></script>
<script>
const kaikeba = new KVue({
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>
<!-- <script src="kvue.js"></script> -->
<!-- <script>
const app = new KVue({
data: {
test: "I am test",
foo: {
bar: "bar"
},
}
});
// app.$data.test = "hello, kaikeba!";
// app.$data.foo.bar = "oh my bar";
app.test = "hello, kaikeba!";
</script> -->
</body>
</html>
Kvue.js
实现KVue的类
1,保存了传入的对象和对象中的data
2,遍历data里面的key值,给每一个key值用defineProperty重写get和set方法
创建dep实例,get方法给dep里面的数组添加watcher,set方法调用dep的函数,遍历watcher数组执行里面的watcher实例的update方法。如果key是个对象的话,就递归调用拦截方法。
3,创建编译器Compile实例传入元素id和当前kVue实例,编译器保存了vm实例和根据id创建了dom元素,获取dom元素的子节点数组。
如果是元素节点,获取元素的html属性数组,遍历找出带k-开头的,后面截取除k-后面的执行,执行对应的更新函数做首次更新html内容,并创建一个watcher实例,传入vm实例,属性的key,和更新文本的函数,让watcher保存,watcher把Dep.target指向自己,调用了一次属性get方法,把当前watcher加到dep里面的数组中。
如果是文本节点,根据正则判断是否带有{{}},更新文本并添加watcher,同上,如果当前节点还有子节点,就递归执行编译方法。
4,改变created函数this指向,并执行生命周期created函数。
//vue实例
class KVue{
constructor(options){
//保存选项
this.$options = options;
//保存data
this.$data = options.data;
//对传入data对象执行响应化处理
this.observe(this.$data);
// //测试
// new Watcher(this,'test');
// this.test;//读取属性 触发依赖收集
new Compile(options.el,this);
if(options.created){
options.created.call(this)
}
}
observe(value){
//参数必须是对象
if(!value || typeof value !== 'object'){
return;
}
//拿到data对象的key数组去遍历
Object.keys(value).forEach(key => {
//执行响应化
this.defineReactive(value,key,value[key]);
//执行代理
this.proxyData(key);
})
}
//obj:data对象 遍历给每一个data里面的对象增加监听
defineReactive(obj,key,val){
//对象里面可能还有对象 递归判断
this.observe(val);
//创建dep 每个属性对应着一个dep
const dep = new Dep();
//定义属性
Object.defineProperty(obj,key,{
get(){
//依赖收集
Dep.target && dep.addDep(Dep.target)
return val;
},
set(newVal){
if(newVal === val){
return
}
val = newVal;
console.log(key+'属性更新了');
dep.notify();
}
})
}
proxyData(key){
//给自定义vue实例添加属性
Object.defineProperty(this,key,{
get(){
return this.$data[key]
},
set(newVal){
this.$data[key] = newVal;
}
})
}
}
//Dep: 管理若干Watcher实例,通知它们更新
class Dep{
constructor(){
this.deps = [];
}
addDep(dep){
this.deps.push(dep);
}
notify(){
//set函数调用
this.deps.forEach(dep => dep.update())
}
}
//watcher 执行具体更新操作
class Watcher{
constructor(vm,key,updater){
this.vm = vm;
this.key = key;
this.updater = updater;
Dep.target = this;//依赖收集
this.vm[this.key];
Dep.target = null;
}
update(){
// console.log('属性'+this.key+'更新了');
this.updater.call(this.vm,this.vm[this.key]);
}
}
compile.js
//new Compile('#app',vm)
class Compile{
constructor(el,vm){
this.$vm = vm;
this.$el = document.querySelector(el);
if(this.$el){
//执行编译
this.compile(this.$el);
}
}
compile(el){
//遍历el el就是真实dom
//拿当前元素的子元素加文本节点 不包括自身
const childNodes = el.childNodes;
//每次拿出一个dom节点
Array.from(childNodes).forEach(node => {
if(this.isElement(node)){
//元素节点
this.compileElement(node);
}else if(this.isInter(node)){
//文本节点 编译文本
this.compileText(node);
}
//递归 遍历树 记得加有条件的递归 看有没有子节点
if(node.childNodes && node.childNodes.length > 0){
this.compile(node);
}
})
}
//判断元素
isElement(node){
//判断元素 1就是元素
return node.nodeType === 1;
}
//判断文本节点
isInter(node){
//判断文本 3是文本节点+xxx{{xxx}}
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent);
}
//替换文本节点的值
compileText(node){
//获取表达式 RegExp.$1是{{}}里面的值 然后从vm属性里面取到该key的值 替换给文本节点
// node.textContent = this.$vm[RegExp.$1];
const exp = RegExp.$1;
this.update(node,exp,'text');
}
//节点 data里面属性的key 命令text
update(node,exp,dir){
let updaterFn = this[dir + 'Updater']
//首次更新值 this.$vm[exp] 就是data里面当前key的值
updaterFn && updaterFn(node,this.$vm[exp]);
//后续更新 额外传一个更新函数
new Watcher(this.$vm,exp,function(value){
updaterFn && updaterFn(node,value);
});
}
textUpdater(node,value){
node.textContent = value;
}
//元素节点更新
compileElement(node){
//获取html属性
const nodeAttrs = node.attributes;
//k-text="test"
Array.from(nodeAttrs).forEach(attr => {
const attrName = attr.name;//k-text
const exp = attr.value;//test
//截取指定后面的值
if(attrName.indexOf('k-') === 0){
//指令k-text k-model
const dir = attrName.substring(2);//拿到text
this[dir] && this[dir](node,exp);//执行text函数
}
//拦截事件
if(attrName.indexOf('@' === 0)){
const dir = attrName.substring(1);
this.eventHandler(node,exp,dir);
}
});
}
text(node,exp){
this.update(node,exp,'text')
}
//k-model 两个处理 1是显示新值 2是重新赋值
model(node,exp){
//执行更新
this.update(node,exp,"model");
//事件监听 exp是data里面的key
node.addEventListener('input',e => {
//data里面的key赋值
this.$vm[exp] = e.target.value;
})
}
modelUpdater(node,value){
node.value = value;
}
html(node,exp){
//执行更新
this.update(node,exp,"html");
}
htmlUpdater(node,value){
node.innerHTML = value;
}
eventHandler(node,exp,dir){
//获取回调函数
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
if(dir && fn){
node.addEventListener(dir,fn.bind(this.$vm));
}
}
}
网友评论