Mac终端常用命令

Mac命令行操作

1、列出文件

1
2
ls 参数 目录名        
参数 -w 显示中文,-l 详细信息, -a 包括隐藏文件

例: 看看驱动目录下有什么:ls /System/Library/Extensions

2、转换目录

1
cd  目录名

例:转换到驱动目录:cd /System/Library/Extensions

3、建立新目录

1
mkdir 目录名

例:在驱动目录下建一个备份目录 backup:mkdir /System/Library/Extensions/backup

4、拷贝文件

1
2
cp 参数 源文件 目标文件    
参数-R 表示对目录进行递归操作

例:想把桌面的Natit.kext 拷贝到驱动目录中 cp -R /User/用户名/Desktop/Natit.kext /System/Library/Extensions参数-R表示对目录进行递归操作,kext在图形界面下看起来是个文件,实际上是个文件夹。

5、删除文件

1
2
rm 参数 文件 
参数 -r当前目录不包含子目录 -rf表示递归和强制

例:想删除驱动的缓存 rm -rf /System/Library/Extensions.mkext,千万要小心使用,如果执行了rm -rf /你的系统就全没了

6、移动文件

1
mv 源路径  目标路径

例:想把AppleHDA.Kext 移到桌面 mv /System/Library/Extensions/AppleHDA.kext /User/用户名/Desktop,把AppleHDA.Kext 移到备份目录中 mv /System/Library/Extensions/AppleHDA.kext /System/Library/Extensions/backup

7、文本编辑

1
nano 文件名

例:编辑natit Info.plist nano /System/Library/Extensions/Natit.kext/Info.plist

Mac命令目录操作

命令名 命令名 使用举例
mkdir 创建一个目录 mkdir dirname
rmdir 删除一个目录 rmdir dirname
mvdir 移动或重命名一个目录 mvdir dir1 dir2
cd 改变当前目录 cd dirname
pwd 显示当前目录的路径名 pwd
ls 显示当前目录的内容 ls -la

Mac命令文件操作

命令名 功能描述 使用举例
cat 显示或连接文件 cat filename
od 显示非文本文件的内容 od -c filename
cp 复制文件或目录 cp file1 file2
rm 删除文件或目录 rm filename
mv 改变文件名或所在目录 mv file1 file2
find 使用匹配表达式查找文件 find . -name “*.c” -print
file 显示文件类型 file filename

Mac命令选择操作

命令名 功能描述 使用举例
head 显示文件的最初几行 head -20 filename
tail 显示文件的最后几行 tail -15 filename
cut 显示文件每行中的某些域 cut -f1,7 -d: /etc/passwd
colrm 从标准输入中删除若干列 colrm 8 20 file2
diff 比较并显示两个文件的差异 diff file1 file2
sort 排序或归并文件 sort -d -f -u file1
uniq 去掉文件中的重复行 uniq file1 file2
comm 显示两有序文件的公共和非公共行 comm file1 file2
wc 统计文件的字符数、词数和行数 wc filename
nl 给文件加上行号 nl file1 >file2

Mac命令进程操作

命令名 功能描述 使用举例
ps 显示进程当前状态 ps u
kill 终止进程 kill -9 30142

Mac命令时间操作

命令名 功能描述 使用举例
date 显示系统的当前日期和时间 date
cal 显示日历 cal 8 1996
time 统计程序的执行时间 time a.out

Mac下常见vim的命令:

  1. 在默认的”指令模式”下按 i 进入编辑模式

  2. 在非指令模式下按 ESC 返回指令模式

  3. 在”指令模式”下输入:

    1
    2
    3
    4
    :w	保存当前文件 
    :q 退出编辑,如果文件为保存需要用强制模式
    :q! 强制退出不保存修改
    :wq 组合指令, 保存并退出
  4. 在”指令模式”下移动:

    1
    2
    3
    4
    h	左 
    j 下
    k 上
    l 右

参考资料

Mac OS X Terminal 101:终端使用初级教程

mac 终端 常用命令

ECMAScript6、React、Vue、Angular2.0学习资料大全

ECMAScript 6学习资料集锦

React学习资料大全

API文档:

React 中文官网

React 入门教程

Redux 中文文档

flux todo-list

React学习精彩博文:

ReactJS 傻瓜教程

Flux 傻瓜教程

React 数据流管理架构之 Redux 介绍

Flux 架构入门教程

深入到源码:解读 redux 的设计思路与用法

使用Redux管理你的React应用

Redux 核心概念

React JS 和 FLUX

Vue学习资料大全

Vue 1.0

Vue 2.0

vue-cli脚手架

Vue 每次更新变化

与webpack 一起使用所需插件

vue-loader-example

vue-html-loader

Vue学习精彩博文:

尤小右

Vue.js:轻量高效的前端组件化方案

勾三股四

Vue + webpack 项目实践

Vue.js 源码学习笔记

稀土掘金

Vue 组件化开发实践

前端乱炖
专栏名:Vue.js 中文入门

其他博文

组件与组件之间的通信传值 – vuex

VUE.JS 官方示例初探(ES6 改写)

Vuejs自己的构建工具

如何用vue.js搭建APP

(1/2)Vue构建单页应用最佳实战

EXPRESS结合WEBPACK的全栈自动刷新

webpack-hot-middleware

webpack-dev-middleware

vue-vueRouter-webpack

Angular学习资料大全

Angular 2.0官网

Angular 2.0文档

Angular GitHub

Angular2-learning-cn

Angular学习精彩博文:

AngularJS2.0教程(一)快速上手

NuclearJS学习资料大全

NuclearJS 官网

NuclearJS GitHub

node版本管理

nvm 和 n

在 node 的版本管理工具中,nvm 自然声名远扬,然而我们也不能忘了来自 TJ 的 n。这两种,是目前最主流的方案。

关于这两个工具如何安装和使用,这里不再赘言,请见它们各自的主页:

  • creationix/nvm
  • tj/n

接下来我们着重关注一下 nvm 和 n 的运作机制和特性。

版本管理工具nvm

如果想在同一台机器,同时安装多个版本的node.js,就需要用到版本管理工具nvm。

1
2
$ git clone https://github.com/creationix/nvm.git ~/.nvm
$ source ~/.nvm/nvm.sh

安装以后,nvm的执行脚本,每次使用前都要激活,建议将其加入~/.bashrc文件(假定使用Bash)。激活后,就可以安装指定版本的Node。

1
2
3
4
5
6
7
8
9
10
11
# 安装最新版本
$ nvm install node

# 安装指定版本
$ nvm install 0.12.1

# 使用已安装的最新版本
$ nvm use node

# 使用指定版本的node
$ nvm use 0.12

nvm也允许进入指定版本的REPL环境。

1
$ nvm run 0.12

如果在项目根目录下新建一个.nvmrc文件,将版本号写入其中,就只输入nvm use命令即可,不再需要附加版本号。

下面是其他经常用到的命令。

1
2
3
4
5
6
7
8
# 查看本地安装的所有版本
$ nvm ls

# 查看服务器上所有可供安装的版本。
$ nvm ls-remote

# 退出已经激活的nvm,使用deactivate命令。
$ nvm deactivate

n

n 是一个需要全局安装的 npm package。

1
npm install -g n

这意味着,我们在使用 n 管理 node 版本前,首先需要一个 node 环境。我们或者用 Homebrew 来安装一个 node,或者从官网下载 pkg 来安装,总之我们得先自己装一个 node —— n 本身是没法给你装的。

然后我们可以使用 n 来安装不同版本的 node。

在安装的时候,n 会先将指定版本的 node 存储下来,然后将其复制到我们熟知的路径 /usr/local/bin,非常简单明了。当然由于 n 会操作到非用户目录,所以需要加 sudo 来执行命令。

所以这样看来,n 在其实现上是一个非常易理解的方案。

n使用

切换node使用版本

