「产品前端架构」技术选择

2018-10-11  本文已影响0人  Rella7

技术选择

模块化

NOTE:以下讨论都是基于 JavaScript 的模块组织(每个模块均以文件形式组织),而非工程的模块化。

The secret to building large app is never build arge apps. Break your applications into small pieces. Then, assemble those testable, bite-sized pieces into your big application.

Justin Meyer

其他语言中的模块支持

但在 JavaScript 中并不存在模块组织在并不支持,于是产生了很多,模块系统。

模块的职责

模块的使用

反模式(Anti-Pattern)

反模式既没有使用任何设计模式。

math.js

function add(a, b) {
  return a + b;
}
function sub(a, b) {
  return a - b;
}

上面的代码有下面的几个缺点:

calculator.js

var action = 'add';
function compute(a, b) {
  switch (action) {
    case 'add': return add(a, b);
    case 'sub': return sub(a, b);
  }
}

上面的代码也有几个缺点:

字面量(Object Literal)

math.js

var math = {
  add: function(a, b) {
    return a + b;
  },
  sub: function(a, b) {
    return a - b;
  }
};

结构性好,但没有访问控制。

calculator.js

var calculator = {
  action: 'add',
  compute: function(a, b) {
    switch (action) {
      case 'add': return add(a, b);
      case 'sub': return sub(a, b);
    }
  }
}

同样没有依赖声明

IIFE(Immediately-invoked Function Expresion)

其为自执行函数。

版本一

calculator.js

var calculator = (function(){
  var action = 'add';
  return {
    compute: function(a, b) {
      switch (action) {
        case 'add': return add(a, b);
        case 'sub': return sub(a, b);
      }
    }
  }
})();

上面的代码可以进行访问控制,但是不能进行依赖声明。

版本二

calculator.js

var calculator = (function(m){
  var action = 'add';
  function compute(a, b) {
    switch (action) {
      case 'add': return m.add(a, b);
      case 'sub': return m.sub(a, b);
    }
  }
  return {
    compute: compute;
  }
})(math)

上面的代码虽然可以显示的声明依赖,但是仍然污染了全局变量,而且必须手动进行依赖管理。

命名空间(Namespace)

命运空间可以解决全局变量的污染的问题。

math.js

namespace('math', [], function(){
  function add(a, b) { return a + b; }
  function sub(a, b) { return a - b; }
  return {
    add: add,
    sub: sub
  }
})

calculator.js

//                     依赖声明        依赖注入
//                         |               |
namespace('calculator', ['math'], function(m){
  var action = 'add';
  function compute(a,b) {
    return m[action](a, b);
  }
  return {
    compute: compute;
  }
})

模块管理

复杂的模块管理,不能单纯的通过代码文件的排列顺序来进行管理。于是引入了模块系统,它有下面的职责:

常用的模块系统有 Common.JSAMD、语言基本的模块化。

CommonJS

CommonJS 是一个模块规范,通常适用于非浏览器环境(NodeJS)。

A module spec for JavaScript outside the browser.

math.js

function add(a, b) {
  return a + b;
}
function sub(a, b) {
  return a - b;
}
exports.add = add;
exports.sub = sub;

calculator.js

// 依赖声明
var math = require('./math');

function Calculator(container) {
  // ...
}
Calculator.prototype.compute = function(){
  this.result.textContent = math.add(...);
}

// 接口暴露
exports.Calculator = Calculator;

优点

缺点

# browserify 为 npm 下命令行工具
# > 为 Linux/Unix 添加至命令
browserify file0.js > file1.js;

打包后的文件如下所示。

(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){

},{}]},{},[1]);
AMD(Asynchronous Module Definition)

适合异步环境的依赖管理方案。

math.js

//   依赖列表
//      |
define([], function(){
  function add(a, b) { return a + b; }
  function sub(a, b) { return a - b; }
// 接口暴露
  return {
    add: add,
    sub: sub
  }
})

calculator.js

define(['./math'], function(math){
  function Calculator(container) {
    // ...
  }
  Calculator.prototype.compute = function(){
    this.result.textContent = math.add(...);
  };

  // 暴露接口
  return {
    Calculator: Calculator;
  }
})

优点

缺点

Simplified CommonJS Wrapping

使用同样的 CommonJS 的依赖管理书写方法,之后在使用正则表达式来提取依赖列表。

define(function(require, exports){
  // 依赖声明
  var math = require('./math');

  function Calculator(container) {
    // ...
  }
  Calculator.prototype.compute = function(){
    this.result.textContent = math.add(...);
  }

  // 接口暴露
  exports.Calculator = Calculator;
})
Loader Plugins

允许调用处理脚本外的其他资源(例如 HTML 与 CSS 文件),这样就可以形成一个完整的组件。

完整组件 = 结构 + 逻辑 + 样式
ECMAScript 6 Module

ECMAScript 6 中的模块化管理。

math.js

function add(a, b) {
  return a + b;
}
function sub(a, b) {
  return a- b;
}
// export 关键字暴漏接口
export {add, sub}

calculator.js

import {add} from './math';


class Calculator {
  constructor(container) {}
  compute(){
    this.result.textContent = add(+this.left.value, +this.right.value);
  }
}
export{Calculator}

优点

缺点

SystemJS

SystemJS 是一个动态模块加载器,下面是它的一下特性:

模块管理的对比

使用插件工具,可以将后三种模块管理系统进行相互转换。

框架

NOTE:以下讨论都是基于 JavaScript 的框架。

库(Library)与框架(Framework)的区别

tech_lib_and_frame.png

