03Vue基礎(chǔ)版實(shí)現(xiàn)

Vue 源碼實(shí)現(xiàn)

理解 Vue 的設(shè)計(jì)思想

  • MVVM 模式
image.png

MVVM 框架的三要素:數(shù)據(jù)響應(yīng)式、模板引擎渲染

數(shù)據(jù)響應(yīng)式:監(jiān)聽數(shù)據(jù)變化并在視圖中更新

  • Object.defineProperty()

  • Proxy

模板引擎:提供描述視圖的模板語(yǔ)法

  • 插值:{{}}

  • 指令:v-bind/v-on/v-model/v-for/v-if

渲染:如何將模板轉(zhuǎn)換為 html

  • 模板=>vdom=>dom

數(shù)據(jù)響應(yīng)式原理

數(shù)據(jù)變更能夠響應(yīng)在視圖中,就是數(shù)據(jù)響應(yīng)式,Vue2 中利用 Object.defineProperty() 實(shí)現(xiàn)變更檢測(cè)。

image.png

簡(jiǎn)單實(shí)現(xiàn)

const obj = {}

function defineReactive (obj, key, val) {
  Object.defineProperty(obj, key, {
    get () {
      console.log(`get ${key}:${val}`)
      return val
    },
    set (newVal) {
      if (newVal !== val) {
        console.log(`set ${key}:${newVal}`)
        val = newVal
      }
    }
  })
}

defineReactive(obj, 'foo', 'foo')

obj.foo = 'test foo'

結(jié)合視圖

<!DOCTYPE html>
<html lang="en">

<head></head>

<body>
  <div id="app"></div>
  <script>
    const obj = {}
    function defineReactive(obj, key, val) {
      Object.defineProperty(obj, key, {
        get() {
          console.log(`get ${key}:${val}`);
          return val
        },
        set(newVal) {
          if (newVal !== val) {
            val = newVal
            update()
          }
        }
      })
    }
    defineReactive(obj, 'foo', '')
    obj.foo = new Date().toLocaleTimeString()
    function update() {
      app.innerText = obj.foo
    }
    setInterval(() => {
      obj.foo = new Date().toLocaleTimeString()
    }, 1000);
  </script>
</body>

</html>

遍歷需要響應(yīng)化的對(duì)象

// 對(duì)象響應(yīng)化:遍歷每個(gè)key,定義getter、setter
function observe (obj) {
  if (typeof obj !== 'object' || obj == null) {
    return
  }
  Object.keys(obj).forEach(key => {
    defineReactive(obj, key, obj[key])
  })
}

const obj = { foo: 'foo', bar: 'bar', baz: { a: 1 } }
observe(obj)
obj.foo = 'foooooooooooo'
obj.bar = 'barrrrrrrrrrr'
obj.baz.a = 10 // 嵌套對(duì)象no ok

當(dāng)有嵌套的對(duì)象怎么辦呢?

解決方法:

function defineReactive (obj, key, val) {
  observe(val)    // 遞歸 observer 方法處理嵌套對(duì)象
  Object.defineProperty(obj, key, {
    //...
  })
}

解決賦的值是對(duì)象的情況

