欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

從零實(shí)現(xiàn)一個(gè)vue文件解析器

 更新時(shí)間:2022年06月24日 14:42:32   作者:咯吱脆  
本文就討論下怎么實(shí)現(xiàn)一個(gè)處理.vue文件的loader,以及用loader處理完.vue文件怎么把內(nèi)容渲染在瀏覽器上并實(shí)現(xiàn)簡(jiǎn)單的響應(yīng)式,對(duì)vue文件解析器相關(guān)知識(shí)感興趣的朋友一起看看吧

如何從 0 處理一個(gè) vue 文件并實(shí)現(xiàn)簡(jiǎn)單的響應(yīng)式?

在現(xiàn)在的前端工程化中,打包工具是不可或缺的,其中webpack無(wú)疑是占據(jù)了主導(dǎo)地位,當(dāng)然也有尤大搞的vite,但是論生態(tài)和使用人數(shù),至少在目前webpack還是更勝一籌。

打包工具能幫助我們打包前端文件,在webpack中,不同后綴的文件通過(guò)不同loader來(lái)處理。

本文就討論下怎么實(shí)現(xiàn)一個(gè)處理.vue文件的loader,以及用loader處理完.vue文件怎么把內(nèi)容渲染在瀏覽器上,并實(shí)現(xiàn)簡(jiǎn)單的響應(yīng)式。

源碼地址 gezhicui/vue-webpack

webpack 部分

首先進(jìn)行 webpack 打包,把.vue 文件通過(guò) vue-loader 處理。

實(shí)現(xiàn)一個(gè)簡(jiǎn)易的vue-loader,通過(guò)一系列正則,最終一個(gè).vue 文件的內(nèi)容會(huì)被包裝到一個(gè)對(duì)象中

比方說(shuō)我現(xiàn)在的.vue 文件寫(xiě)了下面這些內(nèi)容:

<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>

那么經(jīng)過(guò) vue-loader 處理,就會(huì)變成一個(gè)對(duì)象:

{
  template:
   `<div>
     <h2>{{ count + 1 }}</h2>
      <button @click="plus(1)">+</button>
  </div>`,
  name: 'App',
  data() {
    return { count: 0 }
  },
  methods: {
    plus(num) { this.count += num; },
  }
}

那么,在瀏覽器執(zhí)行這個(gè)文件的時(shí)候,我們就能通過(guò)createApp方法,把這個(gè)對(duì)象使用 createApp 進(jìn)行處理,掛載到頁(yè)面上

createApp 實(shí)現(xiàn)部分

在 vue 的main.js文件中,我們通常會(huì)把根組件傳遞給createApp作為入?yún)ⅲ?

import App from './App';
import { createApp } from '../modules/vue';
createApp(App).mount('#app');

那我們實(shí)現(xiàn)的重點(diǎn)就在于createApp對(duì)vue 組件的處理,以及在createApp的返回內(nèi)容(就是 vm)中添加mount方法,實(shí)現(xiàn)處理完的節(jié)點(diǎn)的掛載

接下來(lái)就一步步實(shí)現(xiàn)createApp,首先,我們先來(lái)定義一個(gè) vm,一會(huì)兒所有的屬性都可以放在 vm 上,同時(shí)把vue-loader解析過(guò)的文件對(duì)象中的內(nèi)容給解構(gòu)出來(lái)

function createApp(component) {
  const vm = {};
  const { template, methods, data } = component;
}

template 解析

在上面經(jīng)過(guò)vur-loader處理后,template以字符串形式被放到對(duì)象中,所以我們可以拿到 dom 元素字符串,把他轉(zhuǎn)成 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 接下來(lái)就是對(duì) js 的操作

data 響應(yīng)式處理

vue 的核心就在于響應(yīng)式,vue2 通過(guò)Object.defineProperty實(shí)現(xiàn)響應(yīng)式,我們來(lái)實(shí)現(xiàn)個(gè)簡(jiǎn)單的響應(yīng)式處理

首先拿到data,為了創(chuàng)建多個(gè)組件時(shí)data不被互相影響,所以data是一個(gè)函數(shù)

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觸發(fā)節(jié)點(diǎn)更新,至于實(shí)現(xiàn)我放到后面再說(shuō)
      update(vm, key);
    },
  });
}

這樣,我們就監(jiān)聽(tīng)了data中每個(gè)屬性的getset,實(shí)現(xiàn)了數(shù)據(jù)的響應(yīng)式處理

初始化數(shù)據(jù)池