1
2
3
4
5
$ n
0.10.1
0.10.15
o 0.10.21
0.11.8

如果你要安装其他的版本(比如0.11.12),那么如下

1
2
3
4
$ n 0.11.12
install : 0.11.12
mkdir : /usr/local/n/versions/0.11.12
fetch : http://nodejs.org/dist/v0.11.12/node-v0.11.12-darwin-x64.tar.gz

安装最新的版本

1
$n latest

安装稳定版本

1
$n stable

注:通过n安装的node存放在/usr/local/n/versions目录中。

查看可使用和安装的node版本

1
n ls

删除某版本node

1
2
3
$n rm 0.10.26 v0.11.12
或者
$n - 0.11.12

以指定的版本来执行脚本

1
2
$n use 0.10.21 some.js
$n use 0.11.12 --harmony some.js #带参数

切换到之前的版本

1
$n prev

查看某版本node的安装路径

1
2
$n bin 0.11.12
/usr/local/n/versions/0.11.12/bin/node

命令别名

1
2
3
4
which   bin
use as
list ls
- rm

参考资料:

京东购物H5活动CP重构规范

##目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
一、页面视觉输出标准

二、开发工作流程

1.开发目录结构

2.JS框架

3.REM换算

4.HTML统一页面结构

5.统一的reset css

三、编码规范

1.HTML/CSS命名规范

2.唯一根节点

3.嵌套层级

4.TAB标准

5.z-index标准

6.1px像素边框

7.活动规则弹窗

四、交付要点(非常重要)

1.优惠券

2.商品模块

3.固定角标元素

4.文字单行多行占位

5.模块活字与图片

6.模块的状态切换

7.TAB的选中与非选中状态

8.模块的显示和隐藏状态

9.弹窗

10.浮层

五、页面性能要求

1.页面兼容的目标(机型/系统)

2.加载速度、请求数与资源压缩

3.其他性能要求点

六、页面交付验收点

七、页面交付流程

京东购物H5活动CP重构规范,旨在统一CP开发人员的编码规范、提高代码质量,方便后期协作,提高开发效率。

提示:初次阅读者请务必认真阅读本文。在页面重构启动前,请先认真查看需求文档、页面交互图以及设计规范,充分了解需求后,再与需求方确认会出现页面业务逻辑场景(特别注意各种弹窗、浮层、模块状态等情况),避免页面最终交付时产生缺失,减少开发过程中的不必要的反复修改。

一、页面视觉输出标准

  • 活动页面采用750px的视觉设计稿输出,全部采用rem单位构建页面布局,部分场景结合使用zoom/scale参考demo

  • 以iPhone6 WeChat页面为基准

二、开发工作流程

1.开发目录结构

1
2
3
4
5
6
7
webapp/
├── css/
├── sass/
├── images/
├── js/
├── index.html
└── other.html

下载 模块文件

2.JS框架

重构页面有涉及到脚本交互,需要使用JS框架请统一采用 zepto.js,引入如下CDN地址。

生产阶段:

1
2
//生产阶段CDN地址,适用http协议
<script src="http://wq.360buyimg.com/fd/promote/base/zepto.min.js"></script>

交付阶段:

1
2
//交付阶段正式CDN地址,适用http协议、https协议
<script src="//wq.360buyimg.com/fd/promote/base/zepto.min.js"></script>

3.REM换算

规定375px宽度下,<html>节点的font-size20px

使用过程中,可以手动转换模块尺寸到rem单位,也可以结合sass函数(推荐)。

REM转换公式:

1
REM值 = 750设计稿中模块实际尺寸/40

若使用sass,则可参考如下转换函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@function pxTorem($px) {
@if $px == 0 {
@return 0;
}
@if $px <=2 and $px > 0 {
@return 1px;
}
@else {
@return $px / ($px * 0 + 1) / 40 * 1rem;
}

}

//使用方式:
.body{
width:pxTorem(750px);
//或 带不带px皆可
width:pxTorem(750);
}

了解rem参考文章:web app变革之rem

4.HTML统一页面结构

为了方便快速接入开发页面,统一页面结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE HTML>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" >
<meta name="format-detection" content="telephone=no" >
<meta http-equiv="x-dns-prefetch-control" content="on"/>
<link rel="dns-prefetch" href="//wq.360buyimg.com">
<title>专题活动名XX</title>
<link rel="stylesheet" type="text/css" href="css/style.css">
<!-- 动态兼容多设备显示脚本 -->
<script type="text/javascript">
!function(){var c=768;document.write('<style id="o2HtmlFontSize"></style>');var d=function(){var e,f;if(document&&document.documentElement){e=document.documentElement.clientWidth,f=document.documentElement.clientHeight}if(!e||!f){if(window.localStorage["o2-cw"]&&window.localStorage["o2-ch"]){e=parseInt(window.localStorage["o2-cw"]),f=parseInt(window.localStorage["o2-ch"])}else{a();return}}var h=c&&c<e?c/375:e/375,g=f/603;window.localStorage["o2-cw"]=e,window.localStorage["o2-ch"]=f;window.zoom=window.o2Zoom=h;document.getElementById("o2HtmlFontSize").innerHTML="html{font-size:"+(h*20)+"px;}.o2-zoom,.zoom{zoom:"+(h/2)+";}.o2-scale{-webkit-transform: scale("+h/2+"); transform: scale("+h/2+");}"},b,a=function(){if(b){return}b=setInterval(function(){document&&document.documentElement&&document.documentElement.clientWidth&&document.documentElement.clientHeight&&(d(),clearInterval(b),b=undefined)},100)};d();window.addEventListener("resize",d)}();
</script>
</head>
<body>
<!-- S 页面内容 -->
<div class="wrapper"></div>
<!-- E 页面内容 -->
</body>
</html>

5.统一的reset css

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
@charset "utf-8";
/* 重置样式 */
* {
-webkit-tap-highlight-color: rgba(0,0,0,0);
outline: 0
}

blockquote,body,button,dd,dl,dt,fieldset,form,h1,h2,h3,h4,h5,h6,hr,input,legend,li,ol,p,pre,td,textarea,th,ul {
margin: 0;
padding: 0;
vertical-align: baseline
}

img {
border: 0 none;
vertical-align: top
}

em,i {
font-style: normal
}

ol,ul {
list-style: none
}

button,h1,h2,h3,h4,h5,h6,input,select {
font-size: 100%;
font-family: inherit
}

table {
border-collapse: collapse;
border-spacing: 0
}

a {
text-decoration: none
}

a,body {
color: #666
}

body {
margin: 0 auto;
height: 100%;
font-size: 14px;
font-family: Helvetica,STHeiti STXihei,Microsoft JhengHei,Microsoft YaHei,Arial;
line-height: 1.5;
-webkit-text-size-adjust: 100%!important;
text-size-adjust: 100%!important
}

input[type=text],textarea {
-webkit-appearance: none;
-moz-appearance: none;
appearance: none
}

三、编码规范

1.HTML/CSS命名规范

1)HTML/CSS文件命名规范

确保文件命名总是以字母开头而不是数字,且字母一律小写,以下划线连接且不带其他标点符号,更多信息参见:凹凸实验室–HTML/CSS文件命名规范

2)ClassName命名规范

ClassName的命名应该尽量精短、明确,必须以字母开头命名,且全部字母为小写,单词之间统一使用下划线 “_” 连接,更多信息参见:凹凸实验室–ClassName命名规范

2.唯一根节点

强制使用<div class="wrapper"></div>做为根节点,每一个页面的只存在一个根节点 class="wrapper"

1
2
3
<!-- S 页面内容 -->
<div class="wrapper"> 包含住页面内容</div>
<!-- E 页面内容 -->

并为这个节点强制添加以下样式:

1
2
3
4
5
6
.wrapper{
width: 18.75rem; // 计算方式 750/40 = 18.75rem
height: auto;//如果没有高度限制,使用auto
overflow: hidden;//必选
margin:0 auto;
}