obj.baz = {a:1}
obj.baz.a = 10 // no ok
set(newVal) {
    if (newVal !== val) {
        observe(newVal) // 新值是對(duì)象的情況
        notifyUpdate()

如果添加、刪除了新屬性無(wú)法檢測(cè)

obj.dong='林慕'
obj.dong // 并沒有 get 信息

測(cè)試

set(obj,'dong','林慕’)
obj.dong

寫到現(xiàn)在,大家應(yīng)該也發(fā)現(xiàn)了,Object.defineProperty() 不支持檢測(cè)對(duì)象,如果修改對(duì)象的話需要 Vue.set 方法,即上面的 set 方法,當(dāng)然,源碼中的set 會(huì)有一些邊界判斷條件,當(dāng)確定是對(duì)象時(shí),執(zhí)行 defineReactive 方法,將對(duì)象進(jìn)行響應(yīng)式綁定。

思考:Vue 數(shù)組的響應(yīng)化是如何處理的呢?

Vue 在處理數(shù)組時(shí)將可以改變數(shù)組的7個(gè)方法進(jìn)行了重寫,分別是 push、pop、shift、unshift、splice、sort 和 reverse。

重寫數(shù)組的實(shí)現(xiàn)方式如下:

const arrayProto = Array.prototype
// 先克隆一份數(shù)組原型
export const arrayMethods = Object.create(arrayProto)
// 七個(gè)改變數(shù)組的方法
const methodsToPatch = [
  'push',
  'pop',
  'shift',
  'unshift',
  'splice',
  'sort',
  'reverse'
]

// 攔截變化方法并發(fā)出事件
methodsToPatch.forEach(function (method) {
  // 緩存原方法
  const original = arrayProto[method]
  def(arrayMethods, method, function mutator (...args) {
    // 執(zhí)行原始方法
    const result = original.apply(this, args)
    // 額外通知變更,只有這7個(gè)方法有這個(gè)待遇
    const ob = this.__ob__
    let inserted
    switch (method) {
      case 'push':
      case 'unshift':
        inserted = args
        break
      case 'splice':
        inserted = args.slice(2)
        break
    }
    // 對(duì)新加入對(duì)象進(jìn)行響應(yīng)化處理
    if (inserted) ob.observerArray(inserted)
    // 通知改變
    ob.dep.notify()
    return result
  })
})

【注】:最后面的總結(jié)部分貼出的源碼,未包含數(shù)組的響應(yīng)式處理,如需添加,可查看數(shù)組的響應(yīng)式處理有何特殊之處

Vue 中的數(shù)據(jù)響應(yīng)化

目標(biāo)代碼

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <div id="app">
    <p>{{counter}}</p>
  </div>
  <script src="node_modules/vue/dist/vue.js"></script>
  <script>
    const app = new Vue({
      el: '#app',
      data: {
        counter: 1
      },
    })
    setInterval(() => {
      app.counter++
    }, 1000);
  </script>
</body>

</html>

Vue 原理分析

初始化流程

image.png
  • 創(chuàng)建 Vue 實(shí)例對(duì)象 init 過程會(huì)初始化生命周期,初始化事件中心,初始化渲染、執(zhí)行 beforeCreate 周期函數(shù)、初始化 data、props、computed、watcher、執(zhí)行 created 周期函數(shù)等。

  • 初始化后,調(diào)用 $mount 方法對(duì) Vue 實(shí)例進(jìn)行掛載(掛載的核心過程包括模板編譯渲染以及更新三個(gè)過程)。

  • 如果沒有在 Vue 實(shí)例上定義 render 方法而是定義了 template,那么需要經(jīng)歷編譯階段。需要先將 template 字符串編譯成 render function,template 字符串編譯步驟如下 :

    • parse 正則解析 template 字符串形成 AST(抽象語(yǔ)法樹,是源代碼的抽象語(yǔ)法結(jié)構(gòu)的樹狀表現(xiàn)形式)

    • optimize 標(biāo)記靜態(tài)節(jié)點(diǎn)跳過 diff 算法(diff 算法是逐層進(jìn)行比對(duì),只有同層級(jí)的節(jié)點(diǎn)進(jìn)行比對(duì),因此時(shí)間的復(fù)雜度只有 O(n)。

    • generate 將 AST 轉(zhuǎn)化成 render function 字符串

  • 編譯成 render function 后,調(diào)用 $mount 的 mountComponent 方法,先執(zhí)行 beforeMount 鉤子函數(shù),然后核心是實(shí)例化一個(gè)渲染 Watcher,在它的回調(diào)函數(shù)(初始化的時(shí)候執(zhí)行,以及組件實(shí)例中監(jiān)測(cè)到數(shù)據(jù)發(fā)生變化時(shí)執(zhí)行)中調(diào)用 updateComponent 方法(此方法調(diào)用 render 方法生成虛擬 Node,最終調(diào)用 update 方法更新 DOM)。

  • 調(diào)用 render 方法將 render function 渲染成虛擬的 Node(真正的 DOM 元素是非常龐大的,因?yàn)闉g覽器的標(biāo)準(zhǔn)就把 DOM 設(shè)計(jì)的非常復(fù)雜。如果頻繁的去做 DOM 更新,會(huì)產(chǎn)生一定的性能問題,而 Virtual DOM 就是用一個(gè)原生的 JavaScript 對(duì)象去描述一個(gè) DOM 節(jié)點(diǎn),所以它比創(chuàng)建一個(gè) DOM 的代價(jià)要小很多,而且修改屬性也很輕松,還可以做到跨平臺(tái)兼容),render 方法的第一個(gè)參數(shù)是 createElement (或者說(shuō)是 h 函數(shù)),這個(gè)在官方文檔也有說(shuō)明。

  • 生成虛擬 DOM 樹后,需要將虛擬 DOM 樹轉(zhuǎn)化成真實(shí)的 DOM 節(jié)點(diǎn),此時(shí)需要調(diào)用 update 方法,update 方法又會(huì)調(diào)用 pacth 方法把虛擬 DOM 轉(zhuǎn)換成真正的 DOM 節(jié)點(diǎn)。需要注意在圖中忽略了新建真實(shí) DOM 的情況(如果沒有舊的虛擬 Node,那么可以直接通過 createElm 創(chuàng)建真實(shí) DOM 節(jié)點(diǎn)),這里重點(diǎn)分析在已有虛擬Node的情況下,會(huì)通過 sameVnode 判斷當(dāng)前需要更新的 Node 節(jié)點(diǎn)是否和舊的 Node 節(jié)點(diǎn)相同(例如我們?cè)O(shè)置的 key 屬性發(fā)生了變化,那么節(jié)點(diǎn)顯然不同),如果節(jié)點(diǎn)不同那么將舊節(jié)點(diǎn)采用新節(jié)點(diǎn)替換即可,如果相同且存在子節(jié)點(diǎn),需要調(diào)用 patchVNode 方法執(zhí)行 diff 算法更新DOM,從而提升DOM操作的性能。

響應(yīng)式流程

image.png
  1. new Vue() 首先先執(zhí)行初始化,對(duì) data 執(zhí)行響應(yīng)化處理,這個(gè)過程發(fā)生在 Observer 中

  2. 同時(shí)對(duì)模板執(zhí)行編譯,找到其中動(dòng)態(tài)綁定的數(shù)據(jù),從 data 中獲取并初始化視圖,這個(gè)過程發(fā)生在 Compile 中

  3. 同時(shí)定義?個(gè)更新函數(shù)和 Watcher,將來(lái)對(duì)應(yīng)數(shù)據(jù)變化時(shí) Watcher 會(huì)調(diào)用更新函數(shù)

  4. 由于 data 的某個(gè) key 在?個(gè)視圖中可能出現(xiàn)多次,所以每個(gè) key 都需要?個(gè)管家 Dep 來(lái)管理多個(gè) Watcher

  5. 將來(lái) data 中數(shù)據(jù)?旦發(fā)生變化,會(huì)首先找到對(duì)應(yīng)的 Dep,通知所有 Watcher 執(zhí)行更新函數(shù)

涉及類型介紹

  • KVue:框架構(gòu)造函數(shù)

  • Observer:執(zhí)行數(shù)據(jù)響應(yīng)化(分辨數(shù)據(jù)是對(duì)象還是數(shù)組)

  • Compile:編譯模板,初始化視圖,收集依賴(更新函數(shù)、watcher 創(chuàng)建)

  • Watcher:執(zhí)行更新函數(shù)(更新dom)

  • Dep:管理多個(gè) Watcher,批量更新

KVue

框架構(gòu)造函數(shù):執(zhí)行初始化

  • 執(zhí)行初始化,對(duì) data 執(zhí)行響應(yīng)化處理,kvue.js
function observe (obj) {
  if (typeof obj !== 'object' || obj === null) {
    return
  }

  new Observer(obj)
}

function defineReactive (obj, key, val) { }

class KVue {
  constructor(options) {
    this.$options = options
    this.$data = options.data

    observe(this.$data)
  }
}

class Observer {
  constructor(value) {
    this.value = value
    this.walk(value)
  }
  walk (obj) {
    Object.keys(obj).forEach(key => {
      defineReactive(obj, key, obj[key])
    })
  }
}
  • 為 $data 做代理
class KVue {
  constructor(options) {
    // ...
    proxy(this, '$data')
  }
}

function proxy (vm) {
  Object.keys(vm.$data).forEach(key => {
    Object.defineProperty(vm, key, {
      get () {
        return vm.$data[key]
      },
      set (newVal) {
        vm.$data[key] = newVal
      }
    })
  })
}

編譯 —— Compile

image.png

初始化視圖

根據(jù)節(jié)點(diǎn)類型編譯,compile.js

class Compile {
  constructor(el, vm) {
    this.$vm = vm
    this.$el = document.querySelector(el)

    if (this.$el) {
      // 編譯模板
      this.compile(this.$el)
    }
  }
  compile (el) {
    // 遞歸遍歷el
    const childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 判斷其類型
      if (this.isElement(node)) {
        console.log('編譯元素:', node.nodeName)
      } else if (this.isInterpolation(node)) {
        console.log('編譯插值文本:', node.textContent)
      }
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }

  // 元素
  isElement (node) {
    return node.nodeType === 1
  }

  // 判斷是否是插值表達(dá)式{{xxx}}
  isInterpolation (node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
}  

編譯插值,compile.js

compile(el) {
  // ...
    } else if (this.isInerpolation(node)) {
      // console.log("編譯插值?本" + node.textContent);
      this.compileText(node);
    }
  });
}
compileText(node) {
  console.log(RegExp.$1);
  node.textContent = this.$vm[RegExp.$1];
}

