摘要:要實(shí)現(xiàn)最小化刷新,我們要將模板中的每個(gè)綁定都收集起來。思考題在最后的實(shí)現(xiàn)下,我們把模板改為下面這樣雖然很少會(huì)有人這樣寫,就會(huì)出現(xiàn)重復(fù)的實(shí)例,該如何解決這個(gè)問題,參考早期源碼學(xué)習(xí)系列之四如何實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定
上一篇文章我們了解了怎樣實(shí)現(xiàn)一個(gè)簡(jiǎn)單模板引擎。但這個(gè)模板引擎只適合靜態(tài)模板,因?yàn)樗菍⒛0逭w編譯成字符串進(jìn)行全量替換。如果每次數(shù)據(jù)改變都進(jìn)行一次替換,會(huì)有兩個(gè)最主要的問題:
性能差。DOM 操作本身就非常大的開銷,更別說每一次都替換這么大的量。
破壞事件綁定。這個(gè)是最麻煩的,如果我們沒有給解綁移除 DOM 綁定的事件,還會(huì)造成內(nèi)存泄露。而且每一次替換都要重新綁定事件。
因此,沒有人會(huì)將這種模板引擎用來編譯動(dòng)態(tài)模板。那我們?nèi)绾尉幾g動(dòng)態(tài)模板呢?
回答這個(gè)問題之前,我們先要了解前端的世界何時(shí)出現(xiàn)了動(dòng)態(tài)模板:它是由 MVVM 框架帶來的,動(dòng)態(tài)模板是 MVVM 框架的視圖層(view)。我們知道的 MVVM 框架有 knockout.js、angular.js、avalon 和 vue。
對(duì)于這些框架,大部分人最熟悉的應(yīng)該就是 vue,所以我下面也是以 vue 1.0 作為參考,來實(shí)現(xiàn)一個(gè)功能更簡(jiǎn)單的動(dòng)態(tài)模板引擎。它是框架自帶的一個(gè)功能,讓框架能夠響應(yīng)數(shù)據(jù)的改變。從而刷新頁(yè)面。
MVVM 動(dòng)態(tài)模板的特點(diǎn)是能最小化刷新:哪個(gè)變量改變了,與之相關(guān)的節(jié)點(diǎn)才會(huì)更新。這樣我們就能避免上面提到的靜態(tài)模板的兩大問題。
要實(shí)現(xiàn)最小化刷新,我們要將模板中的每個(gè)綁定都收集起來。這個(gè)收集工作是框架在完成第一次渲染前就已經(jīng)完成了,每個(gè)綁定都會(huì)生成一個(gè) Directive 實(shí)例:
class Directive { constructor(vm, el, exp, update) { this.vm = vm this.el = el this.exp = exp this.update = update this.watchers = [] this.get = getEvaluationFn(exp).bind(this, vm.$data) this.bind() } } function getEvaluationFn(exp) { return new Function("data", "with(data) { return " + exp + "}") }
我們知道,每個(gè)綁定都由指令和指令值(指令值可能是表達(dá)式,可能是語(yǔ)句,也可能就是一個(gè)變量,還可能是框架自定義的語(yǔ)法)構(gòu)成,每種指令都有對(duì)應(yīng)的刷新函數(shù)(update)。如節(jié)點(diǎn)值的綁定的刷新函數(shù)是:
function updateTextNode() { const value = this.get() this.el.nodeValue = value console.log(this.exp + " updated: " + value) }
有了刷新函數(shù),那如何做到在數(shù)據(jù)改變時(shí)調(diào)用刷新函數(shù)更新節(jié)點(diǎn)的值呢?我們就還要將每個(gè)指令里的相關(guān)變量都跟這個(gè) Directive 實(shí)例關(guān)聯(lián)起來。我們用一個(gè) $binding 對(duì)象來記錄,它的鍵是變量,值是 Binding 實(shí)例:
class Binding { constructor() { this.subs = [] } addChild(key) { return this[key] || new Binding() } addSub(watcher) { this.subs.push(watcher) } }
那上面的 subs 里添加的為什么不是 Directive 實(shí)例呢,而是 watcher 呢?它其實(shí)是 Watcher 的實(shí)例,這是為了以后能夠?qū)崿F(xiàn) $watch 方法提前引入的概念,Watcher 實(shí)例的 cb 既可以是指令的刷新函數(shù),也可以是 $watch 方法的回調(diào)函數(shù):
class Watcher { constructor(vm, path, cb, ctx) { this.id = ++uid this.vm = vm this.path = path this.cb = cb this.ctx = ctx || vm this.addDep() } }
class Directive { bind() { this.watchers.push(new Watcher(this.vm, this.exp, this.update, this)) } }
我們先考慮最簡(jiǎn)單的情況,指令值就是一個(gè)變量,根據(jù)上面的思路,我們就可以寫出最簡(jiǎn)單的實(shí)現(xiàn)了,代碼就不貼了,有興趣的直接看源碼。
MVVM
My name is {{name.first}}-{{name.last }},{{age}} years old
上面實(shí)現(xiàn)的動(dòng)態(tài)模板是在我們假定了指令值是最簡(jiǎn)單的變量的情況下實(shí)現(xiàn)的。那要是把上面的模板改為下面這樣呢?
MVVM
My name is {{name.first}}-{{name.last }},{{"age: " + age}} years old
salary: {{ salary.toLocaleString() }}
那我們上面的實(shí)現(xiàn)有一些數(shù)據(jù)就不能動(dòng)態(tài)刷新了,原因很簡(jiǎn)單,就是我們是直接將 "age: " + age 和 Directive 實(shí)例關(guān)聯(lián),而我們修改的只是 age,自然就找不到對(duì)應(yīng)的實(shí)例了。那我們?nèi)绾谓鉀Q呢?
首先想到的肯定是按照現(xiàn)有的實(shí)現(xiàn)來擴(kuò)展,讓它支持模板插值是表達(dá)式的情況。已有的實(shí)現(xiàn)是直接解析得到變量,那我們就繼續(xù)想辦法直接解析表達(dá)式得到變量。像 "age: " + age 這種表達(dá)式直接解析出 age 其實(shí)不難。但 salary.toLocaleString() 這種就不好做了,要是 salary.toLocaleString().slice(1) 這種可以說是沒辦法解析了。
既然這條路行不通,其實(shí)我們是有更簡(jiǎn)單的方法。既然我們都已經(jīng)將 data 進(jìn)行了代理,那我們就可以在 get 獲取變量值時(shí)進(jìn)行依賴收集。因?yàn)槲覀儽緛砭蜁?huì)運(yùn)行 Directive 實(shí)例的求值函數(shù)進(jìn)行初始值的替換,這就會(huì)觸發(fā)變量的 get 。具體的代碼怎么寫就不說了,詳細(xì)的修改和支持表達(dá)式的源碼。
當(dāng)然現(xiàn)在只實(shí)現(xiàn)動(dòng)態(tài)模板最簡(jiǎn)單的插值指令。還有一些更復(fù)雜的指令如:if 和 for 的實(shí)現(xiàn)方式,下次有機(jī)會(huì)再分享。
思考題在最后的實(shí)現(xiàn)下,我們把模板改為下面這樣(雖然很少會(huì)有人這樣寫),就會(huì)出現(xiàn)重復(fù)的 Watcher 實(shí)例,該如何解決這個(gè)問題?
參考MVVM
hello,My name is {{name.first + "-" + name.last }}
vue早期源碼學(xué)習(xí)系列之四:如何實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)綁定
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.hztianpu.com/yun/107754.html
摘要:看這篇之前,如果沒有看過之前的文章,移步拉到文章末尾查看之前的文章。而該組件實(shí)例的父實(shí)例卻并不固定,所以我們將這些在使用時(shí)才能確定的參數(shù)在組件實(shí)例化的時(shí)候傳入。系列文章地址優(yōu)化優(yōu)化總結(jié) 看這篇之前,如果沒有看過之前的文章,移步拉到文章末尾查看之前的文章。 前言 在上一步,我們實(shí)現(xiàn) extend 方法,用于擴(kuò)展 Vue 類,而我們知道子組件需要通過 extend 方法來實(shí)現(xiàn),我們從測(cè)試?yán)?..
摘要:面向?qū)ο笫亲约航M裝電腦,硬件已生產(chǎn)完畢。面向過程吃狗屎面向?qū)ο蠊烦允捍_切的講是一種軟件設(shè)計(jì)規(guī)范,早在年的理念就已經(jīng)誕生。后期的維護(hù)成本會(huì)減少很多。減輕了開發(fā)人員的負(fù)擔(dān),也減少了操作邏輯導(dǎo)致業(yè)務(wù)邏輯混亂的可能性。 什么是MVC,什么是MVVM? 面向過程 --> 面向?qū)ο?--> MVC --> MV* 面向過程: 開發(fā)人員按照需求邏輯順序開發(fā)代碼邏輯,主要思維模式在于如何實(shí)現(xiàn)。先細(xì)節(jié),...
摘要:接下來要看看這個(gè)訂閱者的具體實(shí)現(xiàn)了實(shí)現(xiàn)訂閱者作為和之間通信的橋梁,主要做的事情是在自身實(shí)例化時(shí)往屬性訂閱器里面添加自己自身必須有一個(gè)方法待屬性變動(dòng)通知時(shí),能調(diào)用自身的方法,并觸發(fā)中綁定的回調(diào),則功成身退。 本文能幫你做什么?1、了解vue的雙向數(shù)據(jù)綁定原理以及核心代碼模塊2、緩解好奇心的同時(shí)了解如何實(shí)現(xiàn)雙向綁定為了便于說明原理與實(shí)現(xiàn),本文相關(guān)代碼主要摘自vue源碼, 并進(jìn)行了簡(jiǎn)化改造,...
閱讀 3357·2023-04-25 16:50
閱讀 981·2021-11-25 09:43
閱讀 3640·2021-09-26 10:11
閱讀 2579·2019-08-26 13:28
閱讀 2590·2019-08-26 13:23
閱讀 2494·2019-08-26 11:53
閱讀 3635·2019-08-23 18:19
閱讀 3052·2019-08-23 16:27