为针对特定问题的解答具有专业性,不控制应用的流程且被动调用。框架 具有控制翻转,决定应用的生命周期,于是便集成了大量的库。

解决方案

常见的解决方案针对的方面:

使用外部专业解决方案的原因 可以提高开发效率,可靠性高(浏览器兼容,测试覆盖),也配备优良的配套(文档及工具)。如果外部框架的质量可可靠性无法保证或无法满足业务学期时则不应该选择外部的框架。

实际项目中的使用

DOM

与其相关的有 SelectorManipulationEvent(DOM)Animation
它的主要职责则为为下面的这些:

常用的 DOM 库有 jQuery(使用链式接口),zepto.JSMOOTOO.JS(使用原生 DOM 对象,通过直接跨站了 DOM 原生对象)。

基础领域

库名 大小 兼容性 优点 缺点
MOOTOO.JS 96KB IE6+ 概念清晰、无包装对象、接口设计优秀、源码清晰易懂、不局限于 DOM 与 AJAX 扩展原生对象(致命)、社区衰弱
jQuery 94KB IE6+ 社区强大普及率高、包装对象、专注于 DOM 包装对象(容易混淆)
zepto.JS 25KB IE10+ 小且启动快、接口与 jQuery 兼容、提供简单手势操作 无法与 jQuery 100% 对于、支持浏览器少、功能弱

专业领域

领域 库名 大小 描述
手势 Hammer.JS 12KB 常见手势封装(Tab、Hold、Transform、Swifp)并支持自定义
高级动画 Velocity.JS 12KB 复杂动画序列实现,不仅局限于 DOM
视频播放 Video.JS 101KB 类似原生 video 标签的使用方式,对低级浏览器使用 flash 播放器
局部滚动 isscroll.JS 13KB 移动端position:fix + overflow:scroll的救星
Communication

与其相关的有 XMLHttpRequestFormJSONPSocket
它的主要职责则为为下面的这些:

库名 大小 支持
Reqwest 3.4KB JSONP支持、稳定 IE6+支持、CORS 跨域、Promise/A 支持
qwest 2.5KB 代码少、支持XMLHttpRequest2、CORS 跨域、支持高级数据类型(ArrayBuffer、Blob、FormData)

实时性要求高的需求

库名 支持
socket.io 实时性、支持二进制数据流、智能自动回退支持、支持多种后端语言(NodeJS 最为稳妥)
Utility(Lang)

与其相关的有 函数增强 & Shim(保证实现与规范一致)Flow Control
它的主要职责则为为下面的这些:

库名 大小 描述
es5-shim 53KB 提供 ES3 环境下的 ES5 支持
es6-shim 38KB
underscore 16.5KB 兼容 IE6+ 的扩展功能函数
Lodash 50KB 其为 underscore 的高性能版本,方法多为 runtime 编译出来的
Templating

与其相关的有 String-basedDOM-basedLiving Template

基于字符串的模板

tech_template0.jpg

之后的数据修改展现不会进行变化,如果重新绘制(性能低)页面则会去除已有的 DOM 事件。

基于 DOM 的模板

tech-template1.jpg

修改数据可以改变显示(性能更好)也会保留 DOM 中的已有事件,最终导致 DOM 树与数据模型相联系。

Living-Template

tech_template0.jpg

其拼接了字符串模板和 DOM 模板的技术(类似 Knockout.JS 注释的实现),最终导致 DOM 树与数据模型相联系。

String-based DOM-based Living-Template
好处 可以服务器端运行
解决方案 dust.JS、hogan、dot.JS Angular.JS、Vue.JS、Knockout Regular.JS、Ractive.JS、htmlbar
初始化时间 ☆☆☆ ☆☆
动态更新 ☆☆☆ ☆☆☆
DOM 无关 ☆☆☆ ☆☆
语法 ☆☆☆ ☆☆
学习成本 ☆☆☆ ☆☆
SVG 支持 ☆☆ ☆☆
安全性 ☆☆☆
Component

与其相关的有 ModalSliderDatePickerTabsEditor(其为产品开发中最耗时也是最必要的一部分)。它的主要职责则为为下面的这些:

组件库名 版本 特定 支持
Bootstrap 3.x Mobile First 流式栅格,基于 LESS与 SASS 组织可定制 UI,提供大量组件 IE8+
Foundation 5.x Mobile First 流式栅格,基于 SASS 组织,可定制 UI,提供大量组件 IE9+

NOTE:有存在不使用 jQuery 版本的 Bootstrap 可供使用。

Router

与其相关的有 Client SideServer Side。它的主要职责则为为下面的这些:

路由库名 大小 特定 支持
page.JS 6.2KB 类似 Express.Router 的路由规则的前端路由库 IE8+
Director.JS 10KB 可以前后端使用同一套规则定义路由 IE6+
Stateman 10KB 处理深层复杂路由的独立路优库 IE6+
crossroad.JS 7.5KB 老牌路由库,API 功能较为繁琐
Architecture(解耦)

与其相关的有 MVCMVVCMV*,解耦又可以通过很多方式来实现(例如事件、分层)。它的主要职责则为为下面的这些:

下面以 MVVM为例:

tech_mvvm.png

NOTE:MV* 不等同于 SPA,路由是 MV* 系统的课定位状态信息来源。
NOTE+:单页系统的普世法则为可定位的应用程序状态都应该统一由路由系统进入,以避免网状的信息流。
NOTE++:库与框架选择站点microjs javascriptOO JavaScripting

上一篇下一篇

猜你喜欢

热点阅读