編譯元素

compile(el) {
  //...
  if (this.isElement(node)) {
    // console.log("編譯元素" + node.nodeName);
    this.compileElement(node)
  }
}
compileElement(node) {
  let nodeAttrs = node.attributes;
  Array.from(nodeAttrs).forEach(attr => {
    let attrName = attr.name;
    let exp = attr.value;
    if (this.isDirective(attrName)) {
      let dir = attrName.substring(2);
      this[dir] && this[dir](node, exp);
    }
  });
}
isDirective(attr) {
  return attr.indexOf("k-") == 0;
}
text(node, exp) {
  node.textContent = this.$vm[exp];
}

k-html

html(node, exp) {
    node.innerHTML = this.$vm[exp]
}

依賴收集

視圖中會(huì)用到 data 中某 key,這稱為依賴。同?個(gè) key 可能出現(xiàn)多次,每次都需要收集出來(lái)用?個(gè) Watcher 來(lái)維護(hù)它們,此過程稱為依賴收集。多個(gè) Watcher 需要?個(gè) Dep 來(lái)管理,需要更新時(shí)由 Dep 統(tǒng)?通知。

看下面案例,理出思路:

new Vue({
  template:
    `<div>
      <p>{{name1}}</p>
      <p>{{name2}}</p>
      <p>{{name1}}</p>
    <div>`,
  data: {
    name1: 'name1',
    name2: 'name2'
  }
});
image.png