3.嵌套层级

标签结构尽量简单,嵌套不宜过深,尽量控制在 5 级内。

1
2
3
4
5
6
7
8
9
10
11
<div class="level1">
<div class="level2">
<div class="level3">
<div class="level4">
<div class="level5">
不能再嵌套了哦!!
</div>
</div>
</div>
</div>
</div>

4.TAB标准

1)吸顶或吸底的TAB状态需齐全

2)吸顶时需有占位处理。

采用以下标准结构:

占位节点  
    └── TAB容器  
            ├── TAB1  
            ├── TAB2  
            ├──  ...  
            └── TABn

参考如下代码:

1
2
3
4
5
6
7
8
<div class="Tabs">
<nav>
<a href="#">TAB</a>
<a href="#">TAB</a>
<a href="#">TAB</a>
<a href="#">TAB</a>
</nav>
</div>

示例:

吸顶部分

5.z-index标准

z-index最大值不得超过700。

z-index值范围:

  • 普通元素:0~100
  • floating/吸顶/吸底:101~200
  • 弹窗:201~300
  • loading/分享蒙层:301~400

6.1px像素边框

不使用rem,直接使用1px,通过transform:scale(.5)进行缩放处理。

示例:

弹窗

参考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
.border:before {
border: 1px solid #e5e5e5;
top: 0;
left: 0;
right: -100%;
bottom: -100%;
z-index: 1;
-webkit-transform: scale(.5);
-ms-transform: scale(.5);
transform: scale(.5);
-webkit-transform-origin: 0 0;
-ms-transform-origin: 0 0;
transform-origin: 0 0;
pointer-events: none;
}

7.活动规则弹窗

活动规则弹窗,如果视觉稿对滚动条没有做定制,则统一为活动规则的容器添加弹性滚动的样式:

1
-webkit-overflow-scrolling:touch;

如果有滚动条定制需求,由于上述代码会与滚动条css冲突,所以定制滚动的情况下不添加弹性滚动。如下是一个滚动条样式参考:

1
2
3
4
5
/*定制滚动条*/
::-webkit-scrollbar{width:2px; height:2px;}
::-webkit-scrollbar-button{width:0;height:0;}
::-webkit-scrollbar-corner{display:block; }
::-webkit-scrollbar-thumb{background-clip:padding-box;background-color:#ffffff;}

四、交付要点(非常重要)

1.优惠券

1)优惠券展示形式自适应

优惠券通常由2个到三个并列展示,需做自适应居中处理,并包含优惠券展示多种形式,参见示例截图。

2)券额占位

优惠券额度通常涉及到2位、3位或更多,务必做好券额度占位,防止位数过多错位,参见示例截图。

示例:

弹窗

2.商品模块

1)整块点击

商品模块外层包裹以及内层元素不允许使用a标签,模块具体的点击区域务必与产品经理沟通。

示例:

弹窗

2) 局部点击

若单品内有按钮交互行为,如领券,按钮,则不允许使用a标签实现,可用span、i等标签代替。

3.固定角标元素

固定角标元素,均需要提供有固定角标和没有固定角标的多种样式展示。

示例:

角标

4.文字单行多行占位

文字单行多行时,采用定高处理方式,溢出省略号,防止文字过少样式错乱,请特别注意。

示例:

弹窗

5.模块活字与图片

1) 凡文字部分尽量做成活字

2) 常见按钮、小标题、商品说明等,须做成活字,且节点宽度自适应

3) 艺术字体如主标题、slogan等明显需要艺术效果的可用图片代替

示例:

按钮

6.模块的状态切换

凡涉及到商品列表售罄或其他状态,均需要提供齐全。

示例:

商品状态

7.TAB的选中与非选中状态

常见的TAB多种状态,须在HTML代码注释中,标明选中与非选中状态时切换的class名称,使用方式说明清楚明了。

示例:

按钮

1
2
3
4
5
6
7
8
<div class="ft_nav_tab ">
<div class="nav_tab_mod">
<!-- 开发注意,选中态添加"on" -->
<a href="" class="on">手机数码</a>
<a href="">家电电器</a>
<a href="">潮流女包</a>
</div>
</div>

8.模块的显示和隐藏状态

在HTML代码注释中,标明显示与隐藏状态时切换的class名称。

1
2
3
4
<!-- 模块显示的class为"show" -->
<div class="mod_sharetips ">
<p>点击右上角,<br>分享到【朋友圈】或发送给微信好友</p>
</div>

9.弹窗

1)弹窗状态齐全

页面含多种弹窗,须标明清楚提供齐全,并统一写在页面上(建议新开页面专门写弹窗如alert.html)。如以下浮层:提示弹窗、页面逻辑弹窗、选择用户/地区弹窗、活动规则弹窗、操作反馈提示弹窗等

示例:

弹窗

提示:在页面重构启动前,先认真查看需求文档和页面交互图,充分了解需求后,再与需求方确认会出现那些弹窗,避免页面最终交付时缺失相关弹窗或状态。

2)弹窗局部滚动区域

针对如活动规则和一些内容比较多的区域,需在当前页面内做局部滚动。

示例:

局部滚动

10.浮层

1)分享蒙层

须分享的活动页面,分享浮层不能遗漏。

弹窗

2)floating

卖场页面须含返回顶部floating。

示例:

弹窗

参考如下代码:

html结构:

1
2
3
4
5
<div class="ex_160425_7">
<!--返回顶部箭头-->
<img class="o2_backtop" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAoCAMAAAChHKjRAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6NTQ4NjVEQTU0MERDMTFFNUJERDFEM0ZEOUJFOTE2RkMiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6NTQ4NjVEQTY0MERDMTFFNUJERDFEM0ZEOUJFOTE2RkMiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDo1NDg2NURBMzQwREMxMUU1QkREMUQzRkQ5QkU5MTZGQyIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDo1NDg2NURBNDQwREMxMUU1QkREMUQzRkQ5QkU5MTZGQyIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PlO0CKoAAAAGUExURf///////1V89WwAAAACdFJOU/8A5bcwSgAAAH1JREFUeNrs0uEOgCAIBOC793/pWmQqHMZaP2XONv1McICFwDleoo5K131AdsEatTRWqCebo7GkDM2Fa2Sb15gVvCFAr+AN72lU8IZtHhS84fPpCt4QYikeEz+PCYg0Yymi4Pgo4ulCcwChcQwxQ0zaFyi070Yb/YlEHAIMAB6iBI03W6MyAAAAAElFTkSuQmCC">

</div>

css样式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
.ex_160425_7 {
position: fixed;
bottom: 50px;
right: 0;
width: 40px;
height: 40px;
background-color: rgba(0, 0, 0, 0.9);
border-top-left-radius: 2px;
border-bottom-left-radius: 2px;
text-align: center;
font-size: 10px;
color: #fff;
line-height: 18px;
z-index: 4;
}
.ex_160425_7 .o2_backtop {
display: block;
width: 18px;
height: 20px;
margin: 10px auto;
}

五、页面性能要求

1.页面兼容的目标(浏览器/机型/系统)

  • 浏览器:微信,手q浏览器
  • 主流移动设备:iPhone 4+ 、三星、魅族、华为、红米、小米1S 以上及主流 Android 千元机型;请特别关注iPhone4/4s、魅族MX4、华为P6等机型
  • 操作系统:iOS 7.0+ 与 Android 4 .0+;

2.加载速度、请求数与资源压缩

  • 以 3G 网络环境 100kb/s 的网络速度计算,详情页首次加载过程中,可以在 2s 内看到 Loading 页面;
  • 活动样式文件1个,脚本文件须经过压缩处理;实色banner大图应以jpg格式为主,图片资源须进行压缩,JPG 图片使用 80% 以下质量,PNG 图片使用TinyPNG进行压缩;
  • 应当对小图片进行 CSS 雪碧图 合并,因低版本系统及低端设备渲染问题,单张图片尺寸不超过 2000x2000 像素,超过时需切分成多张雪碧图;
  • 禁止使用gif动画图片
  • canvas游戏页面,png图片宽高都应该控制在1024px内

