项目管理优化

代码库

规范 commit 信息

我们可以利用 commitizen 和 husky 来规范代码库的 commit。

如果安装过 commitizen,那么先全局安装:

1
npm install commitizen -g

安装以下依赖:

1
npm install @commitlint/cli @commitlint/config-conventional husky  -D

在 package.json 中增加 husky 字段。

1
2
3
4
5
6
7
{
"husky": {
"hooks": {
"commit-msg": "commitlint -E HUSKY_GIT_PARAMS"
}
},
}

husky 是 git hook 工具,使用 husky,我们可以方便的在 package.json 中配置 git hook 脚本,例如: pre-commit、 pre-push、 commit-msg 等的。

创建 commitlint.config.js 文件

1
2
3
module.exports = {
extends: ["@commitlint/config-conventional"],
};

使用 git cz 来进行填写 commit 的内容。

不喜欢默认的交互,可以使用 cz-customizable 来进行定制。

自定义提交说明

1
2
// 安装 cz-customizable
npm install cz-customizable -D

cz-customizable 是可自定义的 Commitizen 插件,可帮助实现一致的 commit message。

cz-customizable 适合大型团队去自定义 scope,和 commit type。

在项目根目录下创建 .cz-config.js 文件:

cz-customizable 会首先在项目根目录下寻找: .cz-config.js 或 .config/cz-config.js,如果找不到,会去主目录寻找。我们也可以在 package.json 中手动去指定配置文件的路径。

1
2
3
4
5
6
7
8
"config": {
"commitizen": {
"path": "node_modules/cz-customizable"
},
"cz-customizable": {
"config": "config/path/to/my/config.js"
}
}

我们希望提交到git库的代码,都能够通过 eslint 检查或者是通过测试。我们可以借助于 pre-commit 这个钩子来做这些事情。

代码提交前检查

1
2
// 安装依赖
npm install lint-staged -D

使用 pre-commit 的 hook

1
2
3
4
5
6
7
8
9
10
11
"husky": {
"hooks": {
"pre-commit": "lint-staged"
}
},
"lint-staged": {
"**/*.js": [
"prettier --write",
"eslint"
]
}

这样配置之后,每次提交的时候,都会对要提交的文件(并不是对整个项目)进行 prettier 格式化和 eslint 检查,都通过之后,才能 commit 成功。

eslint 和 prettier 配置

react 项目配置。

1
2
// 安装 eslint 和 prettier 相关依赖:
npm install eslint eslint-config-prettier eslint-plugin-promise eslint-plugin-react eslint-plugin-react-hooks prettier babel-eslint -D

新建 .prettierrc.js

也可以在 package.json 的 prettier 字段中配置,配置成了独立的文件,以便后期维护。

1
2
3
4
module.exports = {
printWidth: 100, //长度超过100断行
singleQuote: true,//使用单引号
};

不需要 prettier 进行格式化,那么可以新建一个 .prettierignore 文件。

1
2
3
dist
node_modules
public

新建 .eslintrc.js 文件

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
module.exports = {
settings: {
react: {
pragma: 'React',
version: 'detect'
}
},
// babel parser to support ES6/7 features
parser: 'babel-eslint',
parserOptions: {
ecmaVersion: 7,
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true
},
sourceType: 'module'
},
extends: [
'prettier',
'prettier/react'
],
plugins: [
'promise',
'react',
'react-hooks'
],
env: {
browser: true,
es6: true,
node: true
},
rules: {
'no-compare-neg-zero': 2, //禁止与 -0 进行比较
'no-cond-assign': 2, //禁止条件表达式中出现赋值操作符
'no-console': 1, //禁用 console
'no-constant-condition': 1, //禁止在条件中使用常量表达式
'no-control-regex': 1, //禁止在正则表达式中使用控制字符
'no-debugger': 2, //禁用 debugger
'no-dupe-args': 2, //禁止 function 定义中出现重名参数
'no-dupe-keys': 2, //禁止对象字面量中出现重复的 key
'no-duplicate-case': 2, //禁止出现重复的 case 标签
'no-const-assign': 1, //禁止修改const声明的变量
'no-empty': 1, //禁止出现空语句块
'no-empty-character-class': 2, //禁止在正则表达式中使用空字符集
'no-ex-assign': 2, //禁止对 catch 子句的异常参数重新赋值
'no-extra-boolean-cast': 1, //禁止不必要的布尔转换
'no-extra-semi': 1, //禁止不必要的分号
'no-func-assign': 2, //禁止对 function 声明重新赋值
'no-inner-declarations': 0, //禁止在嵌套的块中出现变量声明或 function 声明,ES6中无需禁止
'no-invalid-regexp': 2, //禁止 RegExp 构造函数中存在无效的正则表达式字符串
'no-irregular-whitespace': 1, //禁止在字符串和注释之外不规则的空白
'no-obj-calls': 2, //禁止把全局对象作为函数调用,比如Math() JSON()
'no-regex-spaces': 1, //禁止正则表达式字面量中出现多个空格
'no-sparse-arrays': 1, //禁用稀疏数组
'no-unexpected-multiline': 1, //禁止出现令人困惑的多行表达式
'no-unreachable': 1, //禁止在return、throw、continue 和 break 语句之后出现不可达代码
'no-unsafe-finally': 2, //禁止在 finally 语句块中出现控制流语句
'no-unsafe-negation': 1, //禁止对关系运算符的左操作数使用否定操作符
'use-isnan': 2, //要求使用 isNaN() 检查 NaN,如 isNaN(foo),而非foo == NaN
'valid-typeof': 2, //强制 typeof 表达式与有效的字符串(如: 'undefined', 'object', 'boolean', 'number', 'string', 'function','symbol')进行比较
'no-case-declarations': 1, //不允许在 case 子句中使用词法声明
'no-empty-pattern': 2, //禁止使用空解构模式
'no-fallthrough': 2, //禁止 case 语句落空
'no-global-assign': 2, //禁止对原生对象或只读的全局对象进行赋值
'no-octal': 1, //禁用八进制字面量
'no-redeclare': 1, //禁止多次声明同一变量
'no-self-assign': 1, //禁止自我赋值
'no-unused-labels': 1, //禁用出现未使用过的标
'no-useless-escape': 1, //禁用不必要的转义字符
'no-delete-var': 2, //禁止删除变量
'no-undef': 2, //禁用使用未声明的变量,除非它们在 /*global */ 注释中被提到
'no-unused-vars': 1, //禁止出现未使用过的变量
'constructor-super': 2, //要求在构造函数中有 super() 的调用
'no-class-assign': 2, //禁止给类赋值
'no-dupe-class-members': 2, //禁止类成员中出现重复的名称
'no-new-symbol': 2, //禁止 Symbol 和 new 操作符一起使用
'no-this-before-super': 2, //禁止在构造函数中,在调用 super() 之前使用 this 或 super
'require-yield': 2, //要求 generator 函数内有 yield
'no-mixed-spaces-and-tabs': 1, //要求不适用space,tab混用
'react/forbid-prop-types': [1, { forbid: ['any'] }], //禁止某些propTypes
'react/prop-types': 1, //没用对props类型进行校验
'react/jsx-closing-bracket-location': 1, //在JSX中验证右括号位置
'react/jsx-curly-spacing': [1, { when: 'never', children: true }], //在JSX属性和表达式中加强或禁止大括号内的空格。
'react/jsx-key': 2, //在数组或迭代器中验证JSX具有key属性
'react/jsx-max-props-per-line': [1, { maximum: 1 }], // 限制JSX中单行上的props的最大数量
'react/jsx-no-duplicate-props': 2, //防止在JSX中重复的props
'react/jsx-no-undef': 1, //在JSX中禁止未声明的变量
'react/no-string-refs': 1, //Using string literals in ref attributes is deprecated
'react/jsx-uses-react': 1, //防止反应被错误地标记为未使用
'react/jsx-uses-vars': 1, //防止在JSX中使用的变量被错误地标记为未使用
'react/no-danger': 1, //防止使用危险的JSX属性
'react/no-did-update-set-state': 2, //防止在componentDidUpdate中使用setState
'react/no-did-mount-set-state': 0, //防止在componentDidUpdate中使用setState
'react/no-direct-mutation-state': 2, //防止this.state赋值
'react/no-unknown-property': 2, //防止使用未知的DOM属性
'react/prefer-es6-class': 1, //为React组件强制执行ES5或ES6类
'react/react-in-jsx-scope': 0, //使用JSX时,必须要引入React
'react/sort-comp': 0, //强制组件方法顺序
'react/sort-prop-types': 0, //强制组件属性顺序
'react/jsx-sort-props': 1,
'react/no-deprecated': 1, //不使用弃用的方法
'react/jsx-equals-spacing': 1, //在JSX属性中强制或禁止等号周围的空格
'react/wrap-multilines': 0,
'comma-dangle': 1, //对象字面量项尾不能有逗号
'react/no-multi-comp': 0, //防止每个文件有多个组件定义
'flowtype/generic-spacing': 0, //泛型对象的尖括号中类型前后的空格规范
'flowtype/space-after-type-colon': 0, //类型注解分号后的空格规范
// react-hooks
'react-hooks/rules-of-hooks': 'error',
'react-hooks/exhaustive-deps': 'warn'
}
};

