日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 前端技术 > javascript >内容正文

javascript

js 取得input绑定的datalist中的值_javascript基础修炼(9)——MVVM中双向数据绑定的基本原理...

發(fā)布時間:2025/3/15 javascript 20 豆豆
生活随笔 收集整理的這篇文章主要介紹了 js 取得input绑定的datalist中的值_javascript基础修炼(9)——MVVM中双向数据绑定的基本原理... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

【小宅按】 開發(fā)者的javascript造詣取決于對【動態(tài)】和【異步】這兩個詞的理解水平。

一. 概述

1.1 MVVM模型

MVVM模型是前端單頁面應(yīng)用中非常重要的模型之一,也是Single Page Application的底層思想,如果你也因為自己學(xué)習(xí)的速度拼不過開發(fā)框架版本迭代的速度,或許也應(yīng)該從更高的抽象層次去理解現(xiàn)代前端開發(fā),因為其實最核心的經(jīng)典思想幾乎都是不怎么變的。關(guān)于MVVM的文章已經(jīng)非常多了,本文不再贅述。

筆者之前聽過一種很形象的描述覺得有必要提一下,Model可以想象成HTML代碼,ViewModel可以想象成瀏覽器,而View可以想象成我們最終看到的頁面, 那么各個層次所扮演的角色和所需要處理的邏輯就比較清晰了。

1.2 數(shù)據(jù)綁定

數(shù)據(jù)綁定,就是將視圖層表現(xiàn)和模型層的數(shù)據(jù)綁定在一起,關(guān)于MVVM中的數(shù)據(jù)綁定,涉及兩個基本概念單向數(shù)據(jù)綁定和雙向數(shù)據(jù)綁定,其實兩者并沒有絕對的優(yōu)劣,只是適用場景不同,現(xiàn)×××發(fā)框架都是同時支持兩種形式的。

雙向數(shù)據(jù)綁定由Angularjs1.x發(fā)展起來,在表單等用戶體驗高度依賴于即時反饋的場景中非常便利,但并不是所有場景下都適用的,Angularjs中也可以通過ng-bind=":expr"的形式來實現(xiàn)單向綁定;在Flux數(shù)據(jù)流架構(gòu)的影響下,更加易于追蹤和管理的單向數(shù)據(jù)流思想出現(xiàn)了,各主流框架也進行了實現(xiàn)(例如redux,vuex),在單向數(shù)據(jù)綁定的框架中,開發(fā)者仍然可以在需要的地方監(jiān)聽變化來手動實現(xiàn)雙向綁定。

關(guān)于Angularjs1.x中如何通過臟檢查機制來實現(xiàn)雙向數(shù)據(jù)綁定和管理,可以參見《構(gòu)建自己的AngularJS,第一部分:Scope和Digest》一文,講述得非常詳細。

二. 基于數(shù)據(jù)劫持的綁定

2.1 Vue2.0源碼的學(xué)習(xí)困惑

Vue2.0版本中的雙向數(shù)據(jù)綁定,很多開發(fā)者都知道是通過劫持屬性的get/set方法來實現(xiàn)的,上圖已經(jīng)展示了雙向數(shù)據(jù)綁定的代碼框架,分析源碼的文章也非常多,許多文章都將重點放在了發(fā)布訂閱模式的實現(xiàn)上,筆者自己閱讀時有兩大困擾點:

第一,即使通過defineProperty劫持了屬性的get/set方法,不知道數(shù)據(jù)模型和頁面之間又是如何聯(lián)系起來的。(很多文章都是順帶一提而沒有詳述,實際上這部分對于整體理解MVVM數(shù)據(jù)流非常重要)

第二,Vue2.0在實現(xiàn)發(fā)布訂閱模式的時候,使用了一個Dep類作為訂閱器來管理發(fā)布訂閱行為,從代碼的角度講這樣做是很好的實踐,它可以將訂閱者管理(例如避免重復(fù)訂閱)這種與業(yè)務(wù)無關(guān)的代碼解耦出來,符合單一職責(zé)的開發(fā)原則。但這樣做對于理清代碼邏輯而言會造成困擾,讓發(fā)布-訂閱相關(guān)的代碼段變得模糊,實際上將Dep類與發(fā)布者類合并在一起,綁定原理會更加清晰,而在代碼迭代中,考慮到更多更復(fù)雜的情況時,即使你是框架的設(shè)計者,也會很自然地選擇將Dep抽象成一個獨立的類。

