关于puppeteer

1
2
3
puppeteer是一个node库,他提供了一组用来操纵ChromeAPI(默认headless也就是无UI的chrome,也可以配置为有UI)

有点类似于PhantomJS,但PuppeteerChrome官方团队进行维护的,前景更好。

puppeteer可以做什么

  1. 利用网页生成PDF、图片
  2. 爬取SPA应用,并生成预渲染内容(即“SSR” 服务端渲染)
  3. 可以从网站抓取内容
  4. 自动化表单提交、UI测试、键盘输入等
  5. 帮你创建一个最新的自动化测试环境(chrome),可以直接在此运行测试用例
  6. 捕获站点的时间线,以便追踪你的网站,帮助分析网站性能问题
阅读全文 »

本文将从 Redux 源码入手,一步一步讲解 Redux 的中间件实现机制,重点讲解 applyMiddleware 如何将中间件串联执行,最后,编写一个我们自己的 Redux 中间件。

这里采用 Redux 的官方示例 TodoMVC 作为 demo,我们会在此工程的基础上拓展和 debug,帮助我们更形象地理解。

阅读全文 »

上下两个div,上面的div固定高度,下面的div自动地撑满父元素的余下高度,这是在做网页布局时经常用到的一种,最近公司的几个项目,我也经常遇到这种场景。因此,打算写一篇文章做个总结,以便在日后的工作中,使用起来更得心应手。

由于自己一直在用一种方式实现,所以此文的另一个目的,是想尽量多的罗列其实现方法,找到最适合的那一个。

先抛出实现效果图如下。

实现效果图

一、calc实现

calc就是英文单词calculate(计算)的缩写,是CSS3的一个新增功能,我们可以使用可以使用calc()给元素的border、margin、pading、font-size和width等属性设置动态值。说到这里,我们应该大概能明白calc实现上述效果的方式了,关于calc具体的使用方式,这里不再详述,如果想了解可以看这里

我们先来看HTML和CSS代码,实现链接

html:

1
2
3
4
<div class="container">
<div class="one"></div>
<div class="two"></div>
</div>

css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
.container {
width: 240px;
height: 400px;
padding: 10px;
margin: 0 auto;
background: #f00;
}

.container > div {
width: 100%;
}

.one {
height: 100px;
background: #000;
}

.two {
height: calc(100% - 100px);
background: #ff0;
}

二、绝对定位实现

绝对定位实现算是一种比较笨拙的实现方式,但是也绝对实用,其实现思路也和calc基本一致,令第二个div的左边缘、右边缘以及下边缘全部和父元素的边缘平齐,上边缘与父元素的上边缘空出第一个div的高度即可。

点击这里查看实现链接

html:

1
2
3
4
<div class="container">
<div class="one"></div>
<div class="two"></div>
</div>

css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.container {
position: relative;
width: 240px;
height: 400px;
border: 10px solid transparent;
margin: 0 auto;
background: #f00;
}

.container > div {
width: 100%;
}

.one {
height: 100px;
background: #000;
}

.two {
position: absolute;
top: 100px;
right: 0;
bottom: 0;
left: 0;
background: #ff0;
}

三、flex实现

flex意为弹性布局,因此,严格来说,它实现的效果与前两种方式还是有一定差别的,我们并不需要写死上面那个div的高度,只需要定义出它应该占据父元素高度的比例即可。点击这里查看实现代码。

html:

1
2
3
4
<div class="container">
<div class="one"></div>
<div class="two"></div>
</div>

css:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
.container {
display: flex;
flex-direction: column /* 排列方向 */;
width: 240px;
height: 400px;
border: 10px solid transparent;
margin: 0 auto;
background: #f00;
}

.container > div {
width: 100%;
}

.one {
flex: 1;
background: #000;
}

.two {
background: #ff0;
flex: 3;
}

image

全选功能在网页开发中算是一个极为常见的功能了。尤其在开发系统的管理后台时,维护人员可能要做重复的机械动作,手动更改每一条数据的状态,这时候如果有全选功能,应该会让我们程序同学和策划同学被人背地里吐槽的几率小很多。

一、要实现的需求

一图胜千言,先来看图。

image

再来细化一下功能:

  1. 需要有一个checkbox负责全选和取消全选
  2. 全选后,取消其中一项,全选取消
  3. 取消全选后,再手动选上所有项,全选checkbox自动勾上

二、思路1 给数组中的每一项绑定checked属性

通过checkedArray这个数组,存储已经选中的数组元素,其作用主要是实现上文中的第3项功能,代码如下。

  • HTML

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <div ng-controller="myCtrl">
    <label for="select-all">全选
    <input
    type="checkbox"
    id="select-all"
    ng-change="selectAll()"
    ng-model="all" >
    </label>
    <ul>
    <li ng-repeat="item in list">
    <input type="checkbox" ng-model="item.checked" ng-change="selectOne()">
    <span ng-bind="item.id"></span>
    </li>
    </ul>
    </div>
  • 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
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', function($scope){
$scope.list = [
{
id: '001',
},
{
id: '002',
},
{
id: '003',
},
{
id: '004',
},
{
id: '005',
},
{
id: '006',
},
{
id: '007',
}
];
$scope.checkedArray = [];
$scope.selectAll = function() {
if ($scope.all) {
$scope.checkedArray = [];
angular.forEach($scope.list, function(item) {
item.checked = true;
$scope.checkedArray.push(item.id);
});
} else { // 清空全选
$scope.checkedArray = [];
angular.forEach($scope.list, function(item) {
item.checked = false;
});
}
};
$scope.selectOne = function() {
angular.forEach($scope.list, function(item) {
var localIndex = $scope.checkedArray.indexOf(item.id);
// 选中
if (localIndex === -1 && item.checked) {
$scope.checkedArray.push(item.id);
} else if (localIndex !== -1 && !item.checked) { // 取消选中
$scope.checkedArray.splice(localIndex, 1);
}

$scope.all = $scope.list.length === $scope.checkedArray.length;
});
}
}]);