资源文件格式标准

文件类似 格式 大小 其它备注
背景音乐 mp3 小于200 KB 开头和结尾音轨建议做使用淡入淡出处理
图片 jpg、png 尽量保持在 100 KB 左右 单张图片尺寸不超过 2000x2000 像素
视频文件 mov/mp4/avi 暂无要求 分辨率>=960×540,视频码率>=1500kbps,
画面比例 4:3或16:9

3.其他性能要求点

  • 使用CSS3动画代替JS动画,不使用伪类做动画处理
  • 尽可能少的使用CSS高级选择器与通配选择器
  • css中 不使用@import
  • 避免使用CSS3渐变阴影效果,可考虑降级效果来提升流畅度

六、页面交付验收点

  • 1.开发目录结构须严格遵守,模块文件下载
  • 2.交付页面稿须严格测试,性能要求达到第五大点提到的页面性能要求
  • 3.交付页面稿中含有css以及js,须提交无压缩和压缩文件(如,style.css/style.min.css、index.js/index.min.js)
  • 4.交付页面稿须认真参考第四大点提到的交付注意点,解决好常见的业务问题
  • 5.交付页面稿须展示清楚设计稿中含有的各种状态、业务场景,使用方式注释清晰明了

七、页面交付流程

  • CP须提供在线预览地址,让需求方扫描二维码体验确认
  • 需求方反馈问题和修改点,CP应及时修改反馈,之后待需求方再次确认
  • 直至页面全部完成后,将文件包邮件发送给需求方和相关开发人员
  • 开发过程中若缺失部分页面内容,CP应全力配合补全,直至页面上线

webpack+express实现热更新

简介

本文阐述通过webpack开发项目过程中使用热更新实现快速调试,对比常见热更新的实现方式,以及优缺点,着重讲解webpack+express如何结合实现热更新。

思路

热更新是什么
常见热更新方式
webpack+express实现热更新应用
实践应用

热更新是什么

热更新就是当你在开发环境修改代码后,不用刷新整个页面即可看到修改后的效果。

如果你的项目中使用了webpack的话,你会很幸运,可以借助webpack-dev-server插件可以实现项目的热更新,通常结合express使用。

Vue 1.0
Vue 2.0
vue-cli脚手架
Vue 每次更新变化

与webpack 一起使用所需插件
vue-loader-example
vue-html-loader

Vue文章

尤小右

Vue.js:轻量高效的前端组件化方案

勾三股四

Vue + webpack 项目实践
Vue.js 源码学习笔记

稀土掘金

Vue 组件化开发实践

其他博文

Vuejs自己的构建工具

如何用vue.js搭建APP

(1/2)Vue构建单页应用最佳实战

EXPRESS结合WEBPACK的全栈自动刷新

webpack-hot-middleware

webpack-dev-middleware

参考资料:

AMD、CMD、UMD 模块的写法

简介

最近几年,我们可以选择的Javascript组件的生态系统一直在稳步增长。虽然陡增的选择范围是极好的,但当组件混合匹配使用时就会出现很尴尬的局面。开发新手们会很快发现不是所有组件都能彼此“和平相处”。

为了解决这个问题,两种竞争关系的模块规范AMD和CommonJS问世了,它们允许开发者遵照一种约定的沙箱化和模块化的方式来写代码,这样就能避免“污染生态系统”。

AMD

随着RequireJS成为最流行的实现方式,异步模块规范(AMD)在前端界已经被广泛认同。

下面是只依赖jquery的模块foo的代码:

1
2
3
4
5
6
7
8
//    文件名: foo.js
define(['jquery'], function ($) {
// 方法
function myFunc(){};

// 暴露公共方法
return myFunc;
});

还有稍微复杂点的例子,下面的代码依赖了多个组件并且暴露多个方法:

1
2
3
4
5
6
7
8
9
10
11
12
// 文件名: foo.js
define(['jquery', 'underscore'], function ($, _) {
// 方法
function a(){}; // 私有方法,因为没有被返回(见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了
// 暴露公共方法
return {
b: b,
c: c
}
});

定义的第一个部分是一个依赖数组,第二个部分是回调函数,只有当依赖的组件可用时(像RequireJS这样的脚本加载器会负责这一部分,包括找到文件路径)回调函数才被执行。

注意,依赖组件和变量的顺序是一一对应的(例如,jquery->$, underscore->_)。

同时注意,我们可以用任意的变量名来表示依赖组件。假如我们把$改成$$,在函数体里面的所有对jQuery的引用都由$变成了$$。

还要注意,最重要的是你不能在回调函数外面引用变量$和_,因为它相对其它代码是独立的。这正是模块化的目的所在!

CommonJS

如果你用Node写过东西的话,你可能会熟悉CommonJS的风格(node使用的格式与之相差无几)。因为有Browserify,它也一直被前端界广泛认同。

就像前面的格式一样,下面是用CommonJS规范实现的foo模块的写法:

1
2
3
4
5
6
7
8
//    文件名: foo.js
// 依赖
var $ = require('jquery');
// 方法
function myFunc(){};

// 暴露公共方法(一个)
module.exports = myFunc;

还有更复杂的例子,下面的代码依赖了多个组件并且暴露多个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//    文件名: foo.js
var $ = require('jquery');
var _ = require('underscore');

// methods
function a(){}; // 私有方法,因为它没在module.exports中 (见下面)
function b(){}; // 公共方法,因为它在module.exports中定义了
function c(){}; // 公共方法,因为它在module.exports中定义了

// 暴露公共方法
module.exports = {
b: b,
c: c
};

UMD: 通用模块规范

既然CommonJs和AMD风格一样流行,似乎缺少一个统一的规范。所以人们产生了这样的需求,希望有支持两种风格的“通用”模式,于是通用模块规范(UMD)诞生了。

不得不承认,这个模式略难看,但是它兼容了AMD和CommonJS,同时还支持老式的“全局”变量规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery);
}
}(this, function ($) {
// 方法
function myFunc(){};

// 暴露公共方法
return myFunc;
}));

保持跟上面例子一样的模式,下面是更复杂的例子,它依赖了多个组件并且暴露多个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
// AMD
define(['jquery', 'underscore'], factory);
} else if (typeof exports === 'object') {
// Node, CommonJS之类的
module.exports = factory(require('jquery'), require('underscore'));
} else {
// 浏览器全局变量(root 即 window)
root.returnExports = factory(root.jQuery, root._);
}
}(this, function ($, _) {
// 方法
function a(){}; // 私有方法,因为它没被返回 (见下面)
function b(){}; // 公共方法,因为被返回了
function c(){}; // 公共方法,因为被返回了

// 暴露公共方法
return {
b: b,
c: c
}
}));

参考资料:

前端性能优化(CSS动画篇)(转载)

原理

现代浏览器在使用CSS3动画时,以下四种情形绘制的效率较高,分别是:

  • 改变位置
  • 改变大小
  • 旋转
  • 改变透明度

层?重绘?回流和重布局?图层重组?

浏览器在渲染一个页面时,会将页面分为很多个图层,图层有大有小,每个图层上有一个或多个节点。在渲染DOM的时候,浏览器所做的工作实际上是:

  1. 获取DOM后分割为多个图层
  2. 对每个图层的节点计算样式结果(Recalculate style–样式重计算)
  3. 为每个节点生成图形和位置(Layout–回流和重布局)
  4. 将每个节点绘制填充到图层位图中(Paint Setup和Paint–重绘)
  5. 图层作为纹理上传至GPU
  6. 符合多个图层到页面上生成最终屏幕图像(Composite Layers–图层重组)

