最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

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

源碼地址 gezhicui/vue-webpack

webpack 部分

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

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

比方說我現(xiàn)在的.vue 文件寫了下面這些內(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)過 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; },
  }
}

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

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

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

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

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

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

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

template 解析

在上面經(jīng)過vur-loader處理后,template以字符串形式被放到對象中,所以我們可以拿到 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 接下來就是對 js 的操作

data 響應(yīng)式處理

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

首先拿到data,為了創(chuàng)建多個組件時data不被互相影響,所以data是一個函數(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)我放到后面再說
      update(vm, key);
    },
  });
}

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

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

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

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

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

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

const allNodes = $node.querySelectorAll('*');
allNodes.forEach((node) => {
  // 這里獲取到的textContent是原原始的沒經(jīng)過任何處理的節(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);
  // 如果有雙括號語法
  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;
    }
  }
}

初始化事件池

處理完雙括號語法,我們還需要處理@click這樣的事件語法,首先,我們創(chuàng)建一個事件池,再定義兩個正則分別匹配函數(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)
      : //無參函數(shù)直接綁定
        methods[vClickVal].bind(vm);

    //存入事件池,節(jié)點(diǎn)為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);
}

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

綁定事件處理

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

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

render 頁面

執(zhí)行完上面的內(nèi)容,我們就到了最后一步 render 頁面了,我們只要把節(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是一個字符串,為了執(zhí)行字符串,所以我們需要new Function
  const r = new Function(
    'vm',
    'node',
    `
    with (vm) {
      node.textContent = ${expression};
    }
  `
  );

  r(vm, node);
}

在這里,我們先解決兩個問題

  • with 是干啥用的?
  • 為什么_render 要抽離出來?

首先先來介紹下 with

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

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

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

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

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

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

相關(guān)文章

  • vue元素樣式實(shí)現(xiàn)動態(tài)改變方法介紹

    vue元素樣式實(shí)現(xiàn)動態(tài)改變方法介紹

    vue通過js動態(tài)修改元素的樣式,如果是固定的幾個樣式,我常用的是綁定元素的calss,給不同的class寫好需要的樣式,js控制是否使用這個class
    2022-09-09
  • Vue 實(shí)現(xiàn)點(diǎn)擊空白處隱藏某節(jié)點(diǎn)的三種方式(指令、普通、遮罩)

    Vue 實(shí)現(xiàn)點(diǎn)擊空白處隱藏某節(jié)點(diǎn)的三種方式(指令、普通、遮罩)

    最近小編接到這樣的需求:彈出框(或Popover)在 show 后,點(diǎn)擊空白處可以將其 hide。針對這個需求,小編整理了三種實(shí)現(xiàn)方式,如果大家對vue 點(diǎn)擊空白隱藏節(jié)點(diǎn)問題感興趣的朋友跟隨小編一起看看吧
    2019-10-10
  • Vue實(shí)現(xiàn)Echarts圖表寬高自適應(yīng)的實(shí)踐

    Vue實(shí)現(xiàn)Echarts圖表寬高自適應(yīng)的實(shí)踐

    本文主要介紹了Vue實(shí)現(xiàn)Echarts圖表寬高自適應(yīng)的實(shí)踐,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-11-11
  • vue-json-editor json編輯器的使用

    vue-json-editor json編輯器的使用

    本文主要介紹了vue-json-editor json編輯器的使用,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Vue.js仿Metronic高級表格(一)靜態(tài)設(shè)計(jì)

    Vue.js仿Metronic高級表格(一)靜態(tài)設(shè)計(jì)

    這篇文章主要為大家詳細(xì)介紹了Vue.js仿Metronic高級表格的靜態(tài)設(shè)計(jì),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • vue中使用iview自定義驗(yàn)證關(guān)鍵詞輸入框問題及解決方法

    vue中使用iview自定義驗(yàn)證關(guān)鍵詞輸入框問題及解決方法

    這篇文章主要介紹了vue中使用iview自定義驗(yàn)證關(guān)鍵詞輸入框問題及解決方法,本文通過實(shí)例結(jié)合代碼的形式給大家介紹解決方法,需要的朋友可以參考下
    2018-03-03
  • Vue混入mixins滾動觸底的方法

    Vue混入mixins滾動觸底的方法

    這篇文章主要介紹了Vue混入mixins滾動觸底的方法,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • vue2.0之多頁面的開發(fā)的示例

    vue2.0之多頁面的開發(fā)的示例

    本篇文章主要介紹了vue2.0之多頁面的開發(fā)的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-01-01
  • 基于vue cli重構(gòu)多頁面腳手架過程詳解

    基于vue cli重構(gòu)多頁面腳手架過程詳解

    本文分步驟給大家介紹了基于vue cli重構(gòu)多頁面腳手架過程,非常不錯,具有參考借鑒價(jià)值,需要的朋友參考下
    2018-01-01
  • Vue路由傳參的三種方式實(shí)例詳解

    Vue路由傳參的三種方式實(shí)例詳解

    vue路由傳參的使用場景一般都是應(yīng)用在父路由跳轉(zhuǎn)到子路由時,攜帶參數(shù)跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于Vue路由傳參的三種方式,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-01-01

最新評論

桐城市| 晋城| 翁牛特旗| 南川市| 博罗县| 凤凰县| 曲阜市| 霍州市| 旺苍县| 右玉县| 固镇县| 白沙| 仪征市| 福海县| 婺源县| 鞍山市| 马山县| 天镇县| 黎川县| 遂溪县| 定边县| 岫岩| 榆社县| 邢台县| 霍林郭勒市| 鹿泉市| 兴化市| 乌审旗| 右玉县| 银川市| 神木县| 邢台市| 遂宁市| 诏安县| 含山县| 榆树市| 常熟市| 新巴尔虎右旗| 崇义县| 和龙市| 宁安市|