如果你也在閱讀博文的時候出現(xiàn)同樣的困惑,強烈建議讀完本篇后自己動手實現(xiàn)一個MVVM的雙向綁定,你會發(fā)現(xiàn)很多時候你不理解一些代碼,是因為你不知道作者面對了怎樣的實際問題。

2.2 從標(biāo)簽開始的代碼推演

ps:下文提及的觀察者類和發(fā)布者類是指同一個類。

2.2.1 示例代碼

我們先來寫幾個包含自定義指令的標(biāo)簽:

<div id="app" class="container"><input type="text" d-model="myname"><br>輸入的是:<span d-bind="myname"></span><br><button d-click="alarm()">廣播報警</button></div><script>var options = { el:'app', data:{ myname:'僵尸'}, methods:{ alarm:function (node,event) { window.alert(`一大 波【${this.data.myname}】正在靠近!`);}}} //初始化var vm = new Dash(options);</script>

需要實現(xiàn)的功能就如同你在所有框架中見到的那樣:<input>標(biāo)簽的值通過d-model指令和數(shù)據(jù)模型中的myname進行雙向綁定,<span>標(biāo)簽的值通過d-bind指令從myname單向獲取,<button>標(biāo)簽的點擊響應(yīng)通過d-click綁定數(shù)據(jù)模型中的alarm()方法。初始化所用到的方法已經(jīng)提供好了,假如我們要在一個叫做Dash的MVVM框架中實現(xiàn)數(shù)據(jù)綁定,那么第一步要做的,是模板解析。

2.2.2 模板解析

DOM標(biāo)簽自身是一個樹形結(jié)構(gòu),所以需要從最外層的<div>為起點以遞歸的方式來進行解析。

compiler.js——模板解析器類