Chrome中满足以下任意情况就会创建图层:

  • 3D或透视变换(perspective transform)CSS属性
  • 使用加速视频解码的video节点
  • 拥有3D(WebGL)上下文或加速的2D上下文的canvas节点
  • 混合插件(如Flash)
  • 对自己的opacity做CSS动画或使用一个动画webkit变换的元素
  • 拥有加速CSS过滤器的元素
  • 元素有一个包含复合层的后代节点(一个元素拥有一个子元素,该子元素在自己的层里)
  • 元素有一个z-index较低且包含一个复合层的兄弟元素(换句话说就是该元素在复合层上面渲染)

需要注意的是,如果图层中某个元素需要重绘,那么整个图层都需要重绘。比如一个图层包含很多节点,其中有个gif图,gif图的每一帧,都会重回整个图层的其他节点,然后生成最终的图层位图。所以这需要通过特殊的方式来强制gif图属于自己一个图层(translateZ(0)或者translate3d(0,0,0)),CSS3的动画也是一样(好在绝大部分情况浏览器自己会为CSS3动画的节点创建图层)

层和CSS动画

简化一下上述过程,每一帧动画浏览器可能需要做如下工作:

  1. 计算需要被加载到节点上的样式结果(Recalculate style–样式重计算)
  2. 为每个节点生成图形和位置(Layout–回流和重布局)
  3. 将每个节点填充到图层中(Paint Setup和Paint–重绘)
  4. 组合图层到页面上(Composite Layers–图层重组)

如果我们需要使得动画的性能提高,需要做的就是减少浏览器在动画运行时所需要做的工作。最好的情况是,改变的属性仅仅印象图层的组合,变换(transform)和透明度(opacity)就属于这种情况

现代浏览器如Chrome,Firefox,Safari和Opera都对变换和透明度采用硬件加速,但IE10+不是很确定是否硬件加速

触发重布局的属性

有些节点,当你改变他时,会需要重新布局(这也意味着需要重新计算其他被影响的节点的位置和大小)。这种情况下,被影响的DOM树越大(可见节点),重绘所需要的时间就会越长,而渲染一帧动画的时间也相应变长。所以需要尽力避免这些属性

一些常用的改变时会触发重布局的属性:
盒子模型相关属性会触发重布局:

  • width
  • height
  • padding
  • margin
  • display
  • border-width
  • border
  • min-height

定位属性及浮动也会触发重布局:

  • top
  • bottom
  • left
  • right
  • position
  • float
  • clear

改变节点内部文字结构也会触发重布局:

  • text-align
  • overflow-y
  • font-weight
  • overflow
  • font-family
  • line-height
  • vertival-align
  • white-space
  • font-size

这么多常用属性都会触发重布局,可以看到,他们的特点就是可能修改整个节点的大小或位置,所以会触发重布局

别使用CSS类名做状态标记

如果在网页中使用CSS的类来对节点做状态标记,当这些节点的状态标记类修改时,将会触发节点的重绘和重布局。所以在节点上使用CSS类来做状态比较是代价很昂贵的

触发重绘的属性

修改时只触发重绘的属性有:

  • color
  • border-style
  • border-radius
  • visibility
  • text-decoration
  • background
  • background-image
  • background-position
  • background-repeat
  • background-size
  • outline-color
  • outline
  • outline-style
  • outline-width
  • box-shadow

这样可以看到,这些属性都不会修改节点的大小和位置,自然不会触发重布局,但是节点内部的渲染效果进行了改变,所以只需要重绘就可以了

手机就算重绘也很慢

在重绘时,这些节点会被加载到GPU中进行重绘,这对移动设备如手机的影响还是很大的。因为CPU不如台式机或笔记本电脑,所以绘画巫妖的时间更长。而且CPU与GPU之间的有较大的带宽限制,所以纹理的上传需要一定时间

触发图层重组的属性

透明度竟然不会触发重绘?

需要注意的是,上面那些触发重绘的属性里面没有opacity(透明度),很奇怪不是吗?实际上透明度的改变后,GPU在绘画时只是简单的降低之前已经画好的纹理的alpha值来达到效果,并不需要整体的重绘。不过这个前提是这个被修改opacity本身必须是一个图层,如果图层下还有其他节点,GPU也会将他们透明化

强迫浏览器创建图层

在Blink和WebKit的浏览器中,一当一个节点被设定了透明度的相关过渡效果或动画时,浏览器会将其作为一个单独的图层,但很多开发者使用translateZ(0)或者translate3d(0,0,0)去使浏览器创建图层。这种方式可以消除在动画开始之前的图层创建时间,使得动画尽快开始(创建图层和绘制图层还是比较慢的),而且不会随着抗锯齿而导出突变。不过这种方法需要节制,否则会因为创建过多的图层导致崩溃

Chrome中的抗锯齿

Chrome中,非根图层以及透明图层使用grayscale antialiasing而不是subpixel antialiasing,如果抗锯齿方法变化,这个效果将会非常显著。如果你打算预处理一个节点而不打算等到动画开始,可以通过这种强迫浏览器创建图层的方式进行

transform变换是你的选择

我们通过节点的transform可以修改节点的位置、旋转、大小等。我们平常会使用left和top属性来修改节点的位置,但正如上面所述,left和top会触发重布局,修改时的代价相当大。取而代之的更好方法是使用translate,这个不会触发重布局

##JS动画和CSS3动画的比较
我们经常面临一个抉择:是使用JavaScript的动画还是使用CSS的动画,下面将对比一下这两种方式

JS动画
缺点:JavaScript在浏览器的主线程中运行,而其中还有很多其他需要运行的JavaScript、样式计算、布局、绘制等对其干扰。这也就导致了线程可能出现阻塞,从而造成丢帧的情况。

优点:JavaScript的动画与CSS预先定义好的动画不同,可以在其动画过程中对其进行控制:开始、暂停、回放、中止、取消都是可以做到的。而且一些动画效果,比如视差滚动效果,只有JavaScript能够完成

CSS动画
缺点:缺乏强大的控制能力。而且很难以有意义的方式结合到一起,使得动画变得复杂且易于出问题。
优点:浏览器可以对动画进行优化。它必要时可以创建图层,然后在主线程之外运行。

前瞻

Google目前正在探究通过JS的多线程(Web Workers)来提供更好的动画效果,而不会触发重布局及样式重计算

结论

动画给予了页面丰富的视觉体验。我们应该尽力避免使用会触发重布局和重绘的属性,以免失帧。最好提前申明动画,这样能让浏览器提前对动画进行优化。由于GPU的参与,现在用来做动画的最好属性是如下几个:

  • opacity
  • translate
  • rotate
  • scale

也许会有一些新的方式使得可以使用JavaScript做出更好的没有限制的动画,而且不用担心主线程的阻塞问题。但在那之前,还是好好考虑下如何做出流畅的动画吧

参考资料

Nodejs下的ES6兼容性与性能分析--《转载》

ES6标准发布后,前端人员也开发渐渐了解到了es6,但是由于兼容性的问题,仍然没有得到广泛的推广,不过业界也用了一些折中性的方案来解决兼容性和开发体系问题,但大家仍很疑惑,使用ES6会有哪些兼容性问题。

目前,最新版Nodejs在严格模式下"use strict";下就可以使用es6语法。

一、Nodejs下ES6兼容性现状

随着iojs的引入,新版的Nodejs开始原生支持部分ES6的特性,既然ES6在浏览器端使用需要使用babel等编译,在Nodejs总可以放心使用了吧。然而事实并非如此,为此在nodejs端,我也做了特性兼容性研究:

ES6新特性在Nodejs下的兼容性列表 这里罗列下nodejs支持的新特性,没列出的新特性均为不支持。

https://iojs.org/en/es6.html

https://kangax.github.io/compat-table/es6/

node和babel对es6性能对比