Vue CLI 3 插件

1
vue add @ziyi2/ui-cz

以上是我对下列视频及文章的归纳和总结
花十分钟的时间武装你的代码库


参考资料
cz-customizable
Cz工具集使用介绍 - 规范Git提交说明

JavaScript 的 API 文档生成

看公众号有一篇关于 JS 工具函数大全,心想着纳入我的博客中,又想起用过 vue-element-admin 中有个日期过滤器不错,也总结到一起,一看过滤器脚本中注释中有 @param 由此便引出了这篇博客。

JSDoc

什么是 JSDoc

JSDoc 3是用于JavaScript的API文档生成器,类似于Javadoc或phpDocumentor。您可以将代码注释直接添加到源代码中,并直接添加到源代码中。JSDoc工具将扫描您的源代码并为您生成一个HTML文档网站。
JSDoc的目的是记录您的JavaScript应用程序或库的API。假定您将要记录诸如模块,名称空间,类,方法,方法参数之类的内容。
JSDoc 注释一般应该放置在方法或函数声明之前,它必须以 / ** 开始,

使用 JSDoc

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
/**
* Book类,代表一个书本.
* @constructor
* @param {string} title - 书本的标题.
* @param {string} author - 书本的作者.
*/
function Book(title, author) {
this.title = title;
this.author = author;
}
Book.prototype = {
/**
* 获取书本的标题
* @returns {string|*}
*/
getTitle: function () {
return this.title;
},
/**
* 设置书本的页数
* @param pageNum {number} 页数
*/
setPageNum: function (pageNum) {
this.pageNum = pageNum;
},
};
阅读更多

前端优化

资源

  • 资源压缩
    (去除无用代码、函数、资源压缩)
    (线上:极端压缩。debug模式:不压缩)

网路请求

  • 减少网络请求
    (资源合并例如:图片采用:tpg、webp、雪碧图、base64。html合并、js合并、css合并)
    (首屏加载、根据相关性加载)

  • 缓存
    (http cache、localstorage缓存、使用离线包)
    (高时效性页面:cgi、html合并。低时效性页面:缓存(离线包))


相关资料
多 “维” 优化——前端高并发策略的更深层思考
完整攻略!让你的网页加载时间降低到 1s 内!
解读新一代 Web 性能体验和质量指标

axios 在 vue 中的应用

Axios

Axios 是什么

  • Axios 是一个基于 promise 的 HTTP 库
  • 可以用于浏览器和 node.js

Axios 有哪些特性

  • 支持 Promise API
  • 拦截请求和响应
  • 转换请求数据和响应数据
  • 取消请求
  • 自动转换 JSON 数据
  • 客户端支持防御 XSRF

Axios 浏览器支持

阅读更多

UI 框架

英文 | https://www.geeksforgeeks.org/10-best-css-frameworks-for-frontend-developers-in-2020/?ref=rp
翻译 | web前端开发(ID:web_qdkf)

Bootstrap

  • 快速成型
  • 大型生态系统
  • 大量的组件
  • LESS和SASS支持
  • 简单的文档和较低的学习曲线
  • 由Twitter开发,因此对社区有长期的长期信任。

Foundation

  • 创建响应式设计
  • 强大的电子邮件框架
  • 极高的灵活性
  • 在线网络研讨会培训支持。
  • 易于定制。
  • 提供了完整的模块化工具集,可让您解决几乎所有的界面任务
  • UI组件及更多:先进的成像系统,定价表组件,表单验证,垂直时间轴布局,RTL支持等等。

Bulma

  • 所有CSS类名称均按逻辑命名,因此易于学习和记住语法。
  • 纯CSS,无JavaScript
  • 大社区
  • 模块化:使用Saas构建,您可以通过仅导入所需的必要功能来设计界面。
  • 大量组件,例如垂直对齐解决方案,布局以及媒体对象
阅读更多

Promise 从入门到实战

JavaScript 编写异步代码

  • 回调函数
  • 事件监听
  • 发布订阅,消息中心
  • Promise/A+
  • Generator
  • async/await
  • Thunk

Promise 示例

Promise 值具有穿透性,穿透一个没有用的 .then() 直接到达 一个有用的 .then()

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
let p1 = new Promise((resolve,reject) => {
resolve(1)
})

p1.then()
.then(value => {
console.log(value);
return value + 1
}).then(value => {
console.log(value);
})

// output: 1 2

// 两者不等价
let p2 = Promise.resolve(1);
p2.then(v => { v + 1 })
.then(v => { console.log(v)});

// output: 2

let p3 = Promise.resolve(1);
p3.then(v => v + 1);
p3.then(v => console.log(v));

// output: 1

let p4 = Promise.resolve(1);
p4.then(v => {
return new Promise((resolve,reject) => {
setTimeout(() => resolve(2))
})
}).then(v => console.log(v));

// output: 2

let p1 = new Promise((resolve , reject) => {
reject(new Error ('some wrong'))
})

let p2 = p1.then(
value => { console.log(value) },
reason => { console.log(reason.message) }
)

// output: something wrong

p2.then(()=>{
throw new Error('something wrong 1')
}).catch(reason => {
console.log(reason.message)
})

// output: something wrong 1

Promise.reject(new Error('error')))
.catch(reason => {
console.log(reason.message);
return `${reason.message} occured`
}).then(v => console.log(v));

// output: error error occured

function sleep(duration){
return new Promise((resolve,reject) =>{
setTimeout(() => {resolve(),duration})
})
}

async function run(){
const start = Date.now();
await sleep(1000);
await sleep(2000);
await sleep(3000);
console.log(Date.now() - start)
}

run()
// ==== 以下是 promise 的书写方法 === //
function run1(){
const start = Date.now();
return Promise.resolve()
.then(function() { return sleep(1000)})
.then(function() { return sleep(2000)})
.then(function() { return sleep(3000)})
.then(function() { console.log(Date.now() - start)})
}

run1();

