今天让我们实现一个简单版的vue吧,文本较长,建议收藏慢慢看,最后求个赞!!!!
一、起步Vue实例函数
让我们在src文件夹下面创建Vue.js文件,这个文件就是我们vueJs的入口文件,我这里直接贴出了代码运行后在控制台能看到和我一样的内容就说明您已经打开了vue神秘的面纱!
主要使用Object.defineProperty()方法去实现数据的双向绑定,不懂的可以参考MDN
class Vue {
constructor(options) {
// 保存属性
this.options = options || {}
this.data = options.data || {}
this.el = options.el && typeof options.el === 'string' ? document.querySelector(options.el) : options.el
this._parxyData()
}
// 便利对象属性注入到vue实例中
_parxyData() {
Object.keys(this.data).forEach(item => {
// 这里传入this,而不是对象是为了将属性注入到实例中
Object.defineProperty(this, item, {
get: () => {
return this.data[item]
},
// newValue是属性的值改变时的赋值
set: (newValue) => {
if(newValue === this.data[item]){
return
}
this.data[item]=newValue
}
})
})
}
}
复制代码
此时我们的控制台应该打印出如下,实例已经被完美的注入了
二、创建观察者函数
观察者函数主要是用来发布数据更新时的通知,当时们更新数据时去通知订阅者更新数据
class Watcher {
constructor(vm,key,cb){
this.vm=vm;
//data中的属性
this.key=key;
//回调函数更新试图
this.cb=cb;
//把watcher对象记录到dep的静态属性target中
Dep.target = this;
//促发get方法,再get方法中调用addsub
this.oldValue=vm[key];
Dep.target=null
}
//当属性变化时更新视图
update(){
let newValue=this.vm[this.key]
if(this.oldValue === newValue){
return
}
this.cb(newValue)
}
}
复制代码
三、创建编译模板函数
模板函数用我们用来解析vue语法,并且获取用户操作的值,告知观察者去通知订阅者更新数据,渲染视图的
class Template {
constructor(vm){
this.el=vm.el
this.vm=vm
this.compile(this.el)
}
//编译模板处理文本模板和元素节点
compile(el){
let childNode = el.childNodes
Array.from(childNode).forEach(node=>{
//处理文本节点
if(this.isTextNode(node)){
this.complieText(node)
}else if(this.isElementNode(node)){
this.compileElement(node)
}
//判断是否有子节点,如果有则递归调用自己
if(node.childNodes&&node.childNodes.length!=0){
this.compile(node)
}
})
}
//编译元素节点处理指令
compileElement(node){
Array.from(node.attributes).forEach(attr=>{
//判断是否时指令
let attrName = attr.name
if(this.isDirective(attrName)){
attrName =attrName.substr(2)
let key = attr.value
this.update(node,key,attrName)
}
})
}
update(node,key,attrName){
let updateFn = this[attrName+'Updater']
updateFn && updateFn.call(this,node,this.vm[key],key)
}
//编译v-text
textUpdater(node,value,key){
node.textContent=value
//创建watcher对象
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
//编译modeo指令
modelUpdater(node,value,key){
node.value=value
//创建watcher对象
new Watcher(this.vm,key,(newValue)=>{
node.value = newValue
})
//为node注册时间实现双向绑定
node.addEventListener('input',()=>{
this.vm[key] = node.value
})
}
//编译文本节点,处理差值表达式
complieText(node){
let reg = /\{\{(.+?)\}\}/
let value = node.textContent
if(reg.test(value)){
let key = RegExp.$1.trim()
node.textContent = value.replace(reg,this.vm[key])
//创建watcher对象
new Watcher(this.vm,key,(newValue)=>{
node.textContent = newValue
})
}
}
//判断元素属性是否时指令
isDirective(attrName){
return attrName.startsWith('v-')
}
//判断节点是否时文本节点
isTextNode(node){
return node.nodeType ===3
}
//判断节点是否时元素节点
isElementNode(node){
return node.nodeType ===1
}
}
复制代码
写到这里完成后我们就可以看到页面数据被渲染了
四、创建发布订阅模式
发布订阅模式
class Dep {
constructor(){
//存储所有的观察者
this.subs = []
}
//添加观察者
addSub(sub){
if(sub && sub.update){
this.subs.push(sub)
}
}
//发送通知
notify(){
this.subs.forEach(sub => {
console.log('我是同志更新',sub)
sub.update()
})
}
}
class Observer {
constructor(data) {
this.kidnap(data)
}
//遍历所有data中的属性
kidnap(data){
//判断是否时对象
if(!data || typeof data != 'object'){
return
}
//遍历对象的所有属性
Object.keys(data).forEach(key=>{
this.defineReacttive(data,key,data[key])
})
}
defineReacttive(obj,key,val){
//创建dep对象
let dep = new Dep()
//如果时对象则转化为响应式数据
this.kidnap(val)
Object.defineProperty(obj,key,{
enumerable:true,
configurable:true,
get:()=>{
//收集依赖,关联上观察者
Dep.target && dep.addSub(Dep.target)
return val
},
set:(newValue)=>{
if(newValue === val){
return
}
val = newValue
this.kidnap(val)
//数据变化了需要发送通知
dep.notify()
}
})
}
}
复制代码
五、试试新技能
到这就基本实现了所有的功能了,让我们试试吧
© 版权声明
文章版权归作者所有,未经允许请勿转载。
THE END