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

簡(jiǎn)單實(shí)現(xiàn)vue框架實(shí)例贪嫂,實(shí)現(xiàn)的目的主要看下幾個(gè)知識(shí)點(diǎn)如何進(jìn)行的:

  • Vue工作機(jī)制
  • Vue響應(yīng)式的原理
  • 依賴收集與追蹤
  • 編譯compile

以及一些相關(guān)操作耐版,代碼如下

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>mvue-test-html</title>
</head>
<body>
  <div id="app">
    <p>{{name}}</p>
    <p m-text="name"></p>
    <p>{{age}}</p>

    <input type="text" m-model="name">
    <button @click="changeName">改變</button>
    <div m-html="html"></div>
  </div>

  <script src="./compile.js"></script>
  <script src="./mvue.js"></script>
  <script>
    const app = new MVue({
      el: '#app',
      data: {
        name: 'lily',
        age: 18,
        html: '<p>html-測(cè)試</p>'
      },
      created() {
        setTimeout(() => {
          this.age++
          this.name = '劉翔'
        }, 1000)
      },
      methods: {
        changeName() {
          console.log(8989898)
          this.name = 'lucy'
        }
      }
    })
  </script>

</body>
</html>

如上祠够, 我們需要實(shí)現(xiàn)幾點(diǎn):

  • 根組件初始化,el掛載
  • data實(shí)現(xiàn)數(shù)據(jù)雙向綁定粪牲,視圖層響應(yīng)更新古瓤, 如 this.name = '劉翔' 賦值后視圖層自動(dòng)更新
  • created生命周期簡(jiǎn)單實(shí)現(xiàn)
  • 指令v-text、表達(dá)式{{name}}腺阳、@click落君、v-model雙向數(shù)據(jù)綁定的實(shí)現(xiàn)
  • data、方法等掛載到this上亭引,可以直接調(diào)用

這里分兩塊去處理這些東西绎速,一部分是我們vue實(shí)例的處理,還一部分是編譯到html的處理焙蚓。我這里寫了兩個(gè)文件纹冤,先實(shí)現(xiàn)vue實(shí)例,然后又寫了個(gè)compile的js文件购公。
MVue
這個(gè)里面首先包含一個(gè)vue實(shí)例萌京,在constructor中我們做一些初始化的事情,然后執(zhí)行響應(yīng)式處理宏浩,將data中的值都做好攔截及監(jiān)聽(tīng)處理知残,最后調(diào)用compile渲染出指定的el

observe 這個(gè)方法主要做響應(yīng)式處理,遍歷data中的所有鍵名一一調(diào)用defineReactive進(jìn)行數(shù)據(jù)響應(yīng)式處理比庄,然后代理到this實(shí)例上求妹。

defineReactive 這個(gè)方法主要是給每個(gè)屬性的get乏盐、set定義攔截,做一些攔截處理制恍。同時(shí)生成dep和key一一對(duì)應(yīng)丑勤,對(duì)所有的依賴進(jìn)行管理。

proxyData 顧名思義就是把data中的值代理到實(shí)例上面吧趣,方便this.name這樣去調(diào)用。

這里面還有一個(gè)Dep和Watcher兩個(gè)類耙厚,他們主要做依賴收集及管理强挫,Dep里面會(huì)管理所有的watcher

// 定義KVue構(gòu)造函數(shù)
class MVue {
  constructor(options) {
    // 保存?zhèn)魅氲倪x項(xiàng)
    this.$options = options;

    // 傳入data
    this.$data = options.data;
    // 響應(yīng)式處理
    this.observe(this.$data);

    this.$methods = options.methods;


    new Compile(options.el, this)

    if (options.created) {
      options.created.call(this)
    }
  }

  // 響應(yīng)式處理
  observe(data) {
    if (!data || typeof data !== "object") {
      return;
    }

    // 遍歷data
    Object.keys(data).forEach(key => {
      // 響應(yīng)式處理
      this.defineReactive(data, key, data[key]);
      // 代理data中的屬性
      this.proxyData(key);
    });
  }

  defineReactive(data, key, val) {
    this.observe(val);

    // 定義一個(gè)Dep
    const dep = new Dep(); // 每個(gè)dep實(shí)例都與key值一一對(duì)應(yīng)
    // 給obj的每個(gè)key定義攔截
    Object.defineProperty(data, key, {
      get() {
        // 依賴收集
        Dep.target && dep.addDep(Dep.target);
        return val;
      },
      set(v) {
        if (v !== val) {
          val = v;
          dep.notify();
        }
      }
    });
  }

  // 講$data中的屬性代理到實(shí)例上
  proxyData(key) {
    Object.defineProperty(this, key, {
      get() {
        return this.$data[key];
      },
      set(v) {
        this.$data[key] = v;
      }
    });
  }
}

// 創(chuàng)建dep:管理所有的watcher
class Dep {
  constructor() {
    // 存儲(chǔ)所有的依賴
    this.deps = [];
  }

  addDep(dep) {
    this.deps.push(dep);
  }

  // 通知更新
  notify() {
    this.deps.forEach(dep => dep.update());
  }
}

// 創(chuàng)建watcher: 保存data中的數(shù)值和頁(yè)面中的掛鉤關(guān)系
class Watcher {
  constructor(vm, key, cb) {
    // 創(chuàng)建實(shí)例時(shí)立刻將該實(shí)例指向Dep.target便于依賴收集
    this.vm = vm;
    this.cb = cb;
    this.key = key;

    // 觸發(fā)依賴收集
    Dep.target = this;
    this.vm[this.key]; // 觸發(fā)依賴收集
    Dep.target = null;
  }