/*** 模板編譯器*/class Compiler{ constructor(){ this.strategy = new Strategy();//封裝的策略類,下一節(jié)描述this.strategyKeys = Object.keys(this.strategy);} /***編譯方法*@params vm Dash類的實例(即VisualModel實例)*@params node 待編譯的DOM節(jié)點*/compile(vm, node){ if (node.nodeType === 3) {//解析文本節(jié)點this.compileTextNode(vm, node);}else{ this.compileNormalNode(vm, node);}} /***編譯文本節(jié)點,此處僅實現(xiàn)一個空方法,實際開發(fā)中可能是字符串轉(zhuǎn)義過濾方法*/compileTextNode(vm, node){} /***編譯DOM節(jié)點,遍歷策略類中支持的自定義指令,如果發(fā)現(xiàn)某個指令dir*則以this.Strategy[str]的方式取得對應(yīng)的處理函數(shù)并執(zhí)行。*/compileNormalNode(vm, node){ this.strategyKeys.forEach(key=>{ let expr = node.getAttribute(key); if (expr) { this.strategy[key].call(vm, node, expr);}}); //遞歸處理當(dāng)前DOM標(biāo)簽的子節(jié)點let childs = node.childNodes; if (childs.length > 0) {childs.forEach(subNode => this.compile(vm, subNode));}} }//為方便理解,此處直接在全局生成一個編譯器單例,實際開發(fā)中請掛載至適當(dāng)?shù)拿臻g下。window.Compiler = new Compiler();

2.2.3 策略封裝

我們使用策略模式實現(xiàn)一個單例的策略類Strategy,將所有指令所對應(yīng)的解析方法封裝起來并傳入解析器,當(dāng)解析器遞歸解析每一個標(biāo)簽時,如果遇到可以識別的指令,就從策略類中直接取出對應(yīng)的處理方法對當(dāng)前節(jié)點進行處理即可,這樣Strategy類只需要實現(xiàn)一個Strategy.register( customDirective, options)方法就可以暴露出未來用以添加自定義指令的接口。(細節(jié)可參考附件中的代碼)

strategy.js——指令解析策略類

//策略類的基本結(jié)構(gòu)class Strategy{ constructor(){ let strategy = { 'd-bind':function(){//...},'d-model':function(){//...},'d-click':function(){//...}} return strategy;} //注冊新的指令register(customDir,options){...} }

模板解析的工作就比較清晰了,相當(dāng)于帶著一本《解析指南》去遍歷處理DOM樹,不難看出,實際上綁定的工作就是在策略對應(yīng)的方法里來實現(xiàn)的,在MVVM結(jié)構(gòu)種,這一步被稱為“依賴收集”。

2.2.4 訂閱數(shù)據(jù)模型變化

以最基本的d-bind指令為例,通過使用strategy['d-bind']方法處理節(jié)點后,被處理的節(jié)點應(yīng)該具備感知數(shù)據(jù)模型變化的能力。以上面的模板為例,當(dāng)this.data.myname發(fā)生變化時,就需要將被處理節(jié)點的內(nèi)容改為對應(yīng)的值。此處就需要用到發(fā)布-訂閱模式。為了實現(xiàn)這個方法,需要一個觀察者類Observer,它的功能是觀察數(shù)據(jù)模型的變化(通過數(shù)據(jù)劫持實現(xiàn)),管理訂閱者(維護一個回調(diào)隊列管理訂閱者添加的回調(diào)方法), 變化發(fā)生時通知訂閱者(依次調(diào)用訂閱者注冊的回調(diào)方法),同時將提供回調(diào)方法并執(zhí)行視圖更新行為的邏輯抽象為一個訂閱者類Subscriber,訂閱者實例擁有一個update方法,當(dāng)該方法被觀察者(同時也是發(fā)布者)調(diào)用時,就會刷新對應(yīng)節(jié)點的視圖,很明顯,subscriber實例需要被添加至指定的觀察者類的回調(diào)隊列中才能夠生效。

//發(fā)布訂閱模式的偽代碼//...'d-bind':function(node, expr){ //實例化訂閱者類let sub = new Subscriber(node, 'myname',function(){ //更新視圖node.innerHTML = VM.data['myname'];}); //當(dāng)觀察者實例化時,需要將這個sub實例的update方法添加進},//...

subscriber.js——訂閱者類

class Subscriber{ constructor(vm, exp, callback){ this.vm = vm; this.exp = exp; this.callback = callback; this.value = this.vm.data[this.exp];} /*** 提供給發(fā)布者調(diào)用的方法*/update(){ return this.run();} /*** 更新視圖時的實際執(zhí)行函數(shù)*/run(){ let currentVal = this.vm.data[this.exp]; if (this.value !== currentVal) { this.value = currentVal; this.callback.call(this.vm, this.value);}} }

2.2.5 數(shù)據(jù)劫持

在生成一個subscriber實例后,還要實現(xiàn)一個observer實例,然后才能夠通過調(diào)用observer.addSub(sub)方法來將訂閱者添加進觀察者的回調(diào)隊列中。先來看一下Observer這個類的定義:

observer.js——觀察者類

/*** 發(fā)布者類,同時為一個觀察者* 功能包括:* 1.觀察視圖模型上數(shù)據(jù)的變化* 2.變化出現(xiàn)時發(fā)布變化消息給訂閱者*/class Observer{ constructor(data){ this.data = data; this.subQueue = {};//訂閱者Mapthis.traverse();} //遍歷數(shù)據(jù)集中各個屬性并添加觀察器具traverse(){ Object.keys(this.data).forEach(key=>{defineReactive(this.data, key, this.data[key], this);});}notify(key){ this.subQueue[key].forEach(fn=>fn.update());} }//修改對象屬性的get/set方法實現(xiàn)數(shù)據(jù)劫持function defineReactive(obj, key, val, observer) { //當(dāng)鍵的值仍然是一個對象時,遞歸處理,observe方法定義在dash.js中l(wèi)et childOb = observe(val); //數(shù)據(jù)劫持Object.defineProperty(obj, key, { enumerable:true, configurable:true, get:()=>{ if (window.curSubscriber) { if (observer.subQueue[key] === undefined) {observer.subQueue[key] = []};observer.subQueue[key].push(window.curSubscriber);} return val;}, set:(newVal)=>{ if (val === newVal) return;val = newVal; //監(jiān)聽新值childOb = observe(newVal); //通知所有訂閱者observer.notify(key);}}) }

觀察者類實例化時,傳入一個待觀察的數(shù)據(jù)對象,構(gòu)造器調(diào)用遍歷方法來改寫數(shù)據(jù)集中每一個鍵的get/set方法,在讀取某個鍵的值時,將訂閱者監(jiān)聽器(細節(jié)下一節(jié)講)添加進回調(diào)隊列,當(dāng)set改變數(shù)據(jù)集中某個鍵的值時,調(diào)用觀察者的notify( )方法找到對應(yīng)鍵的回調(diào)隊列并以此觸發(fā)。

上面的代碼可以應(yīng)付一般情況,但存在一些明顯的問題就是集中式的回調(diào)隊列管理,subQueue實際上是一個HashMap結(jié)構(gòu):

subQueue = { 'myname':[fn1, fn2, fn3], 'otherAttr':[fn11,fn12, fn13], //...}

不難看出這種管理回調(diào)的方式存在很多問題,遇到嵌套或重名結(jié)構(gòu)就會出現(xiàn)覆蓋,這個時候就不難理解Vue2.0源碼中的做法了,在進行數(shù)據(jù)劫持時生成一個Dep實例,實例中維護一個回調(diào)隊列用來管理發(fā)布訂閱,當(dāng)數(shù)據(jù)模型中的屬性被set修改時,調(diào)用dep.notify( )方法來依次調(diào)用訂閱者添加的回調(diào),當(dāng)屬性被讀取而觸發(fā)get方法時,向dep實例中添加訂閱者的回調(diào)函數(shù)即可。

2.2.6 發(fā)布訂閱的連接

截止目前為止,還有最后一個問題需要處理,就是訂閱者實例sub和發(fā)布訂閱管理器實例dep存在于兩個不同的作用域里,那么要怎么通過調(diào)用dep.addSub(sub)來實現(xiàn)訂閱動作呢?換個問法或許你就發(fā)現(xiàn)這個問題其實并不難回答,在SPA框架中,兄弟組件之間如何通信呢?通常都是借助數(shù)據(jù)上浮(公用數(shù)據(jù)提升到共同的父級組件中)或者EventBus來實現(xiàn)的。

這里的做法是一致的,在策略類中某個指令對應(yīng)的處理方法中,當(dāng)我們準(zhǔn)備從數(shù)據(jù)模型this.data中讀取對應(yīng)的初值前,先將訂閱者實例sub掛載到一個更高的層級(附件的demo中簡單粗暴地掛載到全局,Vue2.0源碼中掛載到Dep.target),然后再去讀取this.data[expr],這個時候在expr屬性被劫持的get方法中,不僅可以訪問到屬于自己的訂閱管理器dep實例,也可以通過Dep.target訪問到當(dāng)前節(jié)點所對應(yīng)的訂閱者實例,那么完成對應(yīng)的訂閱邏輯就易如反掌了。

2.2.7 邏輯整合

了解了上述細節(jié),我們整理一下思路,整體看一下數(shù)據(jù)綁定所經(jīng)歷的各個環(huán)節(jié):

2.2.8 Demo

有關(guān)上面示例中d-model和d-click指令綁定的實現(xiàn),本文不再贅述,筆者提供了包含詳細注釋的完整Demo,有需要的讀者可以直接從附件中取用,最后Demo也會存放在我的github倉庫。

2.2.9 Vue2.0中有關(guān)雙向綁定的源碼

了解了上述細節(jié),可以閱讀《vue的雙向綁定原理及實現(xiàn)》來看看 Vue2.0的源代碼中是如何更加規(guī)范地實現(xiàn)雙向數(shù)據(jù)綁定的。

2.3 數(shù)據(jù)劫持綁定存在的問題

基于劫持的數(shù)據(jù)綁定方法是無法感知數(shù)組方法的,Vue2.0中使用了Hack的方法來實現(xiàn)對于數(shù)組元素的感知,其基本原理依舊是通過代理模式實現(xiàn),在此直接給出源碼Vue源碼鏈接:

//Vue2.0中有關(guān)數(shù)組方法const arrayProto = Array.prototypeexport const arrayMethods = Object.create(arrayProto)// hack 以下幾個函數(shù)const methodsToPatch = [ 'push', 'pop', 'shift', 'unshift', 'splice', 'sort', 'reverse'] methodsToPatch.forEach(function (method) { // 獲得原生函數(shù)const original = arrayProto[method]def(arrayMethods, method, function mutator (...args) { // 調(diào)用原生函數(shù)const result = original.apply(this, args) const ob = this.__ob__ let inserted switch (method) { case 'push': case 'unshift':inserted = args breakcase 'splice':inserted = args.slice(2) break} if (inserted) ob.observeArray(inserted) // 觸發(fā)更新ob.dep.notify() return result}) })

大致的思路是為Array.prototype上幾個原生方法設(shè)置了訪問代理,并將訂閱管理器的消息發(fā)布方法混入其中,實現(xiàn)了對特定數(shù)組方法的監(jiān)控。

三. 基于Proxy的數(shù)據(jù)綁定

Vue官方已經(jīng)確認3.0版本重構(gòu)數(shù)據(jù)綁定代碼,改為Proxy實現(xiàn)。

Proxy對象是ES6引入的原生化的代理對象,和基于defineProperty實現(xiàn)數(shù)據(jù)劫持在思路上其實并沒有什么本質(zhì)區(qū)別,都是使用經(jīng)典的“代理模式”來實現(xiàn)的,只是原生支持的Proxy編寫起來更簡潔,整個天然支持對數(shù)組變化的感知能力。Proxy和Reclect對象基本是成對出現(xiàn)使用的,屬于元編程范疇,可以從語言層面改變原有特性,Proxy可以攔截對象的數(shù)十種方法,比手動實現(xiàn)的代理模式要清晰很多,也要方便很多。

基本實現(xiàn)如下:

//使用Proxy代理數(shù)據(jù)模型對象let watchVmData = (obj, setBind, getLogger) => { let handler = {get(target, property, receiver){getLogger(target, property); return Reflect.get(target, property, receiver);},set(target, property, value, receiver){setBind(value); return Reflect.set(target, property, value);}}; return new Proxy(obj, handler); };//使用Proxy代理let data = { myname : 1 };let value;let vmproxy = watchVmData(obj, (v) => {value = v; },(target, property)=>{ console.log(`Get ${property} = ${target[property]}`); });

四. What's next

數(shù)據(jù)綁定只是MVVM模型中的冰山一角,如果你自己動手實現(xiàn)了上面提及的Demo,一定會發(fā)現(xiàn)很多明顯的問題,例如訂閱者刷新函數(shù)是直接修改DOM的,稍有開發(fā)經(jīng)驗的前端工程師都會想到需要將變化收集起來,盡可能將高性能消耗的DOM操作合并在一起處理來提升效率,這就引出了一系列我們常常聽到的Virtual-DOM(虛擬DOM樹)和Life-Cycle-Hook(生命周期鉤子)等等知識點,如果你對三大框架的底層原理感興趣,可以繼續(xù)探索,那是一件非常有意思的事情。

五. 總結(jié)

通過原理的學(xué)習(xí)就會發(fā)現(xiàn)學(xué)習(xí)【設(shè)計模式】的重要性,很多時候別人用設(shè)計模式的術(shù)語交流并不是在裝X,而是它真的代表了一些久經(jīng)驗證的思想,僅僅是數(shù)據(jù)綁定這樣一個小小的知識點,就包含了類模式,代理模式,原型模式,策略模式,發(fā)布訂閱模式的運用,代碼的實現(xiàn)中也涉及到了單一職責(zé),開放封閉等等開發(fā)原則的考量,框架編寫是一件非常考驗基本功的事情,在基礎(chǔ)面前,技巧只能是浮云。

easy_mvvm.rar

更多精彩內(nèi)容,請滑至頂部點擊右上角關(guān)注小宅哦~


來源:華為云社區(qū)原創(chuàng) 作者:大史不說話

總結(jié)

以上是生活随笔為你收集整理的js 取得input绑定的datalist中的值_javascript基础修炼(9)——MVVM中双向数据绑定的基本原理...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。