【JS實用技巧】優化動態建立元素的方式,讓代碼更加優雅且利於維護

【JS實用技巧】優化動態建立元素的方式,讓代碼更加優雅且利於維護

引言

在前端開發中,常常須要動態添加一些元素到頁面上。那麼如何經過一些技巧,優化動態建立頁面元素的方式,使得代碼更加優雅,而且更易於維護呢?接下來咱們經過研究一些實例,一步步地找出最優方案。html

這篇文章儘可能寫得思路清晰且通俗易懂,由淺入深爲剛入門前端的新手們帶來一些思路和啓發。前端

老手們也能夠順着看下去,當作複習一次。亦或者直接跳到後半部分,去看稍微深刻一點的模板數據替換示例,一塊兒交流交流哦。jquery

因爲DOM和HTML會存在必定的歧義,因此爲了區別開來,文章中這兩個術語的意思分別是:git

  • DOM :專指文檔對象,是在JS上以對象的形式存在的。github

  • HTML:專指HTML文本,是一連串字符的集合。ajax

實例一:如何動態添加元素到頁面中

話很少說,咱們先來思考一下最基本的問題,如何用JS動態添加元素到頁面中去呢?瀏覽器

假設在點擊「添加一個乘客」按鈕的時候,須要JS動態建立出一個新的輸入框來填寫姓名:app

<h2>乘客列表:</h2>
<form class="form">
    <div class="form-group">
        乘客姓名:<input type="text" class="form-control" name="member[]">
    </div>
    <button class="create-passenger" type="button">添加一個乘客</button>
    <button type="submit">保存</button>
</form>

從上面能夠看出,要實現這個功能,咱們須要處理的HTML片斷是:框架

<div class="form-group">
    乘客姓名:<input type="text" class="form-control" name="member[]">
</div>

勉強的方案:手動複製粘貼HTML拼接成JS字符串

那麼咱們先來看看傳統的作法是這樣子的:dom

先直接手動複製粘貼HTML拼接成JS字符串,而後再插入到表單中。

$('.create-passenger').on('click', function() {
    
    // 先直接手動複製粘貼HTML拼接成JS字符串
    var html = 
        '<div class="form-group">' +
        '    乘客姓名:<input type="text" class="form-control" name="member[]">' +
        '</div>';
    // 而後再插入到表單中
    $('.form').append(html);
});

點評&分析

這是種偷懶的實現方式,在部分中小型網站、教科書上,最多見到它的身影。

在開發時的時候,某些狀況下使用這種方案,的確可能會比較快速,直接複製粘貼HTML拼成JS字符串就能夠了。

但知足這樣的條件必須是:

  1. 要拼接的HTML字符串很短;

  2. 頁面結構已經很穩定,能保證之後不會須要做出修改;

  3. 頁面HMTL和JS的代碼量都很少,或者已經直接把JS寫在頁面上了,因此即便設計不合理也能比較容易查看和維護。

問題&思考

沒有作好HTML和JS的分離,腳本強烈耦合了HTML,不妥不妥。

要是後期頁面上的HTML有了改動,必須同時記得去找出相關的腳本文件,在JS代碼中搜索並修改裏面寫死的HTML字符串才行。

換個角度再想想,若是插入的HTML很複雜,有幾百行的話。要在JS腳本中手動拼接龐大的字符串,是件很是的麻煩事情,還十分容易出差錯。

更好的方案:模板分離原則

模板分離原則:將定義模板的那一部分,與JS的代碼邏輯分離開來,讓代碼更加優雅且利於維護。

1、利用頁面上現有的DOM元素做爲模板

經過分析頁面咱們能夠知道,表單初始的時候是至少會存在一個乘客輸入項的。

因此咱們能夠複製表單上第一個乘客的DOM來做爲模板:

$('.create-passenger').on('click', function() {

    // 複製第一個乘客的DOM做爲模板
    var template = $('.form .form-group:first-child').clone();
    // 將DOM模板插入到表單中,造成新的一行
    $('.form').append(template);
});

注意點&細節解析

實例中用了jquery的clone()方法,能夠複製整個jquery包裝過的DOM對象(不包括對象綁定的事件,若是要連事件也一塊兒複製的話,能夠加個參數clone(true)哦)。

有時候複製過來的DOM對象有可能不是最原始的狀態,因此記得要初始化一下。例若有像input這樣的輸入項,要記得把value的值先初始化哦template.find('input').val('')

2、在隱藏的標籤中定義模板

若是頁面原本就沒有相關的DOM,這時候能夠手動新建一個隱藏的<div>,而後在裏面定義咱們的模板:

<div id="passenger-template" style="display: none;">
    <div class="form-group">
        乘客姓名:<input type="text" class="form-control" name="member[]">
    </div>
</div>