實(shí)現(xiàn)思路

  1. defineReactive 時(shí)為每?個(gè) key 創(chuàng)建?個(gè) Dep 實(shí)例

  2. 初始化視圖時(shí)讀取某個(gè) key,例如 name1,創(chuàng)建?個(gè) watcher1

  3. 由于觸發(fā) name1 的 getter 方法,便將 watcher1添加到 name1 對(duì)應(yīng)的 Dep 中

  4. 當(dāng) name1 更新,setter 觸發(fā)時(shí),便可通過對(duì)應(yīng) Dep 通知其管理所有 Watcher 更新

image.png

創(chuàng)建 Watcher,kvue.js

const watchers = []  // 臨時(shí)用于保存 watcher 測(cè)試用
// 監(jiān)聽器:負(fù)責(zé)更新視圖
class Watcher {
  constructor(vm, key, updateFn) {
    // kvue 實(shí)例
    this.vm = vm;
    // 依賴 key
    this.key = key;
    // 更新函數(shù)
    this.updateFn = updateFn;
    // 臨時(shí)放入 watchers 數(shù)組
    watchers.push(this)
  }
  // 更新
  update () {
    this.updateFn.call(this.vm, this.vm[this.key]);
  }
}

編寫更新函數(shù)、創(chuàng)建 watcher

// 調(diào) update 函數(shù)執(zhí)插值文本賦值
compileText(node) {
  // console.log(RegExp.$1);
  // node.textContent = this.$vm[RegExp.$1];
  this.update(node, RegExp.$1, 'text')
}
text(node, exp) {
  this.update(node, exp, 'text')
}
html(node, exp) {
  this.update(node, exp, 'html')
}
update(node, exp, dir) {
  const fn = this[dir + 'Updater']
  fn && fn(node, this.$vm[exp])
  new Watcher(this.$vm, exp, function (val) {
    fn && fn(node, val)
  })
}
textUpdater(node, val) {
  node.textContent = val;
}
htmlUpdater(node, val) {
  node.innerHTML = val
}

聲明 Dep

class Dep {
  constructor() {
    this.deps = []
  }
  addDep (dep) {
    this.deps.push(dep)
  }
  notify () {
    this.deps.forEach(dep => dep.update());
  }
}

創(chuàng)建 watcher 時(shí)觸發(fā) getter