这种思路比较完整地实现了我们需要的功能,但是在实际项目中,数组list中的元素引入了一个可能不需要的属性checked,这种写法引入了脏属性,在表单提交的时候,可能需要额外处理checked属性,构造合法的请求数据格式,引起不必要的麻烦。

三、思路2 ng-true-value等指令的运用

思路阐述:

  1. 根据现有的list数组初始化一个初始化一个数组checkedArray,并在ng-repeat的时候,将其每一项初始化为false
  2. 考虑到要给服务端返回选中的id数组,引入ng-true-value指令,ng-true-value="item.id"
  3. 使用$watchCollection方法监控数组checkedArray变化,并使用数组的every 方法判断是否都不是false,决定全选checkbox的状态。

代码如下。

  • HTML
1
2
3
4
5
6
7
8
9
10
11
12
<div ng-controller="myCtrl">
<label for="select-all">全选
<input type="checkbox" id="select-all" ng-click="selectAll()" ng-model="isAll">
</label>
<ul>
<li ng-repeat="item in list track by $index" ng-init="checkedArray[$index]=false">
<input type="checkbox" ng-model="checkedArray[$index]" ng-true-value="{{item.id}}" ng-false-value="false">
<span ng-bind="item.id"></span>
</li>
</ul>
<input type="button" value="获取所有选中的元素" ng-click="getCheckedList()">
</div>
  • 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
var app = angular.module('myApp', []);
app.controller('myCtrl', ['$scope', function($scope) {
$scope.list = [{
'id': 101
}, {
'id': 102
}, {
'id': 103
}, {
'id': 104
}, {
'id': 105
}, {
'id': 106
}, {
'id': 107
}];

$scope.checkedArray = new Array($scope.list.length);

$scope.selectAll = function() {
if ($scope.isAll) {
$scope.checkedArray = $scope.list.map(function(item) {
return item.id;
});
} else {
$scope.checkedArray = $scope.list.map(function(item) {
return false;
});
}
};

$scope.$watchCollection('checkedArray', function(newCollection) {
if (newCollection.every(function(item) {
return item !== false;
})) {
$scope.isAll = true;
} else {
$scope.isAll = false;
}
});

$scope.getCheckedList = function() {
console.log($scope.checkedArray.filter(function(item) {
return item
}));
}

}]);

四、思路3 组合指令

全选是一个很常见的功能,如果能做成指令复用,将会大大提高我们的开发效率。我们依照要实现的功能,拆分成四个指令:commonOperatecheckSinglecheckAlloperateBatch

将公共的方法和属性写在指令commonOperate中,在其他三个指令中require这个指令,完成单选、全选、导出选中项的功能。这种写法的好处是可以嵌入到现有的模板中,使用及其方便快捷。

  • HTML
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
<div ng-controller="myCtrl">
<div common-operate>
<table border="1">
<thead>
<tr>
<th><input type="checkbox" check-all> NO.</th>
<th>name</th>
<th>gender</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="checkbox" check-single="1"> 1</td>
<td>David</td>
<td>male</td>
</tr>
<tr>
<td><input type="checkbox" check-single="2"> 2</td>
<td>Bob</td>
<td>male</td>
</tr>
<tr>
<td><input type="checkbox" check-single="3"> 3</td>
<td>Kate</td>
<td>female</td>
</tr>
<tr>
<td><input type="checkbox" check-single="4"> 4</td>
<td>Xiaoming</td>
<td>male</td>
</tr>
</tbody>
</table>
<button type="button" operate-batch="getBatch">导出选中项目</button>
</div>
</div>
  • 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
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
var app = angular.module('myApp', []);

app.controller('myCtrl', ['$scope', function($scope) {
$scope.getBatch = function(list) {
console.log(list)
}

}]);

app.directive('commonOperate', function() {
return {
restrict: 'EA',
controller: ['$scope', '$element', '$attrs', '$timeout',
function($scope, $element, $attrs, $timeout) {
var checkedClassName = $attrs['commonOperate'],
checkList = [];
this.scope = $scope;
$scope.checkCommonOperateCheckList = checkList;

// 设置选中状态
this.setCheckedStatus = function(element, needStatus) {
if (checkedClassName) {
if (needStatus) {
element.removeClass(checkedClassName)
.addClass(checkedClassName);
} else {
element.removeClass(checkedClassName);
}
} else {
element.prop('checked', needStatus);
}
};

// 获取点击之前的状态
this.getCheckedStatus = function(element) {
return checkedClassName ? element.hasClass(checkedClassName)
: !element.prop('checked');
};

// 清除选中列表
this.clearCheckList = function() {
checkList = [];
$timeout(function() {
$scope.checkCommonOperateCheckList = checkList;
});
this.setCheckedStatus($element.find('[check-single]'), false);
};

// 获取选中列表
this.getCheckList = function() {
return checkList;
}

// 增加选中列表
this.setCheckList = function(checkId, needStatus) {
var selectAllDOM = $element.find('[check-all]');
if (needStatus) {
checkList.push(checkId);
if (checkList.length === $element.find('[check-single]').length) {
this.setCheckedStatus(selectAllDOM, true);
}
} else {
this.setCheckedStatus(selectAllDOM, false);
var localIndex = checkList.indexOf(checkId);
if (localIndex !== -1) {
checkList.splice(localIndex, 1);
}
}
$timeout(function () {
$scope.espCommonOperateCheckList = checkList;
}, 0);
return checkList;
};

// 全选
this.selectAll = function(needStatus) {
var singleList = $element.find('[check-single]');
this.setCheckedStatus(singleList, needStatus);
if (needStatus) {
checkList = singleList.map(function (item) {
return $(item).attr('check-single');
});
$timeout(function () {
$scope.espCommonOperateCheckList = checkList;
}, 0);
} else {
this.clearCheckList();
}
}

}]
}
});

