1. 判断元素

    vue 本质上是使用 HTML 字符串作为模板,将字符串模板转化为 AST(抽象语法树) ,再转换成 VNode

    • 模板 -> AST
    • AST -> VNode
    • VNode -> DOM

    最消耗性能的是字符串解析(模板 -> AST)

    小例子: let s = “1 + 2 (3 + 4 (5 + 6))” 得到结果
    建议:将字符串表达式转化为波兰式表达式,使用栈结构来运算

    1

    vue源码如何区分HTML标签和自定义组件?

    vue源码把所有可用的HTML标签都存起来

    使用柯里化操作将需要遍历的 HTML 标签存储起来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    function makeMap(keys) {
    const obj = Object.create(null)
    keys.map(item => {
    obj[item] = true
    return item
    })
    return function(key) {
    return !!obj[key]
    }
    }
    let htmlkey = ['p','div','a','ul','li','ol','dl']
    const htmlMap = makeMap(htmlkey)
    htmlMap('div') // true
    htmlMap('img') // false

    函数柯里化

  2. 虚拟 DOM 的 render 方法

    vue项目 模板转化为抽象语法树

    • 页面一开始加载需要渲染
    • 每一个属性(响应式)数据在发生变化时要渲染
    • watch,computed
      每次需要渲染的时候,模板就会被解析一次

    render 的作用是将虚拟DOM 转化为真正的DOM

    • 虚拟DOM 可以降级理解为AST
    • 一个项目运行的时候,模板是不会变得,就表示AST是不会变得
    • 将虚拟DOM 缓存起来,生成一个函数,只需要传入参数,就可以的到真正的DOM
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      <div id="app">
      <div>
      <div>
      {{name}} {{behavior}}
      </div>
      <div>
      {{other.name}} {{other.behavior.name}} {{other.behavior.time}}
      </div>
      </div>
      <p>人员:{{name}}</p>
      <p>行为:{{behavior}}</p>
      </div>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      151
      152
      153
      154
      155
      156
      157
      158
      159
      160
      161
      162
      163
      164
      165
      166
      167
      168
      169
      170
      171
      172
      173
      function JGVue(options) {
      this._data = options.data
      this._template = document.querySelector(options.el) // vue 中这里是DOM
      this.mount() // 挂载
      }

      JGVue.prototype.mount = function() {
      // 需要提供一个render方法生成虚拟dom
      this.render = this.createRenderFn()
      this.mountComponent()
      }
      JGVue.prototype.mountComponent = function() {
      // 执行mountComponent
      let mount = () => {
      // 调用update方法渲染dom
      this.update(this.render())
      }
      mount.call(this) // 本质上应该交给watcher来调用,此操作并不会改变this的指向
      }
      /*
      * 在真正的 vue 中使用了 二次提交的设计结构
      * 1 在页面中 DOM 和虚拟 DOM 是一一对应的关系
      * 数据发生变化的时候,就会通过 diff 方法 对比需要更新的虚拟 DOM 节点,更新 DOM
      * 凡是解析就会涉及到 AST
      * 2 现有AST和数据生成VNode
      * 3 将新的VNode 和旧的 VNode 比较(diff)更新(update)
      */
      // 生成render 函数,缓存抽象语法树(使用虚拟DOM来模拟)
      JGVue.prototype.createRenderFn = function() {
      // 获取抽象语法树
      let ast = getVnode(this._template)
      console.log(ast)
      // Vue: 将AST + data => VNode
      return function render() {
      // 将带坑的VNode (ast) 转化为真正的带数据的VNode
      let _temp = combine(ast, this._data)
      console.log(_temp)
      return _temp
      }
      }
      // 将虚拟DOM渲染到页面中 diff算法就在这里
      JGVue.prototype.update = function(data) {
      let dom = parseVnode(data)
      let app = document.getElementById("app")
      let parent = app.parentNode
      parent.replaceChild(dom, app)
      }
      // node 为页面中真实的 DOM 节点
      // 使用递归的方法提取出 DOM 节点中所有的子孙节点
      // Vue源码中使用 栈 的方式
      // 由HTML DOM -> VNode, 将这个函数当做compiler函数
      function getVnode(node) {
      let nodeType = node.nodeType
      let _vnode = null
      // 判断nodeType类型
      if(nodeType === 1) { // 元素节点
      let nodeName = node.nodeName
      // 获取 node 元素的 所有 attribute 属性, 是一个伪数组,需要转换成 对象
      let attrs = node.attributes
      let _attrs = Object.create(null)
      for(let i = 0; i < attrs.length; i++) {
      _attrs[attrs[i].nodeName] = attrs[i].nodeValue
      }
      _vnode = new VNode(nodeName, _attrs, undefined, nodeType)
      // 然后处理元素节点的所有自节点
      let childNodes = node.childNodes
      for(let i = 0; i < childNodes.length; i++) {
      _vnode.appendCild(getVnode(childNodes[i]))
      }
      } else if(nodeType === 3) { // 文本节点
      // 文本节点没有 tag 属性 data属性
      _vnode = new VNode(undefined, undefined, node.nodeValue, nodeType)
      }
      return _vnode
      }
      // 将带有‘{{}}’的虚拟DOM和传入的data数据结合起来,生成带有数据的DOM树
      function combine(node, data) {
      let _type = node.type
      let _data = node.data
      let _value = node.value
      let _tag = node.tag
      let _children = node.children
      let _vnode = null
      // 判断nodeType类型
      if(_type === 1) { // 元素节点
      _vnode = new VNode(_tag, _data, _value, _type)
      // 然后处理元素节点的所有自节点
      _children.map(item => _vnode.children.push(combine(item, data)))
      } else if(_type === 3) { // 文本节点
      _value = _value.replace(curlyRE, (_, g) => {
      /*
      * 第一个参数是正则表达式匹配到的东西
      * 第二个参数及以后是第 n-1 个分组
      */
      const split = createSplitAttribute(g.trim())
      return split(data)
      });
      // 文本节点没有 tag 属性 data属性
      _vnode = new VNode(_tag, _data, _value, _type)
      }
      return _vnode
      }
      // 将虚拟dom 转化为真实DOM
      function parseVnode(vnode) {
      const {tag, data, type, value, children} = vnode
      let dom = null
      if(type === 1) {
      dom = document.createElement(tag)
      for(let key in data) {
      dom.setAttribute(key, data[key])
      }
      dom.nodeType = type
      for(let i = 0; i < children.length; i++) {
      dom.appendChild(parseVnode(children[i]))
      }
      } else if(type === 3) {
      dom = document.createTextNode(tag)
      dom.nodeValue = value
      dom.nodeType = type
      }
      return dom
      }
      // 使用柯里化 存储需要替换的内容
      function createSplitAttribute(g) {
      let paths = g.split('.')
      return function splitAttribute(data) {
      let res = data
      // g.split('.').map(item => {
      // res = res[item]
      // })
      //2.
      let prop
      while(prop = paths.shift()) {
      // 当对一个变量赋值对象时,是对象的引用,变量中的某个值发生变化,对象中对应的值也会发生变化
      // 但是给变量重新赋值对象,并不会造成原先对象的值发生变化,因为此时变量的引用已经发生变化,不在指向源对象
      console.log(res, prop)
      res = res[prop]
      }
      return res
      }
      }
      const options = {
      el: '#app',
      data: {
      name: '优·库里伍德·海尔赛兹',
      behavior: '吃橘子',
      other: {
      name: '相川步',
      behavior: {
      name: '约的人',
      time: '12m'
      }
      }
      }
      }
      const curlyRE = /\{\{(.+?)\}\}/g
      class VNode {
      /*
      * tag: node.nodeValue
      * data: node.attributes => obj => {node.attribute.nodeName: node.attribute.nodeValue}
      */
      constructor(tag, data, value, type) {
      this.tag = tag && tag.toLowerCase()
      this.data = data
      this.value = value
      this.type = type
      this.children = []
      }
      appendCild(vnode) {
      this.children.push(vnode)
      }
      }
      let app = new JGVue(options)