资讯专栏INFORMATION COLUMN

参与知乎 live — 编写优雅的前端业务代码总结

CNZPH / 2292人阅读

摘要:知乎原地址编写优雅的前端业务代码前言当我们在写业务代码的时候,我们到底在写什么其实是对交互的一些处理。遍历,通过事件委派,將事件綁定在上。事件绑定滥用使用进行统一管理。写代码要说人话。

知乎 live 原地址:编写优雅的前端业务代码

前言

当我们在写业务代码的时候,我们到底在写什么?

其实是对交互的一些处理。所有的交互都是基于用户或者浏览器的一些行为来触发的,比如渲染页面,在页面onload方法触发之后,我们的js代码才会执行,比如说懒加载,根据用户滚动或者可视区域的变化来触发,比如按钮的点击之后页面的局部刷新,比如input框输入、表单提交、上传文件等等,以上所说的这些行为组成起来的就是我们前端要写的业务逻辑。

我们所写的业务都是基于事件来对产品功能和场景进行描述。

页面的执行顺序是怎样的?

页面在初始化的时候,先加载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));

上述代码做的事情:

进行了生命周期的定义,把文件的入口移到了一个构造器里面,通过new这个构造器来进行这个js程序的入口的初始化。

定义eventMap,定义bindEventinitOrdinaryEvents_scanEventsMap_delegate方法。遍历eventMap,通过事件委派,將事件綁定在$(documet)上。之前的使用的是onclick或者on方法来进行事件的绑定,维护的时候要到各个地方去找,现在只需要关注eventMap就很方便,能够清楚知道事件、选择器和事件处理函数名。

给构造器绑定了一个静态属性Eles,那就不需要用$(".paper"),而是用this.pap(initEles方法实现的)即可,好处是在压缩的时候,字符串是不能被压缩的,而this.pap会被压缩,压缩率会更高。

代码优化的技巧

if (e.target.id === "titleDrag" || e.target.id == "subtitleDrag") {}

// 改为
if (["titleDrag", "subtitleDrag"].indexOf(e.target.id) > -1) {}

$(".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)
});

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) + "}"
};

$(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);

this.centerBox.addClass("hidden");

// 改为
utils.hide(centerBox);

var ul = this.widgetUl;
var ht; 
switch (e.target.id) {
    case "thin":
        ht = "
  • "; ul.append(ht); break; case "middle": ht = "
  • "; ul.append(ht); break; case "thick": ht = "
  • "; ul.append(ht); break; } // 改为 this.widgetUl.append("
  • ");

    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 业务场景分析以及解决思路:

    选择器滥用

    把所有的选择器的属性挂载构造函数静态属性上,统一进行管理,并通过initEles方法将其挂载在this上。

    事件绑定滥用

    使用eventMap进行统一管理。

    生命周期混乱,没概念

    app在实例化的时候会往页面里加这个组件,调用this.destory时会解绑所有事件(还可以补充把html删掉)。

    复用性和沙盒安全

    模板渲染技巧

    参考artTemplate.js。

    解耦你的业务 js 代码,如何在业务中使用设计模式?

    模块化和继承到底怎么用。

    模块化就是把文件拆分成小文件。

    拆分维度问题和作用域传递。

    找到utils,拒绝ctrl+c/v。

    常见的一些业务优化方法总结:

    判断太多怎么办。

    参照上述优化代码中的switch...case例子。

    dom操作到底怎么做才是最好的,找到最优解。

    建议用没有样式意义的自定义属性data-*来作为选择器,而不是用具有样式意义的class和id,因为如果有一天css类名变了,那么js也得改变。

    样式该怎么加。

    jQuery的css方法,或者对jQuery的css方法的进一步封装。

    增加你的项目可维护性和代码可读性:

    注释真的好吗?

    先写伪代码,所有的方法不考虑它的实现,把它拆成粒度比较细的颗粒,用方法名来描述业务逻辑,把方法名都写好了之后,互相调用,最后再添加最细粒度的方法的实现。用这种方法来写的话,不写注释也可以,因为方法名就把业务逻辑解释了。

    格式化的问题。

    单词难拼,句子好读,代码50行好读,300行难读。

    写代码要说人话。

    文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

    转载请注明本文地址:https://www.ucloud.cn/yun/95117.html

    相关文章

    • python

      Python装饰器为什么难理解? 无论项目中还是面试都离不开装饰器话题,装饰器的强大在于它能够在不修改原有业务逻辑的情况下对代码进行扩展,权限校验、用户认证、日志记录、性能测试、事务处理、缓存等都是装饰器的绝佳应用场景,它能够最大程度地对代码进行复用。 但为什么初学者对装饰器的理解如此困难,我认为本质上是对Py… Python 实现车牌定位及分割 作者用 Python 实现车牌定位及分割的实践。 ...

      chenatu 评论0 收藏0
    • 前端开发负责人修炼指北

      摘要:大家好,我叫,江湖人称吃土小叉,目前担任公司的前端负责人半年多了,一路上摸爬滚打,历经团队人员变动,近日颇有感触,于是结合自己近半年的前端负责人实践经验,权当作一个学习记录,整理归纳一下小作坊团队前端负责人的修炼要点大部分只是记录了关键词, 大家好,我叫XX,江湖人称吃土小2叉,目前担任公司的前端负责人半年多了,一路上摸爬滚打,历经团队人员变动,近日颇有感触,于是结合自己近半年的前端负...

      Drummor 评论0 收藏0
    • 学习实践 - 收藏集 - 掘金

      摘要:官网地址聊天机器人插件开发实例教程一创建插件在系统技巧使你的更加专业前端掘金一个帮你提升技巧的收藏集。我会简单基于的简洁视频播放器组件前端掘金使用和实现购物车场景前端掘金本文是上篇文章的序章,一直想有机会再次实践下。 2道面试题:输入URL按回车&HTTP2 - 掘金通过几轮面试,我发现真正那种问答的技术面,写一堆项目真不如去刷技术文章作用大,因此刷了一段时间的博客和掘金,整理下曾经被...

      mikyou 评论0 收藏0
    • Python

      摘要:最近看前端都展开了几场而我大知乎最热语言还没有相关。有关书籍的介绍,大部分截取自是官方介绍。但从开始,标准库为我们提供了模块,它提供了和两个类,实现了对和的进一步抽象,对编写线程池进程池提供了直接的支持。 《流畅的python》阅读笔记 《流畅的python》是一本适合python进阶的书, 里面介绍的基本都是高级的python用法. 对于初学python的人来说, 基础大概也就够用了...

      dailybird 评论0 收藏0
    • JavaScript 函数式编程到底是个啥

      摘要:函数是一等公民。其实闭包本身也是函数式编程的一个应用。劣势不能算是严格意义上的函数式语言,很多函数式编程的特性并没有。 随着大前端时代的到来,在产品开发过程中,前端所占业务比重越来越大、交互越来越重。传统的老夫拿起JQuery就是一把梭应付当下重交互页面已经十分乏力。于是乎有了Angular,React,Vue这些现代框架。 但随之而来的还有大量的新知识新名词,如MVC,MVVM,Fl...

      denson 评论0 收藏0

    发表评论

    0条评论

    最新活动
    阅读需要支付1元查看
    <