app.directive('checkSingle', function() {
return {
restrict: 'A',
require: '^commonOperate',
link: function($scope, $element, $attrs, commonOperate) {
var itemId = $attrs['checkSingle'];
var mapValue = $attrs['mapValue'];
$element.click(function(e) {
var needStatus = !commonOperate.getCheckedStatus($element);
commonOperate.setCheckList(itemId, needStatus);
commonOperate.setCheckedStatus($element, needStatus);
});
}
}
});

app.directive('checkAll', function() {
return {
restrict: 'A',
scope: {
reloadPage: '=?',
page: '=?'
},
require: '^commonOperate',
link: function($scope, $element, $attrs, commonOperate) {
$element.click(function (e) {
var needStatus = !commonOperate.getCheckedStatus($element);
commonOperate.selectAll(needStatus);
commonOperate.setCheckedStatus($element, needStatus);
});
$scope.$watch('reloadPage', function () {
commonOperate.setCheckedStatus($element, false);
commonOperate.clearCheckList();
});
$scope.$watch('page', function () {
commonOperate.setCheckedStatus($element, false);
commonOperate.clearCheckList();
});
}
}
});

app.directive('operateBatch', function() {
return {
restrict: 'A',
require: '^commonOperate',
link: function ($scope, $element, $attrs, commonOperate) {
$element.click(function (e) {
var funcName = $attrs['operateBatch'];
if (funcName && commonOperate.scope[funcName]) {
commonOperate.scope[funcName](commonOperate.getCheckList());
}
});
}
}
});

从以上的代码中可以看出,当需要为现有的页面加入全选功能时,只需要将这几个指令嵌入到相关的DOM上即可,极大地提高了其复用性。

最近几天好不容易闲下来,有时间思考一些东西,这篇博客花了两天时间构思,一天时间写内容和demo,第三个思路是参考同事写的东西,整理得来。写之前总觉得自己有千言万语,到了真正动笔的时候,又不知道从哪里下手,真心羡慕那些能写出赏心悦目文字的作者。

加油2017。

原文链接:https://blog.aria.ai/post/why-use-flow/

Flow是一个由 Facebook 开源的 JavaScript 静态类型检查器。旨在解决JavaScript编程中的多种痛点,写出更优雅、更易理解的 JavaScript 代码。

引用 Flow 主页上的介绍:

Flow 可以在 JavaScript 代码运行之前检测错误,包括

  • 静态类型转换,
  • 空指针引用,
  • 和可怕的调用函数未定义的错误

以及

Flow 还会逐步地将类型断言融入到你的代码中

所以 Flow 可以解决很多常见的 JavaScript 问题,你可以逐步将其引入你的代码库中。这很酷吧!

类型

在我们使用Flow之前,我们要先弄清楚什么是类型。我们看一下维基百科上的数据类型文章中的定义

类型是用来标识各种不同种类的数据的,如实数类型、整型或布尔型。它可以确定该类型的可能值,可对该类型的值进行的操作,其数据的含义,以及该类型的值可用的存储方式。

用我自己的话简单来讲,类型就是你程序中约束数据的规则,这些规则帮助计算机确定你可以在数据上做哪些事情,不能做哪些事情。如果你不小心试图破坏这些规则,它可以提醒你,这一点大有裨益。

当你使用不同的语言编写代码时,你会发现,类型的表现方式可能会迥然不同,从必须声明到可选,再到几乎不需要。通常,编程语言的类型系统分为两类:强类型 vs. 弱类型,以及动态类型 vs. 静态类型。

强类型 vs. 弱类型

维基百科有一篇很好的文章介绍这一点,目前人们普遍认为,强类型和弱类型有些模棱两可,因为二者之间还没有一个成文的约定。我决定按前面讲到的那篇维基百科上的文章来讲。

隐式类型转换和“类型双关”


在像 python 这样的强类型语言中,变量在第一次声明之后就不能再改变它的类型,除非你临时显式转换为其他类型,或者之后重新声明。

1
2
x = 5
print x + "" # cannot add integers to strings

这样会抛出下列错误

1
`TypeError: unsupported operand type(s) for +: 'int' and 'str'`

但这样写是可以的

1
2
3
x = 5
x = ""
print x + "" # redeclared x so it's fine

或者这样

1
2
x = 5
print str(x) + "" # casted x to a string so it's fine

在 JavaScript 等弱类型语言中,由于变量在使用时被隐式转换,会变得更加灵活。你可以将字符串和对象相加,数组和对象相加,数值和null相加。更糟糕的是,运算出错时并不会抛出异常。

1
2
3
4
5
console.log({} + {}) // NaN
console.log({} + []) // 0
console.log([] + []) // ''
console.log({} + 2) // [object Object]2
console.log({} + 'hello') // [object Object]hello

我想你能想象到可能发生的各种问题,而这些问题都不会抛出错误。

动态类型 vs. 静态类型

动态类型 vs. 静态类型比强类型 vs. 弱类型有更多的争议。我不会说二者哪个更好,也不会逐一全面分析它们的优点。相反,我只是简单介绍一下二者的优点,如果你想了解更多关于它们二者哪个更好的辩论,可以看看下面的文章。

现在给出我自己的免责观点:

在静态类型的语言中,你需要显式地写出变量的类型。很多人都了解 Java 这种强类型的静态类型语言,你需要在 Java 语言中写明变量类型,如intString,以及函数的返回值类型和参数类型,如int add(int a, int b)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Hello {
public static void main(String[] args) {
int x = 5;
int y = 10;
String s = "1.23131";

System.out.println(add(x, y)); // 15
System.out.println(add(x, s)); // Incompatible types: String cannot be converted to int
}

public static int add(int a, int b) {
return a + b;
}
}

因为String类型和int类型不能相加,这段代码在编译时第8行会报错。

注意:

  • 错误在编译的时候就会捕获,而不是这代码运行的时候,这意味着,只有你修复了错误才能运行代码。
  • 如果你使用IDE编写代码,IDE会提示你add(x, s)写法错误。因为你事先指定了语言类型,所以IDE可以在更高级别进行分析,而无须编译,以发现错误。
  • 如果函数被命名为其他很随意的名字而不是add,你仍然可以看出它需要接受两个整型参数并返回一个整型结果,这是非常有用的信息。

