<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在現在的前端工程化中,打包工具是不可或缺的,其中webpack
無疑是佔據了主導地位,當然也有尤大搞的vite
,但是論生態和使用人數,至少在目前webpack
還是更勝一籌。
打包工具能幫助我們打包前端檔案,在webpack
中,不同字尾的檔案通過不同loader
來處理。
本文就討論下怎麼實現一個處理.vue
檔案的loader
,以及用loader
處理完.vue
檔案怎麼把內容渲染在瀏覽器上,並實現簡單的響應式。
原始碼地址 gezhicui/vue-webpack
首先進行 webpack 打包,把.vue
檔案通過 vue-loader
處理。
實現一個簡易的vue-loader
,通過一系列正則,最終一個.vue
檔案的內容會被包裝到一個物件中
比方說我現在的.vue 檔案寫了下面這些內容:
<template> <div> <h2>{{ count + 1 }}</h2> <button @click="plus(1)">+</button> </div> </template> <script> export default { name: 'App', data () { return { count: 0 } }, methods: { plus (num) { this.count += num; } } } </script>
那麼經過 vue-loader
處理,就會變成一個物件:
{ template: `<div> <h2>{{ count + 1 }}</h2> <button @click="plus(1)">+</button> </div>`, name: 'App', data() { return { count: 0 } }, methods: { plus(num) { this.count += num; }, } }
那麼,在瀏覽器執行這個檔案的時候,我們就能通過createApp
方法,把這個物件使用 createApp
進行處理,掛載到頁面上
在 vue 的main.js
檔案中,我們通常會把根元件傳遞給createApp
作為入參,如:
import App from './App'; import { createApp } from '../modules/vue'; createApp(App).mount('#app');
那我們實現的重點就在於createApp
對vue 元件的處理,以及在createApp
的返回內容(就是 vm)中新增mount
方法,實現處理完的節點的掛載
接下來就一步步實現createApp
,首先,我們先來定義一個 vm,一會兒所有的屬性都可以放在 vm 上,同時把vue-loader
解析過的檔案物件中的內容給解構出來
function createApp(component) { const vm = {}; const { template, methods, data } = component; }
在上面經過vur-loader
處理後,template
以字串形式被放到物件中,所以我們可以拿到 dom 元素字串,把他轉成 dom 元素
/* template: `<div> <h2>{{ count + 1 }}</h2> <button @click="plus(1)">+</button> </div>`, */ vm.$node = createNode(template); function createNode(template) { const _tempNode = document.createElement('div'); _tempNode.innerHTML = template; return getFirstChildNode(_tempNode); }
這樣,我們就拿到了 html 接下來就是對 js 的操作
vue 的核心就在於響應式,vue2 通過Object.defineProperty
實現響應式,我們來實現個簡單的響應式處理
首先拿到data
,為了建立多個元件時data
不被互相影響,所以data
是一個函數
vm.$data = data(); for (let key in vm.$data) { Object.defineProperty(vm, key, { get() { return vm.$data[key]; }, set(newValue) { vm.$data[key] = newValue; // update觸發節點更新,至於實現我放到後面再說 update(vm, key); }, }); }
這樣,我們就監聽了data
中每個屬性的get
和set
,實現了資料的響應式處理
在上面的 template 解析中,我們已經拿到了template
轉換過後的節點,但是有個問題,節點的內容沒有經過任何處理,如{{count + 1}}
會原封不動的展示在瀏覽器中,我們希望的是最終展示的是 count 這個變數+1 的結果,所以我們需要對雙括號語法進行解析
我們先定義一個正規表示式,匹配{{}}
中的內容,以及定義一個節點資料池
// 節點資料池 const exprPool = new Map(); // 正則獲取雙括號中內容 const regExpr = /{{(.+?)}}/;
然後,從我們剛剛定義的vm.$node
中拿到所有節點,並檢視該節點是否有雙括號語法,如果有的話存入節點資料池中
const allNodes = $node.querySelectorAll('*'); allNodes.forEach((node) => { // 這裡獲取到的textContent是原原始的沒經過任何處理的節點內容,如{{count + 1}} const vExpression = node.textContent; /* exprMatched:{ 0: "{{ count + 1 }}" 1: " count + 1 " groups: undefined index: 0 input: "{{ count + 1 }}" } */ const exprMatched = vExpression.match(regExpr); // 如果有雙括號語法 if (exprMatched) { const poolInfo = checkExpressionHasData($data, exprMatched[1].trim()); // 把節點存入節點資料池 poolInfo && exprPool.set(node, poolInfo); } }); function checkExpressionHasData(data, expression) { for (let key in data) { if (expression.includes(key) && expression !== key) { // count + 1,返回{key:count,expression:count+1} return { key, expression, }; } else if (expression === key) { // count,返回{key:count,expression:count} return { key, expression: key, }; } else { return null; } } }
處理完雙括號語法,我們還需要處理@click
這樣的事件語法,首先,我們建立一個事件池,再定義兩個正則分別匹配函數
const eventPool = new Map(); // 匹配函數名 const regStringFn = /(.+?)((.+?))/; // 匹配函數引數 const regString = /'(.+?)'/;
同樣的,我們也需要遍歷所有節點
const allNodes = $node.querySelectorAll('*'); allNodes.forEach((node) => { const vClickVal = node.getAttribute(`@click`); if (vClickVal) { /* 比如@click='plus(1)',解析完成的fnInfo就是 fnInfo:{ args: [1] methodName: "plus" } */ const fnInfo = checkFunctionHasArgs(vClickVal); const handler = fnInfo ? //有參函數傳入args methods[fnInfo.methodName].bind(vm, ...fnInfo.args) : //無參函數直接繫結 methods[vClickVal].bind(vm); //存入事件池,節點為key,事件為value eventPool.set(node, { type: vClick, handler, }); //刪除dom上的attr,不然瀏覽器檢視原始碼就會顯示自定義事件 這樣不好 node.removeAttribute(`@${vClick}`); } }); function checkFunctionHasArgs(str) { const matched = str.match(regStringFn); if (matched) { const argArr = matched[2].split(','); const args = checkIsString(matched[2]) ? argArr // ['1'] : argArr.map((item) => Number(item)); return { methodName: matched[1], args, }; } } function checkIsString(str) { return str.match(regString); }
這樣,我們有擁有了節點資料池和事件池,接下來我們就要拿節點資料池和事件池做操作了
有了事件池,我們就要把事件池中的事件繫結到 dom 元素上去,讓事件能夠觸發。這步其實是很容易的,因為我們把 vue
事件加入事件池中時,key 是 dom 元素,value 是事件處理常式,只要把他們兩個互相繫結就行
function (vm) { //node:key info:value for (let [node, info] of eventPool) { // type:事件型別 handler:事件處理常式 let { type, handler } = info; //在vue中,是用this.function 去存取方法,所以方法要被繫結到vm上 vm[handler.name] = handler; //給節點繫結事件處理常式 node.addEventListener(type, vm[handler.name], false); } }
執行完上面的內容,我們就到了最後一步 render 頁面
了,我們只要把節點資料池中的節點內容渲染到瀏覽器上
function render(vm) { exprPool.forEach((info, node) => { _render(vm, node, info); }); } function _render(vm, node, info) { //info:{key: 'count',expression 'count + 1'} const { expression } = info; //expression是一個字串,為了執行字串,所以我們需要new Function const r = new Function( 'vm', 'node', ` with (vm) { node.textContent = ${expression}; } ` ); r(vm, node); }
在這裡,我們先解決兩個問題
首先先來介紹下 with
with 的作用是用來改變識別符號的查詢優先順序,優先從 with 指定物件的屬性中查詢。e.g:
var a = 1; var obj = { a: 2, }; with (obj) { console.log(a); //2 }
那為什麼_render 要單獨抽成一個函數? 因為在前面的 data 響應式處理 中,set
被觸發時,我們需要拿到新的資料值去update
頁面元素,這時候就也會用到render
函數,那就簡單實現下上面提到的updata
export function update(vm, key) { //在節點資料池中查詢哪個節點的key==當前改變的key,找到則重新render exprPool.forEach((info, node) => { if (info.key === key) { _render(vm, node, info); } }); }
到此為止,就能實現一個完整的不通過任何第三方外掛解析 vue 檔案,並實現簡單的響應式處理了!!
到此這篇關於實現一個vue檔案解析器的文章就介紹到這了,更多相關vue檔案解析器內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45