接下來用JS去取這個元素的內容做爲模板:

var template = $('#passenger-template > div').clone();

注意點&細節解析

用一個標籤來包裹模板的理由,

  • 一是取模板的時候能夠很方便,直接clone()或者html()就能夠了;

  • 二是爲了更好地分類和規範。例如定義模板時,要求你們都用同一種標籤和CSS類:<div class="template">

固然不必定去用<div>,也可使用別的標籤,或者自定義一個<template>標籤專門放模板,不過這時候要注意IE8下面自定義標籤會有些許問題哦。

3、在<script>標籤中定義模板

若是想更加清晰地區分開模板和正常的頁面元素的話,還能夠用<script>標籤:

<script id="passenger-template" type="text/html"> <!- 注意標籤內的type屬性 -!>
    <div class="form-group">
        乘客姓名:<input type="text" class="form-control" name="member[]">
    </div>
</script>

注意點&細節解析

<script>標籤內的type="text/html",它能告訴瀏覽器這個標籤裏面的內容不是JS腳本,能夠直接忽略不用去解析。這樣瀏覽器就不會報錯了。

還有一點是這時候就不能直接使用clone()了哦,由於<script>標籤裏面的內容不是DOM對象,而是字符串類型的HTML片斷。

因此記得要經過html()方法獲取咱們字符串形式的模板:

var template = $('#passenger-template').html(); // 獲取的是字符串,不是DOM對象

模板分離原則的好處

  1. JS和HTML作到了徹底的解耦,十分利於後期的修改和維護。

  2. 腳本上沒有了多餘的代碼,咱們在開發的時候,只需關注業務邏輯了。

  3. 不用再去手動複製粘貼HTML來拼接JS字符串,寫HTML比拼JS字符串要來的輕鬆,並且不容易出錯。因此是一個明智之舉,也算是有技巧地偷懶。

  4. 若是複製頁面現有的DOM做爲模板的話,能夠徹底脫離後期須要維護模板的限制。之後即便頁面有修改了,JS這個「添加一個乘客」的功能,也同樣能正常工做,適應性極強。

實例二:如何添加數據到動態添加的元素中

咱們繼續之前面的主題展開研究。不過此次的重點,是探究幾種添加數據的實現方式,一步步找出最佳的方案。

新增的需求是這樣的:若是想把特定的乘客信息,添加到新增的頁面元素中,那樣該怎麼辦呢?

$.ajax({
    url: '/getPassengers', // 後臺獲取全部乘客的信息
    success: function(passengers) {
        
        var html = ''; // 儲存要插入到頁面的HTML片斷
        var len = passengers.length;
        for (var i = 0; i < len; i++) {
            // 獲取帶有該乘客信息的HTML片斷
            html += get_passenger_html(passengers[i]); // 後面將詳細講這個函數的實現方式
        }
        $('.form').append(html);
    }
});

下面將集中講一下,改如何生成帶有指定乘客信息的HTML片斷,也就是這個get_passenger_html()的內部實現方式。

勉強的方案:手動將數據拼接到HTML字符串中去

function get_passenger_html(passenger) {
    
    var html = '';
    html += '<div class="form-group">';
    html += '    乘客姓名:<input type="text" class="form-control" name="member[]" ';
    html += '    value="' + passenger.name + '">'; // 將乘客姓名拼接到HTML字符串中
    html += '</div>';
    
    return html;
}

點評&分析

這個也是最傳統的數據跟HTML字符串拼接的的方式,沒有用到模板,腳本上會存在冗長的HTML字符串拼接代碼。

問題&思考

這種作法沒辦法使用以前提到的模板技術,後期維護難是一個重大問題。

數據多一點或者html複雜一點,手動拼接字符串耗費精力、容易出錯的弊端就會愈來愈顯現。

更好的方案:分離數據操做和模板定義

能不能先定義好模板,而後再作數據插入的操做呢?這樣就能夠將模板定義和數據操做分離開來了,跟JS的字符串拼接Say good bye啦。

下面展現兩種分離數據操做和模板定義的實現方式:

1、操做DOM對象來插入數據

若是要插入的數據恰好是在某個標籤或屬性內,可使用操做DOM對象的方式來插入數據:

function get_passenger_html(passenger) {
    
    var html = $('#passenger-template').html(); // 獲取HTML字符串模板
    var dom = $(html); // 先即將HTML字符轉成DOM對象
    dom.find('.name').html(passenger.name); // 找到存放乘客姓名的DOM節點並插入數據
    dom.find('.tel').html(passenger.tel); // 找到存放乘客電話的DOM節點並插入數據
    // 把處理完畢的DOM轉回HTML字符串並返回
    return dom.prop("outerHTML"); 
}