可见,es6的新特性在Nodejs中比babel还要差,而新版的babel已经能够支持es6的90%新特性了~

二、Nodejs ES6性能分析

尽管目前Node下使用ES6我们仍然会大失所望,但es6发展的趋势定是必然,这里还是有必要对ES6的原生性能做了详细的对比测试。测试基本方法:

1,对于重复操作循环执行100万次

2,所有程序运行在Nodejs下执行

3,环境描述

  • CPU: Interl(R) Core(Tm) i5-3470 CPU @ 3.2GHz

  • 内存:4.00GB

  • 操作系统: 64位操作系统

  • node版本:node v5.1.1

2.1、let, const, 块

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
'use strict'
let i = 0;
let t1 = +new Date(), //这里+new Date()是转换时间戳,常见方式new Date().getTime() 、 Date.parse(new Date()) 、 (new Date()).valueOf()
t2;

while(i++ < 1000000){
const a = 1;
const b = '1';
const c = true;
const d = {};
const e = [];
}

t2 = +new Date() - t1;
console.log(t2);

node和babel对es6性能对比

结果让我震惊了,使用let,const声明变量的速度竟然比var快了约65%左右。原因可能是使用var会去检查作用域上的同名变量,而使用let或const不用考虑。

2.2、class类使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
'use strict'
let i = 0;
let t1 = +new Date(),
t2;

while(i++ < 100000){
class A{
constructor() {
this.name = 'ouven';
}
getName(){
return this.name;
}
}

const a = new A();
a.getName();
}

t2 = +new Date() - t1;
console.log(t2);

node和babel对es6性能对比

可见使用Nodejs的Class比ES的function构造方法慢约25%

2.3、Map,Set 和 WeakMap,WeakSet

1
2
3
4
5
6
7
8
9
10
11
12
13
'use strict'
let i = 0;
let t1 = +new Date(),
t2;

while(i++ < 1000000){

let map = new Map();
map.set('key','value');
}

t2 = +new Date() - t1;
console.log(t2);

node和babel对es6性能对比

测试结果看,Map的效率相对普通的对象key-value的结果相比慢的多,但是Map的Key可以使负责类型,这里的参考性也就不是绝对准确。建议是不到必须情况,不要使用Map等复杂类型。Set、WeakMap、WeakSet均相对object结构执行效率慢得多。

2.4、字符串模板

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
'use strict'
let i = 0;
let t1 = +new Date(),
t2;

let vars = {
name: 'ouven',
address: 'tencent'
};

while(i++ < 1000000){

let str = `string text ${vars.name} string ${vars.address}`;

}

t2 = +new Date() - t1;
console.log(t2);

node和babel对es6性能对比

ES6的字符串模板看起来很好,但是由于执行时必须扫描这个串,找出里面的模板变量,所以整体上性能就相对ES5的字符串拼接慢了很多。

其它的特性实现有兴趣的同学可以自己继续研究。相信结果大概可以预测到。

三、小结

这里选择了ES6中的少数特性和ES5的实现的执行效率做了对比,整体上说,ES6的新特性相对ES5的实现效率慢些,而有些特性当然是ES5无法实现的。所以在了解使用ES6的同时,除了了解它的新特性和优点,对于ES6本身的一些问题也要做到心中有数。当然,随着ES6的完善和Node的更新,相信这些也不会是大的问题,而且这些也不会影响ES6的发展。

https://github.com/ouvens/es6-code-style-guide

参考资料:

pm2学习实践

简介

PM2是Node.js应用程序的进程管理管理,目前已在生产环境被普遍使用。

PM2主要特性

  • 内建负载均衡(使用Node cluster 集群模块)
  • 后台运行
  • 0秒停机重载,我理解大概意思是维护升级的时候不需要停机.
  • 具有Ubuntu和CentOS 的启动脚本
  • 停止不稳定的进程(避免无限循环)
  • 控制台检测
  • 提供 HTTP API
  • 远程控制和实时的接口API ( Nodejs 模块,允许和PM2进程管理器交互 )

pm2用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
$ npm install pm2 -g     # 命令行安装 pm2 
$ pm2 start app.js -i 4 #后台运行pm2,启动4个app.js
# 也可以把'max' 参数传递给 start
# 正确的进程数目依赖于Cpu的核心数目
$ pm2 start app.js --name my-api # 命名进程
$ pm2 list # 显示所有进程状态
$ pm2 monit # 监视所有进程
$ pm2 logs # 显示所有进程日志
$ pm2 stop all # 停止所有进程
$ pm2 restart all # 重启所有进程
$ pm2 reload all # 0秒停机重载进程 (用于 NETWORKED 进程) pm2 reload id demo
$ pm2 stop 0 # 停止指定的进程
$ pm2 restart 0 # 重启指定的进程
$ pm2 startup # 产生 init 脚本 保持进程活着
$ pm2 web # 运行健壮的 computer API endpoint (http://localhost:9615)
$ pm2 delete 0 # 杀死指定的进程
$ pm2 delete all # 杀死全部进程

pm2实战

pm2 start启动程序 在当前目录下可以直接启动,pm2 start app.js),程序将在后台默默启动,实时监控程序的运行

1
2
3
4
5
6
7
8
9
10
$ pm2 start app.js 
[PM2] Spawning PM2 daemon
[PM2] Success
[PM2] Process app.js launched
┌──────────┬────┬──────┬─────┬────────┬───────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ PID │ status │ restarted │ uptime │ memory │ watching │
├──────────┼────┼──────┼─────┼────────┼───────────┼────────┼─────────────┼──────────┤
│ app │ 0 │ fork │ 308 │ online │ 0 │ 0s │ 21.879 MB │ disabled │
└──────────┴────┴──────┴─────┴────────┴───────────┴────────┴─────────────┴──────────┘
Use `pm2 info <id|name>` to get more details about an app

pm2 restart [app]则上述restarted那栏变成1,可以显示程序运行了多长时间、占用内存大小

1
2
3
4
5
6
7
8
 pm2 restart app
[PM2] restartProcessId process id 0
┌──────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├──────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ app │ 0 │ fork │ 4298 │ online │ 1 │ 0s │ 13.867 MB │ disabled │
└──────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app

pm2 stop [app] 终止程序运行

1
2
3
4
5
6
7
8
9
 pm2 stop app
[PM2] Stopping app
[PM2] stopProcessId process id 0
┌──────────┬────┬──────┬─────┬─────────┬─────────┬────────┬────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├──────────┼────┼──────┼─────┼─────────┼─────────┼────────┼────────┼──────────┤
│ app │ 0 │ fork │ 0 │ stopped │ 2 │ 0 │ 0 B │ disabled │
└──────────┴────┴──────┴─────┴─────────┴─────────┴────────┴────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app

pm2 list列举出所有用pm2启动的程序

1
2
3
4
5
6
7
 pm2 list
┌──────────┬────┬──────┬─────┬─────────┬─────────┬────────┬────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├──────────┼────┼──────┼─────┼─────────┼─────────┼────────┼────────┼──────────┤
│ app │ 0 │ fork │ 0 │ stopped │ 2 │ 0 │ 0 B │ disabled │
└──────────┴────┴──────┴─────┴─────────┴─────────┴────────┴────────┴──────────┘
Use `pm2 show <id|name>` to get more details about an app

pm2 describe id 查看启动程序的详细信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
 pm2 describe 0