实现 Promise 需要注意

  • 符合 Promise/A+ 标准
  • 不暴露过多的函数和变量
  • Promise 状态只能修改一次
  • then() 和 catch 需要异步执行
  • then 能够实现链式调用
  • then 返回新的 Promise
  • then 值能够穿透
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
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
//  Promise 状态有 3 个  
const PENDING = 0;
const FULFILLED = 1;
const REJECTED = 2;
class Promise {
constructor(fn){
// 初始状态
this._state = PENDING;
this._data = null;

this._onFulfilledCallbacks = [];
this._onRejectedCallbakcs = [];

run(this,fn);
}

static deferred (){
const dfd = {}
dfd.promise = new promise ((resonlve,reject) => {
dfd.resolve = resolve;
dfd.reject = reject;
})
return dfd;
}

then(onFulfilled, onRejected){
if(typeof onFulfilled !== 'function'){
onFulfilled = function(data){
return data
}
}
if(typeof onRejected !== 'function'){
onRejected = function(reason){
throw reason
}
}

let promise2;

if(this._state === FULFILLED){
promise2 = new Promise((resolve,reject ) =>{
setTimeout(()=>{
try{
const x = onFulfilled(this._data)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
})
})
}else if(this._state === REJECTED){
promise2 = new Promise((resolve,reject ) =>{
setTimeout(()=>{
try{
const x = onRejected(this._data)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e);
}
})
})

}else if(this._state === PENDING){
promise2 = new Promise((resolve,reject)=>{
this._onFulfilledCallbacks.push(data => {
try{
const x = onFulfilled(data)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
this._onRejectedCallbakcs.push(reason =>{
try{
const x = onRejected (reason)
resolvePromise(promise2,x,resolve,reject)
}catch(e){
reject(e)
}
})
})
}
}
return promise2
}

catch(onRejected){
return this.then(undefined,onRejected)
}

function resolvePromise (promise2,x,resolve,reject){
if(x instanceof Promise){
return x.then(resolve,reject);
}else{
resolve(x)
}
}
function run (promise,fn){
try{
fn(
data => {
// do resolve
resolve(promise,data)
},
reason => {
// do reject
reject(promise,reason)
}
)
}catch(e){
// reject
reject(promise,e)
}
}

function resolve(promise,data) {
// 处理值是 Promise 的情况
if(data instanceof Promise){
return data.then(
d => { resolve (promise,d)},
r => { reject (promise,r)}
)
}
if(promise._state !== PENDING){
callback(data)
}

setTimeout(()=>{
promise._state = FULFILLED;
promise._data = data;

for(let callback of promise._onFulfilledCallbacks) {
callback(data)
}
})

}

function reject(promise,reason) {
if(promise._state !== PENDING){
return
}

setTimeout(()=>{
promise_state = REJECTED
promise_data = reason

for (let callback of promise__onRejectedCallbakcs){
callback(reason )
}
})
}

module.exports = Promise;

校验 是否符合 promise 规范

promises-aplus-tests

Promise 是什么

按照用途来解释

  • 主要用于异步计算。
  • 可以将异步操作队列化,按照期望的顺序执行,返回符合预期的结果。
  • 可以在对象之间传递和操作 Promise,帮助我们处理队列。

为什么会有 Promise

JavaScript 包含大量异步操作

JavaScript 为检查表单而生。
创造它的首要目标是操作 DOM。
所以,JavaScript 的操作大多是异步的。
是 UI 语言造成界面冻结是大忌

为什么异步操作可以避免界面冻结

同步问题:按顺序执行,按顺序完成。

异步:完成顺序和执行顺序无关。

异步操作的常见语法

事件监听与响应:

1
2
3
4
5
6
7
8
  document.getElementById("start").addEventListener("click", start, false);

function start() {
// 响应事件,进行相应的操作
}

// jQuery 用 '.on()' 也是事件监听
$("#start").on("click", start);

异步操作的常见语法

1
2
3
4
5
6
7
8
9
10
$.ajax("http://baidu.com", {
success: function (res) {
// 这里就是回到函数了
},
});

// 或者在页面加载完毕后回调
$(function () {
// 这里也是回调函数
});

浏览器中的 JavaScript

  • 异步操作以事件为主
  • 回调主要出现在 Ajax 和 File API

Node.js 的出现

对异步的依赖进一步加剧了

  • 无阻赛高并发,是 Node.js 的招牌。
  • 异步操作是其保障。
  • 大量操作依赖回调函数。

异步回调的问题

  • 嵌套层次很深,难以维护
  • 无法正常使用 returnthrow
  • 无法正常检索堆栈信息
  • 多个回调之间难以建立联系

Promise 详解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise(
/* 执行器 executor */
function (resolve, reject) {
// 一段耗时很长的异步操作

resolve(); // 数据处理完成

reject(); // 数据处理出 错
}
).then(
function A() {
// 成功,下一步
},
function B() {
// 失败,做相应处理
}
);

Promise 是一个代理对象,它和原先要进行的操作并无关系。
它通过引入一个回调,避免更多的回调。

Promise 有 3 个状态

pending [待定] 初始状态
fulfilled [实现] 操作成功
reject [被否定] 操作失败

Promise 状态发生改变,就会触发 .then() 里的响应函数处理后续步骤。
Promise 状态一经改变,不会再变。

Promise 执行顺序

范例

1
2
3
4
5
6
7
new Promise((resolve) => {
setTimeout(() => {
resolve("hello");
}, 2000);
}).then((value) => {
console.log(value + "world");
});

两步执行范例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 两步执行范例
new Promise( resolve =>{
setTimeout(()=>{
resolve('hello');
},2000);
}).then( value => {
console.log(value);
return new Promise( resolve =>{
setTimeout(() => {
resolve('world');
},2000);
});
}).then( value =>{
console.log( value + 'world');
})

对已完成的 promise 执行

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对已完成的 promise 执行
let promise = new Promise(resolve =>{
setTimeout(() => {
console.log('the promise fulfilled');
resolve('hello,world');
},1000)
})

setTimeout(()=>{
promise.then( value =>{
console.log(value);
})
},3000)

then 里不返回 promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// then 里不返回 promise
new Promise( resolve =>{
setTimeout(() =>{
resolve('hello');
},2000);
}).then(value =>{
console.log(value);
console.log('everyone');
(function (){
return new Promise(resolve => {
setTimeout(()=>{
console.log('Mr.Laurence');
resolve(' Merry Xmas ');
},2000)
});
}());
return false;
}).then( value =>{
console.log(value + ' world');
})

.then()

  • .then() 接受两个函数作为参数,分别代表 fulfilled 和 rejected
  • .then() 返回一个新的 Promise 实例,所以它可以链式调用
  • 当前面的 Promise 状态改变时,.then() 根据其最终状态,选择特定的状态响应函数执行
  • 状态响应函数可以返回新的 Promise,或其他值
  • 如果返回新的 Promise,那么下一级 .then() 会在新 Promise 状态改变之后执行。
  • 如果返回其他任何值,则会立刻执行下一级 .then()

.then()里有 .then()的情况

  • 因为 .then() 返回的还是 Promise 实例。
  • 会等里面的 .then() 执行完,再执行外面的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
console.log('start')
new Promise( resolve =>{
console.log('Step 1');
setTimeout(() =>{
resolve('1000');
},1000);
}).then(value =>{
return new Promise( resolve =>{
console.log('Step 1-1');
setTimeout(()=>{
resolve(110);
},1000)
}).then( value => {
console.log('Step 1-2');
return value;
}).then(value => {
console.log('Step 1-3');
return value;
})
}).then( value =>{
console.log(value);
console.log('Step 2');
})
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 展开
console.log('start')
new Promise( resolve =>{
console.log('Step 1');
setTimeout(() =>{
resolve('1000');
},1000);
}).then(value =>{
return new Promise( resolve =>{
console.log('Step 1-1');
setTimeout(()=>{
resolve(110);
},1000)
})
}).then( value => {
console.log('Step 1-2');
return value;
}).then(value => {
console.log('Step 1-3');
return value;
}).then( value =>{
console.log(value);
console.log('Step 2');
})

下面 Promise 的区别

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
doSomething().then(function(){
return doSomethingElse()
});


// doSomething().then(function(){
// return doSomethingElse();
// }).then(finalHandler);

// 答案
// doSomething
// |-----------|
// doSomethingElse(undefined)
// |------------|
// finalHandler(resultOfDoSomethingElse)
// |------------|


doSomething().then(function(){
doSomethingElse()
});

// doSomething().then(function(){
// doSomethingElse();
// }).then(finalHandler);

// 答案
// doSomething
// |-----------|
// doSomethingElse(undefined)
// |------------|
// finalHandler(resultOfDoSomethingElse)
// |------------|

doSomething().then(
doSomethingElse()
);

// doSomething().then(
// doSomethingElse();
// ).then(finalHandler);

// 答案
// doSomething
// |-----------|
// doSomethingElse(undefined)
// |----------------------------|
// finalHandler(resultOfDoSomethingElse)
// |------------|


doSomething().then(
return doSomethingElse
);

// doSomething().then(
// doSomethingElse;
// ).then(finalHandler);

// 答案
// doSomething
// |-----------|
// doSomethingElse(resultOfDosomething)
// |------------|
// finalHandler(resultOfDoSomethingElse)
// |------------|

错误处理

Promise 会自动捕获内部异常,并交给 rejected 响应函数处理。
错误处理两种做法:

  • reject(‘错误信息’).then(null,message =>{})
  • throw new Error(‘错误信息’).catch( message => {})

推荐第二种,更加清晰好读,并且可以捕获前面的错误。

1
2
3
4
5
6
7
8
9
new Promise( resolve =>{
setTimeout(()=>{
throw new Error('bye');
},2000);
}).then( value =>{
console.log(value + 'world');
}).catch( error =>{
console.log( 'Error:',error.message )
})
1
2
3
4
5
6
7
8
9
new Promise( (resolve,reject) =>{
setTimeout(()=>{
reject('bye');
},2000);
}).then( value =>{
console.log(value + 'world');
},value =>{
console.log('Error',value);
})

.catch() + .then()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
new Promise(resolve =>{
setTimeout(()=>{
resolve();
},1000)
}).then(()=>{
console.log('start');
throw new Error('test error');
}).catch( err => {
console.log('I catch ',err);
// throw new Error('another error');
}).then(()=>{
console.log('arrive here');
}).then(()=>{
console.log('...and here');
}).catch( err =>{
console.log('No,I catch',err);
})
注意: 强烈建议在所有队列最后都加上 .catch(),以避免漏掉错误处理造成意想不到的问题。
1
2
3
4
5
6
doSomething()
.doAnotherThing()
.doMoreThing()
.catch( err =>{
console.log(err);
})

Promise 常用函数

Promise.all()

批量执行

  • Promise.all([p1,p2,p3,…]) 用于将多个 Promise 实例,包装成一个新的 Promise 实例
  • 返回的实例就是普通 Promise
  • 接收一个数组作为参数。
  • 数组里可以是 Promise 对象,也可以是别的值,只有 Promise 会等待状态改变。
  • 当所有子 Promise 都完成,该 Promise 完成,返回值是全部值的数组
  • 有任何一个失败,该 Promise 失败,返回值是第一个失败的子 Promise 的结果。
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
Promise.all([1,2,3]).then( all => {
console.log('1: ',all);
return Promise.all([function(){
console.log('ooxx');
},'xxoo',false]);
}).then(all =>{
console.log('2: ',all);
let p1 = new Promise( resolve =>{
setTimeout(()=>{
resolve('I\'m P1');
},1500)
})
let p2 = new Promise( resolve =>{
setTimeout(()=>{
resolve('I\'m P2');
},1450)
})
return Promise.all([p1,p2]);
}).then( all =>{
console.log('3: ',all);
let p1 = new Promise( resolve =>{
setTimeout(() => {
resolve('I\'m P1');
},1500)
})
let p2 = new Promise( (resolve,reject) =>{
setTimeout(() => {
reject('I\'m P2');
},1000)
})
let p3 = new Promise( (resolve,reject) =>{
setTimeout(() => {
reject('I\'m P3');
},3000)
})
}).then( all =>{
console.log('all',all);
}).catch(err =>{
console.log(err);
})

Promise.add() 和 .map() 连用

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
const fs = require('fs');
const path = require('path');
const FileSystem = require('./FileSystem');

function findLargest(dir) {
return FileSystem.readDir(dir, 'utf-8').then( files => {
return Promise.all( files.map( file => {
return new Promise (resolve => {
fs.stat(path.join(dir, file), (err, stat) => {
if (err) throw err;
if (stat.isDirectory()) {
return resolve({
size: 0
});
}
stat.file = file;
resolve(stat);
});
});
}));
}).then( stats => {
let biggest = stats.reduce( (memo, stat) => {
if(memo.size < stat.size) {
return stat;
}
return memo;
});
return biggest.file;
})
}

实现队列

使用 .forEach()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
function queue(things){
let promise = Promise.resolve();
things.forEach( thing =>{
promise = promise.then(()=>{
return new Promise( resolve =>{
doThing(thing,()=>{
resolve();
});
})
})
})
return promise;
}
queue('lots','of','things',...)
常见错误: 没有把 .then()产生的新 Promise 实例赋给 promise,没有生成队列。

使用 .reduce()

1
2
3
4
5
6
7
8
9
10
11
12
function queue(things){
return things.reduce((promise,thing) => {
return promise.then(() => {
return new Promise( resolve => {
doThing(thing,()=>{
resolve();
});
});
});
},Promise.resolve());
}
queue('lots','of','things',...)
常见错误: Promise 实例创建之后,会立刻运行执行器代码,所以这个也无法达成队列的效果。

半成品爬虫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let url = ['http://blog.meathill.com/'];
function fetchAll(urls) {
return urls.reduce((promise, url) => {
return promise.then( () => {
return fetch(url);
});
}, Promise.resolve());
}
function fetch(url) {
return spider.fetch(url)
.then( content => {
return saveOrOther(content);
})
.then( content => {
let links = spider.findLinks(content);
return fetchAll(links);
});
}
fetchAll(url);

Promise.resolve()

返回一个 fulfilled 的 Promise 实例,或原始 Promise 实例。

  • 参数为空,返回一个状态为 fulfilled 的 Promise 实例。
  • 参数是一个跟 Promise 无关的值,同上,不过 fulfilled 响应函数会得到这个参数
  • 参数为 Promise 实例,则返回该实例,不做任何修改
  • 参数为 thenable,立刻执行它的 .then()。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Promise.resolve().then( (value) => {
console.log('Step 1', value);
return Promise.resolve('Hello');
}).then( value =>{
console.log(value,'World');
return Promise.resolve(new Promise( resolve => {
setTimeout(()=>{
resolve('Good');
},2000);
}));
}).then( value => {
console.log(value, 'evening');
return Promise.resolve({
then(){
console.log(', everyone');
}
})
})

Promise.reject()

返回一个 rejected 的 Promise 实例。

Promise.reject() 不认 thenable
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let promise = Promise.reject('something wrong');

promise.then( () => {
console.log('it\'s ok');
}).catch( () => {
console.log('no, it\'s not ok');
return Promise.reject({
then() {
console.log('it will be ok');
},
catch() {
console.log('not yet');
}
});
});

Promise.race()

类似 Promise.all(),区别在于它有任意一个完成就算完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let p1 = new Promise(resolve => {
// 这是一个长时间的调用
setTimeout(() => {
resolve('I\'m P1');
}, 10000);
});

let p2 = new Promise(resolve => {
// 这是个稍短的调用
setTimeout(() => {
resolve('I\'m P2');
}, 2000)
});

Promise.race([p1, p2]).then(value => {
console.log(value);
});

常见用法:

  • 把异步操作和定时器放在一起
  • 如果定时器先触发,就认为超时,告知用户

实际开发的 Promise

异步回调包装成 Promise

优点:

  • 可读性更好
  • 返回的结果可以加入任何 Promise 队列
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
const fs = require('fs');

module.exports = {
// 读取目录
readDir: function (path, options) {
return new Promise( resolve => {
fs.readdir(path, options, (err, files) => {
if (err) {
throw err;
}
resolve(files);
});
});
},
// 读取文件
readFile: function (path, options) {
return new Promise( resolve => {
fs.readFile(path, options, (err, content) => {
if (err) {
throw err;
}
resolve(content);
});
});
}
};

// 使用
const fs = require('./FileSystem');

fs.readFile('../README.md','utf-8').then(content =>{
console.log(content);
})

把任意异步操作包装成 Promise

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 弹出窗体
let confirm = popupManager.confirm('您确定么?');
confirm.promise.then(() => {
// do confirm staff
})
.catch(() => {
// do cancel staff
});

// 窗体的构造函数
class Confirm {
constructor() {
this.promise = new Promise( (resolve, reject) => {
this.confirmButton.onClick = resolve;
this.cancelButton.onClick = reject;
})
}
}

两步执行

1
2
3
4
5
6
7
8
9
10
11
12
13
// 对已完成的 promise 执行
let promise = new Promise(resolve =>{
setTimeout(() => {
console.log('the promise fulfilled');
resolve('hello,world');
},1000)
})

setTimeout(()=>{
promise.then( value =>{
console.log(value);
})
},3000)

实际开发会对请求进行 promise 化,两种情况

返回的数据直接更改值

1
2
3
4
5
6
7
8
9
10
getlist(data){
this.api.getlist(data).then(res=>{
this.list = res.data.data;
})
}

// 下拉刷新
pullDown(){
this.getlist(data);
}

返回数据传递给下个调用的函数

1
2
3
4
5
6
7
8
9
10
11
12
getlist(data){
return this.api.getlist(data).then(res=>{
return Promise.resolve(res.data.data);
})
}

// 模糊搜索
querySearchAsync(){
this.getlist(data).then((value)=>{
this.searchlist = value;
})
}

jQuery

jQuery 已经实现了 Promise。1.5 版开始尝试, 3.0版完成的 Promise。

参考 jQuery 的 ajax 实现。

1
2
3
4
5
$.ajax(url,{
dataType: 'json'
}).then(json =>{
// 进行操作
})

IE

如果需要在 IE 中使用 Promise,有两个选择:

Fetch API

Fetch API 是 XMLHttpRequest 的现代化替代方案。
可以在 service workers、Cache API 使用。

  • 更强大,更友好。
  • 直接返回一个 Promise 实例。
  • response 是一个二进制数据流,需要调用 json() 方法可以转换成 json 数据
1
2
3
4
5
6
7
fetch('some.json').then( res =>{
return res.json()
}).then( json =>{
// do something with the json
}).catch( err =>{
console.log(err);
})

async/await

ES2017 新增运算符,新的语言元素

  • 赋予 JavaScript 以顺序手法编写异步脚本的能力
  • 既保留异步运算的无阻塞特性,还继续使用同步写法。
  • 还能正常使用 return/try/catch。

async/await 仍然需要 Promise!

希望全面了解,请参考 async/await 入门

1
2
3
4
async function f1(){
const result = await resolveAfter2Seconds();
console.log(result);
}

协程

  1. 协程A开始执行。
  2. 协程A执行到一半,进入暂停,执行权转移到协程B。
  3. (一段时间后)协程B交还执行权。
  4. 协程A恢复执行。

Thunk

参数求值

传名调用,在执行时求值

1
2
3
4
5
6
7
8
9
10
11
12
13
function f(m){
return m * 2;
}

f(x + 5);
// 等同于
var thunk = function () {
return x + 5;
};

function f(thunk){
return thunk() * 2;
}

在 JavaScript 语言中,Thunk 函数替换的不是表达式,而是多参数函数,将其替换成单参数的版本,且只接受回调函数作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
// 正常版本的readFile(多参数版本)
fs.readFile(fileName, callback);

// Thunk版本的readFile(单参数版本)
var readFileThunk = Thunk(fileName);
readFileThunk(callback);

var Thunk = function (fileName){
return function (callback){
return fs.readFile(fileName, callback);
};
};

任何函数,只要参数有回调函数,就能写成 Thunk 函数的形式。下面是一个简单的 Thunk 函数转换器。

1
2
3
4
5
6
7
8
9
10
11
12
13
var Thunk = function(fn){
return function (){
var args = Array.prototype.slice.call(arguments);
return function (callback){
args.push(callback);
return fn.apply(this, args);
}
};
};

// 使用上面的转换器,生成 fs.readFile 的 Thunk 函数。
var readFileThunk = Thunk(fs.readFile);
readFileThunk(fileA)(callback);

Thunkify 模块

生产环境的转换器,建议使用 Thunkify 模块

Thunk 函数的自动流程管理

控制执行权

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
var fs = require('fs');
var thunkify = require('thunkify');
var readFile = thunkify(fs.readFile);

var gen = function* (){
var r1 = yield readFile('/etc/fstab');
console.log(r1.toString());
var r2 = yield readFile('/etc/shells');
console.log(r2.toString());
};


var g = gen();
var r1 = g.next();
r1.value(function(err, data){
if (err) throw err;
var r2 = g.next(data);
r2.value(function(err, data){
if (err) throw err;
g.next(data);
});
});


function run(fn) {
var gen = fn();
function next(err, data) {
var result = gen.next(data);
if (result.done) return;
result.value(next);
}
next();
}
run(gen);

Generator

Generator 函数是协程在 ES6 的实现,最大特点就是可以交出函数的执行权(即暂停执行)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function* gen(x){  // * 号区别
try {
var y = yield x + 2; // yield 暂停
} catch (e){
console.log(e);
}
return y;
}

var g = gen(1); // 返回的是指针对象
g.next() // { value: 3, done: false } value 属性是 yield 语句后面表达式的值
g.next() // { value: undefined, done: true } Generator 是否执行完毕
// 有参数2,这个参数可以传入 Generator 函数,作为上个阶段异步任务的返回结果,被函数体内的变量 y 接收。
g.next(2) // { value: 2, done: true }
g.throw('出错了'// 捕获错误并抛出

相关资料
Promise 入门
Generator 函数的含义与用法
Thunk 函数的含义和用法
fetch polyfill兼容库
彻底搞懂JS事件中的循环机制 Event Loop
巨大的提升!重启的async函数和promises“译”

async/await的基础用法

Promise 初入门径
Promise 探讨
jQuery的deferred对象详解
Promises/A+ 规范


相关代码


开源
es6-promise

秒杀活动

秒杀活动特点

“限时限量”、“价格极地”、“几秒内就被抢光”

秒杀活动带来的问题:
1、对现有网站业务的冲击
2、高并发情况以及数据库负载
3、突然增加的网路和服务器宽带
4、直接下单
5、防止机器秒杀

解决方案
1、秒杀系统单独部署,如果有需要甚至是独立的域名,根网站完全隔离开,除了底层数据库中某些数据,也就是从 Web 服务器、数据库服务器等都会有单独的一套。
2、在没有开始秒杀之前,把商品的展示页面做静态化,也就是简单的 HTML 页面不做任何业务逻辑的处理,也不访问数据库,就只是很简单的展示,等真正需要秒杀的时候再打开另外一个页面,这个页面有业务逻辑,也会有可能访问数据库的。在秒杀活动之前大多数用户会不停的刷新页面。
3、可以根运营商购买或者是租借网络服务器带宽,将秒杀商品的静态页面缓存到 CDN,租借 CDN。
4、避免让用户在秒杀活动前知道秒杀活动下单的接口或者页面。对下单页面添加动态验证参数,并且该页面是在秒杀开始前无法访问到的。
5、ip 的阻止,一个 ip 可以访问几次,验证码。

windows server 2012 standard 搭建 git 服务 探索

服务器系统介绍

windows server 2012 standard

搭建项目托管平台

在搭建项目托管平台之前我们应该先搞明白版本控制这个概念。

版本控制

什么是 版本控制?

来源 百度百科
版本控制是指对软件开发过程中各种程序代码、配置文件及说明文档等文件变更的管理,是软件配置管理的核心思想之一。
版本控制包括:检入检出控制、分支和合并、历史记录。

来源 TOWER
你可以把一个版本控制系统(缩写VCS)理解为一个“数据库”,在需要的时候,它可以帮你完整地保存一个项目的快照。当你需要查看一个之前的快照(称为“版本” )时,版本控制系统可以显示出当前版本与上一个版本之间的所有替换的细节。

来源 git
版本控制是一种记录一个或若干文件内容变化,以便将来查阅特定版本修订情况的系统。

为什么用 版本控制?

1、备份用
2、协调开发用

版本控制的变革

来源 git
本地版本控制系统
本地版本控制系统
缺点:
1、中央服务器故障,将无法提交更新,无法协同工作。如果中心数据库所在的磁盘发生损坏,又恰当无备份,,毫无疑问将丢失所有数据
包括项目的整个变更历史。
2、多人协作繁琐。

集中化的版本控制系统
集中化的版本控制系统
缺点:中央服务器故障,将无法提交更新,无法协同工作。如果中心数据库所在的磁盘发生损坏,又恰当无备份,,毫无疑问将丢失所有数据
包括项目的整个变更历史。
记住这个概念下面会有涉及到。

分布式版本控制系统
分布式版本控制系统
解决了本地版本控制系统 集中化的版本控制系统 的缺点。
解决方式:客户端并不只提取最新版本的文件快照, 而是把代码仓库完整地镜像下来,包括完整的历史记录。
记住这个概念下面会有涉及到。

简单的来说版本控制系统会记录所有对项目文件的更改。

版本控制工具

现在主流版本控制工具是 Git 和 SVN。
咱们上面提到两个概念分别是 集中化的版本控制系统 和 分布式版本控制系统。

SVN 属于 集中化的版本控制系统。
Git 属于 分布式版本控制系统。

SVN VS Git

SVN 简介

来源SVN
Apache® Subversion®
“Enterprise-class centralized version control for the masses”

google 翻译
Apache Subversion
面向群众的企业级集中版本控制

提取关键字:企业级、集中、版本控制。
SVN 是一款集中式版本控制系统。
特点:可靠性高、模型和用法的简单性、覆盖面广(支持从个人到大型企业运营的各种用户和项目需求的能力)。

SVN 的一些概念

  • repository(源代码库)
  • Checkout(提取)
  • Commit(提交)
  • Update (更新)

SVN 的主要功能

  • 1、目录版本控制
  • 2、真实的版本历史
  • 3、自动提交
  • 4、纳入版本控管的元数据
  • 5、选择不同的网络层
  • 6、一致的数据处理方式
  • 7、有效的分支(branch)与标签(tag)
  • 8、Hackability

SVN 生命周期

1、创建版本库
2、检出
3、更新
4、执行变更
5、复查变化
6、修复错误
7、解决冲突
8、提交更改

SVN 客户端工具

TortoiseSVN 官网上下载 TortoiseSVN,TortoiseSVN有对应的语言包。
菜鸟教程TortoiseSVN 使用教程

1、新建一个工作目录例如 runoob01,用于存储工作档案

新建工作目录

2、将工作档案更新到本地

SVN checkout
检出至目录
检出至目录
检出至目录
检出至目录

3、新增档案及目录到 Repository 中

新增文件
加入文件
加入文件
加入文件
加入文件
添加到Repository

Git 简介

来源 Git
Git 是一个免费的开源分布式版本控制系统,旨在快速高效地处理从小型到大型项目的所有内容。
Git 易于学习, 占地面积小,具有闪电般的快速性能。它具有Subversion,CVS,Perforce和ClearCase之类的SCM工具,具有廉价的本地分
支,方便的暂存区域和 多个工作流等功能。

Git SVN 能做的 Git 基本都能实现。

Git 的一些概念

本来要搭建 SVN 我提议搭建 GitLab ,我但是我也没有搭建过 GitLab,服务器系统是 windows server,我查了查资料,得到结果是 如果要在 windows server 上搭建 GitLab 就要装 虚拟机 然后装 Ubuntu 之类的 操作系统然后再装 GitLab 那可是真………… ,gitlab官网也没有资料。

查找资料的过程中我发现 GitLab Runner 可以安装在 windows 上,查了下资料大致意思是 GitLab Runner 是将代码发送到 GitLab 上的服务。??…………

GitLab Runner 官方解释
GitLab Runner是一个开源项目,用于运行您的作业并将结果发送回GitLab。它与GitLab CI / CD结合使用,GitLab CI / CD是GitLab随附的用于协调作业的开源持续集成服务。

查找资料的过程中 我发现了 Gitblit 而且相关的搭建文章有很多,我开始了新的尝试。

Git 与 SVN 区别

1、Git 是分布式的,SVN 不是:这是 Git 和其它非分布式的版本控制系统,例如 SVN,CVS 等,最核心的区别。
2、Git 把内容按元数据方式存储,而 SVN 是按文件:所有的资源控制系统都是把文件的元信息隐藏在一个类似 .svn、.cvs 等的文件夹里。
3、Git 分支和 SVN 的分支不同:分支在 SVN 中一点都不特别,其实它就是版本库中的另外一个目录。
4、Git 没有一个全局的版本号,而 SVN 有:目前为止这是跟 SVN 相比 Git 缺少的最大的一个特征。
5、Git 的内容完整性要优于 SVN:Git 的内容存储使用的是 SHA-1 哈希算法。这能确保代码内容的完整性,确保在遇到磁盘故障和网络问题时降低对版本库的破坏。

托管平台

  • GitHub
  • GitLab
  • Bitbucket
  • 码云
  • coding

相关资料
版本控制
版本控制
版本控制
程序员编程开发网站,为什么要有版本控制工具这种东西?
Apache® Subversion®
菜鸟教程 SVN
TortoiseSVN
Git
Git工作流程
Git教程
菜鸟教程 Git
Git、Github、Gitlab的区别
Git、GitHub、GitLab三者之间的联系以及区别
gitLab的使用 和 git 、 github、gitlab的区别
Git、GitHub和GitLab的区别
Git与GitHub,Gitlab的区别
如何在你自己的服务器搭建类似github的服务,git部署站点
Git:本地部署代码到服务器(GitHub服务器 或 局域网搭建的git服务器)
如何搭建一个GitHub在自己的服务器上?
Svn与Git的区别,为什么使用git?
SVN与Git比较
版本管理工具介绍——SVN篇
SVN常见问题
版本控制工具 - git和svn
SVN与Git比较的优缺点差异
对比Git 与 SVN,这篇讲的很易懂
你还在用svn?git你无法拒绝的版本管理工具
Bonobo Git Server官网
Bonobo Git Server安装
Bonobo Git Server 先决条件
windows系统部署Bonobo.git服务器
在Windows上搭建Git Server
5分钟搞定 Git 服务端 安装 windows 2003 gitblit
gitblit搭建git服务器
Windows平台下Git服务器搭建
Windows平台下Git(gitblit)服务器搭建
怎么看电脑是否支持使用Hyper-V虚拟机
gitlab
gitblit
搭建gitlab仓库
在Windows上安装GitLab Runner
GitLab Runner
Gitblit GO安装和设置
windows下使用docker搭建gitlab的报错处理
使用docker-compose安装GitLab
安装Docker Compose
安装Docker引擎
服务器上的Git(Windows Server 2012)
Windows下使用docker部署gitlab CI服务
在Windows Server 2012上安装IIS 8
Internet信息服务(IIS)管理器在哪里打开
windows Server2012 IIS8.0配置安装完整教程
在Windows下搭建Gitlab服务器
windows server 2012 r2 安装docker

篇幅过长,回头填坑。

纯 web 调用手机摄像头扫描二维码 探索

html+js调用手机摄像头扫描二维码

根据 html+js调用手机摄像头扫描二维码 文章尝试发现 createObjectURL 废弃了 然后根据 谷歌浏览器更新后无法使用摄像头Failed to execute ‘createObjectURL’ on ‘URL’ 文章修改然后报错 error! notreadableerror could not start video source。…………

下载他的apk运行也报错
error could not start audio source。…………

jsqrcode

mui如何实现扫码功能

根据 mui如何实现扫码功能 可以实现扫码。

mui 实现原理是 HTML5+


相关资料
html+js调用手机摄像头扫描二维码
谷歌浏览器更新后无法使用摄像头Failed to execute ‘createObjectURL’ on ‘URL’
NotReadableError: Failed to allocate videosource
用于 JavaScript 的跨浏览器QRCode生成器
qrcodejs
Javascript QRCode扫描器
jsqrcode
zxing-typescript
mui如何实现扫码功能


开源
instascan
html5-qrcode

MUI 视频教程

MUI 介绍、新项目创建、基础布局

MUI 为何诞生

1、性能和体验的差距,一直是 mobile app 开发者放弃 HTML5 的首要原因。
2、浏览器天生的切页白屏、不忍直视的转页动画、浮动元素的抖动、无法流畅下拉刷新、侧滑抽屉卡顿等问题,这些都让 HTML5 开发者倍感挫败,尤其在 Android 低端机运行。
3、浏览器默认控件样式又少又丑,制作一个漂亮的控件非常麻烦,也有一些制作简单的 ui 框架但性能低下。mui 框架有效的解决了这些问题,这是一个可以方便开发出高性能 App 的框架,也是目前最接近原生 App 效果的框架。

MUI 的定位是:最接近原生体验的移动 App 的 UI 框架

基于 mui 的定位,产生了 mui 的几个特点,轻、小、只涉及 UI、只为移动 App 而生、界面风格原生话。

1、mui 不是 jq,不封装 dom 操作

与 ui 无关的 mui 不做,可以用 jq 或 zepto 就用,并不冲突。
但并不建议在 移动 App 里引入 jq 或 zepto 这些框架,原因如下:
为了性能,层层封装的框架,尤其是遍历 dom 时,影响效率,尤其在低端 Android 手机上。

原生 JS 挺简单,为何需要 jq ?

手机上只有 webkit 浏览器(忽略 wp,反正 mui 不支持 wp),根本就不需要 jq 这种封装来操作 dom。
而且 HBuilder 提供了代码块来简化开发,敲 dg、dq,直接生成 document.getElementById(“”)、document.querySelectorAll(“”),非常快捷方便,而且执行性能非常高,而且没有浏览器兼容问题。

2、mui、HTML5+、5+ Runtime 的关系说明

mui 是一个前端框架,HTML5+ 是一套 HTML5 能力扩展规范,HTML5+ Runtime 是实现 HTML5+ 规范的强化浏览器引擎。

开始体验 MUI

1、安装新版本 HBuilder

HBuilder

2、新建 app 项目

创建 MUI 项目

基础布局

头部

核心 css mui-bar mui-bar-nav

1
2
3
4
<header class="mui-bar mui-bar-nav">
<a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>
<h1 class="mui-title">test</h1>
</header>

主体部分

核心 css mui-content

1
2
3
<div class="mui-content">
test...
</div>

代码效果

组件

折叠面板

1
2
3
4
5
6
7
8
<ul class="mui-table-view">
<li class="mui-table-view-cell mui-collapse">
<a class="mui-navigate-right" href="#">面板1</a>
<div class="mui-collapse-content">
<p>面板1子内容</p>
</div>
</li>
</ul>

代码效果

常用按钮

1
2
3
4
5
6
<button type="button" class="mui-btn">默认</button>
<button type="button" class="mui-btn mui-btn-primary">蓝色</button>
<button type="button" class="mui-btn mui-btn-success">绿色</button>
<button type="button" class="mui-btn mui-btn-warning">黄色</button>
<button type="button" class="mui-btn mui-btn-danger">红色</button>
<button type="button" class="mui-btn mui-btn-royal">紫色</button>

代码效果

操作表

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
<div class="mui-content">
<button type="button" class="mui-btn" onclick="handleClick()">操作表</button>
</div>
<div id="sheet1" class="mui-popover mui-popover-bottom mui-popover-action ">
<!-- 可选择菜单 -->
<ul class="mui-table-view">
<li class="mui-table-view-cell">
<a href="#">菜单1</a>
</li>
<li class="mui-table-view-cell">
<a href="#">菜单2</a>
</li>
</ul>
<!-- 取消菜单 -->
<ul class="mui-table-view">
<li class="mui-table-view-cell">
<a href="#sheet1"><b>取消</b></a>
</li>
</ul>
</div>
<script>
mui.init();
mui.plusReady(function () {});
function handleClick() {
mui("#sheet1").popover("toggle");
}
</script>

代码效果

数字角标

1
2
3
4
5
6
7
8
9
10
11
12
<span class="mui-badge">1</span>
<span class="mui-badge mui-badge-primary">12</span>
<span class="mui-badge mui-badge-success">123</span>
<span class="mui-badge mui-badge-warning">3</span>
<span class="mui-badge mui-badge-danger">45</span>
<span class="mui-badge mui-badge-purple">456</span>
<span class="mui-badge mui-badge-inverted">1</span>
<span class="mui-badge mui-badge-primary mui-badge-inverted">2</span>
<span class="mui-badge mui-badge-success mui-badge-inverted">3</span>
<span class="mui-badge mui-badge-warning mui-badge-inverted">4</span>
<span class="mui-badge mui-badge-danger mui-badge-inverted">5</span>
<span class="mui-badge mui-badge-royal mui-badge-inverted">6</span>

代码效果

单选框

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
<div class="mui-content">
<div class="mui-input-row mui-radio">
<label>radio</label>
<input name="radio2" type="radio" />
</div>
<div class="mui-input-row mui-radio">
<label>radio</label>
<input name="radio2" type="radio" />
</div>
<div class="mui-input-row mui-radio mui-left">
<label>面条</label>
<input class="radio1" name="radio1" type="radio" value="面条" />
</div>
<div class="mui-input-row mui-radio mui-left">
<label>包子</label>
<input
class="radio1"
name="radio1"
type="radio"
value="包子"
checked="checked"
/>
</div>
<ul class="mui-table-view mui-table-view-radio">
<li class="mui-table-view-cell">
<a class="mui-navigate-right">Item 1</a>
</li>
<li class="mui-table-view-cell mui-selected">
<a class="mui-navigate-right">Item 2</a>
</li>
<li class="mui-table-view-cell">
<a class="mui-navigate-right">Item 3</a>
</li>
</ul>
<button type="button" class="mui-btn" onclick="getVals()">获取值</button>
</div>
<script>
mui.init();
mui.plusReady(function () {});
function getVals() {
let res = getRadioRes("radio1");
if (res == null) {
mui.toast("请选择");
return;
}
mui.toast(res, { duration: "long", type: "div" });
}
function getRadioRes(className) {
let rdsObj = document.getElementsByClassName(className);
let checkVal = null;
for (let i = 0; i < rdsObj.length; i++) {
if (rdsObj[i].checked) {
checkVal = rdsObj[i].value;
}
}
return checkVal;
}
</script>

代码效果

复选框

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
<div class="mui-content">
<div class="mui-input-row mui-checkbox mui-left">
<label>checkbox左侧显示示例</label>
<input name="checkbox1" class="checkbox1" value="Item 1" type="checkbox" />
</div>
<div class="mui-input-row mui-checkbox mui-left">
<label>checkbox左侧显示示例</label>
<input name="checkbox1" class="checkbox1" value="Item 2" type="checkbox" />
</div>
<div class="mui-input-row mui-checkbox mui-left">
<label>checkbox左侧显示示例</label>
<input
name="checkbox1"
class="checkbox1"
value="Item 3"
type="checkbox"
checked="checked"
/>
</div>
<button type="button" class="mui-btn" onclick="getVals()">获取值</button>
</div>
<script>
mui.init();
mui.plusReady(function () {});
function getVals() {
let res = getRadioRes("checkbox1");
if (res.length == 0) {
mui.toast("请选择");
return;
}
mui.toast(res, { duration: "long", type: "div" });
}
function getRadioRes(className) {
let rdsObj = document.getElementsByClassName(className);
let checkVal = [];
for (let i = 0; i < rdsObj.length; i++) {
if (rdsObj[i].checked) {
checkVal.push(rdsObj[i].value);
}
}
return checkVal;
}
</script>

代码效果

系统日期选择框

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
<button type="button" class="mui-btn" onclick="pickDate()">获取日期</button>
<script>
function pickDate() {
let dDate = new Date();
// 设置当前日期(不设置默认当前日期)
dDate.setFullYear(2016, 7, 16);

let minDate = new Date()
// 最小时间
minDate.setFullYear(2010, 1, 1);
let maxDate = new Date();
// 最大时间
maxDate.setFullYear(2020, 11, 31);
plus.nativeUI.pickDate(
function(e) {
let d = e.date;
mui.toast('您选择的日期是:' + d.getFullYear() + '-' + (d.getMonth() + 1) + '-' + d.getDate());
},
function(e) {
mui.toast('你没有选择日期');
}, {
title: '请选择日期',
date: dDate,
minDate: minDate,
maxDate: maxDate
})
}
</script>

代码效果

系统时间选择框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<button type="button" class="mui-btn" onclick="pickTime()">获取时间</button>
<script>
function pickTime(){
var myDate = new Date();
var h = myDate.getHours();
var min = myDate.getMinutes();
var dTime = new Date();
dTime.setHours(h,min);
plus.nativeUI.pickTime(function(e){
var d = e.date;
mui.toast('您选择的时间是' + d.getHours() + ":" + d.getMinutes());
},function(e){
mui.toast('您没有选择时间')
},{
title:'请选择时间',
is24Hour: true,
time:dTime
})
}
</script>

代码效果

对话框

警告框

1
2
3
4
5
6
7
8
<button type="button" class="mui-btn" onclick="alert()">警告框</button>
<script>
function alert() {
mui.alert('请不要随便点击', '警告消息', '确定', function() {
mui.toast('提示对话框上关闭后的回调函数');
})
}
</script>

确认框

1
2
3
4
5
6
7
8
9
10
11
12
<button type="button" class="mui-btn" onclick="confirm()">确认框</button>
<script>
function confirm() {
mui.confirm('MUI 是个好框架?', 'Hello MUI', new Array('否', '是'), function(e) {
if (e.index == 1) {
mui.toast('感谢您的支持')
} else {
mui.toast('MUI 没有得到你的认可,继续加油!');
}
})
}
</script>

输入对话框

1
2
3
4
5
6
7
8
9
10
11
12
<button type="button" class="mui-btn" onclick="prompt()">输入对话框</button>
<script>
function prompt() {
mui.prompt('请输入您对 MUI 的评语', '性能好', 'Hello MUI', new Array('取消', '确认'), function(e) {
if(e.index == 1){
mui.toast(e.value);
}else{
mui.toast('您取消了输入');
}
});
}
</script>

消息提示框

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<button type="button" class="mui-btn" onclick="shorttoast()">短的消息提示框</button>
<button type="button" class="mui-btn" onclick="longtoast()">长的消息提示框</button>

<script>
function shorttoast(){
mui.toast('short 消息提示框',{
"duration" : "short"
})
}

function longtoast(){
mui.toast('long 消息提示框',{
"duration" : "long"
})
}
</script>

表单元素

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<form class="mui-input-group">
<div class="mui-input-row">
<label>用户名</label>
<input type="text" class="mui-input-clear" placeholder="请输入用户名">
</div>
<div class="mui-input-row">
<label>密码</label>
<input type="password" class="mui-input-password" placeholder="请输入密码">
</div>
<div class="mui-button-row">
<button type="button" class="mui-btn mui-btn-primary" >确认</button>
<button type="button" class="mui-btn mui-btn-danger" >取消</button>
</div>
</form>

表单

初始化

mui 在 mui.int() 中会自动初始化基本控件,但是动态添加的元素需要重新进行初始化

1
mui('.mui-input-row input').input();

轮播组件

连接模拟器

连接模拟器需要用到 ADB 。

1
2
3
4
5
// 连接模拟器端口
adb connect 127.0.0.1:62001

// 查看连接设备
adb devices

也可以通过 修改 HBuilder 运行配置 Android 模拟器端口 来实现连接。

注意:引入顺序。mui.min.js 需要在 demo渲染前 加载。如果在 demo 渲染后加载会莫名报错

1
2
3
4
5
6
7
8
9
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
<meta name="HandheldFriendly" content="true"/>
<meta name="MobileOptimized" content="320"/>
<title></title>
<script src="js/mui.min.js"></script>
<link href="css/mui.min.css" rel="stylesheet" />
</head>

以上是我对下列视频及文章的归纳和总结。
MUI 视频教程


相关资料
HBuilder X 中使用模拟器进行 App 开发
ADB 常用命令及其用法大全
如何查看夜神模拟器的端口
DCloud appid 用途/作用/使用说明
Android 平台签名证书(.keystore)生成指南
Android 平台本地(离线)打包指南 - Android Studio
Android 平台云端打包证书使用说明
manifest.json 文档说明
MUI – plus初始化原理及plus is not defined,mui is not defined 错误汇总
Hbuilderx真机运行失败,失败原因:手机上没有信任本计算机的授权,请在手机上信任该

You need to set client_id and slot_id to show this AD unit. Please set it in _config.yml.