有了前一篇文章的defineReactive的知识前提,这一节,我们开始动手实现自己的简易版vue,我们的目标是实现下面html中的kvue.js,保证正常的功能即可
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<p>{{counter}}</p>
<p>{{counter}}</p>
<p k-text="counter"></p>
<p k-html="desc"></p>
</div>
<script src="kvue.js"></script>
<script>
const app = new KVue({
el: '#app',
data: {
counter: 1,
desc: '<span style="color:red;">html</span>',
},
})
setInterval(() => {
app.counter++;
}, 1000);
</script>
</body>
</html>
首先分析一下需求,在我们引入kvue.js以后,我们会有一个kvue的类,它可以绑定我们的#appDOM ,会编译dom中的双花括号语法来替换成我们vue对象中的data数据,并且可以识别我们的k-text和k-html指令,这里的k-text可以理解成和{{}}一样的效果,k-html相当于在元素中插入html,并且下面的定时器可以触发页面的dom变化
上面这张图就是kvue的核心思想,在new vue的时候,主要做两件事
-
1.创建一个observer对象,这个对象的负责接受我们data上的数据,来对这些数据做响应式,在创建observer的get的时候,去创建Dep(依赖)的实例,用来做通知更新
-
2.compile模板引擎解析模板,初始化页面,收集依赖,并在初始化的同时做订阅
涉及类型介绍
- KVue:框架构造函数
- Observer:执⾏数据响应化
- Compile:编译模板,初始化视图,收集依赖(更新函数、watcher创建)
- Watcher:执⾏更新函数(更新dom)
- Dep:管理多个Watcher,批量更新
开始实现我们kvue的类
//数据响应式实现
function defineReactive(obj,key,val){
//递归
observe(val);
Object.defineProperty(obj,key,{
get(){
console.log('get',key);
return val
},
set(newVal){
console.log('set',key);
//保证nweval是新对象 做响应式处理
observe(newVal);
if(newVal !== val){
val = newVal
}
}
})
}
// 遍历obj所有key 做响应式处理
function observe(obj){
if(typeof(obj) !== 'object' || obj === null){
return
}
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key]);
})
}
class KVue{
constructor(options){
this.$outions = options;
this.$data = options.data;
observe(this.$data);
}
}
写完以后,我们已经把传入的data使用observe做响应式拦截了,但在页面发现并不能触发get和set的log输出,这是因为data现在不是在我们的实例上的,(我们把app.count++变成 app.$data.count++ 就可以看到log输出了),我们先改写一下observe,创建一个Observer类
function observe(obj){
if(typeof(obj) !== 'object' || obj === null){
return
}
new Observer(obj);
}
class Observer{
constructor(value){
this.value = value;
if(Array.isArray(value)){
}else{
this.walk(value);
}
}
walk(obj){
Object.keys(obj).forEach(key=>{
defineReactive(obj,key,obj[key]);
})
}
}
然后我们需要做完响应式以后做一步代理,外面实例就可以直接访问data了
function proxy(vm){
Object.keys(vm.$data).forEach(key=>{
Object.defineProperty(vm, key, {
get(){
return vm.$data[key];
},
set(v){
vm.$data[key] = v;
}
})
})
}
//1.对data选项做响应式处理
//2.编译模板
class KVue{
constructor(options){
this.$options = options;
this.$data = options.data;
observe(this.$data);
proxy(this);
}
}
接下来实现编译器,这个就需要慢慢扣着一点点看了,取到元素遍历,解析指令,设置dom...
class Compile {
constructor(el,vm){
this.$el = document.querySelector(el);
this.$vm = vm;
if(this.$el){
this.compile(this.$el);
}
}
compile(el){
const childNodes = el.childNodes;
childNodes.forEach(node => {
// 1 元素
if(node.nodeType === 1){
const attrs = node.attributes;
Array.from(attrs).forEach(attr=>{
const attrName = attr.name
const exp = attr.value
if(attrName.startsWith('k-')){
const dir = attrName.substring(2);
this[dir] && this[dir](node,exp);
}
})
}else if(this.isInter(node)){//3.文本
this.compileText(node);
}
if(node.childNodes){
this.compile(node);
}
})
}
update(node, exp, dir){
//1 初始化
const fn = this[dir + 'Updater'];
fn && fn(node, this.$vm[exp]);
}
text(node, exp){
this.update(node,exp,'text');
}
textUpdater(node,value){
node.textContent = value;
}
html(node, exp){
this.update(node,exp,'html');
}
htmlUpdater(node,value){
node.innerHTML = value;
}
//是否插值表达式
isInter(node){
return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
}
compileText(node){
this.update(node,RegExp.$1,'text');
}
}
//1.对data选项做响应式处理
//2.编译模板
class KVue{
constructor(options){
this.$options = options;
this.$data = options.data;
observe(this.$data);
proxy(this);
new Compile(options.el, this);
}
}
接下来,重头戏来了,收集依赖,在我们Compile的时候,每发现一个动态的东西,都创建一个watcher,我们都称他为贴身小秘书,他负责管理当前元素的更新,1对1,每一个watcher都有一个dep大管家来管理,需要更新时由dep统一通知,
//监听器 负责依赖收集
class Watcher {
constructor (vm, key, updateFn) {
this.vm = vm;
this.key = key;
this.updateFn = updateFn;
Dep.target = this;
this.vm[this.key];
Dep.target = null;
}
//未来被Dep调用
update(){
// 执行实际更新操作
this.updateFn.call(this.vm, this.vm[this.key]);
}
}
class Dep {
constructor() {
this.deps = [];
}
addDep(dep){
this.deps.push(dep)
}
notify() {
this.deps.forEach(dep => {
dep.update();
})
}
}
//数据响应式实现
function defineReactive(obj,key,val){
observe(val);
const dep = new Dep();
Object.defineProperty(obj,key,{
get(){
console.log('get',key);
Dep.target && dep.addDep(Dep.target);
return val
},
set(newVal){
console.log('set',key);
//保证nweval是新对象 做响应式处理
if(newVal !== val){
observe(newVal);
val = newVal;
dep.notify();
}
}
})
}