协作流程
1.职责
页面工程师
前端工程师
接口设计
1.页面入口规范
基本信息
输入参数
模板列表
接口列表
2.同步数据规范
基本信息
预填数据
注入接口
3.异步接口规范
基本信息
输入数据
输出结果
同步请求,异步请求?
版本管理
版本控制系统VCS
(Version control system)
1.分支模型
产品级的分支模型:
2.git
git是一个基于内容寻址的存储系统。基于文件内容,而不是基于文件。
安装
Windows: msysgit http://msysgit.github.io
Mac: brew install git
Ubuntu: apt-get install git
git基础操作
1.git config
用户配置:
git config --global user.name "Darcy"
git config --global user.name text.example.com
配置级别:
2.git init
初始化之后会出现一个.git/目录,下面存储着包括config文件在内的几乎所有git相关文件。
3.git status
跟踪:track
4.git add
添加文件内容到暂存区
,同时文件被跟踪
。
批量添加: git add .
添加当前目录所有文件
5..gitignore
如果有不希望跟踪的文件,那么需要配置忽略文件。
仅作用于未跟踪的文件。
gitignore常见配置:github中的示例
6.git rm
从暂存区
删除文件。
git rm --cached
仅仅从暂存区删除git rm
同时从暂存区和工作目录删除git rm $(git ls-files --deleted)
删除所有被跟踪,但是在工作目录被删除的文件
7.git commit
提交。
git commit -m "initial commit"
git commit -a -m "initial commit"
直接提交
8.git log
提交历史记录。
git log
git log --oneline
只有7位hash和提交时输入的commit messagegit log --color --graph --pretty=format:(此处省略2行)
更美观,且有分支链
上面的命令太长了,不可能每次都这样输入,因此需要配置别名alias
。
语法:
git config --global alias.shortname
例子:
git config --global alias.lg "log --color --graph --pretty=format:(此处省略2行)"
这样就可以用git lg
来表示上面那行命令了。
别名其实也存储在gitcofig文件中
9.git diff
显示版本差异。
git diff
工作目录与暂存区的差异
,指向当前的提交git diff -cached[
暂存区与某次提交的差异,默认是] git diff [
工作目录与某次提交的差异]
10.git checkout --
撤销本地修改。
即:将工作内容从暂存区复制到工作目录。
11.git reset HEAD
取消暂存。
即:将文件内容从上次提交复制到暂存区。
12.git checkout HEAD --
撤销全部改动:取消暂存 + 撤销本地修改。
git分支操作
13.git branch
git branch
新建分支git branch -d
删除指定分支git branch -v
显示所有分支信息
14.git checkout
通过移动HEAD检出版本,可用于分支切换。
git checkout
切换分支git checkout -b
新建一个分支并切换到新分支git checkout -b
切换到其他引用对象,比如commit id
或 标签git checkout -
回到上一个分支(把HEAD移动到上一个分支)
15.git reset
将当前分支恢复到某个历史版本。以下三种模式的主要区别是内容是否会恢复到工作目录和暂存区。
git reset --mixed e33e42
--mixed是默认参数,不写也行,当前内容(即原来的提交)会被复制到暂存区git reset --hard e33e42
--hard 时,当前内容(即原来的提交)会被复制到暂存区和工作目录git reset --soft e33e42
--soft时,暂存区和工作目录都不会有任何改变,原来的提交变成了一个无索引的提交,有可能会被回收,可以用git reflog
找回来捷径:
git reset HEAD^/HEAD~1/HEAD~n
HEAD的上一次提交,前第n次提交
区分reset
与 checkout
在操作分支与操作文件时的不同
16.git stash
我们在git checkout
切换分支的时候,经常会被提示“当前有未提交的内容,请commit
或stash
”,而我们通常是写到一半不希望commit的,所以这时就需要git stash
。
作用是:保存目前的工作目录和暂存区状态,返回一个干净的工作空间。
git stash save 'push to stash area'
第一步:保存git stash list
第二步:查看已有列表 会显示:stash@{0}: On master: push to stash area
git stash apply stash@{0}
第三步:把保存的内容恢复到工作目录git stash drop stash@{0}
第四步:把对应的stash命令删除掉git stash pop stash@{0}
捷径:第三步+第四步
17.git merge
假定当前在master
分支。
git merge next
合并 next 分支的内容到master分支
如有冲突,会是下面这样:
<<<<<<< HEAD
next
=======
origin/master
>>>>>>> origin/master
====上面指当前分支的提交,下面是要merge过来的分支的提交内容。
18.git rebase
修剪提交历史的基线,俗称“变基”。
git rebase master
不要在共有分支使用rebase。
19.git tag
标签,一个不变的别名。用于标记某次发布。指向一个commit对象。
git tag v0.1 e39d0b2
打完标签之后,可以直接使用标签名切换分支: git checkout v0.1
git远程操作
20.git push
提交本地历史到远程。
21.git remote
git remote add origin ~/git-server
添加一个远程仓库别名origingit remote -v
查看远程仓库信息
22.git fetch
获取远程仓库的提交历史。
git fetch origin/master
git merge origin/master
23.git pull
git pull
=
git fetch + git merge
23.git clone
获取一个远程仓库作为本地仓库。
git clone ~/git-server test2
会克隆远程仓库到 test2目录下
技术选型
模块化(JS)
一、模块
1.模块的职责:
封装实现
暴露接口
声明依赖
2.第一步:没有应用任何模块系统(反模式 Anti-Pattern)
math模块:
//math.js
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
caculator模块:
//caculator.js
var action = "add";
function compute(a, b){
switch(action){
case "add": return add(a, b);
case "sub": return sub(a, b);
}
}
可以看出 caculator
模块是依赖math
模块的。
math
模块特点:
无封装性:变量全部散落在全局里。
接口结构不明显:如果我们没有简化代码,那么并不能清楚的知道
math
到底输出了哪些接口。
caculator
模块特点:
没有依赖声明:依赖了math模块但是却没有声明。
使用全局状态:使用了action这个全局状态,应该尽量避免。
3.第二步:使用字面量(Object Literal)优化
math模块:
//math.js
var math = {
add: function add(a, b) {
return a + b;
}
sub: function sub(a, b) {
return a - b;
}
}
caculator模块:
//caculator.js
var caculator = {
action: "add",
compute: function compute(a, b){
switch(action){
case "add": return add(a, b);
case "sub": return sub(a, b);
}
}
}
math
模块特点:
结构性好:用字面量把接口进行了结构化。
访问控制差:依然没有进行控制。
caculator
模块特点:
依然没有依赖声明:依赖了math模块但是却没有声明。
无法设置私有属性:
action
虽然是成员属性,但在外部依然可以访问到。
4.第三步:使用立即执行的函数表达式IIFE
(Immediately-invoked Function Expression)解决无法设置私有属性
的问题。
caculator模块:
//caculator.js
var caculator = (function(){
var action = "add";
return {
compute: function compute(a, b){
switch(action){
case "add":
return math.add(a, b);
case "sub":
return math.sub(a, b);
}
}
}
})();
caculator
模块特点:
依然依然没有依赖声明:依赖了math模块但是却没有声明。
有了私有属性:
action
是我们要的私有属性,compute
函数可以访问到,而且在caculator
外面无法访问到。
5.第四步:增加依赖声明
caculator模块:
//caculator.js
var caculator = (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: conpute
}
})(math);
caculator
模块特点:
显示了依赖声明:把math模块作为参数传了进去,并且可以对形参进行命名,这里命名为
m
。math模块仍然污染了全局变量。
必须手动进行依赖管理:math模块是手动传进去的,必须手动保证math是在这之前就被加载了。
注意
return
的部分与原来不一样了:学名叫 揭露模块模式review module pattern
,优点是在暴露的模块进行增删查改的时候会非常方便。
6.第五步:使用命名空间(name space)解决污染全局空间的问题
帮助我们只暴露一个类似于namespace
的全局变量。而不是将math
这样的模块都注册在全局作用域中。
math模块:
//math.js
namespace("math", [], function(){
function add(a, b) {
return a + b;
}
function sub(a, b) {
return a - b;
}
return {
add: add,
sub: sub
}
})
//第一个参数为模块声明,第二个参数为依赖的模块,第三个参数为模块的构成
caculator模块:
//caculator.js
namespace("caculator", ["math"], function(m){
var action = "add";
function compute(a, b){
return m[action](a, b);
}
return {
compute: compute
}
}
依赖是统一注册在某个地方,而不是全局中的一个变量。
namespace的实现:
cache
中缓存了所有的模块。
实际返回的是createModule
这个函数,参数包括:模块名,依赖的模块,当前模块的实现。
如果只传入了一个参数,就返回这个模块cache[name]
。
取得所有依赖的模块deps
,即保证前面的模块都已经被定义好了,这样当前模块(这里为caculator
模块)才能运行。
最后初始化模并返回定义的模块cache[name]
。
该方法特点:
不再污染全局环境:把模块都定义在一个
namespace
变量中。没有依赖管理:依然是我们手动进行依赖管理。
依赖管理(dependency manage)
如果这些模块分散在不同的文件中,我们在用的时候就要对引入的脚本顺序进行手动排序。
比如 module2.js中依赖了module1.js,那么写的时候就要先写module.js,像这样:
但是我们在实际开发过程中的依赖总是很复杂。那是一条又长又复杂的依赖链。非要人肉分析是会抓狂的。而这其实是模块系统的工作。
二、模块系统
1.模块系统的职责
依赖管理(加载 / 注入 / 分析 / 初始化)
决定模块写法
2.CommonJS
//main.js
function add(a, b){
return a + b;
}
function sub(a, b){
return a - b;
}
exports.add = add
exports.sub = sub
比原来的写法多了接口暴露:exports.add = add
exports.sub = sub
//caculator.js
var math = require("./math");
function Caculator(container){
//...
}
exports.Caculator = Caculator
比原来的写法多了依赖声明: var math = require("./math");
和 接口暴露:exports.Caculator = Caculator
。
优点:
运行时支持,模块定义非常简单:只是利用了几个全局变量
exports, module, require
。文件级别的模块作用域隔离:这几个全局变量的作用域都是文件级别的,虽然JS没有文件级别的作用域,但我们对它进行了封装,使得使用时一个文件有一个作用域,它们使用起来非常安全。
可以处理循环依赖。
缺点:
不是标准组织的规范。
同步的require,没有考虑浏览器环境。而我们的浏览器文件加载是一个异步的过程,这是最大的问题,这是否就意味着我们的浏览器没办法使用了呢?当然不是。现在有很多工具比如
browserify
,比如webpack
,可以帮助我们把多个文件级别的模块打包成一个文件,这样我们引入单个文件就可以在浏览器里使用了。
因为CommonJS天然的不适合异步环境,所以出现了天然异步的AMD(Asynchronous Module Definition)
3.AMD
与我们前面的namespace非常像。
//main.js
define([], function(){
function add(a, b){
return a + b;
}
function sub(a, b){
return a - b;
}
return {
add: add,
sub: sub
}
})
比原来的写法多了包裹函数:define
,第一个参数为依赖的模块列表,第二个参数为当前模块的实现。
//caculator.js
define(["./math"], function(math){
function Caculator(container){
//...
}
return {
Caculator: Caculator
}
}
同时AMD还支持一个简单的CommonJS写法,只不过要用一层函数包裹起来define(function(require, exports){ ... })
。
优点:
专为异步I/O环境打造,适合浏览器环境。
支持类似CommonJS的书写方式。
通过插件支持可以加载非JS资源。
成熟的打包构建工具,并可结合插件实现一些预处理的工作。
缺点:
模块定义繁琐,需要额外的函数嵌套。
只是库级别的支持,需要引入额外的库,比如requireJS。
无法处理循环依赖。
无法实现条件加载,因为只是库级别的。
4.原生JS语言级别支持的模块化标准ES6 Module(Javascript module definition for future)
//main.js
function add(a, b){
return a + b;
}
function sub(a, b){
return a - b;
}
export { add, sub }
比原来的写法多了接口暴露:export {add, sub}
//caculator.js
import { math } from './math';
function Caculator(container){
//...
}
export { Caculator }
比原来的写法多了依赖声明: import { math } from './math';
和 接口暴露:export { Caculator }
。
优点:
真正官方的规范,未来的趋势。
语言级别的支持。
适应所有的JavaScript运行时环境,包括浏览器。
可以处理循环依赖。
框架(JS框架)
什么是库和框架
库
针对特定问题的解答,就有专业性
不控制应用程序的流程
被动的被调用
比如,一个DatePicker时间选择器是一个库,一个Backbone.view是一个框架。
框架
控制反转 Inverse of control <···主要区别
决定应用程序生命周期
一般会集成大量的库
下面这个图很好的解释了控制反转:
框架决定了什么时候调用库,什么时候要求你的代码去实现某些功能。
框架和库,他们都是解决方案。关于解决方案,分为7各方面:
DOM
communication 通信
Utility 工具库
Template 模板技术
Component 组件
Route 路由
Architecture MV*架构
1.DOM解决方案
重点:Selector / Manipulation(操作) / Event(dom) / Animation
jQuery
zepto.JS
Mootools
手势支持:Hammer.js
局部滚动:iScroll.js
高级动画:Velocity.js
视频播放:video.js
2.Communication(通信)解决方案
重点:XMLHttpRequest / Form / JSONP / Socket
作用:
处理与服务器的请求与响应
预处理请求数据/响应数据 & Error/Success的判断封装
多种类型请求,统一接口
处理浏览器兼容性
jQuery
zepto.JS
Reqwest
qwest
以上都是异步的请求,但对于实时性要求非常高的产品比如im聊天工具,就需要立即响应。这时需要用websocket。推荐下面的库:
socket.io
3.Utility(工具包)解决方案
重点:函数增强 & shim / Flow Control
职责:
提供JS原生不提供的功能
方法门面包装,使其易于使用。即shim(语言垫片),保证实现与规范一致。
异步队列 / 流程控制 比如promise
3.Template
三种类型:String-based / Dom-based / Living Template
4.Component组件
常用组件: Modal / Slider / DatePicker / Tabs / Editor
Bootstrap
Foundation
5.Routing路由
分类:Client Side / Server Side
职责:
监听url变化,并通知注册的模块,进行页面切换
通过Javascript进行主动跳转
历史管理
对目标浏览器的兼容性的支持
route库
6.Architecture架构(目的:解耦)
分类:MVC / MVVM / MV*
职责:
提供一种范式帮助(强制)开发者进行模块解耦
试图与模型分离
更容易进行单元测试
更容易实现应用程序的扩展
各种框架比较的参考网站:
http://todomvc.com/
https://www.javascripting.com/
https://www.javascriptoo.com/
http://microjs.com/#
7.Component组件
开发实践
系统设计
1.系统说明
2.系统分解
3.接口设计
数据类型(每个页面的每个模块都要单独定义包含的数据类型列表)
模板资源
异步接口(请求方式,请求地址,输入参数,输出结果)
页面摘要
4.工程构建
项目结构
初始代码
模拟数据
系统实现
1.组件封装
通用原件(logo,输入框,图标,按钮,翻页,复选框列表,loading)
通用列表(歌单。歌手,收藏的节目)
复合组件(比如评论)
浮层弹窗
一个组件(BannerSlifer)的栗子:
2.逻辑实现
测试发布
1.测试联调
本地测试
异步测试
对接联调
2.发布上线
打包发布
优化配置