Vue實(shí)現(xiàn)原理

Vue是目前炙手可熱的JS框架习瑰,作為一個(gè)視圖庫(kù),最重要的功能當(dāng)然是數(shù)據(jù)綁定了抡笼,數(shù)據(jù)變化苏揣,模板變化。

接下來(lái)讓我看看Vue實(shí)現(xiàn)的大致原理是怎樣的推姻。

這是我們的模板平匈。

<div id="app">
  <input type="text" v-model="name">
  <p>
    {{ name }}
  </p>
  <strong>{{ name }}</strong>
</div>

像下面這樣實(shí)例化

var vm = new Vue({
  el: "#app",
  data: {
    name: 'ok'
  }
})

我們先不討論v-model指令。

我們初始化實(shí)例時(shí)傳入一個(gè)data。data的name屬性對(duì)應(yīng)模板中的name增炭。

我們要實(shí)現(xiàn)的功能點(diǎn)有

  • vm實(shí)例代理data上的屬性忍燥。
  • 在vm對(duì)象內(nèi)部,我們用this指代vm隙姿。當(dāng)在vm內(nèi)部this.name 被賦了一個(gè)新的值時(shí)梅垄,模板中的name也會(huì)同步變化。

這是單向數(shù)據(jù)綁定输玷。

然后再考慮v-model队丝,v-model相同于一個(gè)語(yǔ)法糖,監(jiān)聽(tīng)了表單控件欲鹏,用戶輸入炭玫。this.name也會(huì)變化。有了上面的條件貌虾,模板也會(huì)變化了。

這就是雙向數(shù)據(jù)綁定裙犹。

那應(yīng)該如何實(shí)現(xiàn)呢尽狠?

構(gòu)造函數(shù)

function Vue(option) {
  let { el, data } = option;
  let node = document.querySelector(el);
  this.data = data;

  observe(data,this) // 代理綁定屬性

  let dom = kidnap(node,this) // 遍歷dom樹(shù),編譯叶圃,返回一個(gè)新的dom樹(shù)

  node.appendChild(dom)
}

代理綁定屬性

Vue對(duì)屬性變化檢測(cè)的核心實(shí)現(xiàn)就是Object.defineProperty方法袄膏。這個(gè)方法可以為對(duì)象定義新的屬性〔艄冢可以設(shè)置getter沉馆,setter回調(diào)。

在這里的實(shí)踐就是遍歷data對(duì)象德崭,data對(duì)象上面的每個(gè)屬性被vm代理斥黑。當(dāng)屬性變化,setter回調(diào)眉厨,廣播通知訂閱者锌奴;getter被回調(diào)時(shí),檢測(cè)是否可以添加訂閱者憾股。

function defineReactive(obj,key,val) {
  var dep = new Dep() // 為每一個(gè)屬性實(shí)例一個(gè)發(fā)布者
  Object.defineProperty(obj,key,{
    get: function() {
      if(Dep.target) {
        dep.addSub(Dep.target); // 添加訂閱者
      }
      return val
    },
    set: function (newVal) {
      if(newVal === obj[key]) return;
      val = newVal;
      dep.notify() // 當(dāng)屬性變化鹿蜀,廣播通知訂閱者
    }
  })
}

function observe(obj,vm) {
  Object.keys(obj).forEach(function (key) {
    defineReactive(vm,key,obj[key]);
  })
}

發(fā)布

class Dep {
  constructor () {
    this.subs = []
  }

  addSub (sub) {
    this.subs.push(sub)
  }

  notify () {
    this.subs.forEach(item => {
      item.update()
    })
  }
}  

每一個(gè)發(fā)布者都維護(hù)了一個(gè)訂閱者數(shù)組,發(fā)布者的notify方法會(huì)遍歷所有訂閱者服球,調(diào)用訂閱者的update方法茴恰。所以每一個(gè)訂閱者必須實(shí)現(xiàn)一個(gè)update方法。

編譯

我們知道當(dāng)vm上的屬性變化時(shí)斩熊,所有的訂閱者都會(huì)收到通知往枣。那么這些訂閱者是誰(shuí)呢?

訂閱者就是模板中“Mustache” 語(yǔ)法(雙大括號(hào))的文本插值。

首先我們要將DOM中劫持過(guò)來(lái)婉商。

function kidnap(node,vm) {
  if(!node) return;
  let frag = document.createDocumentFragment();
  while (child = node.firstChild) {
    frag.appendChild(child)
  }
  DFS(frag,function(node) {
    compile(node,vm)
  })
  return frag;
}

值得一提的是上面代碼中的appendChild方法似忧。
DOM規(guī)定,一個(gè)DOM節(jié)點(diǎn)不能同屬于兩個(gè)父節(jié)點(diǎn)丈秩,所以對(duì)一個(gè)擁有父節(jié)點(diǎn)的節(jié)點(diǎn)執(zhí)行appendChild其實(shí)是將它搬移到另一個(gè)節(jié)點(diǎn)盯捌。
同時(shí)進(jìn)行while循環(huán),可以巧妙的搬運(yùn)一個(gè)節(jié)點(diǎn)下的所有子節(jié)點(diǎn)蘑秽。

拿到模板之后對(duì)模板進(jìn)行一些處理饺著。

