架构
传统前端项目用三剑客 JavaScript、HTML、CSS,就传统的项目结构已经不能满足日益状大的大型应用的需求。如果想构建一个易维护、代码简洁、性能优化程度高的项目就离不开前端的架构。
架构的目的
提升质量和效率
如果没有架构,不同人写代码风格不一样,存在不同缺陷,甚至低级的bug。很难更新项目用到的技术。如果滥用组件库和各种框架就会导致项目体积的臃肿,很难和前端新出现的技术接轨,没办法跟上新规范。新技术无法得到引入,技术无法统一,使得团队的整体技术能力无法得到提升,也无法提供技术上的通用解决方案,从团队的角度来考量的话,效率非常低下。
同时,因为技术过于陈旧,再加上代码没有统一规范,导致碰到页面业务逻辑比较复杂,或者对老页面进行维护的时候,产生Bug的概率非常高,产品质量不能得到保障。
怎么进行架构
- 架构是一个抽象的过程,它是架构师根据自己的经验对大量具体业务项目进行分析,发现其中的规律,抽象出具体的规范,最终又应用于具体的业务项目中去。比如常说的MVVM就是一种规范。
- 要把跟业务无关的问题都在架构层面处理掉。如果代码压缩,打包这种工程化的问题都要在架构层面统一解决的。要做到业务的归业务,架构的归架构。
- 架构要考虑到可以方便团队成员提供和使用通用技术解决方案。比如分页组件这种。
- 架构设计的时候要综合考虑当前的主流技术跟自己业务系统的实际情况。因为前端正处在高速发展,各种新技术,工具,插件,框架层出不穷,要结合项目实际情况运用已经成熟的技术。
合理的架构应该是:模块化、组件化、工程化、规范化。
一、工程化
工程化把工作所需要的工具和流程做到标准化。减少人的操作,把重复的工作交给代码来做,保证质量和标准的统一。
工程化工具:
- 模块化与组件化:npm、es6、seajs, React、Vue、AngularJS
- 代码版本管理: git
- 代码风格管理:jscs,editorconfig
- 代码编译:babel,less、sass、scss,imgmin、csssprit、inline-svg
- 代码质量管理(QA):eslint、mocha
- 代码构建:webpack
- 项目脚手架:yeoman
- 持续集成/持续交付/持续部署:jenkins
- 本地化与国际化
工程化项目:
- 在配置初始化项目文件结构和基本文件依靠命令行(工具)自动生成。
- 确定代码规范,缩进,换行,以及各种预编译工具less,coffee,保证输出代码的标准一致。
- 对JS文件是否规范化,进行单元测试,不用手动复制到jshint上去检测,现在配置grunt监听文件变动,自动检测显示检验结果,还可以通过配置构建工具自动刷新浏览器,实现文件实时变动监听。
- 以前压缩合并文件用手工复制到压缩工具,然后复制到一个文件里面,现在配置一个grunt,gulp可以做自动任务,实时编译,并且检测文件改变而做出响应。
- 以前发布到服务器上,要手动使用FTP软件上传,现在也可以用工具自动打包上传。
二、模块化
通行的JavaScript模块规范主要有两种:CommonJS和AMD
CommonJS
我们先从CommonJS谈起,因为在网页端没有模块化编程,只是页面JavaScript逻辑复杂,但也可以工作下去,在服务端却一定要有模块,所以虽然JavaScript在web端发展这么多年,第一个流行的模块化规范却由服务器端的JavaScript应用带来,CommonJS规范是由NodeJS发扬光大,这标志着JavaScript模块化编程正式登上舞台。
定义模块
根据CommonJS规范,一个单独的文件就是一个模块。每个模块都是一个单独的作用域,也就是说,在该模块内部定义的变量,无法被其他模块读取,除非定义为global对象的属性。
模块输出
模块只有一个出口,module.exports对象,我们需要把模块希望输出的内容放入该对象。
加载模块
加载模块使用require方法,该方法读取一个文件并执行,返回文件内部的module.exports对象。
不同的实现对require时的路径有不同要求,一般情况下可以省略js拓展名,可以使用相对路径,也可以使用绝对路径,甚至可以省略路径直接使用模块名
AMD
AMD 既 Asynchronous Module Definition,中文名是异步模块定义的意思
requirejS主要解决两个问题
- 多个js文件可能有依赖关系,被依赖的文件需要早于依赖它的文件加载到浏览器。
- js加载的时候浏览器会停止页面渲染,加载文件越多,页面失去响应的时间越长。
CMD
CMD 既 Common Module Definition 通用模块定义,CMD规范是国内发展出来的,就像AMD有个requireJS,CMD有个浏览器的实现SeaJS,SeaJS要解决的问题和requireJS一样,只不过在模块定义方式和模块加载(可以说运行、解析)时机上有所不同
AMD和CMD区别
最明显的区别就是在模块定义时对依赖的处理不同
- AMD推崇依赖前置,在定义模块的时候就要声明其依赖的模块。
- CMD推崇异步懒加载,只有在用到某个模块的时候再去require
AMD和CMD最大的区别是对依赖模块的执行时机处理不同,注意不是加载的时候或者方式不同。很多人说requireJS是异步加载模块,SeaJS是同步加载模块,这么理解实际上是不准确的,其实加载模块都是异步的,只不过AMD依赖前置,js可以方便知道依赖模块是谁,立即加载,而CMD就近依赖,需要使用把模块变为字符串解析一遍才知道依赖了那些模块,这也是很多人诟病CMD的一点,牺牲性能带来开发的便利性,实际上解析模块用的时间短到可以忽略。
webpack时代
webpack的优点:
- require.js的所有功能它都有
- 编译过程更快,因为require.js会去处理不需啊的文件
- 还有一个额外的好处就是你不需要再做一个封装的函数,require.js中你得这样define(['jquery'],function(jquery){})
- 现在你需要一个很大的封装去定义每个模块,然后你需要在require.js的配置文件中将每个模块的路径都配出来,用过requirejs都会遇到
对比requirejs seajs webpack特有的属性:
- 对CommonJS、AMD、ES6的语法做了兼容
- 对js、css、图片等资源文件都支持打包(css都可以合成多个css文件包,sass和less虽然也是模块化的加载合并,可是css和js分离的关联不大,这里的css可以和js有更大的关联,更细致区分加载的js)
- 串联式模块加载器以及插件机制,让其具有更好的灵活性和扩展性,列如提供对CoffeeScript、ES6的支持
4.有独立的配置文件webpack.config.js
5.可以将代码切割成不同的chunk,实现按需加载,降低了初始化时间
6.支持SourceUrls和sourceMaps,易于调试
7.具有强大的Plugin接口,大多是内部插件,使用起来比较灵活
8.webpack使用异步IO并具有多级缓存。这使得webpack很快且在增量编译上更加快 - 双服务器模式
三、组件化
保证组件的封闭性。因为JS方面是模块化的。组件的功能界限问题。也就是什么是应该在组件内部实现,什么是应该由组件的调用者来实现的。对组件功能界限的定义是只负责UI相关的功能,所有的业务逻辑都是从调用者传递过的。也既是写在param.js。所以param.js文件是非常重要的一个文件,里面基本包涵了这个页面所有业务处理逻辑。很显然,随着页面业务逻辑变得复杂,js文件将会变得越来越大。没关系,把不同的组件参数拆分到不同的js文件里面去实现,然后建个专门文件夹把它们组织起来。
规范化
项目目录结构非常清晰。当进行开发的时候,哪些代码应该放到哪里都进行了明确的规定,并且每个文件的功能都尽量清晰并且单一。
比如:顶级目录结构如下图:
image
- src文件夹存放的是所有的源代码和其他静态资源(比如图片,iconfont)。
-
dist文件夹存放的是所有编译后的代码。
3.build文件夹存放的是所有工程化所需要的代码。
4.document文件夹存放的文档
src目录结构,如图:
image - app文件夹里的每一个子文件夹代表了一个页面,每个页面所用到的所有静态资源都存放在这个子文件下面(除了引用的公共资源以外),构建的时候,每个子文件夹会生成自己的静态资源供页面引用。
- common文件夹里面的所有代码在构建的时候会单独生成js文件和css文件供页面引用。所以一个页面会引用两个js和两个css,里面存放的事每个页面都有用到的一些公用资源。比如触屏端使用了react,那么跟react相关的那些包就会放在common里面。
- components文件夹里面存放的是公用组件,每一个子文件夹代表了一个组件。有可能是通用的功能组件,比如分页组件、Loading组件、ModalDialog组件。也有可能是一个通用的业务组件,比如站点通用头部,通用footer,通用分享组件。注意,在其他地方引用这些组件时,是不需要写相对路径的,直接写组件名字就可以了,比如import pager from 'pager' 。这样对使用者更方便。
- lib文件夹存放的是通用的js类库。比如检测浏览器的browserDetect.js,处理日期用的dateUtil.js。同样的,在其他地方需要引入这些JS时,也不需要写相对路径,直接写JS的名字就可以了。比如import {isIE} from 'browserDetect'。
- style文件夹里面存放的一些公用的sass资源。比如function,mixing,variable。其他的sass文件需要引入这些资源的时候,使用方式跟使用通用js一样,直接@import "base.scss"即可。
架构是个不断完善的过程,而把架构尤其是跟规范相关的部分落实到具体业务系统里面更是个团队不断磨合的过程。它最终考验的,同时也是最终磨合出来的是团队的成熟度。