class Watcher {
  constructor(vm, key, updateFn) {
    Dep.target = this;
    this.vm[this.key];
    Dep.target = null;
  }
}

依賴收集,創(chuàng)建 Dep 實(shí)例

defineReactive(obj, key, val) {
  this.observe(val);
  const dep = new Dep()
  Object.defineProperty(obj, key, {
    get () {
      Dep.target && dep.addDep(Dep.target);
      return val
    },
    set (newVal) {
      if (newVal === val) return
      dep.notify()
    }
  })
}

總結(jié)

以上是一個(gè)簡(jiǎn)單的 Vue 實(shí)現(xiàn),此時(shí) Watcher 監(jiān)聽的粒度太過于精細(xì),導(dǎo)致 Watcher 過多,不需要 vdom。

后面的文章會(huì)寫類似于 Vue2.0 的監(jiān)聽粒度問題,Vue2.0 的監(jiān)聽粒度會(huì)折中,每個(gè)組件一個(gè) Watcher,當(dāng)組件內(nèi)部的值發(fā)生變化時(shí),響應(yīng)式系統(tǒng)已經(jīng)知道是哪個(gè)組件發(fā)生了變化,然后在組件內(nèi)部進(jìn)性 diff 算法的操作,最后更新為最新的節(jié)點(diǎn)信息。

整體代碼

html 部分:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>example</title>
  <script src="./lvue.js" charset="utf-8"></script>
</head>

<body>
  <div id="app">
    <p>{{name}}</p>
    <p k-text="name"></p>
    <p>{{age}}</p>
    <p>
      {{doubleAge}}
    </p>
    <input type="text" k-model="name">
    <button @click="changeName">呵呵</button>
    <div k-html="html"></div>
  </div>
  <script src='./compile.js'></script>
  <script src='./lvue.js'></script>

  <script>
    let k = new KVue({
      el: '#app',
      data: {
        name: 'i am test',
        age: 12,
        html: '<button>這是一個(gè)按鈕</button>'
      },
      created() {
        console.log('開始啦')
        setTimeout(() => {
          this.name = '我是蝸牛'
        }, 1600)
      },
      methods: {
        changeName() {
          this.name = 'changed name'
          this.age = 1
          this.id = 'xxx'
          console.log(1, this)
        }
      }
    })
  </script>
</body>

</html>

Watcher、Dep 部分

class KVue {
  constructor(options) {
    this.$options = options // 掛載實(shí)例
    this.$data = options.data  // 數(shù)據(jù)響應(yīng)化
    // 監(jiān)聽攔截?cái)?shù)據(jù)
    this.observe(this.$data)

    // // 模擬一個(gè) watcher 創(chuàng)建
    // new Watcher()
    // this.$data.a
    // new Watcher()
    // this.$data.c.d
    // // 模擬結(jié)束
    new Compile(options.el, this)
    // created 執(zhí)行
    if (options.created) {
      options.created.call(this)
    }
  }
  observe (value) {
    if (!value || typeof value !== 'object') {
      return
    }
    // 遍歷該對(duì)象
    Object.keys(value).forEach(key => {
      this.defineReactive(value, key, value[key])
      // 代理 data 中的屬性到 vue 實(shí)例上
      this.proxyData(key)
    })
  }
  // 數(shù)據(jù)響應(yīng)化
  defineReactive (obj, key, val) {

    this.observe(val) // 遞歸解決數(shù)據(jù)的嵌套

    const dep = new Dep() // 每執(zhí)行一次 defineReactive,就創(chuàng)建一個(gè) Dep 實(shí)例

    Object.defineProperty(obj, key, { // 數(shù)據(jù)劫持
      configurable: true,
      enumerable: true,
      get () {
        Dep.target && dep.addDep(Dep.target)
        return val
      },
      set (newVal) {
        if (newVal === val) {
          return
        }
        val = newVal
        console.log(`${key}屬性更新了:${val}`)
        dep.notify()
      }
    })
  }
  proxyData (key) {
    Object.defineProperty(this, key, {
      configurable: true,
      enumerable: true,
      get () {
        return this.$data[key]
      },
      set (newVal) {
        this.$data[key] = newVal
      }
    })
  }
}
// Dep:用來(lái)管理 Watcher
class Dep {
  constructor() {
    this.deps = [] // 這里存放若干依賴(watcher),一個(gè)依賴對(duì)應(yīng)一個(gè)屬性,依賴就是視圖上的引用
  }
  addDep (dep) {
    this.deps.push(dep)
  }
  notify () {
    this.deps.forEach(dep => dep.update())
  }
}
// Watcher:小秘書,界面中的一個(gè)依賴對(duì)應(yīng)一個(gè)小秘書
class Watcher {
  constructor(vm, key, cb) {
    this.vm = vm
    this.key = key
    this.cb = cb
    // 將當(dāng)前 Watcher 實(shí)例指定到 Dep 靜態(tài)屬性 target
    Dep.target = this
    this.vm[this.key]  // 觸發(fā) getter、添加依賴
    Dep.target = null
  }
  update () {
    console.log('Watcher監(jiān)聽的屬性更新了')
    this.cb.call(this.vm, this.vm[this.key])
  }
}