在动态类型语言中,你根本不用指明变量的类型。其主要好处是你的代码不会那么混乱,你不必在开始编程之前考虑类型,这提升了生产力。python 就是一门强类型的动态类型语言,下面的代码可以实现与上一段代码相同的功能。

1
2
3
4
5
6
7
8
9
10
def main():
x = 5
y = 10
s = "1.23131"

print add(x, y) # 15
print add(x, s) # TypeError: unsupported operand type(s) for +: 'int' and 'str'

def add(a, b):
return a + b

运行代码时,这段代码将在第7行抛出错误,因为string类型和int类型是不能相加的。

值得注意的几点:

  • 这样写代码更加简洁。
  • 你不能确定变量ab的类型,intstringfloat等类型都是有可能的。
  • 这段代码仍然会在运行时抛出错误,注意是在运行时而不是在编译时,这与静态类型语言比较是一个很大的区别。也意味着测试对于动态类型的语言更加重要,因为即使代码包含类型错误,也可能不会有太大问题。

静态类型语言中的类型推断

我之前说过,静态类型语言需要显式地写出类型,这句话并不完全正确。在没有类型推断的语言(如 Java )中,这是对的,但在类型推断语言中,计算机可以帮助你来确定使用什么类型。例如,在下面的例子中,是用 Haskell 写的一段与前文代码功能相同的一段代码,这是一门以其真正强大的类型系统而闻名的语言。在我写 let x = 1 let add’ = (+)`时,Haskell 会自动推断其类型,而不需要显式写出。

Haskell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main :: IO()
main = do
let x = 1
let y = 2
let s = ""

-- Type inference
let add' = (+)

print (add x y) -- 3
print (add' x y) -- 3

print (add x s) -- throws error

-- With Explicit Types
add :: Int -> Int -> Int
add = (+)

在 Flow 等其他类型系统中都有类型推断。然而,即使类型推断让你的编码更加简单,编码量更加少,你也不应该完全依赖于它。

回到JavaScript和Flow上面来

现在我们了解了更多关于类型的东西,可以回到手头上的问题来,这会让你在写 JavaScript 的时候犯更少的错误。

JavaScript 既是弱类型语言又是动态类型语言,这是一个灵活但又及其容易出错的组合。正如前文所说,由于隐式转换的原因,不同类型的值之间所有的操作都不会抛出错误,无论这些操作是否有效(弱类型),并且在从来不用显式地写出类型(动态类型)。

这种动态类型和弱类型的大杂烩是相当不幸的,从下面的例子和许多人对这门语言的批判中就可以体现。

这些问题的大多数解决方案是 Flow,通过静态类型和类型推理,如前文所述,解决了许多语言的痛点。

这不是一篇教程,如果你需要教程可以移步flow入门指南

让我们继续分析,回到我们在讲弱类型部分的第一个JS示例,这次我们使用 Flow 来检查代码。

我们将// @flow添加到代码的第一行,然后使用命令行工具flow来检查代码(集成在IDE中也是有可能的):

1
2
3
4
5
6
7
8
9
// @flow
// ^^^^^ that's necessary to activate flow
// flow is opt-in to allow you to gradually add types

console.log({} + {}) // NaN
console.log({} + []) // 0
console.log([] + []) // ''
console.log({} + 2) // [object Object]2
console.log({} + 'hello') // [object Object]hello

于是每一行代码都会立即生成类似于下面的错误。

1
2
3
4
5
index.js:3
3: console.log({} + {}) // NaN
^^ object literal. This type cannot be added to
3: console.log({} + {}) // NaN
^^^^^^^ string

不需要做任何额外的工作来添加类型注释,Flow 就已经指明了代码中的错误。wat视频中提到的那些问题也不会再出现。

注释代码的好处

虽然 Flow 会帮助发现上面的错误,但是你要想真正从中获益,你必须自己编写类型注释,这意味着你可以使用 Flow 的内置类型,如numberstringnullboolean等等,来指定值的类型或自定义某些类型别名,请看下面的例子:

1
2
3
4
5
type Person = {
age: number,
name: string,
gender: 'male' | 'female'
}

现在你可以将函数:

1
2
3
function xyz(x, y, z) {
return x + y + z
}

转换成

1
2
3
4
5
// @flow

function xyz(x: number, y: number, z: number): number {
return x + y + z
}

在上述实例中,我们知道函数的参数x、y、z应该接受三个数值,并且其返回值也应该是数值。如果你试图这样传参,xyz({}, '2', []),在JavaScript是百分之百可以的,而 Flow 则会抛出错误。随着你越来越多地开始这样写,Flow 会更加了解你的代码库,并更好地提示你代码中的错误。

一些例子

捕获函数参数数量不正确的错误。

代码如下:

1
2
3
4
5
6
7
// @flow

function xyz(x: number, y: number, z: number): number {
return x + y + z
}

xyz(1, 2)

报错如下:

1
2
3
4
5
6
7
index.js:7
7: xyz(1, 2)
^^^^^^^^^ function call
7: xyz(1, 2)
^^^^^^^^^ undefined (too few arguments, expected default/rest parameters). This type is incompatible with
3: function xyz(x: number, y: number, z: number): number {

^^^^^^ number

捕获函数参数类型不正确的错误。

代码如下:

1
2
3
4
5
6
7
// @flow

function xyz(x: number, y: number, z: number): number {
return x + y + z
}

xyz(1, 2, '')

报错如下:

1
2
3
4
5
6
7
index.js:7
7: xyz(1, 2, '')
^^^^^^^^^^^^^ function call
7: xyz(1, 2, '')
^^ string. This type is incompatible with
3: function xyz(x: number, y: number, z: number): number {

^^^^^^ number

确认你不要忘记检查 NULL。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
// @flow

function xyz(x: number, y: number, z: number): ?number {
return Math.random() < 0.5 ? x + y + z : null
}

function printNumber(x: number): void {
console.log(x)
}

printNumber(xyz(1, 2, 3))

报错如下:

1
2
3
4
5
6
7
index.js:11
11: printNumber(xyz(1, 2, 3))
^^^^^^^^^^^^^^^^^^^^^^^^^ function call
11: printNumber(xyz(1, 2, 3))
^^^^^^^^^^^^ null. This type is incompatible with
7: function printNumber(x: number): void {

^^^^^^ number

确认你的返回值是否正确。

代码如下:

1
2
3
4
5
6
7
// @flow

function xyz(x: number, y: number, z: number): number {
return Math.random() < 0.5
? x + y + z
: null
}

报错如下:

1
2
3
4
5
index.js:6
6: : null
^^^^ null. This type is incompatible with the expected return type of
3: function xyz(x: number, y: number, z: number): number {
^^^^^^ number

确认对象包含所有它应该包含的属性。

代码如下:

1
2
3
4
5
6
7
8
9
// @flow

type Person = {
age: number,
name: string,
gender: 'male' | 'female'
}

const person: Person = { name: 'joe', age: 10 }

报错如下:

1
2
3
4
5
index.js:9
9: const person: Person = { name: 'joe', age: 10 }
^^^^^^ property `gender`. Property not found in
9: const person: Person = { name: 'joe', age: 10 }
^^^^^^^^^^^^^^^^^^^^^^^^ object literal

确认不存在的对象属性不被访问。

代码如下:

1
2
3
4
5
6
7
8
9
10
// @flow

type Person = {
age: number,
name: string,
gender: 'male' | 'female'
}

const person: Person = { name: 'joe', age: 10, gender: 'male' }
console.log(person.job)

报错如下:

1
2
3
4
5
index.js:9
9: console.log(person.job)
^^^ property `job`. Property not found in
9: console.log(person.job)
^^^^^^ object type

更深入的探讨

还有一些常见的好处,我可能忘了。但上面的例子已经涵盖了绝大部分。如果你在想,“就只有这些吗?”,那么还可以进行更加深入的研究。

Giulio Canti 写了相当多的关于 Flow 更高级的东西,而不仅仅是使用 Flow 来限制变量类型、参数类型以及返回类型。

他还撰写了flow-static-land,这是很让人兴奋的。

长话短说的总结

  • JavaScript 既是弱类型语言又是动态类型语言,极其容易出错,也是它成为糟糕语言的一个重要原因。
  • 由于前期成本很低,并且具有缓慢演进的能力,Flow 通过向 JavaScript 添加类型系统来解决这两个问题。

原文链接:https://www.nczonline.net/blog/2016/10/the-ecmascript-2016-change-you-probably-dont-know/

相比 ECMAScript 6 (也被称为 ECMAScript 2015), ECMAScript 2016在 JavaScript 的语言规范上有小幅度的更新。由于ECMAScript版本会每年一更,每个新版其实就是所有就绪特性的集合。有鉴于此,大多数文章仅列出 ECMAScript 2016 中两个显著的变化。

  1. 增加了乘幂运算符(**)。
  2. 增加了Array.prototype.includes()方法。

这两项功能对JavaScript开发者有着最直接的影响,实际上, ECMAScript 2016 还有一些经常被人忽略的重要更新。在我写的书Understanding ECMAScript 6中有提到这些更新,但是我仍然会收到关于它的一些疑问,所以在这篇文章中,我想深入地探讨一下 ECMAScript 2016 。

首先,我会陈述 ECMAScript 2016 的一些更新,然后再讲讲其背后的理论基础。

更新点

ECMAScript 2016 规定,"use strict"严格模式不允许用于参数具有默认值、使用解构参数或多余参数的函数内部,规范将简单参数定义为仅包含标识符的参数列表(ECMAScript 5 仅支持简单参数列表)[1]. 这一更新会影响所有的函数类型,包括函数声明、函数表达式、箭头函数和对象字面量。以下是一些示例代码。

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
// this is okay
function doSomething(a, b) {
"use strict";

// code
}

// syntax error in ECMAScript 2016
function doSomething(a, b=a) {
"use strict";

// code
}

// syntax error in ECMAScript 2016
const doSomething = function({a, b}) {
"use strict";

// code
};

// syntax error in ECMAScript 2016
const doSomething = (...a) => {
"use strict";

// code
};

const obj = {

// syntax error in ECMAScript 2016
doSomething({a, b}) {
"use strict";

// code
}
};

即使函数是一个具有非简单参数的函数,你仍然可以在函数体之外全局使用"use strict",以使该函数在严格模式下执行。例如:

1
2
3
4
5
6
// this is okay
"use strict";

function doSomething(a, b=a) {
// code
}

在这种情况下,函数体外的"use strict"语法是正确的。即使你使用 ECMAScript 模块,也不必担心这个问题,因为它会以严格模式执行所有代码。

为什么要做这项更新?

鉴于严格模式和非简单参数列表的工作方式,此项更新显得举足轻重。当在 ECMAScript 5 中使用严格模式时,解构和默认参数值不再存在,因此先解析参数列表然后再使用"use strict"就不会有问题。此时,"use strict"不能影响解析参数列表的结果,它只用于验证参数标识符(不允许重复并检查被禁止的标识符,例如evalarguments)。然而,随着在 ECMAScript 6 中引入解构和默认函数参数值,情况已经有所改变,因为规范指出参数列表应该按照与函数体相同的模式进行解析(这意味着"use strict"在函数体内必须触发严格模式)。

首先要意识到的是严格模式需要改变JavaScript代码的解析和执行[2]。举个非常简单的例子,严格模式不允许旧式的八进制数值表示形式(如070)。如果代码在严格模式下解析,070会抛出语法错误。考虑到这一点,你认为以下代码应该怎样写?

1
2
3
4
5
6
// syntax error in ECMAScript 2016
function doSomething(value=070) {
"use strict";

return value;
}

如果你用JavaScript解析器解析这段代码,参数列表则会在函数体之前被解析。这意味着,070会被解析为合法的值,之后在函数体中遇到"use strict",它会告诉解析器,“实际上你应该在严格模式下解析参数列表”。于是,解析器将不得不再次在严格模式下回溯并重新解析参数列表,因此会抛出070的语法错误。这可能还无足轻重,但如果默认参数值更复杂呢?

1
2
3
4
5
6
7
8
// syntax error in ECMAScript 2016
function doSomething(value=(function() {
return doSomeCalculation() + 070;
}())) {
"use strict";

return value;
}

在其默认参数值是一个函数的情况下,将会引发更多的问题。你要理清更多的tokens,还必须将函数设置为默认在严格模式下运行。要确保默认参数表达式被正确解析,并在严格模式下运行,是极度复杂的。解构参数包含默认值,同样会导致类似的问题。例子如下:

1
2
3
4
5
6
// syntax error in ECMAScript 2016
function doSomething({value=070}) {
"use strict";

return value;
}

这里,解构参数value具有严格模式下不允许的默认值,会导致与默认参数值相同的问题。

最后,似乎TC-39决定[3]简单地禁止函数体使用"use strict",以避免上述的错误发生,这在 ECMAScript 5 中是没有说明的。这意味着具有默认参数值、解构参数或多余参数的函数在函数体中不能有"use strict"。这也包括"use strict"没有作用的情况,例如:

1
2
3
4
5
6
7
8
9
10
function outer() {
"use strict";

// syntax error in ECMAScript 2016
function doSomething(value=070) {
"use strict";

return value;
}
}

在上面的代码中,将具有非简单参数的函数嵌套在具有"use strict"的另一个函数中。doSomething()自动处于严格模式下,但JavaScript引擎仍会在doSomething()的函数体中的"use strict"指令上抛出语法错误。

替代方案

这种更新一般不会影响很多开发者,这也可能是你不知道它的原因。"use strict"指令会渐渐成为过去,因为ECMAScript的模块和类都会自动在严格模式下运行,而无需另外指定,这意味着这些情况下不再需要使用"use strict"。但是,在极少数情况下,你如果需要一个带有非简单参数的函数在严格模式下运行,可以使用IIFE模式创建函数:

1
2
3
4
5
6
7
const doSomething = (function() {
"use strict";

return function(value=42) {
return value;
};
}());

在这段代码中,函数创建在以严格模式运行的IIFE中。这允许返回的函数在使用默认参数值的情况下以严格模式运行。因为外部作用域是以严格模式运行的,所以解析默认参数值是不会出错的,并且在函数体中不需要额外使用"use strict"

总结

禁止有非简单参数列表的函数的函数体使用"use strict"指令,ECMAScript 2016 这一小的改变,也表现出这样一门流行的编程语言的发展历程是多么坎坷。在这样的情况下,TC-39决定引入一个新的语法错误来消除歧义,如果上述问题出现的早的话,这也可能已经是ECMAScript 6(2015)的一部分了。添加这个语法错误是最明智的选择,因为它对现有的代码影响甚微(规范更改是与JavaScript引擎实现非简单参数列表同步的),而且不大可能影响以后的JavaScript代码,因为ECMAScript的模块和类是以严格模式运行的。

参考文献

  1. Static Semantics: IsSimpleParameterList (ecma-international.org)
  2. It’s time to start using JavaScript strict mode (nczonline.net)
  3. The scope of “use strict” with respect to destructuring in parameter lists

免责声明:本文中表达的任何观点和看法都是Nicholas C. Zakas的观点和看法,并且不以任何方式反映我的雇主,我的同事,Wrox出版社O’Reilly出版社或任何其他人的观点和看法。 我仅代表个人的观点,与他人无关。

原文链接:https://www.sitepoint.com/saved-from-callback-hell/

Devil standing over offce worker with receivers hanging everywhere

这篇文章已经由Mallory van Achterberg, Dan Prince 和 Vildan Softic 三位同行审核,在此感谢所有同行为创造优秀网站做出的努力。

作者的其他文章

  1. Quick Tip: How to Throttle Scroll Events
  2. Getting Started with the Raspberry Pi GPIO Pins in Node.js

在实际开发中,回调地狱是真实存在的,开发者将它视为洪水猛兽,甚至到了想逃离它们的地步。而JavaScript的灵活性却对此爱莫能助。从表面上看,回调似乎并不完美,所以我们总想找到其替代者。

好消息是,只需要简单的几步即可逃离回调地狱。我觉得,在你的代码中不使用回调就好比壮士断腕。回调函数是JavaScript的核心之一,也是它的精粹所在。如果你用其他方式来代替回调,那也只是换汤不换药,无济于事。

有朋友告诉我,回调这东西一无是处,并劝我去学习更好的编程语言。那么,回调真的如此吗?

在JavaScript中使用回调有它自身的闪光点。如果仅仅是因为回调的话,我们没有理由不用JavaScript。

让我们来研究一下合理的编程方式是怎么处理回调的。我的偏好是坚持SOLID原则,让我们来一探究竟。

回调地狱是什么

我知道你可能在想,回调地狱到底是什么,为什么我应该关注它呢?在JavaScript中,回调的角色是充当一个委托函数,这个委托可能会在未来的任一时刻执行。在JavaScript中,当接收函数调用回调时发生委托。接收函数可能在执行过程中的任意时刻调用回调。

简而言之,回调是作为参数传递给另一个函数的函数。什么时候调用回调是由接收函数决定的,因此回调并不会立即执行,以下是代码示例。

1
2
3
4
5
6
7
8
9
10
function receiver(fn) {
return fn();
}

function callback() {
return 'foobar';
}

var callbackResponse = receiver(callback);
// callbackResponse == 'foobar'

如果你曾写过ajax请求,那就肯定遇到过回调函数。因为我们无法知道回调将在什么时候执行,所以异步代码使用这种方式。

回调的问题在于依赖于其他回调的异步代码。下面我会使用setTimeout来模拟带有回调的异步调用。

代码已经放在Github,可以自由查看拷贝。后文中大部分代码片段都在这个库里面,你可以拷贝下来自己运行。

看吧,下面就是回调深渊。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
setTimeout(function (name) {
var catList = name + ',';

setTimeout(function (name) {
catList += name + ',';

setTimeout(function (name) {
catList += name + ',';

setTimeout(function (name) {
catList += name + ',';

setTimeout(function (name) {
catList += name;

console.log(catList);
}, 1, 'Lion');

}, 1, 'Snow Leopard');

}, 1, 'Lynx');

}, 1, 'Jaguar');

}, 1, 'Panther');

从上面的代码可以看到,我们向setTimeout中传递了一个将在1ms后执行的回调函数。最后一个参数用来给回调函数传递数据。这和期望从服务器返回name参数的ajax请求类似。

在MDN上,对setTimeout有一个很详细的介绍

我通过这段异步代码,收集了一组猫的名字列表。每个回调都会返回给我一个猫的名字,然后我将其添加到列表中。我试着更合理地实现它,但是,鉴于JavaScript函数的灵活性,这简直就是噩梦。

匿名函数

在前面的例子中,你可能注意到了匿名函数的使用。匿名函数是一个用于赋给一个变量或作为参数传递给其他函数的未命名函数表达式。

在有些编码标准中不建议使用匿名函数。最好是为它们命名,使用function getCat(name){}而不是function (name){}。使用具名函数会让你的程序更加清晰易读。匿名函数很容易定义使用,但是会让你直坠回调地狱,当你走上这条不归路时,最好停下来反思。

有个打破回调的简单方式是使用函数声明:

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
setTimeout(getPanther, 1, 'Panther');

var catList = '';

function getPanther(name) {
catList = name + ',';

setTimeout(getJaguar, 1, 'Jaguar');
}

function getJaguar(name) {
catList += name + ',';

setTimeout(getLynx, 1, 'Lynx');
}

function getLynx(name) {
catList += name + ',';

setTimeout(getSnowLeopard, 1, 'Snow Leopard');
}

function getSnowLeopard(name) {
catList += name + ',';

setTimeout(getLion, 1, 'Lion');
}

function getLion(name) {
catList += name;

console.log(catList);
}

在仓库中没有这段代码,但是其改进在这个提交上面。

每个函数都有它自己的声明,其优点是我们可以不再使用金字塔式的回调。每个函数都被隔离,专注于自己的任务。现在每个函数都有理由做出改变,这说明我们的研究方向是正确的。例如像getPanther()这样被当做参数传入的函数,JavaScript不关心你是如何创建回调的。然而,这样写又有什么值得改进的地方呢?

为了了解更加透彻,可以看这篇文章SitePoint article on Function Expressions vs Function Declarations

不过,有个缺点是每个函数的作用域不再限定在其回调内。每个函数被放到全局作用域下,而不是作为一个闭包回调。所以,这就是为什么catList申明在全局作用域下,是为了保证回调函数能访问到列表。有时候,使用全局作用域并不是一个好的方案。因为它在将猫名字添加到列表的同时调用下一个回调,这也造成了代码冗余。

这些代码总有一些回调地狱的影子,所以为了处理回调地狱常常需要付出巨大的努力而且开始时可能还会觉得事倍功半,无济于事。那有没有更好的编码方式呢?

依赖倒置

依赖倒置原则告诉我们,应该对编码进行抽象,而不是关注其具体细节。其核心思想是将一个大的问题分解成小的依赖。这些依赖变得彼此独立,与实现细节无关。

SOLID原则建议:

1
当遵循这项原则时,传统的依赖关系建立从高层次/策略设定模块到底层的依赖模块倒置,从而使高层次的模块独立于底层模块的实现细节。

如何理解这段话呢?好消息是通过为参数分配一个回调,猜猜会发生什么?实际上你已经这样做了,至少在一定程度上,为了解耦,将回调看做依赖。这种依赖变成一个契约,从这点就表明你已经开始做SOLID编程了。

获得回调自由的一种方式是创建一个契约:

1
`fn(catList);`

这定义了我打算用回调来实现的功能,它需要跟踪一个单一的参数,也就是上文中猫的列表。

此依赖关系现在可以通过参数传递:

1
2
3
4
5
6
7
function buildFerociousCats(list, returnValue, fn) {
setTimeout(function asyncCall(data) {
var catList = list === '' ? data : list + ',' + data;

fn(catList);
}, 1, returnValue);
}

注意,函数表达式asyncCall的作用域在buildFerociousCats下。当与异步编程中的回调配合时,这种写法是很有效果的。该契约异步执行,并获取其所需参数。由于其解耦性,该组合获得了所需的回调自由。JavaScript的灵活性让编码更明了,充分发挥了自身的优势。

接下来的写法不言而喻,以下是其中一种:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
buildFerociousCats('', 'Panther', getJaguar);

function getJaguar(list) {
buildFerociousCats(list, 'Jaguar', getLynx);
}

function getLynx(list) {
buildFerociousCats(list, 'Lynx', getSnowLeopard);
}

function getSnowLeopard(list) {
buildFerociousCats(list, 'Snow Leopard', getLion);
}

function getLion(list) {
buildFerociousCats(list, 'Lion', printList);
}

function printList(list) {
console.log(list);
}

注意,以上片段没有重复的代码。在不使用全局变量的情况下,回调可以追踪自己的状态。例如,回调getLion可以与契约之后的任何东西相关联。这是一个需要猫的列表作为参数的回调。这段代码已经上传至GitHub

多态回调

这是什么呢?让我们更疯狂一点。如果我们想将行为从创建逗号分隔列表更改为管道分隔呢?我发现的一个问题是buildFerociousCats与实现细节相关联。可以使用list + ',' + data做到这一点。

简单来说就是回调行为的多态性。其原则仍然是:像契约一样处理回调,并使其实现互不关联。一旦回调提升到抽象,特定的细节可以随意改变。

多态打开了在JavaScript中复用代码的新大门。将多态回调想象成定义严格契约的一种方式,同时允许其有足够的自由,这时候细节就不再重要。值得注意的是我们仍然在讨论依赖倒置。多态回调只是一个花哨的名字,指出了进一步采取该想法的一种方式。

我们可以定义一个使用listdata参数的契约:

cat.delimiter(cat.list, data);

然后使用buildFerociousCats并做一些微小的调整:

1
2
3
4
5
6
7
function buildFerociousCats(cat, returnValue, next) {
setTimeout(function asyncCall(data) {
var catList = cat.delimiter(cat.list, data);

next({ list: catList, delimiter: cat.delimiter });
}, 1, returnValue);
}

现在JavaScript对象cat封装了list数据和delimiter方法。回调链的异步回调,在以前我们称之为fn。注意,我们可以随意使用JavaScript对象将参数分组。cat对象同时需要listdelimiter两个特定的键。此JavaScript对象现在是契约的一部分,其余代码保持不变。

我们可以这样来实现:

1
2
buildFerociousCats({ list: '', delimiter: commaDelimiter }, 'Panther', getJaguar);
buildFerociousCats({ list: '', delimiter: pipeDelimiter }, 'Panther', getJaguar);

回调被交换。只要契约得到满足,细节就不再重要。我们可以轻松地改变其行为。现在回调被看成是一种依赖,被反转成一个高级别的契约。这个想法采用我们已经知道的回调,并将其提高到一个新的水平。 通过减少回调到契约,它提升了抽象和解耦软件模块。

激进的是,从独立模块自然过渡到单元测试。delimiter契约是一个纯函数。这意味着在多次输入的情况下,每次都可以得到相同的输出。这个级别的可测试性提高了解决方案的可行性。毕竟,模块化的独立赋予了自我评估的权利。

pipe delimiter的有效单元测试可能是这样的:

1
2
3
4
5
6
7
describe('A pipe delimiter', function () {
it('adds a pipe in the list', function () {
var list = pipeDelimiter('Cat', 'Cat');

assert.equal(list, 'Cat|Cat');
});
});

你可以随意想象一下其实现细节是什么样子,同时也可以在github上查看代码

总结

通过其细节才能掌握JavaScript中的回调。我希望你能看到JavaScript函数的微妙变化。当我们缺乏基础知识时,可能会误解回调函数。一旦理清JavaScript函数,SOLID原则也会很好理解。实现SOLID编程需要有强大的基础知识,语言本身灵活性的体现在于程序员如何使用它。

我最欣慰的是JavaScript培养了良好的编程能力。对基础知识和细节的把握会使你在一门语言上造诣更高。这种方式在回调函数中显得尤为重要。一些不起眼的细节往往会提升你的编程技能。

原文链接:http://theholmesoffice.com/node-js-fundamentals-how-to-upgrade-the-node-js-version/

Node.js的开发非常活跃,其最新的稳定版本也频繁升级。你经常会发现某些模块在你当前的Node.js版本下无法使用,而需要升级。

幸运的是,现在有一个非常简单的方法来管理你的Node.js版本,即Node binary管理模块’n’。

  1. 查看机器上目前的Node.js版本。
1
$node -v v0.6.12
  1. 清除npm缓存。
1
sudo npm cache clean -f
  1. 安装’n’模块。
1
sudo npm install -g n
  1. 升级到最新的版本(这一步可能要花点时间),也可以指定需要安装的版本。
1
sudo n 0.8.11

也可以向下面这样直接安装最新的稳定版本

1
sudo n stable
  1. 查看Node.js的版本,确定是否升级成功。
1
$node -v

如果第5步显示还是原来的版本,你可能需要重启一下你的机器,或者试试重启终端(译者注)。

最新版的sublime text 3 plugin - LiveReload + chrome extension - LiveReload组合使用,彻底解放F5,特别适合在多屏幕上的前端开发,下面是我的安装过程(windows平台):

在sublime text 3上安装LiveReload插件

按下ctrl+shift+p,输入并选择Package Control: Install Package,跳出对话框后,输入LiveReload安装该插件。(其实就和安装普通sublime插件的过程一样)。

重新启动sublime,每次重开sublime都需要执行一次下面的过程才生效。

  1. ctrl+shift+p 打开包管理器
  2. LiveReload: Enable/disable plugins
  3. Enable - SimpleReload

这时候它的livereload服务才可以真正使用,可以打开http://localhost:35729/livereload.js?snipver=1以测试,如果livereload服务正常,则会正确显示JS内容,反则之服务没正常启动。
此时也可以在sublime底部看到xxx has been enabled的提示。

在chrome上安装LiveReload扩展(需要翻墙)

安装方式和普通扩展安装方式相同,安装后启用,此时可看到右上角的LiveReload图标。

以服务器的方式打开页面

我这里用的是 http-server,例如localhost:3000,接着点击刚刚安装的chrome插件,中间变为实心黑色圆点表示开始运作了。

以下是演示效果,很爽有木有!

今天把一个codeigniter程序部署到linux(centos)下的nginx的时候出现了
Unable to locate the model you have specified:*_model 这个问题.

顺手粘贴到网上搜索了一下,这是由于大小写问题引起的。

在codeigniter3.0中,model文件名的首字母必须大写,如 User_model.php,在这个文件中,书写规则如下:

1
2
3
4
5
6
7
8
9
10
11
// 注意这里的'User_model'
class User_model extends CI_Model{
//验证用户是否已经登陆
public function adminValid(){
$user = $this->session->userdata('user_name');
if (empty($user)) {
$url = site_url('login/index');
echo "<script>window.location='$url';</script>";
}
}
}

controller中,使用如下:

1
2
3
4
5
6
7
8
9
public function __construct(){
parent::__construct(); //调用父类的构造方法
// header("Content-Type:text/html;charset=utf-8");
error_reporting(E_ALL ^E_NOTICE);
//登陆验证...
$this->load->model('user_model','user');
$this->user->adminValid();
//权限验证...
}

这个规则是必须遵守的,在Windows下无所谓,但是在Linux下就会出问题。