Describing process with id 0 - name app
┌───────────────────┬───────────────────────────────────────────┐
│ status │ online │
│ name │ app │
│ node.js version │ 5.3.0 │
│ id │ 0 │
│ path │ /Users/gaolu11/work/node/jdcwidget/app.js │
│ args │ N/A │
│ exec cwd │ /Users/gaolu11/work/node/jdcwidget │
│ error log path │ /Users/gaolu11/.pm2/logs/app-error-0.log │
│ out log path │ /Users/gaolu11/.pm2/logs/app-out-0.log │
│ pid path │ /Users/gaolu11/.pm2/pids/app-0.pid │
│ mode │ fork_mode │
│ node v8 arguments │ N/A │
│ watch & reload │ ✘ │
⌬ PM2 monitoring (To go further check out https://app.keymetrics.io)

pm2 monit获取进程(以及集群)的CPU的使用率和内存占用

1
2
 ● app                                 [                              ] 0 %
[0] [fork_mode] [||||||||| ] 50.816 MB

pm2 logs实时集中log处理

1
2
3
4
5
6
7
8
9
10
11
12
[PM2] Tailing last 20 lines for [all] processes 

PM2: 2016-04-08 11:38:08: 1547 pid can not be killed { [Error: kill EPERM] code: 'EPERM', errno: 'EPERM', syscall: 'kill' }
PM2: 2016-04-08 11:38:08: [PM2] Exited peacefully
PM2: 2016-04-08 11:40:54: [PM2][WORKER] Started with refreshing interval: 30000
PM2: 2016-04-08 11:40:54: [[[[ PM2/God daemon launched ]]]]
PM2: 2016-04-08 11:40:54: BUS system [READY] on port /Users/gaolu11/.pm2/pub.sock
PM2: 2016-04-08 11:40:54: RPC interface [READY] on port /Users/gaolu11/.pm2/rpc.sock
PM2: 2016-04-08 11:40:54: Starting execution sequence in -fork mode- for app name:app id:0
PM2: 2016-04-08 11:40:54: App name:app id:0 online
PM2: 2016-04-08 14:06:34: Stopping app:app id:0
PM2: 2016-04-08 14:06:34: App [app] with id [0] and pid [653], exited with code [0] via signal [null]

pm2 web监控所有被PM2管理的进程,而且同时还想监控运行这些进程的机器的状态

1
2
3
4
5
6
7
8
9
10
11
pm2 web
Launching web interface on port 9615
[PM2] Starting /usr/local/lib/node_modules/pm2/lib/HttpInterface.js in fork_mode (1 instance)
[PM2] Done.
[PM2] Process launched
┌────────────────────┬────┬──────┬──────┬────────┬─────────┬────────┬─────────────┬──────────┐
│ App name │ id │ mode │ pid │ status │ restart │ uptime │ memory │ watching │
├────────────────────┼────┼──────┼──────┼────────┼─────────┼────────┼─────────────┼──────────┤
│ app │ 0 │ fork │ 4461 │ online │ 2 │ 3m │ 41.734 MB │ disabled │
│ pm2-http-interface │ 1 │ fork │ 4700 │ online │ 0 │ 0s │ 14.063 MB │ disabled │
└────────────────────┴────┴──────┴──────┴────────┴─────────┴────────┴─────────────┴──────────┘

最后启动程序的时候顺便在浏览器访问:http://localhost:9615,可以在浏览器中看到部署的服务器的信息和程序的信息

写在最后,以上仅为个人观点,如有纰漏之处,欢迎各位大侠拍砖!

参考资料:

Node读写Excel文件探究实践

简介

和弦
本文介绍用 Node.js 中的依赖库来处理 Excel 文件,深入分析对比常见npm库处理Excel 文件存在的优缺点,主要阐述用js-xlsx、excel-export 库来处理 Excel 文件。

思路

  • 有哪些外部模块支持读写Excel
  • 引入依赖模块
  • 编写业务逻辑函数
  • 实践应用

支持读写Excel的node.js模块

通过npm搜索,支持读写excel文件的模块有很多,但是都各有忧缺点,有些仅支持xls/xlsx的一种格式,有些仅支持读取数据,有些仅支持导出文件,有些需要依赖python解析。常见的npm依赖模块如下:

  • js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格XLSX / XLSM / XLSB / XLS / CSV,解析采用纯js实现,写入需要依赖nodejs或者FileSaver.js实现生成写入Excel,可以生成子表Excel,功能强大,但上手难度稍大。不提供基础设置Excel表格api例单元格宽度,文档有些乱,不适合快速上手;
  • node-xlsx: 基于Node.js解析excel文件数据及生成excel文件,仅支持xlsx格式文件;
  • excel-parser: 基于Node.js解析excel文件数据,支持xls及xlsx格式文件,需要依赖python,太重不太实用;
  • excel-export : 基于Node.js将数据生成导出excel文件,生成文件格式为xlsx,可以设置单元格宽度,API容易上手,无法生成worksheet字表,比较单一,基本功能可以基本满足;
  • node-xlrd: 基于node.js从excel文件中提取数据,仅支持xls格式文件,不支持xlsx,有点过时,常用的都是XLSX 格式。

通过以上分析对比,本人比较推崇js-xlsxexcel-export来读写Excel文件,可以结合使用js-xlsx解析Excel、excel-export生成,效果更加,接下来分别实践js-xlsxexcel-export

第一讲:利用 js-xlsx 处理 Excel 文件

安装

node中使用通过npm:

1
$ npm install xlsx

浏览器使用:

1
<script lang="javascript" src="dist/xlsx.core.min.js"></script>

通过bower安装:

1
bower install js-xlsx

注意,在客户端使用时,建议使用dist/xlsx.full.min.js,包含了js-xlsx所有模块。

一些概念

在使用这个库之前,先介绍库中的一些概念。

  • workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。
  • worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。
  • cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。

它们的关系如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// workbook
{
SheetNames: ['sheet1', 'sheet2'],
Sheets: {
// worksheet
'sheet1': {
// cell
'A1': { ... },
// cell
'A2': { ... },
...
},
// worksheet
'sheet2': {
// cell
'A1': { ... },
// cell
'A2': { ... },
...
}
}
}

用法

基本用法

1.用 XLSX.read 读取获取到的 Excel 数据,返回 workbook
2.用 XLSX.readFile 打开 Excel 文件,返回 workbook
3.用 workbook.SheetNames 获取表名
4.用 workbook.Sheets[xxx] 通过表名获取表格
5.用 worksheet[address]操作单元格
6.用XLSX.utils.sheet_to_json针对单个表获取表格数据转换为json格式
7.用XLSX.writeFile(wb, 'output.xlsx')生成新的 Excel 文件

具体用法
读取 Excel 文件

1
2
workbook = XLSX.read(excelData, {type: 'base64'});
workbook = XLSX.writeFile('someExcel.xlsx', opts);

获取 Excel 文件中的表

1
2
3
4
// 获取 Excel 中所有表名
var sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2',……]
// 根据表名获取对应某张表
var worksheet = workbook.Sheets[sheetNames[0]];

通过 worksheet[address] 来操作表格,以 ! 开头的 key 是特殊的字段。

1
2
3
4
5
6
7
8
9
10
11
// 获取 A1 单元格对象
let a1 = worksheet['A1']; // 返回 { v: 'hello', t: 's', ... }
// 获取 A1 中的值
a1.v // 返回 'hello'

// 获取表的有效范围
worksheet['!ref'] // 返回 'A1:B20'
worksheet['!range'] // 返回 range 对象,{ s: { r: 0, c: 0}, e: { r: 100, c: 2 } }

// 获取合并过的单元格
worksheet['!merges'] // 返回一个包含 range 对象的列表,[ {s: { r: 0, c: 0 }, c: { r: 2, c: 1 } } ]

获取 Excel 文件中的表转换为json数据

1
XLSX.utils.sheet_to_json(worksheet)  //针对单个表,返回序列化json数据

生成新的 Excel 文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//服务端通过XLSX.writeFile
XLSX = require("xlsx");
XLSX.writeFile(wb, 'output.xlsx')

//客服端,只能通过XLSX.write(wb, write_opts) 写入 表格数据,借助FileSaver生成,且只支持在高版本浏览器。
var wopts = { bookType:'xlsx', bookSST:false, type:'binary' };

var wbout = XLSX.write(wb,wopts);

function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}