Compile 部分

// 用法 new Compile(el,vm)
class Compile {
  constructor(el, vm) {
    // 要遍歷的宿主節(jié)點(diǎn)
    this.$el = document.querySelector(el)
    this.$vm = vm
    // 開始編譯
    if (this.$el) {
      // 轉(zhuǎn)換內(nèi)部?jī)?nèi)容為片段 Fragment
      this.$fragment = this.node2Fragment(this.$el)
      // 執(zhí)行編譯
      this.compile(this.$fragment)
      // 將編譯完的 html 結(jié)果追加至 $el
      this.$el.appendChild(this.$fragment)
    }
  }
  // 將宿主元素中代碼片段拿出來(lái)遍歷,這樣做比較高效
  node2Fragment (el) {
    const frag = document.createDocumentFragment()
    // 將 el 中所有子元素搬家至 frag 中
    let child
    while (child = el.firstChild) {
      frag.appendChild(child)
    }
    return frag
  }
  // 編譯過程
  compile (el) {
    const childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      // 類型判斷
      if (this.isElement(node)) {
        // 元素
        console.log('編譯元素' + node.nodeName)
        // 查找k-/@/:
        const nodeAttrs = node.attributes
        Array.from(nodeAttrs).forEach((attr) => {
          const attrName = attr.name // 屬性名
          const exp = attr.value  // 屬性值
          if (this.isDirective(attrName)) {
            // k-text
            const dir = attrName.substring(2)
            // 執(zhí)行指令
            this[dir] && this[dir](node, this.$vm, exp)
          }
          if (this.isEvent(attrName)) {
            const dir = attrName.substring(1) // @click
            this.eventHandler(node, this.$vm, exp, dir)
          }
        })
      } else if (this.isInterpolation(node)) {
        // 插值文本
        console.log('編譯文本' + node.textContent)
        this.compileText(node)
      }
      // 遞歸子節(jié)點(diǎn)
      if (node.childNodes && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }
  compileText (node) {
    console.log(RegExp.$1)
    // node.textContent = this.$vm.$data[RegExp.$1]
    this.update(node, this.$vm, RegExp.$1, 'text')
  }
  // 更新函數(shù)
  update (node, vm, exp, dir) {
    const updaterFn = this[dir + 'Updater']
    // 初始化
    updaterFn && updaterFn(node, vm[exp])
    // 依賴收集
    new Watcher(vm, exp, function (value) {
      updaterFn && updaterFn(node, value)
    })
  }
  html (node, vm, exp) {
    this.update(node, vm, exp, 'html')
  }
  htmlUpdater (node, value) {
    node.innerHTML = value
  }
  text (node, vm, exp) {
    this.update(node, vm, exp, 'text')
  }
  // 雙綁
  model (node, vm, exp) {
    // 指定 input 的 value 屬性
    this.update(node, vm, exp, 'model')
    // 視圖對(duì)模型響應(yīng)
    node.addEventListener('input', e => {
      vm[exp] = e.target.value
    })
  }
  modelUpdater (node, value) {
    node.value = value
  }
  textUpdater (node, value) {
    node.textContent = value
  }
  // 事件處理器
  eventHandler (node, vm, exp, dir) {
    let fn = vm.$options.methods && vm.$options.methods[exp]
    if (dir && fn) {
      node.addEventListener(dir, fn.bind(vm))
    }
  }
  isDirective (attr) {
    return attr.indexOf('k-') === 0
  }
  isEvent (attr) {
    return attr.indexOf('@') === 0
  }
  isElement (node) {
    return node.nodeType === 1
  }
  // 插值文本
  isInterpolation (node) {
    return node.nodeType === 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容