注意點&細節解析

  • 若是模板不是clone()得來的,要先用$(html)將HTML字符串轉成DOM對象,而後才能用find()去找到對應的DOM節點來操做哦。

  • html()方法只能獲取子元素的HTML字符串,要獲取包括本身的HTML字符串的話,要去讀取outerHTML屬性,這是個DOM對象原生的屬性,因此要用prop()才能獲取獲得哦。

2、替換自定義的佔位符成指定數據

第一步先安照前面講到的模板分離原則,定義了一個模板。在定義這個模板的時候,順帶添加一些帶有特殊含義的佔位符:{name}{tel}

<script id="passenger-template" type="text/html">
    <ul class="passenger-list">
        <li>
            乘客姓名:
            <span class="name">{name}</span>
        </li>
        <li>
            乘客電話:
            <span class="tel">{tel}</span>
        </li>
    </ul>
</div>

第二步就是利用String.replace()逐個替換掉這些自定義的佔位符:

function get_passenger_html(passenger) {
    
    var html = $('#passenger-template').html(); // 獲取HTML字符串模板
    // 用乘客姓名替換掉咱們自定義的佔位符
    html = html.replace(passenger.name, '{name}'); // 替換姓名佔位符
    html = html.replace(passenger.tel, '{tel}'); // 替換電話佔位符
    
    return html;
}

注意點&細節解析

佔位符的邊界要特殊一點,例如用{},這樣子就能避免在替換的時候,把其餘有類似的字符被抹掉了。

更通用的方案:智能結合模板和數據

介紹通用方案前,假設咱們獲取到的模板是下面這一段字符串:

var template = '乘客姓名:{name},他的電話是:{tel},哈哈哈哈哈。';

想要替換掉佔位符的JSON數據是:

var data = {
    name: '小神遊',
    tel: 12312423423
};

按以前介紹的方法,要一個個寫死:

template.replace('{name}', data.name); 
template.replace('{tel}', data.tel);

太麻煩了,原本已經模板上定了一次佔位符。可是到了對應的JS上也要再手寫一次,而且數據屬性名也要手寫,纔可以保證能夠替換成功。這樣子代碼寫得一點都不優雅。

懶惰的咱們,從不喜歡重複勞動。這時候新建了個通用方法,能將特定模板和對應數據智能地匹配。

使用方法是這樣的:

// 直接傳入模板和數據便可
var html = template_replace(template, data);
console.log(html);
// 輸出替換了數據的模板字符:乘客姓名:小神遊,他的電話是:12312423423,哈哈哈哈哈。

哈哈哈,直接搞定!可以智能匹配模板和數據,並且還能複用在別的地方,之後能夠偷懶了!

那麼怎樣寫這個方法,把模板和數據智能地匹配呢?

以替換佔位符{name}爲例,大致思路是:

  1. 找出模板佔位符的左右邊界,也就是{}

  2. 獲取邊界內的字符串,獲得數據屬性名,也就是name

  3. 把整個佔位符用屬性值替換掉,也就是{name}替換成data['name']

方法的總體結構

// 將模板和數據結合起來
var template_replace = function(template, data) {

    // 內部方法:獲取第一個匹配到的佔位符位置,也就是"{"和"}"的索引
    function get_next_placeholder_index_range() { ... }

    // 內部方法:將索引範圍內的字符串,替換成data中具體的屬性值
    function set_replacement(indexRange) { ... }

    // 內部方法:替換全部佔位符成爲對應數據
    function begin_replace() { ... }
    
    // 開始執行替換
    begin_replace();
    
    return template; // 返回替換完畢的模板字符串
};

內部方法:獲取佔位符位置

這個內部方法get_next_placeholder_index_range()
用於獲取第一個匹配到的佔位符位置,也就是"{"和"}"的索引

從索引0開始,查找第一個匹配到的左邊界{的索引值

var leftIndex = template.indexOf('{', 0);

從左邊界的索引開始,查找第一個匹配到的右邊界}的索引值

var rightIndex = template.indexOf('}', leftIndex);

根據狀況返回包含左右邊界索引值的對象

if (leftIndex === -1 || rightIndex === -1) { // 沒有搜素到匹配的佔位符
    return false;
} else { // 存在佔位符,返回開始和結束的索引
    return {left: leftIndex, right: rightIndex};
}

注意點:若是沒有匹配的項,indexOf()會返回-1

內部方法:替換數據

這個內部方法set_replacement(),用於將索引範圍內的字符串,替換成data中具體的屬性值。

獲取左右邊界內的字符串,不包括{}

var key = template.slice(indexRange.left + 1, indexRange.right);

注意點:slice()的第一個參數表示從哪一個index開始截取(包括這個index的字符),因此若是要忽略{的話,要從indexRange.left + 1開始截取。

注意點:slice()的第二個參數表示獲取這個index值以前的字符串,因此恰好能夠直接寫indexRange.right來忽略}了。

