vue.js是一个mvvm的渐进式js框架。从字面就可以理解为视图关联数据数据关联视图。所以我们要做到的就是把数据关联到视图中,操作数据会重新渲染视图,操作视图会改变数据等。
1.使用中可以知道Vue本身是一个构造函数所以第一步我需要一个构造函数。
class Vue {
constructor(opt) {
this.$el = opt.el;
this.$data = opt.data;
this.computed = opt.computed;
// 专门编译模板的
if (this.$el) {
console.log(this.computed)
for(let key in this.computed){
console.log(key)
Object.defineProperty(this.$data,key,{
get:()=>{
return this.computed[key].call(this)
}
});
}
// 数据劫持
new Obsever(this.$data)
this.proxyVM(this.$data)
// 调用编译器
new Complier(this.$el, this)
}
}
proxyVM(data){
for(let key in data){
Object.defineProperty(this,key,{
get(){
return data[key]
}
})
}
}
}
2.编译器负责把数据挂载在DOM中
// 编译器(把数据挂在DOM种)
class Complier {
constructor(el, vm) {
// 是元素就为元素否则就获取
this.el = this.isElementNode(el) ? el : document.querySelector(el)
this.vm = vm
// 调用文档碎片,把DOM都放到内存中提高性能,文档碎片就相当于一个塑料袋,只操作一次DOM
let frag = this.fragmentNode(this.el)
// 处理文档碎片中的属性
this.complier(frag)
// 把文档碎片放到el中
this.el.append(frag)
}
// 判断是否为元素节点
isElementNode(node) {
// 判断节点类型是否为元素节点
return node.nodeType === 1
}
// 文档碎片
fragmentNode(node) {
// 创建文档碎片
let frag = document.createDocumentFragment()
// DOM操作都是剪切,所以要一直哪到第一个子节点,赋值给变量
let firstChild;
// 循环拿到子节点
while (firstChild = node.firstChild) {
// 把每个子节点放到文档碎片种
frag.append(firstChild)
}
// 把生成好的文档碎片return 出去
return frag
}
// 处理文档碎片中的属性
complier(frag) {
// console.log(frag.childNodes)
// 因为文档碎片中的所有的子节点组成的是一个类数组,所以要转化成一个数组才方便进行操作
let nodes = [...frag.childNodes]
// console.log(nodes)
// 循环所有的子节点
nodes.forEach(node => {
// 判断是否为元素节点
if (this.isElementNode(node)) {
// 获取所有属性
let attrs = [...node.attributes]
console.log(attrs)
// 循环属性
attrs.forEach(attr => {
// 看看有没有v-的属性
// attr.nodeName.startWith('v-')
if(/^v-/.test(attr.nodeName)){
// 拿到属性值
console.dir(attr)
let {nodeValue} = attr
// 当对数据进行赋值该值操作时就需要这个watcher
new Watcher(this.vm,nodeValue,(newVal) => {
node.value = newVal
})
// 在数据里拿到数据
let value = this.vm.$data[nodeValue]
// 当改变值的时候数据也变
node.oninput = (ev) => {
this.vm.$data[nodeValue] = ev.target.value
console.log(this.vm.$data.num)
}
// 赋值
node.value = value;
}
})
}else{ // 当不是元素节点时也就是文本节点
// 判断文本节点是否有{{}},如果有就同步数据
console.dir(node)
// /\{\{(\w+)\}\}/.test(node.nodeValue)
if(/\{\{(\w+)\}\}/.test(node.nodeValue)){
// 拿到文本
let str = node.nodeValue,key;
// console.log(str)
// 替换数据
let nodeVal = str.replace(/\{\{(\w+)\}\}/g,(...arg) => {
key = arg[1]
console.log(key)
// console.log(this.vm.$data)
return this.vm.$data[arg[1]]
})
console.log(nodeVal)
// console.log(key)
new Watcher(this.vm,key,(newVal) => {
node.nodeValue = newVal //把最新的数据赋值给有小胡子的{{}}文本
})
// 给新的数据赋值
// console.dir(nodeVal)
// console.log(node)
node.nodeValue = nodeVal
}
}
})
}
}
3.发布订阅器
class Dep{
constructor(){
this.sub = []
}
addSub(watcher){
this.sub.push(watcher)
console.log(this.sub)
}
notify(){
this.sub.forEach(watcher => {
watcher.updata()
})
}
}
4.观察者模式,通过监控当前值的变化来关联视图,当新值和老值不一样的时候执行回调函数
class Watcher{
// vm -> vm.$data key -> 监控的数据 cb -> 当数据变化的时候执行的回调
constructor(vm,key,cb){
// 当实例化Watcher的时候把实例挂到Dep的属性下方便所有人拿到
Dep.target = this;
this.vm = vm
this.key = key
this.cb = cb
// 一上来就获取一次作为老的值
this.oldVal = this.get()
//把实例用完之后把Dep.target给置空,防止只要是get数据就push Watcher
Dep.target = null;
}
get(){
// 通过参数获取老值
let val = this.vm.$data[this.key]
return val
}
// 数据更新的时候进行对比,当set的时候才拿得到新值
updata(){
let newVal = this.get()
if(this.oldVal !== newVal){
// 当数据改变时调用回调函数
this.cb(newVal)
}
}
}
数据劫持
class Obsever{
constructor(data){
// 调用循环
this.obsever(data)
}
obsever(data){
// 判断有数据并且为对象就循环对象,并且进行数据劫持
if(data && Object.prototype.toString.call(data) === '[object Object]'){
for(let key in data){
this.defineReactive(data,key,data[key])
}
}
console.log(data) // 每个数据都有数据数据劫持
}
// 数据劫持
defineReactive(obj,key,val){
// 进行深度劫持,
if(typeof val === 'object'){
this.obsever(val)
}
let dep = new Dep
Object.defineProperty(obj,key,{
get(){
// 在读取数据的时候进行订阅
// console.log(Dep.target,'1111')
Dep.target && dep.addSub(Dep.target)
// console.log(Dep.target,'1111')
return val
},
// 箭头函数this是上下文作用域
set:(newVal) => {
if(val !== newVal){
// 当最新的val是引用数据类型,保证新值的数据也被数据劫持了
this.obsever(newVal)
val = newVal
// 在
dep.notify()
}
}
})
}
}