/* the saveAs call downloads a file on the local machine */
saveAs(new Blob([s2ab(wbout)],{type:""}), "test.xlsx")

js-xlsx实战

解析 Excel 生成 JSON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function to_json(workbook) {
var result = {};

// 获取 Excel 中所有表名
var sheetNames = workbook.SheetNames; // 返回 ['sheet1', 'sheet2']

workbook.SheetNames.forEach(function(sheetName) {
var worksheet = workbook.Sheets[sheetName];
result[sheetName] = XLSX.utils.sheet_to_json(worksheet);
});

console.log("打印表信息",JSON.stringify(result, 2, 2)); //显示格式{"表1":[],"表2":[]}
return result;
}

导出表格
1.构建特定的数据结构,通过new Blob如下。

1
2
3
4
5
6
7
8
9
10
11
// workbook
{
SheetNames: ['mySheet'],
Sheets: {
'mySheet': {
'!ref': 'A1:E4', // 必须要有这个范围才能输出,否则导出的 excel 会是一个空表
A1: { v: 'id' },
...
}
}
}

2.调用 XLSX.write, 借助FileSaver中new Blob生成即可。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
var _headers = ['id', 'name', 'age', 'country', 'remark']
var _data = [ { id: '1',
name: 'test1',
age: '30',
country: 'China',
remark: 'hello' },
{ id: '2',
name: 'test2',
age: '20',
country: 'America',
remark: 'world' },
{ id: '3',
name: 'test3',
age: '18',
country: 'Unkonw',
remark: '???' } ];

var headers = _headers
// 为 _headers 添加对应的单元格位置
// [ { v: 'id', position: 'A1' },
// { v: 'name', position: 'B1' },
// { v: 'age', position: 'C1' },
// { v: 'country', position: 'D1' },
// { v: 'remark', position: 'E1' } ]
.map((v, i) => Object.assign({}, {v: v, position: String.fromCharCode(65+i) + 1 }))
// 转换成 worksheet 需要的结构
// { A1: { v: 'id' },
// B1: { v: 'name' },
// C1: { v: 'age' },
// D1: { v: 'country' },
// E1: { v: 'remark' } }
.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

var data = _data
// 匹配 headers 的位置,生成对应的单元格数据
// [ [ { v: '1', position: 'A2' },
// { v: 'test1', position: 'B2' },
// { v: '30', position: 'C2' },
// { v: 'China', position: 'D2' },
// { v: 'hello', position: 'E2' } ],
// [ { v: '2', position: 'A3' },
// { v: 'test2', position: 'B3' },
// { v: '20', position: 'C3' },
// { v: 'America', position: 'D3' },
// { v: 'world', position: 'E3' } ],
// [ { v: '3', position: 'A4' },
// { v: 'test3', position: 'B4' },
// { v: '18', position: 'C4' },
// { v: 'Unkonw', position: 'D4' },
// { v: '???', position: 'E4' } ] ]
.map((v, i) => _headers.map((k, j) => Object.assign({}, { v: v[k], position: String.fromCharCode(65+j) + (i+2) })))
// 对刚才的结果进行降维处理(二维数组变成一维数组)
// [ { v: '1', position: 'A2' },
// { v: 'test1', position: 'B2' },
// { v: '30', position: 'C2' },
// { v: 'China', position: 'D2' },
// { v: 'hello', position: 'E2' },
// { v: '2', position: 'A3' },
// { v: 'test2', position: 'B3' },
// { v: '20', position: 'C3' },
// { v: 'America', position: 'D3' },
// { v: 'world', position: 'E3' },
// { v: '3', position: 'A4' },
// { v: 'test3', position: 'B4' },
// { v: '18', position: 'C4' },
// { v: 'Unkonw', position: 'D4' },
// { v: '???', position: 'E4' } ]
.reduce((prev, next) => prev.concat(next))
// 转换成 worksheet 需要的结构
// { A2: { v: '1' },
// B2: { v: 'test1' },
// C2: { v: '30' },
// D2: { v: 'China' },
// E2: { v: 'hello' },
// A3: { v: '2' },
// B3: { v: 'test2' },
// C3: { v: '20' },
// D3: { v: 'America' },
// E3: { v: 'world' },
// A4: { v: '3' },
// B4: { v: 'test3' },
// C4: { v: '18' },
// D4: { v: 'Unkonw' },
// E4: { v: '???' } }
.reduce((prev, next) => Object.assign({}, prev, {[next.position]: {v: next.v}}), {});

// 合并 headers 和 data
var output = Object.assign({}, headers, data);
// 获取所有单元格的位置
var outputPos = Object.keys(output);
// 计算出范围
var ref = outputPos[0] + ':' + outputPos[outputPos.length - 1];

// 构建 workbook 对象
var wb = {
SheetNames: ['mySheet'],
Sheets: {
'mySheet': Object.assign({}, output, { '!ref': ref })
}
};

// 导出 Excel
//XLSX.writeFile(wb, 'output.xlsx');

var wopts = { bookType:'xlsx', bookSST:false, type:'binary' };

var wbout = XLSX.write(wb,wopts);

function s2ab(s) {
var buf = new ArrayBuffer(s.length);
var view = new Uint8Array(buf);
for (var i=0; i!=s.length; ++i) view[i] = s.charCodeAt(i) & 0xFF;
return buf;
}

/* the saveAs call downloads a file on the local machine */
saveAs(new Blob([s2ab(wbout)],{type:""}), "test.xlsx")

实践Demo:RD快速生成excel表

第二讲:利用 excel-export 生成 Excel 文件

excel-export模块,上手起来就比较容易了,其中原理是通过修改,修改header 信息、拼接字符串、修改字符集、输出字符串的形式实现的,在部分firefox低版本下载中文名会出现乱码情况。我们只需要按照API设置好数据参数,通过nodeExcel.execute调用执行,系统调用模版”styles.xml”就可以生成Excel文件,比较好的就是,它可以设置单元格的宽度,类型。
我们先看看,官方提供的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
var express = require('express');
var nodeExcel = require('excel-export');
var app = express();

app.get('/Excel', function(req, res){
var conf ={};
conf.stylesXmlFile = "styles.xml";
conf.name = "mysheet";
conf.cols = [{
caption:'string',
type:'string',
beforeCellWrite:function(row, cellData){
return cellData.toUpperCase();
},
width:28.7109375
},{
caption:'date',
type:'date',
beforeCellWrite:function(){
var originDate = new Date(Date.UTC(1899,11,30));
return function(row, cellData, eOpt){
if (eOpt.rowNum%2){
eOpt.styleIndex = 1;
}
else{
eOpt.styleIndex = 2;
}
if (cellData === null){
eOpt.cellType = 'string';
return 'N/A';
} else
return (cellData - originDate) / (24 * 60 * 60 * 1000);
}
}()
},{
caption:'bool',
type:'bool'
},{
caption:'number',
type:'number'
}];
conf.rows = [
['pi', new Date(Date.UTC(2013, 4, 1)), true, 3.14],
["e", new Date(2012, 4, 1), false, 2.7182],
["M&M<>'", new Date(Date.UTC(2013, 6, 9)), false, 1.61803],
["null date", null, true, 1.414]
];
var result = nodeExcel.execute(conf);
res.setHeader('Content-Type', 'application/vnd.openxmlformats');
res.setHeader("Content-Disposition", "attachment; filename=" + "Report.xlsx");
res.end(result, 'binary');
});

app.listen(3000);
console.log('Listening on port 3000');

分析生成excel流程:
1.配置excel文件名conf.name
2.设置表caption,每列单元格数据类型,宽度
3.填充表中每行数据conf.rows,nodeExcel.execute生成数据结构,设置头部,拼接生成表

写在最后,以上仅为个人观点,如有纰漏之处,欢迎各位大侠拍砖!

参考资料: