(五) -compiler——编译模板:编译解析模板中的差值表达式和指令,从而实现数据变化更新视图,视图变化更新数据

compiler——编译模板:编译解析模板中的差值表达式和指令,从而实现数据变化更新视图,视图变化更新数据

vue.js文件的作用——把data中的成员挂载到vue实例上,并设置setter和getter

observer.js文件的作用——数据劫持——即:监听数据的变化
1.把$data中的属性设置为getter/setter
2.当数据发生改变的时候,要发送通知(发送消息)

数据劫持也是在vue.js中进行调用,只是具体实现定义到一个文件中,保证vue.js文件中的代码是少的;

各个文件各司其职:vue.js是初始化,是个入口

    // 数据劫持,就是把data中的成员设置为setter/getter
    new Observer(this.$data)

    // 编译模板,就是编译模板中的差值表达式和指令
    new Compiler(this)

o n on和 emit 必须是同一个对象,才可以使用

compiler.js

// 编译模板
// 编译模板中的差值表达式和指令

function Compiler(vm) {
    this.vm = vm

    // 开始编译
    this.compile(vm.$el)
}

// 编译模板
Compiler.prototype.compile = function(el) {
    // Array.from传入一个伪数组,返回一个数组
    // 遍历el中的所有子节点
    Array.from(el.childNodes).forEach(node => {
        // 判断节点的类型
        if (this.isElementNode(node)) {
            // 元素节点
            this.compileElement(node)

            // 如果是元素节点,继续遍历该元素节点的子节点
            this.compile(node)
        } else if (this.isTextNode(node)) {
            // 文本节点
            this.compileTextNode(node)
        }
    })
}

// 判断节点是否是元素节点
Compiler.prototype.isElementNode = function(node) {
        return node.nodeType === 1
    }
    // 判断节点是否是文本节点
Compiler.prototype.isTextNode = function(node) {
    return node.nodeType === 3
}

// 编译元素节点
Compiler.prototype.compileElement = function(node) {
        // 遍历元素中的所有属性节点 node.attributes
        Array.from(node.attributes).forEach(attr => {
            // attr 属性节点,对象
            const key = attr.name

            // 判断属性的名字是否是指令
            if (this.isDirective(key)) {
                // 判断指令的类型
                // v-model
                if (key === 'v-model') {
                    // 如果当前的属性是v-model,获取属性的值 v-model="name"
                    // value -> name
                    const value = attr.value
                    node.value = this.vm[value]

                    // 订阅事件,当属性的值发生变化要更新视图,即给node.value赋值
                    em.$on(value, () => {
                        node.value = this.vm[value]
                    })

                    // 当视图变化,更新数据
                    node.oninput = () => {
                        // 把文本框中的数据,设置给data
                        this.vm[value] = node.value

                        // this.vm.name = node.value
                    }

                } else if (key === 'v-text') {
                    // v-text
                    const value = attr.value
                    node.textContent = this.vm[value]

                    // 当数据变化的时候更新视图
                    em.$on(value, () => {
                        node.textContent = this.vm[value]
                    })
                }
            }
        })
    }
    // 编译文本节点
Compiler.prototype.compileTextNode = function(node) {
        // 获取文本节点中的内容
        const txt = node.textContent
            // 判断文本节点中是否有差值表达式
        const reg = /\{\{(.+)\}\}/
        if (reg.test(txt)) {
            // 获取到差值表达式中的key
            // {{ name }} ====> name
            const key = RegExp.$1.trim()
                // data[key] 值
            const value = this.vm[key]
            node.textContent = txt.replace(reg, value)

            // 当值发生变化,重新更新视图
            em.$on(key, () => {
                node.textContent = this.vm[key]
            })
        }
    }
    // 判断是否是指令  v-开头
Compiler.prototype.isDirective = function(attrName) {
    return attrName.startsWith('v-')
}

eventEmitter.js

function EventEmitter () {
  this.subs = {}
}

// 注册事件
// 1. 事件类型  2. 事件处理函数
EventEmitter.prototype.$on = function (eventType, handler) {
  this.subs[eventType] = this.subs[eventType] || []
  this.subs[eventType].push(handler)
}

// 触发事件
// 在function (eventType, ...args) 此时的args是剩余参数
// 剩余参数在参数列表的最后,args是数组
EventEmitter.prototype.$emit = function (eventType, ...args) {
  if (this.subs[eventType]) {
    // [fn, fn1]
    this.subs[eventType].forEach(handler => {
      // 此时的...args 是展开运算符
      // 此时的this就是em对象
      handler.call(this, ...args)
    })
  }
}

// 创建一个em的对象,让observer和compiler去使用
// observer 发布通知
// compiler 订阅事件
const em = new EventEmitter()

编译

获取节点

function Compiler (vm) {
  this.vm = vm
  this.compile(this.vm.$el)
}
Compiler.prototype.compileText = function (node) {
  console.log('text', node)
}

Compiler.prototype.compileElement = function (node) {
  console.log('element', node)
}
Compiler.prototype.compile = function (el) {
  // 获取所有子节点
  el.childNodes.forEach((node) => {
    switch (node.nodeType) {
      case 1:
        // nodeType === 1 元素节点
        this.compileElement(node)
        // 如果是元素节点,递归调用
        this.compile(node)
        break
      case 3:
        // nodeType === 3 文本节点
        this.compileText(node)
        break
    }
  })
}

编译文本节点

Compiler.prototype.compileText = function (node) {
  const reg = /\{\{(.+)\}\}/
  // 判断是否匹配正则
  if (reg.test(node.nodeValue)) {
    // 获取正则匹配第一个分组的内容
    const key = RegExp.$1.trim()
    // 把正则匹配的结果替换成,指定的数据
    node.nodeValue = this.vm[key]
  }
}

编译元素节点

Compiler.prototype.compileElement = function (node) {
  // 所有属性节点
  // console.dir(node)
  Array.from(node.attributes).forEach((attr) => {
    let { name, value } = attr
    name = name.toLocaleLowerCase()
    // 判断是否以 v- 开头
    if (!name.startsWith('v-')) {
      return
    }
    switch (name) {
      case 'v-model':
        node.value = this.vm[value]
        break
    }
  })
}

监听数据变化

const em = new EventEmitter()

// 在处理文本节点的时候,如果 data 中的值变化了,给本文重新赋值
em.$on(key, () => {
  node.nodeValue = this.vm[key]
})

// 在处理 v-model 的时候,如果 data 中的值变化了,重新给元素赋值
em.$on(value, () => {
  node.value = this.vm[value]
})
// 当文本框的值改变把文本框的值赋给 data
node.oninput = () => {
  this.vm[value] = node.value
}

// 在属性的 set 方法中,触发事件
em.$emit(key)

推荐阅读

发布了199 篇原创文章 · 获赞 1 · 访问量 5471

猜你喜欢

转载自blog.csdn.net/weixin_44867717/article/details/104899830