在上面的 template 解析中,我們已經(jīng)拿到了template轉(zhuǎn)換過(guò)后的節(jié)點(diǎn),但是有個(gè)問(wèn)題,節(jié)點(diǎn)的內(nèi)容沒(méi)有經(jīng)過(guò)任何處理,如{{count + 1}}會(huì)原封不動(dòng)的展示在瀏覽器中,我們希望的是最終展示的是 count 這個(gè)變量+1 的結(jié)果,所以我們需要對(duì)雙括號(hào)語(yǔ)法進(jìn)行解析

我們先定義一個(gè)正則表達(dá)式,匹配{{}}中的內(nèi)容,以及定義一個(gè)節(jié)點(diǎn)數(shù)據(jù)池

// 節(jié)點(diǎn)數(shù)據(jù)池
const exprPool = new Map();
// 正則獲取雙括號(hào)中內(nèi)容
const regExpr = /\{\{(.+?)\}\}/;

然后,從我們剛剛定義的vm.$node中拿到所有節(jié)點(diǎn),并查看該節(jié)點(diǎn)是否有雙括號(hào)語(yǔ)法,如果有的話(huà)存入節(jié)點(diǎn)數(shù)據(jù)池中

const allNodes = $node.querySelectorAll('*');
allNodes.forEach((node) => {
  // 這里獲取到的textContent是原原始的沒(méi)經(jīng)過(guò)任何處理的節(jié)點(diǎn)內(nèi)容,如{{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);
  // 如果有雙括號(hào)語(yǔ)法
  if (exprMatched) {
    const poolInfo = checkExpressionHasData($data, exprMatched[1].trim());
    // 把節(jié)點(diǎn)存入節(jié)點(diǎn)數(shù)據(jù)池
    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;
    }
  }
}

初始化事件池

處理完雙括號(hào)語(yǔ)法,我們還需要處理@click這樣的事件語(yǔ)法,首先,我們創(chuàng)建一個(gè)事件池,再定義兩個(gè)正則分別匹配函數(shù)

const eventPool = new Map();
// 匹配函數(shù)名
const regStringFn = /(.+?)\((.+?)\)/;
// 匹配函數(shù)參數(shù)
const regString = /\'(.+?)\'/;

同樣的,我們也需要遍歷所有節(jié)點(diǎn)

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
      ? //有參函數(shù)傳入args
        methods[fnInfo.methodName].bind(vm, ...fnInfo.args)
      : //無(wú)參函數(shù)直接綁定
        methods[vClickVal].bind(vm);

    //存入事件池,節(jié)點(diǎn)為key,事件為value
    eventPool.set(node, {
      type: vClick,
      handler,
    });
    //刪除dom上的attr,不然瀏覽器查看源代碼就會(huì)顯示自定義事件  這樣不好
    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);
}

這樣,我們有擁有了節(jié)點(diǎn)數(shù)據(jù)池和事件池,接下來(lái)我們就要拿節(jié)點(diǎn)數(shù)據(jù)池和事件池做操作了

綁定事件處理

有了事件池,我們就要把事件池中的事件綁定到 dom 元素上去,讓事件能夠觸發(fā)。這步其實(shí)是很容易的,因?yàn)槲覀儼?vue 事件加入事件池中時(shí),key 是 dom 元素,value 是事件處理函數(shù),只要把他們兩個(gè)互相綁定就行

function (vm) {
  //node:key  info:value
  for (let [node, info] of eventPool) {
        // type:事件類(lèi)型  handler:事件處理函數(shù)
    let { type, handler } = info;
    //在vue中,是用this.function 去訪(fǎng)問(wèn)方法,所以方法要被綁定到vm上
    vm[handler.name] = handler;
    //給節(jié)點(diǎn)綁定事件處理函數(shù)
    node.addEventListener(type, vm[handler.name], false);
  }
}

render 頁(yè)面

執(zhí)行完上面的內(nèi)容,我們就到了最后一步 render 頁(yè)面了,我們只要把節(jié)點(diǎn)數(shù)據(jù)池中的節(jié)點(diǎn)內(nèi)容渲染到瀏覽器

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是一個(gè)字符串,為了執(zhí)行字符串,所以我們需要new Function
  const r = new Function(
    'vm',
    'node',
    `
    with (vm) {
      node.textContent = ${expression};
    }
  `
  );

  r(vm, node);
}

在這里,我們先解決兩個(gè)問(wèn)題

  • with 是干啥用的?
  • 為什么_render 要抽離出來(lái)?

首先先來(lái)介紹下 with

with 的作用是用來(lái)改變標(biāo)識(shí)符的查找優(yōu)先級(jí),優(yōu)先從 with 指定對(duì)象的屬性中查找。e.g:

var a = 1;
var obj = {
  a: 2,
};
with (obj) {
  console.log(a); //2
}

那為什么_render 要單獨(dú)抽成一個(gè)函數(shù)? 因?yàn)樵谇懊娴?data 響應(yīng)式處理 中,set被觸發(fā)時(shí),我們需要拿到新的數(shù)據(jù)值去update頁(yè)面元素,這時(shí)候就也會(huì)用到render函數(shù),那就簡(jiǎn)單實(shí)現(xiàn)下上面提到的updata

export function update(vm, key) {
  //在節(jié)點(diǎn)數(shù)據(jù)池中查找哪個(gè)節(jié)點(diǎn)的key==當(dāng)前改變的key,找到則重新render
  exprPool.forEach((info, node) => {
    if (info.key === key) {
      _render(vm, node, info);
    }
  });
}

到此為止,就能實(shí)現(xiàn)一個(gè)完整的不通過(guò)任何第三方插件解析 vue 文件,并實(shí)現(xiàn)簡(jiǎn)單的響應(yīng)式處理了??!

到此這篇關(guān)于實(shí)現(xiàn)一個(gè)vue文件解析器的文章就介紹到這了,更多相關(guān)vue文件解析器內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Element Carousel 走馬燈的具體實(shí)現(xiàn)

    Element Carousel 走馬燈的具體實(shí)現(xiàn)

    這篇文章主要介紹了Element Carousel 走馬燈的具體實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • VUE中的無(wú)限循環(huán)代碼解析

    VUE中的無(wú)限循環(huán)代碼解析

    本文通過(guò)實(shí)例代碼給大家介紹了vue中的無(wú)限循環(huán),代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧
    2017-09-09
  • Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法

    Vue Cli 3項(xiàng)目使用融云IM實(shí)現(xiàn)聊天功能的方法

    這篇文章主要介紹了Vue Cli 3項(xiàng)目 使用融云IM實(shí)現(xiàn)聊天功能,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-04-04
  • vue項(xiàng)目base64轉(zhuǎn)img方式

    vue項(xiàng)目base64轉(zhuǎn)img方式

    這篇文章主要介紹了vue項(xiàng)目base64轉(zhuǎn)img方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue2項(xiàng)目增加eslint配置代碼規(guī)范示例

    vue2項(xiàng)目增加eslint配置代碼規(guī)范示例

    這篇文章主要為大家介紹了vue2項(xiàng)目增加eslint配置代碼規(guī)范示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-08-08
  • Vue3中使用this的詳細(xì)教程

    Vue3中使用this的詳細(xì)教程

    在vue3中新的組合式API中沒(méi)有this,那我們?nèi)绻枰玫絫his怎么辦?下面這篇文章主要給大家介紹了關(guān)于Vue3中使用this的詳細(xì)教程,需要的朋友可以參考下
    2023-07-07
  • 在Vue中延遲執(zhí)行某個(gè)函數(shù)的實(shí)現(xiàn)方式

    在Vue中延遲執(zhí)行某個(gè)函數(shù)的實(shí)現(xiàn)方式

    在Vue中延遲執(zhí)行某個(gè)函數(shù),你可以使用setTimeout()函數(shù)或者Vue提供的生命周期鉤子函數(shù),本文通過(guò)一些示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作有一定的幫助,需要的朋友可以參考下
    2023-12-12
  • Vue2 的12種組件通訊

    Vue2 的12種組件通訊

    之前文章我們描述給過(guò)Vue3 的七通信使用,今天中五篇文章我們?cè)賮?lái)看看Vue2 的通信使用寫(xiě)法的相關(guān)資料,需要的朋友可以參考下面文章的具體內(nèi)容
    2021-09-09
  • vue.js實(shí)現(xiàn)圖書(shū)管理功能

    vue.js實(shí)現(xiàn)圖書(shū)管理功能

    這篇文章主要為大家詳細(xì)介紹了vue.js實(shí)現(xiàn)圖書(shū)管理功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • VUE3使用Element-Plus時(shí)如何修改ElMessage中的文字大小

    VUE3使用Element-Plus時(shí)如何修改ElMessage中的文字大小

    在使用Element-plus的Elmessage時(shí)使用默認(rèn)的size無(wú)法滿(mǎn)足我們的需求時(shí),我們可以自定義字體的大小,但是直接重寫(xiě)樣式后,并沒(méi)有起作用,甚至使用::v-deep深度選擇器也沒(méi)有效果,本文介紹VUE3使用Element-Plus時(shí)如何修改ElMessage中的文字大小,感興趣的朋友一起看看吧
    2023-09-09

最新評(píng)論