  // 更新
  update() {
    console.log(this.key + "更新了");
    this.cb.call(this.vm, this.vm[this.key])
  }
}

compile 主要做一些指令等一系列操作的處理,包括實(shí)例中的el元素經(jīng)過(guò)處理后掛載到dom上等操作

這里主要使用了正則去匹配相應(yīng)的表達(dá)式薛躬、指令等俯渤,然后做出相關(guān)操作處理。具體看代碼操作即可型宝。

// 遍歷dom八匠,解析指令和插值表達(dá)式
class Compile {
  // el 待編譯的模板, vm-MVue實(shí)例
  constructor(el, vm) {
    this.$vm = vm;
    this.$el = document.querySelector(el);

    // 把模版中的內(nèi)容移到片段操作
    this.$fragment = this.node2Fragment(this.$el);
    // 執(zhí)行編譯
    this.compile(this.$fragment)
    // 放回至el中
    this.$el.appendChild(this.$fragment)

  }

  node2Fragment(el) {
    // 創(chuàng)建片段
    const fragment = document.createDocumentFragment();

    let child;
    while(child = el.firstChild) {
      fragment.appendChild(child)
    }
    return fragment;
  }

  compile(el) {
    const childNodes = el.childNodes
    Array.from(childNodes).forEach(node => {
      if (node.nodeType == 1) {
        // 元素
        // console.log('編譯元素' + node.nodeName)
        this.compileEle(node)
      } else if (this.isInter(node)) {
        // 只關(guān)心{{XXX}}
        // console.log('編譯插值文本' + node.textContent)
        this.compileText(node)
      }
      // 遞歸子節(jié)點(diǎn)
      if (node.children && node.childNodes.length > 0) {
        this.compile(node)
      }
    })
  }

  isInter(node) {
    return node.nodeType == 3 && /\{\{(.*)\}\}/.test(node.textContent)
  }

  compileEle(node) {
    const nodeAttr = node.attributes;
    // 匹配 m-xxx
    Array.from(nodeAttr).forEach(attr => {
      // 規(guī)定 m-xxx="yyyy"
      const attrName = attr.name;
      const exp = attr.value;
      if (attrName.indexOf('m-') == 0) {
        // 指令
        const dir = attrName.substring(2);
        // 執(zhí)行
        this[dir] && this[dir](node, this.$vm, exp)
      } else if (attrName.indexOf('@') == 0) {
        // 事件
        const method = attrName.substring(1)
        this.addEvent(node, this.$vm, exp, method)
      }
    })
  }

  // 文本替換
  compileText(node) {
    // console.log(RegExp.$1);
    // console.log(this.$vm[RegExp.$1])
    // 表達(dá)式
    const exp = RegExp.$1
    this.update(node, this.$vm, exp, 'text')
  }

  update(node, vm, exp, type) {
    const updater = this[type + 'Updater']
    updater && updater(node, vm[exp])
    new Watcher(vm, exp, function(val) {
      updater && updater(node, val)
    })
  }

  textUpdater(node, val) {
    node.textContent = val
  }

  htmlUpdater(node, val) {
    node.innerHTML = val
  }

  modelUpdater(node, val) {
    node.value = val
  }

  text(node, vm, exp) {
    this.update(node, vm, exp, 'text')
  }

  html(node, vm, exp) {
    this.update(node, vm, exp, 'html')
  }

  model(node, vm, exp) {
    this.update(node, vm, exp, 'model')
    node.addEventListener('input', (e) => {
      vm[exp] = e.target.value
    })
  }

  addEvent(node, vm, exp, method) {
    const fn = vm.$options.methods && vm.$options.methods[exp]
    node.addEventListener(method, fn.bind(vm))
  }
}

image.png

這只是一個(gè)非常簡(jiǎn)單的vue模仿趴酣,距離框架真正處理差了十萬(wàn)八千里梨树,不過(guò)里面的一些思路還是比較溫和的,僅供vue框架源碼初探岖寞,后面會(huì)具體分析vue的源碼抡四。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市仗谆,隨后出現(xiàn)的幾起案子指巡,更是在濱河造成了極大的恐慌,老刑警劉巖隶垮,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件藻雪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡狸吞,警方通過(guò)查閱死者的電腦和手機(jī)勉耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)捷绒,“玉大人瑰排,你說(shuō)我怎么就攤上這事∨龋” “怎么了椭住?”我有些...
    開(kāi)封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)字逗。 經(jīng)常有香客問(wèn)我京郑,道長(zhǎng)宅广,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任些举,我火速辦了婚禮跟狱,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘户魏。我一直安慰自己驶臊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布叼丑。 她就那樣靜靜地躺著关翎,像睡著了一般。 火紅的嫁衣襯著肌膚如雪鸠信。 梳的紋絲不亂的頭發(fā)上纵寝,一...
    開(kāi)封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音星立,去河邊找鬼爽茴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛绰垂,可吹牛的內(nèi)容都是我干的室奏。 我是一名探鬼主播,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼辕坝,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼窍奋!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起酱畅,我...
    開(kāi)封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤琳袄,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后纺酸,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體窖逗,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年餐蔬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碎紊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡樊诺,死狀恐怖仗考,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情词爬,我是刑警寧澤秃嗜,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響锅锨,放射性物質(zhì)發(fā)生泄漏叽赊。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一必搞、第九天 我趴在偏房一處隱蔽的房頂上張望必指。 院中可真熱鬧,春花似錦恕洲、人聲如沸塔橡。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)谱邪。三九已至,卻和暖如春庶诡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咆课。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工末誓, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人书蚪。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓喇澡,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親殊校。 傳聞我的和親對(duì)象是個(gè)殘疾皇子晴玖,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354