知乎 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));

上述代码做的事情:

  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. 写代码要说人话。

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

  1. js基础代码大全_关于前端业务代码的一些见解

    前言 如何写出可维护和可读性高的代码,这一直是一个困扰很多人的问题.关于变量如何起名.如何优化 if...else 之类的小技巧,这里就不做介绍了,推荐去看<代码大全2>,千书万书,都不如 ...

  2. 程序员笔记|如何编写优雅的Dockerfile

    导读 Kubernetes要从容器化开始,而容器又需要从Dockerfile开始,本文将介绍如何写出一个优雅的Dockerfile文件. 文章主要内容包括: Docker容器 Dockerfile 使 ...

  3. html5转apicloud,使用APICloud编写优雅的HTML5代码

    使用APICloud编写优雅的HTML5代码<一>一.实现下拉刷新: 默认样式>代码清晰简洁明了,符合ECMA262规范的callback,最少只需5行代码: apiready = ...

  4. Java编写优雅接口,看看别人后端API接口写得,那叫一个优雅!

    在分布式.微服务盛行的今天,绝大部分项目都采用的微服务框架,前后端分离方式.题外话:前后端的工作职责越来越明确,现在的前端都称之为大前端,技术栈以及生态圈都已经非常成熟:以前后端人员瞧不起前端人员,那 ...

  5. 初学者必知的Python中优雅的用法

    初学者必知的Python中优雅的用法:http://python.jobbole.com/81393/

  6. 如何优雅处理前端异常?

    如何优雅处理前端异常? 参考文章: (1)如何优雅处理前端异常? (2)https://www.cnblogs.com/fundebug/p/how_to_handle_frontend_error. ...

  7. Python好书推荐《Python代码整洁之道》——编写优雅的代码

    前言 Python是当今最流行的语言之一.相对较新的领域如数据科学.人工智能.机器人和数据分析,以及传统的专业如Web开发和科学研究等,都在拥抱Python.随着时间的推移,Python有可能会发展成 ...

  8. [转]新兵训练营系列课程——编写优雅代码

    原文:http://weibo.com/p/1001643877361430185536 课程大纲 什么是好代码 如何编写优雅的代码 如何做出优雅的设计 如何规划合理的架构 如何处理遗留代码 什么是好 ...

  9. 降低前端业务复杂度新视角:状态机范式

    无论做业务需求还是做平台需求的同学,随着需求的不断迭代,通常都会出现逻辑复杂.状态混乱的现象,维护和新增功能的成本也变的十分巨大,苦不堪言.下图用需求.业务代码.测试代码做对比: 图中分了 3 个阶段 ...

最新文章

  1. 存储引擎:MySQL系列之七
  2. VMware虚拟机安装Ubuntu
  3. java lr分析表建立程序_[源码和文档分享]基于Java实现的LR(1)分析法语法分析程序...
  4. Codeforces Round #327 (Div. 2) B Rebranding
  5. 配置Struts2.0
  6. 以实例让你真正明白mapreduce---填空式、分布(分割)编程
  7. laravel5 centos6.4下的配置体验
  8. 前端学习(2005)vue之电商管理系统电商系统之获取动态参数列表
  9. go init函数_面试录 Go语言篇 内存模型
  10. 动态执行sql语句用法
  11. ppt设置外观样式_PPT办公技巧:PPT内置主题样式的3种运用办法
  12. 机器学习—数据清洗总结
  13. Android ViewDragHelper的简单分析及应用(二)
  14. 概率论与环境数理统计 20210222
  15. 【高数】数学符号及读法大全and数学运算符号及含义
  16. [转]RUP (From 中科永联)
  17. python爬取豆瓣T250电影及保存excel(易上手)
  18. 玉米社:seo优化推广 网站收录一直不太好什么原因?
  19. 河南省多校联盟二-A
  20. 谷歌浏览器不支持javascript 属性对象获取问题

热门文章

  1. 网络协议从入门到上瘾--Scapy初探
  2. matplotlib罗列条形图(bottom)
  3. 华中科大微型计算机接口技术课后答案,2018考研华中科技大学811微机原理及接口技术考试大纲...
  4. Linux嵌入式开发——shell脚本
  5. [总结]FFMPEG视音频编解码零基础学习方法【转】
  6. apple_截至2020年3月,所有与Apple CarPlay兼容的车辆
  7. PCL 点云分割与分类 Segmentation RANSAC随机采样一致性 平面模型分割 欧氏距离分割 区域聚类分割算法 最小分割算法 超体聚类 渐进式形态学滤波器
  8. 获取一个数的每一位数
  9. C#--浮点数取小数点后两位和保留两位
  10. 前端常用60个工具方法