function compile(node,vm) {
  if(!node) return;
  var reg = /\{\{(.*)\}\}/;

  if(node.nodeType == 1) {
    let attr = node.attributes;
    for (var i = 0; i < attr.length; i++) {
      if(node.tagName.toLowerCase() == "input" && attr[i].name == "v-model") {
        var name = attr[i].nodeValue;
        node.addEventListener("keyup",function (e) {
          vm[name] = e.target.value;
        })
        node.value = vm[name]
        node.removeAttribute("v-model")
        new Watcher(node,vm,name) // 訂閱者
      }
    }
  }

  if(node.nodeType == 3) {
    if(reg.test(node.nodeValue)) {
      var name = RegExp.$1;
      name = name.trim();
      node.nodeValue = vm[name]
      new Watcher(node,vm,name) // 訂閱者
    }
  }
}

遍歷每一個(gè)DOM節(jié)點(diǎn)。這里用到了DFS,深度優(yōu)先搜索肠牲∮姿ィ可以參見(jiàn)我的上一篇文章。代碼如下缀雳。

function DFS(node, cb) {
  let deep = 1;
  DFSdom(node,deep,cb)
}

function DFSdom(node, deep, cb) {
  if(!node)
    return;

  cb(node,deep)

  if(!node.childNodes.length) {
    return;
  }

  deep++;

  Array.from(node.childNodes).forEach(item => DFSdom(item,deep,cb))
}

訂閱

我們可以看到在遍歷DOM樹(shù)的時(shí)候渡嚣,對(duì)符合我們語(yǔ)法條件的節(jié)點(diǎn)進(jìn)行了watch。watcher相當(dāng)于訂閱者肥印。

class Watcher {
  constructor (node, vm, name) {
    Dep.target =  this;
    this.name = name;
    this.node = node;
    this.vm = vm;
    this.update()
    Dep.target = null;
  }

  update () {
    this.value = this.vm[this.name];
    this.node.nodeValue = this.value;
    if(this.node.nodeType == 1) {
      this.node.value = this.value
    }
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末识椰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子深碱,更是在濱河造成了極大的恐慌腹鹉,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件敷硅,死亡現(xiàn)場(chǎng)離奇詭異功咒,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)绞蹦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)力奋,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人幽七,你說(shuō)我怎么就攤上這事刊侯。” “怎么了锉走?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,328評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵滨彻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我挪蹭,道長(zhǎng)亭饵,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,147評(píng)論 1 292
  • 正文 為了忘掉前任梁厉,我火速辦了婚禮辜羊,結(jié)果婚禮上踏兜,老公的妹妹穿的比我還像新娘。我一直安慰自己八秃,他們只是感情好碱妆,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,160評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著昔驱,像睡著了一般疹尾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上骤肛,一...
    開(kāi)封第一講書(shū)人閱讀 51,115評(píng)論 1 296
  • 那天纳本,我揣著相機(jī)與錄音,去河邊找鬼腋颠。 笑死繁成,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的淑玫。 我是一名探鬼主播巾腕,決...
    沈念sama閱讀 40,025評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼絮蒿!你這毒婦竟也來(lái)了祠墅?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,867評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤歌径,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后亲茅,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體回铛,經(jīng)...
    沈念sama閱讀 45,307評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,528評(píng)論 2 332
  • 正文 我和宋清朗相戀三年克锣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茵肃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,688評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡袭祟,死狀恐怖验残,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情巾乳,我是刑警寧澤您没,帶...
    沈念sama閱讀 35,409評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站胆绊,受9級(jí)特大地震影響氨鹏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜压状,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,001評(píng)論 3 325
  • 文/蒙蒙 一仆抵、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦镣丑、人聲如沸舔糖。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,657評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)金吗。三九已至,卻和暖如春慨蛙,著一層夾襖步出監(jiān)牢的瞬間辽聊,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,811評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工期贫, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留跟匆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,685評(píng)論 2 368
  • 正文 我出身青樓通砍,卻偏偏與公主長(zhǎng)得像玛臂,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子封孙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,573評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理迹冤,服務(wù)發(fā)現(xiàn),斷路器虎忌,智...
    卡卡羅2017閱讀 134,651評(píng)論 18 139
  • 這篇筆記主要包含 Vue 2 不同于 Vue 1 或者特有的內(nèi)容泡徙,還有我對(duì)于 Vue 1.0 印象不深的內(nèi)容。關(guān)于...
    云之外閱讀 5,048評(píng)論 0 29
  • 工廠模式類(lèi)似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品膜蠢,去做同樣的事情堪藐,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 7,750評(píng)論 2 17
  • 1.安裝 可以簡(jiǎn)單地在頁(yè)面引入Vue.js作為獨(dú)立版本挑围,Vue即被注冊(cè)為全局變量礁竞,可以在頁(yè)面使用了。 如果希望搭建...
    Awey閱讀 11,014評(píng)論 4 129
  • 看了三毛的《雨季不再來(lái)》杉辙,再看《撒哈拉的故事》模捂,完全2種感覺(jué),第一部給人負(fù)面情緒比較多蜘矢,比較情緒化狂男,陰暗,但第二部...
    46ca35a03e00閱讀 99評(píng)論 0 0