參與知乎 live — 編寫優雅的前端業務代碼總結

知乎 live 原地址:編寫優雅的前端業務代碼css

前言

當咱們在寫業務代碼的時候,咱們到底在寫什麼?html

實際上是對交互的一些處理。全部的交互都是基於用戶或者瀏覽器的一些行爲來觸發的,好比渲染頁面,在頁面onload方法觸發以後,咱們的js代碼纔會執行,好比說懶加載,根據用戶滾動或者可視區域的變化來觸發,好比按鈕的點擊以後頁面的局部刷新,好比input框輸入、表單提交、上傳文件等等,以上所說的這些行爲組成起來的就是咱們前端要寫的業務邏輯。前端

咱們所寫的業務都是基於事件來對產品功能和場景進行描述。vue

頁面的執行順序是怎樣的?react

頁面在初始化的時候,先加載css,而後加載html的節點,最後將js放在頁尾按順序執行,而後css加載,模板輸出以後css生效,js再加載,js再對頁面的模板進行交互處理,好比編譯模板,最後再把交互功能好比事件進行綁定。不管寫什麼業務,都會是這個生命週期。設計模式

一個頁面的輸出是有它的生命週期的,同時,一個js程序,好比vue或者react也是有本身的生命週期的,因此每個類,每個業務邏輯都是有本身的生命週期的。瀏覽器

下面是react的組件的生命週期:最開始的時候先拿到默認的參數屬性,而後初始化狀態,在render以前有一個事件的廣播,在render以後有一個事件的廣播,這時候這個組件就render到頁面上了。組件在運行的時候有兩個方式,一個是狀態的改變,它會觸發update,再去觸發state的改變,而後再對應地從新render,在render以前會先觸發事件廣播,render以後也會觸發一個事件廣播。另外一個方式是卸載,卸載以前會觸發一個廣播。安全

vue的生命週期和react的實際上是很像的,它只不過比react多了一步對template和el進行一個判斷的掃描,對應的也是render渲染,在渲染以後會有一個屬性的update,有一個銷燬的過程。前端框架

總結一下,vue和react的生命週期其實就是完成它這個前端框架的業務邏輯。閉包

實例

下面這段代碼是將一個面向過程的js程序改成面向對象方式以後的js程序。

(function (global, $, _, doc) {
    "use strict";

    // 定義一個構造器,是這個文件的入口
    var app = function (options) { 
        options = options || {};
        this.a = options.a;
        // ...
        this.eventMap = {
            'click .title': 'titleClick',
            'dbclick .input': 'inputDbclick',
        };

        // 初始化全部節點屬性
        this.initEles();
        this.init();
    };

    // 定義構造函數靜態屬性,掛載全部選擇器的屬性
    app.Eles = {
        pap: $('.paper'),
        centryY: $('.centry-y')
    };

    // 工具方法
    var utils = {
        has: function(arr, name) {
            return arr.indexOf(name) > -1;
        },
        // ...  
    };

    app.prototype = {
        constructor: app,
        initEles: function () {
            var eles = app.Eles;
            for (var name in eles) {
                if (eles.hasOwnProperty(name)) {
                    this[name] = $(eles[name]);
                }
            }
        },
        init: function () {
            this.bindEvent(this.eventMap);
        },
        initDrag: function () {
            // ...
        },
        uninitDrag: function () {
            // ...
        },
        bindEvent: function (maps) {
            this.initDrag();
            this.initOrdinaryEvents(maps);
        },
        unbindEvent: function (maps) {
            this.uninitDrag();
            this.unInitOrdinaryEvents(maps);
        },
        initOrdinaryEvents: function (maps) {
            this._scanEventsMap(maps, true);
        },
        unInitOrdinaryEvents: function (maps) {
            this._scanEventsMap(maps, false);
        },
        _scanEventsMap: function (maps, isOn) {
            var delegateEventSplitter = /^(\S+)\s*(.*)$/,
                bind = isOn ? this._delegate : this._undelegate;

            for (var keys in maps) {
                if (maps.hasOwnProperty(keys)) {
                    var matchs = keys.match(delegateEventSplitter);
                    bind(matchs[1], matchs[2], this[maps[keys]].bind(this));
                }
            }
        },
        _delegate: function (name, selector, func) {
            // 事件委派,將事件綁定在$(documet)上
            doc.on(name, selector, func);
        },
        _undelegate: function (name, selector, func) {
            doc.off(name, selector, func);
        },
        destroy: function() {
            this.unbindEvent();
        }
    };

    // 將構造函數掛在window上,那麼在外部也能訪問閉包內的屬性,至關於對外暴露了一個接口
    global.app = app;

    $(function () {
        // 實例化構造函數,至關於這個構造函數就開始執行了
        new app();
    });
})(this, this.jQuery, this._, this.jQuery(document));

上述代碼作的事情:

  1. 進行了生命週期的定義,把文件的入口移到了一個構造器裏面,經過new這個構造器來進行這個js程序的入口的初始化。
  2. 定義eventMap,定義bindEventinitOrdinaryEvents_scanEventsMap_delegate方法。遍歷eventMap,經過事件委派,將事件綁定在$(documet)上。以前的使用的是onclick或者on方法來進行事件的綁定,維護的時候要到各個地方去找,如今只須要關注eventMap就很方便,可以清楚知道事件、選擇器和事件處理函數名。
  3. 給構造器綁定了一個靜態屬性Eles,那就不須要用$('.paper'),而是用this.pap(initEles方法實現的)便可,好處是在壓縮的時候,字符串是不能被壓縮的,而this.pap會被壓縮,壓縮率會更高。

