前言
源码并非遥不可及,vue源码还是较简单易懂。纸上得来终觉浅,实操敲几遍,对每个类加深理解,相信对vue的理解会更上一层楼,一起加油进步鸭!
1、vue中几个核心类
- 1.Observe数据监听器:对data里的属性添加getter/setter,进行依赖收集以及派发更新。
- 2.Dep消息订阅器:用于收集当前响应式对象的依赖关系,每个响应式对象都有一个dep实例。dep.subs=watcher[],当数据发生变化时,触发dep.notify,该方法会遍历subs数组,调用每一个watcher的update方法。
- 3.Watcher观察者:作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知Watcher类有多种,比如computed watcher,user watcher(自己在watch里定义的需要监听数据变化的watcher)。
- 4.Compile指令解析器:它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
依赖收集
- initstate,对computed属性初始化时,触发computed watcher依赖收集
- initstate,对监听属性初始化时,触发user watcher依赖收集
- render,会触发render依赖收集
派发更新 Object.defineProperty
- 1.组件中对相应的数据发生了修改,会触发setter
- 2.dep.notify
- 3.遍历subs数组,调用每一个watcher的update方法
2、结构
index.html
index.js
vue.js
observe.js
dep.js
compiler.js
watch.js
3、vue.js
/*
* @Author: Mx
* @Date: 2022-01-18 13:12:27
* @Description: Vue构造函数 配置参数等
*/
import Observer from "./observer.js";
import Complier from "./compiler.js";
export default class Vue {
constructor (options = {}){
this.$options = options
this.$data = options.data
this.$methods = options.methods
// 初始化el 对传进来的根元素进行判断
this.initRootElement(options)
// 利用Object.defineProperty将data里的属性注入到vue实例中
this._proxyData(this.$data)
// 实例化observer对象 监听数据变化
new Observer(this.$data)
// 实例化Complier对象 解析指令和模板
new Complier(this)
}
/**
* 获取根元素 并存储到vue实现 检查传入的el是否合规
*/
initRootElement(options){
if(typeof options.el === 'string'){
this.$el = document.querySelector(options.el)
}else if (options.el instanceof HTMLElement){
//真实的元素
this.$el = options.el
}
if(!this.$el){
throw new Error("el不合法")
}
}
_proxyData(data){
//通过Object.defineProperty将data里的属性绑定到vue实例上
Object.keys(data).forEach(key=>{
Object.defineProperty(this,key,{
//表示可以被枚举,也就是可以被循环
enumerable:true,
//表示可以进行相应配置
configurable:true,
get(){
return data[key]
},
set(newValue){
if(data[key] === newValue){
return
}
data[key] = newValue
}
})
})
}
//_proxyData函数用于将属性绑定在vue实例上,而不是用于进行依赖的收集和派发更新,所以不用递归的对每一个值进行劫持
}
4、observe.js
/*
* @Author: Mx
* @Date: 2022-01-31 09:55:30
* @Description: 数据监听器Observer
* 依赖收集和派发更新就是在这里实现
*/
import Dep from "./dep.js"
export default class Observer {
constructor(data){
this.traverse(data)
}
/**递归遍历data里的所有属性 */
traverse(obj){
console.log('obj',obj);
if(!obj || typeof obj !== 'object'){
return
}
//遍历监听数据
Object.keys(obj).forEach(data => {
this.defineReactive(obj,data,obj[data])
});
}
/**给传入数据设置setter getter */
defineReactive(obj,data,value){
//可能又是一个object
this.traverse(value)
//实例化dep
let dep = new Dep()
//保存this
const that = this
Object.defineProperty(obj,data,{
enumerable:true,
configurable:true,
get(){
//在这里添加依赖 拿到绑定dep身上的watcher
Dep.targer && dep.addsubs(Dep.targer)
//这一步不能够用obj[data],会造成循环的get这个值
return value
},
set(newValue){
if(value === newValue){
return
}
value = newValue
//可能是一个object
that.traverse(newValue)
//派发更新
dep.notify()
}
})
}
}
5、dep.js
/*
* @Author: Mx
* @Date: 2022-01-31 09:55:30
* @Description: Dep
* 发布订阅模式 存储所有的观察者 每个watcher都有一个update方法 通知subs里每个watcher实例 触发update
*/
export default class Dep {
constructor(){
//储存所有的观察者
this.subs = []
}
/** 添加观察者 */
addsubs(watcher){
if(watcher && watcher.unpdate){
this.subs.push(watcher)
}
}
/** 发送通知 */
notify(){
//遍历subs数组,调用每一个watcher的updatae方法
this.subs.forEach(watcher=>{
watcher.update()
})
}
}
//dep类在什么时候实例化?在哪里addSubs?
// => Observer遍历各个属性的时候实例化
// => get 收集依赖 addSubs
//dep类在什么时候调用notify方法?
// => set 派发更新 notify
6、watcher.js
/*
* @Author: Mx
* @Date: 2022-01-31 09:55:30
* @Description:
* Watcher 订阅者, 作为连接 Observer 和 Compile 的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应回调函数
*/
import Dep from "./dep.js";
export default class Watcher {
/**
* @description:
* @param {*} vm vue实例
* @param {*} key data属性名
* @param {*} callback 回调函数
*/
constructor(vm,key,cb){
this.vm = vm
this.key = key
this.callback = cb
//为什么要往dep.target上添加watcher实例
Dep.target = this;
//拿到旧值
//同时注意一点,在这里会触发变量的get方法
this.oldValue = vm[key]
Dep.target = null;
}
/**数据发生变化更新视图 */
update() {
let newValue = this.vm[this.key]
if(this.oldValue === newValue){
return
}
this.cb(newValue)
}
}
//为什么要往dep.target上添加watcher实例?是为了能够将在同一时间只维持一份watcher,因为在computed里,watch里用到时,会添加多个watcher,容易造成数据紊乱,
// 所以在一个时间里只有一个watcher,保证watcher是正常添加的
7、complier.js
/*
* @Author: Mx
* @Date: 2022-01-31 09:55:30
* @Description: 指令解析器,它的作用对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数
*/
import Watcher from "./watcher.js";
export default class Compiler{
constructor(vm){
this.vm = vm
this.el = vm.$el
this.methods = vm.$methods
this.complie(vm.$el)
}
/** 编译模板 对模板进行替换*/
complie(el){
//拿到元素的子节点,一个类数组
const childNodes = el.childNodes;
Array.from(childNodes).forEach(node =>{
console.log('node',node);
//判断节点类型
if(this.isTextNode(node)){
//文本节点
this.complieText(node)
}else if(this.isElementType(node)){
//元素节点
this.compileElement(node);
}
if(node.childNodes && node.childNodes.length > 0){
this.complie(node)
}
})
}
//文本节点编译
complieText(node){
//{{msg}} msg hello mx
const reg = /\{\{(.+?)\}\}/
const value = node.textContent //hello mx
if(reg.test(value)){
const key = RegExp.$1.trim(); // 拿到msg
console.log('key',key);
node.textContent = value.replace(reg,this.vm[key])
//数据需要动态改变,所以需要依赖收集
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
}
//元素节点编译
compileElement(node){
console.log('node11',node,node.attributes);
if(node.attributes.length > 0){
Array.from(node.attributes).forEach(attr=>{
//属性名
console.log('attrObj',attr);
const attrName = attr.name
console.log('attrName',attrName);
//判断是否是v-开头
if(this.isVStartsWith(attrName)){
//特殊判断是:号 例如v-on:click
const directiveName = attrName.indexOf(':') > -1 ? attrName.substr(5):attrName.substr(2)
//拿到值 v-model='msg'的msg
console.log('directiveName',directiveName);
let key = attr.value
this.update(node,key,directiveName)
}
})
}
}
// 进行拼接 找到对应函数
update(node,key,directiveName){
//v-model v-text v-html v-on:click
const updaterFn = this[directiveName+'Updater']
updaterFn && updaterFn.call(this,node,this.vm[key],key,directiveName)
}
//v-text
textUpdater(node,value,key){
console.log('textUpdater');
node.textContent = value
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
//v-model
modelUpdater(node,value,key){
console.log('modelUpdater');
//对应于input
node.value = value
new Watcher(this.vm,key,(newValue)=>{
node.value = newValue
})
node.addEventListener('input',()=>{
//触发setter
this.vm[key] = node.value
})
}
//v-html
htmlUpdater(node,value,key){
console.log('htmlUpdater',value);
console.log('node',node);
node.innerHTML = value
new Watcher(this.vm,key,(newValue)=>{
node.innerHTML = newValue
})
}
//v:on-click
clickUpdater(node,value,key,directiveName){
node.addEventListener(directiveName,this.vm.$methods[key])
}
//判断文本节点
isTextNode(node){
return node.nodeType === 3;
}
//判断元素节点
isElementType(node){
return node.nodeType === 1;
}
//判断v-开头
isVStartsWith(attr){
console.log('attr',attr);
return attr.startsWith('v-');
}
}
8、完整代码
9、❤️感谢阅读
- 如果本文对你有帮助,不要吝啬你的赞哟,你的「赞」是我前行的动力。
- 欢迎关注公众号 【冥想侃前端】 一起学习进步。