用屬性值提替換掉佔位符內的字符串,包括{}

template = template.replace('{' + key + '}', data[key]);

注意點:示例沒有作二維三維數據的轉換,有須要的話能夠擴展下代碼:

var key = template.slice(indexRange.left + 1, indexRange.right);
var keys = key.split('.'); // 根據點語法獲取各級的屬性名
var value = ''; // 屬性值
switch (keys.length) {
    case 1: // 一維,如{name}
        value = data[keys[0]];
        break;
    case 2: // 二維,如{name.firstName}
        value = data[keys[0]][keys[1]];
        break;
    case 3: // 三維,如{name.firstName.firstWord}
        value = data[keys[0]][keys[1]][keys[2]];
        break;
    default:;
}
template = template.replace('{' + key + '}', value);

不過擴展時要注意適度的權衡。當咱們擴展的代碼愈來愈多的時候,就證實這個自定義的函數已經開始知足不了需求了,這時候建議轉向使用第三方解決方案,後面會有介紹一個最佳的模板框架。

注意點:這個簡單示例沒有作容錯機制,目的是展現數據替換的方法。因此前提是假設模板的佔位符都已經和數據是對應的哦。

繼續遞歸替換

begin_replace(); // 繼續遞歸替換

利用begin_replace方法,檢查模板中還有沒有下一個佔位符,若是存在下一個佔位符的話,begin_replace會繼續遞歸調用get_replacement來替換下一個,這兩個函數的互相調用會一直輪迴,直到模板全部佔位符替換結束爲止。

內部方法:統籌遞歸替換數據

這個方法begin_replace()將會調用前面的定義兩個內部函數,目的是爲了統籌遞歸替換數據的操做。

檢查模板中還有沒有佔位符

var indexRange = get_next_placeholder_index_range();

開始進行替換

若是有佔位符,能夠開始進行替換了:

if (indexRange) { // 
  set_replacement(indexRange);
}

完整的使用示例

var template = '乘客姓名:{name},他的電話是:{tel},哈哈哈哈哈。';
var data = {
    name: '小神遊',
    tel: 12312423423
};
// 直接傳入模板和數據便可
var html = template_replace(template, data);
console.log(html);
// 乘客姓名:小神遊,他的電話是:12312423423,哈哈哈哈哈。

完整的代碼示例

var template_replace = function(template, data) {

  function get_next_placeholder_index_range() { 
  
    var leftIndex = template.indexOf('{', 0);
    var rightIndex = template.indexOf('}', leftIndex);
    if (leftIndex === -1 || rightIndex === -1) {
      return false;
    } else {
      return {left: leftIndex, right: rightIndex};
    }
  }

  function set_replacement(indexRange) {
    
    var key = template.slice(indexRange.left + 1, indexRange.right);
    template = template.replace('{' + key + '}', data[key]);
    begin_replace();
  }

  function begin_replace() {

    var indexRange = get_next_placeholder_index_range();
    if (indexRange) {
      set_replacement(indexRange);
    }
  }

  begin_replace();
  
  return template;
};

代碼最後大概20行左右,今後就能夠大大提升生產力,也讓之後寫的代碼都更加優雅。

第三方解決方案:ArtTemplate.js

當你須要更Power的模板功能的時候,不必定要本身寫,更理智的作法是使用成熟的模板引擎。

這裏給出我多年一直在使用的、認爲是最好的模板引擎:Artemplate.js
Github地址是:https://github.com/aui/artTem...

ArtTemplate是騰訊出的模板引擎,支持不少高級的模板操做,例如循環遍歷、條件分支等等;而且它的解析速度是衆多模板引擎中最快的。

哈哈,在咱們嘗試寫過簡單的模板解析,理解了應該怎樣善用模板和處理模板,讓代碼更加優雅且利於維護以後。用起第三方的模板引擎的時候會更加的感動:個人天,這東西怎麼會這麼方便。

結語

經過對比「勉強的方案」,和介紹各類「更好的方案」,其實總結起來都離不開一句話:讓代碼更加優雅且利於維護

記住這一點,在「夠用」和「更好」之間,咱們總要逆流而上,敢於在實踐中尋找「更好」。

其實不止是這篇文章中提到的一些小技巧,咱們在開發中還須要去處理各類類型的問題,對應的解決方案也確定不止一個,並且正在使用的方案也不必定是最優。因此要時刻有不將就的精神,多花點時間去優化。你能夠的!

文章中若有錯誤但願你們多多指正和多多包涵,我會當即改正的哈。還有要多多評論,多多交流,多多點贊哦~

謝謝你看到了最後,你們一塊兒加油!

相關文章
相關標籤/搜索