代碼優化的技巧

  1. if (e.target.id === 'titleDrag' || e.target.id == 'subtitleDrag') {}
    
    // 改成
    if (['titleDrag', 'subtitleDrag'].indexOf(e.target.id) > -1) {}
  2. $('.center-box').removeClass('hidden').css({
        width: ui.helper.width(),
        left: parseInt(centerY.css('left')) - Math.floor(ui.helper.width() / 2)
    });
    
    // 改成
    var width = ui.helper.width();
    var left = parseInt(centerY.css('left'), 10);
    this.centerBox.removeClass('hidden').css({
        width: width,
        left: left - Math.floor(width / 2)
    });
  3. posObj[event.target.id] = {
        id: event.target.id,
        outerHTML: this.delStyle(event.target.outerHTML),
        style: '#' + event.target.id + '{position: absolute;left:' + (ui.offset.left - 260) / mmToPx + 'mm;top:' + (ui.offset.top - 40) / mmToPx + 'mm;}'
    };
    
    // 改成
    posObj[id] = {
        id: id,
        outerHTML: this.delStyle(target.outerHTML),
        style: '#' + id + '{' + this._getPositionLT(top, left, mmToPx) + '}'
    };
  4. $(target).next().removeClass('hidden');
    $(target).next().find('.line-left').css({
        top: 40,
        left: ui.offset.left,
        height: pap.css('height'),
        width: parseInt(pap.css('width')) - ui.offset.left + 260 + 'px'
    });
    $(target).next().find('.line-top').css({
        left: 260,
        top: ui.offset.top,
        width: pap.css('width'),
        height: parseInt(pap.css('height')) - ui.offset.top + 40 + 'px'
    });
    
    // 改成
    this._drawNextLine(nextEle, left, top);
  5. this.centerBox.addClass('hidden');
    
    // 改成
    utils.hide(centerBox);
  6. var ul = this.widgetUl;
    var ht; 
    switch (e.target.id) {
        case 'thin':
            ht = '<li><div class="hr thin"></div></li>';
            ul.append(ht);
            break;
        case 'middle':
            ht = '<li><div class="hr middle"></div></li>';
            ul.append(ht);
            break;
        case 'thick':
            ht = '<li><div class="hr thick"></div></li>';
            ul.append(ht);
            break;
    }
    
    // 改成
    this.widgetUl.append('<li><div class="hr ' + e.target.id + '"></div></li>');
  7. var txt = this.txt;
    var cus = this.cus;
    var mmToPx = this.mmToPx;
    var pap = this.pap;
    var target = $(e.target);
    txt.text(target.text());
    switch (e.target.id) {
        case 'a4':
            pap.css({
                width: 210 * mmToPx + 'px',
                height: 297 * mmToPx + 'px'
            });
            cus.addClass('hidden');
            break;
        case 'b5':
            pap.css({
                width: 176 * mmToPx + 'px',
                height: 250 * mmToPx + 'px'
            });
            cus.addClass('hidden');
            break;
        case '16k':
            pap.css({
                width: 184 * mmToPx + 'px',
                height: 260 * mmToPx + 'px'
            });
            cus.addClass('hidden');
            break;
        case 'cus':
            cus.removeClass('hidden');
            break;
    }
    
    // 改成
    var txt = this.txt;
    var cus = this.cus;
    var id = e.target.id;
    var target = $(e.target);
    var setpapCss = {
        'a4': [210, 297],
        'b5': [176, 250],
        '16k': [184, 260]
    };
    txt.text(target.text());
    if (setpapCss[id]) {
        var wh = setpapCss[id];
        this.setPapWH(wh[0], wh[1]);
        utils.hide(cus);
    }
    if (id === 'cur') {
        utils.show(cus);
    }

總結

  • 常見的 js 業務場景分析以及解決思路:

    1. 選擇器濫用

      把全部的選擇器的屬性掛載構造函數靜態屬性上,統一進行管理,並經過initEles方法將其掛載在this上。

    2. 事件綁定濫用

      使用eventMap進行統一管理。

    3. 生命週期混亂,沒概念

      app在實例化的時候會往頁面里加這個組件,調用this.destory時會解綁全部事件(還能夠補充把html刪掉)。

    4. 複用性和沙盒安全
    5. 模板渲染技巧

      參考artTemplate.js。

  • 解耦你的業務 js 代碼,如何在業務中使用設計模式?

    1. 模塊化和繼承到底怎麼用。

      模塊化就是把文件拆分紅小文件。

    2. 拆分維度問題和做用域傳遞。
    3. 找到utils,拒絕ctrl+c/v。
  • 常見的一些業務優化方法總結:

    1. 判斷太多怎麼辦。

      參照上述優化代碼中的switch...case例子。

    2. dom操做到底怎麼作纔是最好的,找到最優解。

      建議用沒有樣式意義的自定義屬性data-*來做爲選擇器,而不是用具備樣式意義的class和id,由於若是有一天css類名變了,那麼js也得改變。

    3. 樣式該怎麼加。

      jQuery的css方法,或者對jQuery的css方法的進一步封裝。

  • 增長你的項目可維護性和代碼可讀性:

    1. 註釋真的好嗎?

      先寫僞代碼,全部的方法不考慮它的實現,把它拆成粒度比較細的顆粒,用方法名來描述業務邏輯,把方法名都寫好了以後,互相調用,最後再添加最細粒度的方法的實現。用這種方法來寫的話,不寫註釋也能夠,由於方法名就把業務邏輯解釋了。

    2. 格式化的問題。
    3. 單詞難拼,句子好讀,代碼50行好讀,300行難讀。
    4. 寫代碼要說人話。
相關文章
相關標籤/搜索