前言
vue最大的特点就是数据响应式,可以实现数据模型和试图的统一,当修改数据时试图中对应位置会相应的改变,其实vue源码中对使用Object.defineProperty对进行了数据劫持和依赖收集。接下来来屡一屡源码是如何实现的?
使用方式
先来看一下熟悉的使用姿势: html模版
<div id="app">
<div>{{name}}</div>
</div>
js
const app = new Vue({
el:"#app",
data:{
name:"tongqi"
},
created(){
setTimeout(() => {
this.name="tong"
}, 1500);
}
})
源码简单实现:
class Vue{
constructor(options){
this.$options=options;
this.$data=options.data;
this.observe(this.$data); // 响应化
new Compiler(options.el,this); // 编译
if (options.created) {
options.created.call(this); // 执行created
}
}
observe(value){
Object.keys(value).forEach(key=>{
this.defineReactive(value,key,value[key]);
})
}
defineReactive(obj,key,val){
this.observe(val); // 递归
const dep = new Dep(); // 创建dep和key对应
Object.defineProperty(obj,key,{
get(){
Dep.target && dep.addDep(Dep.target); // 依赖收集,watcher实例被加入到deps中
return val
},
set(newVal){
if (newVal===val) {
return
}
val=newVal;
dep.notify();
}
})
}
}
// dep -->和data中的每个key对应,管理watcher
class Dep{
constructor(){
this.deps=[]; // 其实是watcher数组
}
addDep(dep){
this.deps.push(dep);
}
notify(){
this.deps.forEach(dep=>{
dep.update();
})
}
}
// watcher类:1.可以读取属性 2.拥有属性更新函数
class Watcher{
constructor(vm,key,cb){
this.vm = vm;
this.key = key;
this.cb =cb;
Dep.target = this; // watcher实例赋值给Dep的target属性
this.vm[this.key];// 触发依赖收集
Dep.target = null;
}
update(){
console.log(`${this.key}更新了`);
this.cb(this.vm[this.key]); // 其实更新是执行了传入的回调函数
}
}
Compiler的定义
// compiler 遍历模版,分析其中那些地方使用到了data中的key,以及事件等指令
// 有一个地方使用就创建一个watcher实例
class Compiler{
constructor(el,vm){
this.$vm=vm;
this.$el=document.querySelector(el);
this.compile(this.$el);
}
compile(el){
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node=>{
if (this.isElement(node)) { // 元素
this.compileElement(node);
this.compile(node)
} else if(this.isInter(node)) { // 插值文本
this.compileText(node);
}
})
}
isElement(node){
return node.nodeType===1;
}
isInter(node){
return node.nodeType===3&&/\{\{(.*)\}\}/.test(node.textContent);
}
compileText(node){
this.update(node,RegExp.$1,'text')
}
compileElement(node){
const nodeAttrs = node.attributes;
Array.from(nodeAttrs).forEach(attr=>{
const attrName = attr.name;
const exp = attr.value;
if (this.isDirective(attrName)) {
const dir = attrName.substring(2);
this[dir] && this[dir](node,exp);
}else if(this.isEvent(attrName)){
const dir = attrName.substring(1);
this.eventHandler(node,exp,dir);// exp 回调函数 dir事件名
}
})
}
isDirective(attr){
return attr.indexOf("k-")==0;
}
// update 函数,负责更新dom,同时创建watcher实例,在两者之间挂钩
update(node,exp,dir){
// 初始化
const updaterFn = this[dir+'Updater'];
updaterFn && updaterFn(node,this.$vm[exp]);
// 更新
new Watcher(this.$vm,exp,function (value) {
updaterFn && updaterFn(node,value);
})
}
textUpdater(node,value){
node.textContent=value;
}
text(node,exp){
this.update(node,exp,'text')
}
htmlUpdater(node,value){
node.innerHTML=value;
}
html(node,exp){
this.update(node,exp,'html')
}
modelUpdater(node,value){
node.value=value;
}
model(node,exp){
this.update(node,exp,"model"); // 赋值
// 事件
node.addEventListener('input',e=>{
this.$vm[exp]=e.target.value;
})
}
isEvent(attr){
return attr.indexOf("@") == 0;
}
eventHandler(node,exp,dir){ // exp 回调函数 dir事件名
const fn = this.$vm.$options.methods && this.$vm.$options.methods[exp];
if (fn) {
node.addEventListener(dir,fn.bind(this.$vm));
}
}
}