日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢(xún)
選擇下列產(chǎn)品馬上在線(xiàn)溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷(xiāo)解決方案
一篇文章教你如何寫(xiě)成Strview.js之源碼剖析

前言

前段時(shí)間我自己開(kāi)發(fā)了一款Strview.js,它是一個(gè)可以將字符串轉(zhuǎn)換為視圖的JS庫(kù)。什么意思呢?就像下面這段代碼:

成都創(chuàng)新互聯(lián)公司長(zhǎng)期為上千家客戶(hù)提供的網(wǎng)站建設(shè)服務(wù),團(tuán)隊(duì)從業(yè)經(jīng)驗(yàn)10年,關(guān)注不同地域、不同群體,并針對(duì)不同對(duì)象提供差異化的產(chǎn)品和服務(wù);打造開(kāi)放共贏平臺(tái),與合作伙伴共同營(yíng)造健康的互聯(lián)網(wǎng)生態(tài)環(huán)境。為萬(wàn)全企業(yè)提供專(zhuān)業(yè)的成都網(wǎng)站建設(shè)、做網(wǎng)站,萬(wàn)全網(wǎng)站改版等技術(shù)服務(wù)。擁有10年豐富建站經(jīng)驗(yàn)和眾多成功案例,為您定制開(kāi)發(fā)。

 
 
 
 
  1.  
  2.  
  3.  
  4.  
  5.      
  6.      
  7.      
  8.     Strview.js 
  9.  
  10.  
  11.  
  12.     
 
  •      
  •      
  •  
  •  
  •  
  • 顯示如下頁(yè)面:

    你會(huì)看到頁(yè)面上顯示了一個(gè)Hello World字樣,而我們看到HTML代碼中除了一個(gè)ID名是app標(biāo)簽之外,其他標(biāo)簽并沒(méi)有,更沒(méi)有Hello World文本。這時(shí),繼續(xù)往下看,在JS代碼中,我們引入了Strview.js,并且我們調(diào)用了它一個(gè)createView方法,最后傳入了一個(gè)對(duì)象。我們?cè)趯?duì)象中發(fā)現(xiàn)了Hello World字符串,并且我們?cè)趖emplate屬性中看到它多所對(duì)應(yīng)的值是一個(gè)標(biāo)簽,就是這個(gè)標(biāo)簽

    {msg}

    ,另外,里面我們會(huì)看到使用{}包裹的msg字符。與data對(duì)象中的msg屬性相對(duì)應(yīng),正好它的值為Hello World。我們現(xiàn)在改變下msg屬性對(duì)應(yīng)的值來(lái)看下頁(yè)面是否發(fā)生改變。

    果然,發(fā)生了改變,所以我們知道Strview.js就是這么將字符串轉(zhuǎn)換為視圖的。

    這里,我們只是簡(jiǎn)單介紹了Strview.js的簡(jiǎn)單用法,如果想繼續(xù)了解其他用法的話(huà),可以去Strview.js中文官網(wǎng):

    https://www.maomin.club/site/strviewjs/zh

    下面的內(nèi)容呢,我們將看下Strview.js源碼,看它是如何實(shí)現(xiàn)的。

    剖析源碼

    本篇分析Strview.js版本為1.9.0

    首先,我們獲取到源碼,這里我們使用生產(chǎn)環(huán)境下的Strview.js,也就是上面實(shí)例中的這個(gè)地址:

    https://cdn.jsdelivr.net/npm/strview@1.9.0/dist/strview.global.js

    我們,先大體看下源碼,加上空行,源碼一共125行。不壓縮的話(huà),僅僅4kb。

     
     
     
     
    1. var Strview = (function (exports) { 
    2.     'use strict'; 
    3.  
    4.     // global object 
    5.     const globalObj = { 
    6.         _nHtml: [], 
    7.         _oHtml: [], 
    8.         _el: null, 
    9.         _data: null, 
    10.         _template: null, 
    11.         _sourceTemplate: null 
    12.     }; 
    13.  
    14.     // initialization 
    15.     function createView(v) { 
    16.         globalObj._data = v.data; 
    17.         globalObj._template = v.template; 
    18.         globalObj._sourceTemplate = v.template; 
    19.         globalObj._el = v.el; 
    20.         v.el ? document.querySelector(v.el).insertAdjacentHTML("beforeEnd", render(globalObj._template)) : console.error("Error: Please set el property!"); 
    21.     } 
    22.  
    23.     // event listeners 
    24.     function eventListener(el, event, cb) { 
    25.         document.querySelector(el).addEventListener(event, cb); 
    26.     } 
    27.  
    28.     // processing simple values 
    29.     function ref() { 
    30.         return new Proxy(globalObj._data, { 
    31.             get: (target, key) => { 
    32.                 return target[key] 
    33.             }, 
    34.             set: (target, key, newValue) => { 
    35.                 target[key] = newValue; 
    36.                 setTemplate(); 
    37.                 return true; 
    38.             } 
    39.         }) 
    40.     } 
    41.  
    42.     // reactiveHandlers 
    43.     const reactiveHandlers = { 
    44.         get: (target, key) => { 
    45.             if (typeof target[key] === 'object' && target[key] !== null) { 
    46.                 return new Proxy(target[key], reactiveHandlers); 
    47.             } 
    48.             return Reflect.get(target, key); 
    49.         }, 
    50.         set: (target, key, value) => { 
    51.             Reflect.set(target, key, value); 
    52.             setTemplate(); 
    53.             return true 
    54.         } 
    55.     }; 
    56.  
    57.     // respond to complex objects 
    58.     function reactive() { 
    59.         return new Proxy(globalObj._data, reactiveHandlers) 
    60.     } 
    61.  
    62.     // update the view 
    63.     function setTemplate() { 
    64.         const oNode = document.querySelector(globalObj._el); 
    65.         const nNode = toHtml(render(globalObj._sourceTemplate)); 
    66.         compile(oNode, 'o'); 
    67.         compile(nNode, 'n'); 
    68.         if (globalObj._oHtml.length === globalObj._nHtml.length) { 
    69.             for (let index = 0; index < globalObj._oHtml.length; index++) { 
    70.                 const element = globalObj._oHtml[index]; 
    71.                 element.textContent !== globalObj._nHtml[index].textContent && (element.textContent = globalObj._nHtml[index].textContent); 
    72.             } 
    73.         } 
    74.     } 
    75.  
    76.     // judge text node 
    77.     function isTextNode(node) { 
    78.         return node.nodeType === 3; 
    79.     } 
    80.  
    81.     // compile DOM 
    82.     function compile(node, type) { 
    83.         const childNodesArr = node.childNodes; 
    84.         for (let index = 0; index < Array.from(childNodesArr).length; index++) { 
    85.             const item = Array.from(childNodesArr)[index]; 
    86.             if (item.childNodes && item.childNodes.length) { 
    87.                 compile(item, type); 
    88.             } else if (isTextNode(item) && item.textContent.trim().length !== 0) { 
    89.                 type === 'o' ? globalObj._oHtml.push(item) : globalObj._nHtml.push(item); 
    90.             } 
    91.         } 
    92.     } 
    93.  
    94.     // string to DOM 
    95.     function toHtml(domStr) { 
    96.         const parser = new DOMParser(); 
    97.         return parser.parseFromString(domStr, "text/html"); 
    98.     } 
    99.  
    100.     // template engine 
    101.     function render(template) { 
    102.         const reg = /\{(.+?)\}/; 
    103.         if (reg.test(template)) { 
    104.             const key = reg.exec(template)[1]; 
    105.             if (globalObj._data.hasOwnProperty(key)) { 
    106.                 template = template.replace(reg, globalObj._data[key]); 
    107.             } else { 
    108.                 template = template.replace(reg, eval(`globalObj._data.${key}`)); 
    109.             } 
    110.             return render(template) 
    111.         } 
    112.  
    113.         return template; 
    114.     } 
    115.  
    116.     // exports 
    117.     exports.createView = createView; 
    118.     exports.eventListener = eventListener; 
    119.     exports.reactive = reactive; 
    120.     exports.ref = ref; 
    121.  
    122.     Object.defineProperty(exports, '__esModule', { value: true }); 
    123.  
    124.     return exports; 
    125. }({})); 

    首先,我們會(huì)看到最外層定義了一個(gè)Strview變量,暴露在外面,并將一個(gè)立即執(zhí)行函數(shù)(IIFE)賦予這個(gè)變量。

    我們先來(lái)看下這個(gè)立即執(zhí)行函數(shù)。

     
     
     
     
    1. var Strview = (function (exports) { 
    2. // ... 
    3.  
    4. }({})); 

    函數(shù)中需要傳一個(gè)形參exports,并且又立即傳入一個(gè)空對(duì)象。

    然后,我們來(lái)看下函數(shù)內(nèi)的內(nèi)容。

    我們會(huì)看到函數(shù)中有很多變量與函數(shù)方法,那么我們就按功能來(lái)分析。

    首先,我們看到了一個(gè)全局對(duì)象,全局對(duì)象中分別定義了幾個(gè)屬性。這樣做是為了減少全局變量污染,JS可以隨意定義保存所有應(yīng)用資源的全局變量,但全局變量可以削弱程序靈活性,增大了模塊之間的耦合性。最小化使用全局變量的一個(gè)方法是在你的應(yīng)用中只創(chuàng)建唯一一個(gè)全局變量。

     
     
     
     
    1. // global object 
    2. const globalObj = { 
    3.     _nHtml: [], // 存放新DOM數(shù)組 
    4.     _oHtml: [], // 存放舊DOM數(shù)組 
    5.     _el: null, // 掛載DOM節(jié)點(diǎn) 
    6.     _data: null, // 存放數(shù)據(jù) 
    7.     _template: null, // 模板字符串 
    8.     _sourceTemplate: null // 源模板字符串 
    9. }; 

    然后,我們接著看初始化階段,這個(gè)階段是將模板字符串轉(zhuǎn)換成視圖。

     
     
     
     
    1. // initialization 
    2. function createView(v) { 
    3.     globalObj._data = v.data; 
    4.     globalObj._template = v.template; 
    5.     globalObj._sourceTemplate = v.template; 
    6.     globalObj._el = v.el; 
    7.     v.el ? document.querySelector(v.el).insertAdjacentHTML("beforeEnd", render(globalObj._template)) : console.error("Error: Please set el property!"); 

    我們看到這個(gè)createView方法傳入了一個(gè)參數(shù),也就是我們之前傳入的那個(gè)對(duì)象:

     
     
     
     
    1. Strview.createView({ 
    2.         el: "#app", 
    3.         data: { 
    4.             msg: 'Hello World' 
    5.         }, 
    6.         template: `

      {msg}

      `, 
    7.     }); 

     我們看到傳入的對(duì)象中的屬性分別賦給全局對(duì)象globalObj。在最后一行中通過(guò)判斷v.el是否是真值,如果是就執(zhí)行這行代碼:

     
     
     
     
    1. document.querySelector(v.el).insertAdjacentHTML("beforeEnd", render(globalObj._template))  

    這行代碼執(zhí)行了insertAdjacentHTML()方法,這個(gè)方法在MDN上是這樣解釋它的。

    • insertAdjacentHTML() 方法將指定的文本解析為 Element 元素,并將結(jié)果節(jié)點(diǎn)插入到DOM樹(shù)中的指定位置。它不會(huì)重新解析它正在使用的元素,因此它不會(huì)破壞元素內(nèi)的現(xiàn)有元素。這避免了額外的序列化步驟,使其比直接使用innerHTML操作更快。

    insertAdjacentHTML()方法傳入的第二個(gè)參數(shù)是是要被解析為HTML或XML元素,并插入到DOM樹(shù)中的DOMString,render(globalObj._template)這個(gè)方法就是返回的DOMString。

    如果是假,就執(zhí)行console.error("Error: Please set el property!"),在瀏覽器上輸出錯(cuò)誤。

    既然這個(gè)用到了render(globalObj._template)這個(gè)方法,那么我們下面來(lái)看下。

     
     
     
     
    1. // template engine 
    2. function render(template) { 
    3.     const reg = /\{(.+?)\}/; 
    4.     if (reg.test(template)) { 
    5.         const key = reg.exec(template)[1]; 
    6.         if (globalObj._data.hasOwnProperty(key)) { 
    7.             template = template.replace(reg, globalObj._data[key]); 
    8.         } else { 
    9.             template = template.replace(reg, eval(`globalObj._data.${key}`)); 
    10.         } 
    11.         return render(template) 
    12.     } 
    13.  
    14.     return template; 

    首先,這個(gè)render(template)方法傳入了一個(gè)參數(shù),第一個(gè)參數(shù)是模板字符串。

    然后,我們進(jìn)入這個(gè)方法中看一下,首先,我們定義了正則/\{(.+?)\}/,用于匹配模板字符串中的{}中的內(nèi)容。如果匹配為真,就進(jìn)入這個(gè)邏輯:

     
     
     
     
    1. const key = reg.exec(template)[1]; 
    2. if (globalObj._data.hasOwnProperty(key)) { 
    3.     template = template.replace(reg, globalObj._data[key]); 
    4. } else { 
    5.     template = template.replace(reg, eval(`globalObj._data.${key}`)); 
    6. return render(template) 

    我們?cè)诘谝恍写a中看到了這行代碼const key = reg.exec(template)[1],這里使用的是reg.exec()方法,MDN這樣解釋它:

    • exec() 方法在一個(gè)指定字符串中執(zhí)行一個(gè)搜索匹配。返回一個(gè)結(jié)果數(shù)組或 null。在設(shè)置了 global 或 sticky 標(biāo)志位的情況下(如 /foo/g or /foo/y),JavaScript RegExp 對(duì)象是有狀態(tài)的。他們會(huì)將上次成功匹配后的位置記錄在 lastIndex 屬性中。使用此特性,exec() 可用來(lái)對(duì)單個(gè)字符串中的多次匹配結(jié)果進(jìn)行逐條的遍歷(包括捕獲到的匹配),而相比之下, String.prototype.match() 只會(huì)返回匹配到的結(jié)果。

    所以,通過(guò)這個(gè)方法我們?nèi)〉搅四0遄址械膡}中的內(nèi)容,它一般是我們存取數(shù)據(jù)_data中的屬性。首先,我們判斷globalObj._data對(duì)象中是否有這個(gè)key,如果有我們就使用字符串替換方法replace來(lái)把對(duì)應(yīng)的占位符key替換成所對(duì)應(yīng)的值。下面接著進(jìn)行遞歸,直到reg.test(template)返回為false。最終,render()方法返回處理后的template。

    看完render()方法,我們來(lái)看下事件處理階段,也就是eventListener()方法。

     
     
     
     
    1. // event listeners 
    2. function eventListener(el, event, cb) { 
    3.     document.querySelector(el).addEventListener(event, cb); 

    這個(gè)方法很簡(jiǎn)單,第一個(gè)參數(shù)傳入DOM選擇器,第二個(gè)參數(shù)傳入一個(gè)事件名,第三個(gè)參數(shù)傳入一個(gè)回調(diào)函數(shù)。

    最后,我們來(lái)看下Strview.js的數(shù)據(jù)響應(yīng)系統(tǒng)。

     
     
     
     
    1. // processing simple values 
    2. function ref() { 
    3.     return new Proxy(globalObj._data, { 
    4.         get: (target, key) => { 
    5.             return target[key] 
    6.         }, 
    7.         set: (target, key, newValue) => { 
    8.             target[key] = newValue; 
    9.             setTemplate(); 
    10.             return true; 
    11.         } 
    12.     }) 
    13.  
    14. // reactiveHandlers 
    15. const reactiveHandlers = { 
    16.     get: (target, key) => { 
    17.         if (typeof target[key] === 'object' && target[key] !== null) { 
    18.             return new Proxy(target[key], reactiveHandlers); 
    19.         } 
    20.         return Reflect.get(target, key); 
    21.     }, 
    22.     set: (target, key, value) => { 
    23.         Reflect.set(target, key, value); 
    24.         setTemplate(); 
    25.         return true 
    26.     } 
    27. }; 
    28.  
    29. // respond to complex objects 
    30. function reactive() { 
    31.     return new Proxy(globalObj._data, reactiveHandlers) 

    上面這些代碼主要是reactive()、ref()這兩個(gè)方法的實(shí)現(xiàn)。reactive()方法是針對(duì)復(fù)雜數(shù)據(jù)的處理,比如嵌套對(duì)象以及數(shù)組。ref()方法主要是針對(duì)簡(jiǎn)單數(shù)據(jù)的處理,像是原始值與單一非嵌套對(duì)象。

    它們兩個(gè)都是基于Proxy代理來(lái)實(shí)現(xiàn)數(shù)據(jù)的攔截與響應(yīng),MDN中這樣定義它。

    • Proxy 對(duì)象用于創(chuàng)建一個(gè)對(duì)象的代理,從而實(shí)現(xiàn)基本操作的攔截和自定義(如屬性查找、賦值、枚舉、函數(shù)調(diào)用等)。

    它們兩個(gè)Proxy對(duì)象第一個(gè)參數(shù)都是我們?cè)诔跏蓟x的globalObj._data,第二個(gè)參數(shù)是一個(gè)通常以函數(shù)作為屬性的對(duì)象。這里都定義了get()方法、set()方法,get()是屬性讀取操作的捕捉器,set()是屬性設(shè)置操作的捕捉器。

    reactive()、ref()這兩個(gè)方法實(shí)現(xiàn)不一樣的地方是reactive()方法加上了對(duì)嵌套對(duì)象判斷來(lái)實(shí)現(xiàn)遞歸。

    我們?cè)趕et()方法中看到它們都調(diào)用了setTemplate()方法,下面,我們來(lái)看下這個(gè)方法。

     
     
     
     
    1. // update the view 
    2. function setTemplate() { 
    3.     const oNode = document.querySelector(globalObj._el); 
    4.     const nNode = toHtml(render(globalObj._sourceTemplate)); 
    5.     compile(oNode, 'o'); 
    6.     compile(nNode, 'n'); 
    7.     if (globalObj._oHtml.length === globalObj._nHtml.length) { 
    8.         for (let index = 0; index < globalObj._oHtml.length; index++) { 
    9.             const element = globalObj._oHtml[index]; 
    10.             element.textContent !== globalObj._nHtml[index].textContent && (element.textContent = globalObj._nHtml[index].textContent); 
    11.         } 
    12.     } 

    首先,我們?nèi)〉匠跏蓟瘯r(shí)掛載的DOM節(jié)點(diǎn),接著我們使用toHtml()方法將render(globalObj._sourceTemplate)方法作為第一個(gè)參數(shù)傳入。

    我們先來(lái)看下toHtml()方法,這里的第一個(gè)參數(shù)domStr,也就是render(globalObj._sourceTemplate)。

     
     
     
     
    1. // string to DOM 
    2. function toHtml(domStr) { 
    3.     const parser = new DOMParser(); 
    4.     return parser.parseFromString(domStr, "text/html"); 

    toHtml()方法第一行我們實(shí)例化了一個(gè)DOMParser對(duì)象。一旦建立了一個(gè)解析對(duì)象以后,你就可以使用它的parseFromString方法來(lái)解析一個(gè)html字符串。

    然后,我們回到setTemplate()方法中,變量nNode被賦值了toHtml(render(globalObj._sourceTemplate)),這里是被處理成一個(gè)DOM對(duì)象。

    接著,執(zhí)行compile()方法。

     
     
     
     
    1. compile(oNode, 'o'); 
    2. compile(nNode, 'n'); 

    我們來(lái)看下這個(gè)compile()方法。

     
     
     
     
    1. // compile DOM 
    2. function compile(node, type) { 
    3.     const childNodesArr = node.childNodes; 
    4.     for (let index = 0; index < Array.from(childNodesArr).length; index++) { 
    5.         const item = Array.from(childNodesArr)[index]; 
    6.         if (item.childNodes && item.childNodes.length) { 
    7.             compile(item, type); 
    8.         } else if (isTextNode(item) && item.textContent.trim().length !== 0) { 
    9.             type === 'o' ? globalObj._oHtml.push(item) : globalObj._nHtml.push(item); 
    10.         } 
    11.     } 

    這個(gè)方法是將遍歷DOM元素并把每一項(xiàng)存儲(chǔ)到我們初始化定義的數(shù)組里面,分別是globalObj._oHtml和globalObj._nHtml,這個(gè)方法中用到了isTextNode()方法。

     
     
     
     
    1. // judge text node 
    2. function isTextNode(node) { 
    3.     return node.nodeType === 3; 

    這個(gè)方法第一個(gè)參數(shù)是一個(gè)Node節(jié)點(diǎn),如果它的nodeType屬性等于3就說(shuō)明這個(gè)節(jié)點(diǎn)是文本節(jié)點(diǎn)。

    最后,我們又回到setTemplate()方法中,接著執(zhí)行以下代碼:

     
     
     
     
    1. if (globalObj._oHtml.length === globalObj._nHtml.length) { 
    2.     for (let index = 0; index < globalObj._oHtml.length; index++) { 
    3.         const element = globalObj._oHtml[index]; 
    4.         element.textContent !== globalObj._nHtml[index].textContent && (element.textContent = globalObj._nHtml[index].textContent); 
    5.     } 

    判斷兩個(gè)數(shù)組的長(zhǎng)度是否一樣,如果一樣就遍歷globalObj._oHtml,最后判斷globalObj._nHtml[index].textContent是否等于globalObj._oHtml[index].textContent,如果不相等,直接將globalObj._nHtml[index].textContent賦于globalObj._OHtml[index].textContent,完成更新。

    最后,將這幾個(gè)定義的方法賦于傳入的exports對(duì)象并返回這個(gè)對(duì)象。

     
     
     
     
    1. // exports 
    2. exports.createView = createView; 
    3. exports.eventListener = eventListener; 
    4. exports.reactive = reactive; 
    5. exports.ref = ref; 
    6.  
    7. Object.defineProperty(exports, '__esModule', { value: true }); 
    8.  
    9. return exports; 

    這里,有一行代碼Object.defineProperty(exports, '__esModule', { value: true }),這行代碼其實(shí)也可以這么寫(xiě)exports.__esModule = true。表面上看就是把一個(gè)導(dǎo)出對(duì)象標(biāo)識(shí)為一個(gè) ES 模塊。

    隨著 JS 不斷發(fā)展和 Node.js 的出現(xiàn),JS 慢慢有了模塊化方案。在 ES6 之前,最有名的就是 CommonJS / AMD,AMD 就不提了現(xiàn)在基本不用。CommonJS 被 Node.js 采用至今,與 ES 模塊共存。由于 Node.js 早期模塊化方案選擇了 CommonJS,導(dǎo)致現(xiàn)在 NPM 上仍然存在大量的 CommonJS 模塊,JS 圈子一時(shí)半會(huì)兒是丟不掉 CommonJS 了。

    Webpack 實(shí)現(xiàn)了一套 CommonJS 模塊化方案,支持打包 CommonJS 模塊,同時(shí)也支持打包 ES 模塊。但是兩種模塊格式混用的時(shí)候問(wèn)題就來(lái)了,ES 模塊和 CommonJS 模塊并不完全兼容,CommonJS 的 module.exports 在 ES 模塊中沒(méi)有對(duì)應(yīng)的表達(dá)方式,和默認(rèn)導(dǎo)出 export default 是不一樣的。

    而__esModule則是用來(lái)兼容 ES 模塊導(dǎo)入 CommonJS 模塊默認(rèn)導(dǎo)出方案。

    結(jié)語(yǔ)

    至此,Strview.js的源碼分析完畢。謝謝閱讀~

    開(kāi)發(fā)版本

    推薦使用StrviewCLI搭建StrviewApp項(xiàng)目腳手架。

    • https://github.com/maomincoding/strview-app

    生產(chǎn)版本

    直接引入CDN鏈接,目前版本為1.9.0。

    • https://cdn.jsdelivr.net/npm/strview@1.9.0/dist/strview.global.js

    分享題目:一篇文章教你如何寫(xiě)成Strview.js之源碼剖析
    網(wǎng)址分享:http://www.dlmjj.cn/article/cochpcp.html