新起点-2018

image

2017年,这一年经历了太多的坎坷与不顺,面对铺天盖地的打击,从一开始的失落,一步一步把挫折当成一种习惯,可以说这一年是在跟自己作斗争。虽然到现在还没有完全走出挫折留下的阴影,但我坚信,终有一天会阳光普照,春暖花开!

回顾2017年,自己所做的事:

  • 创建自己的博客
  • 深入学习JavaScript核心知识
  • 学习物联网相关技术
  • 坚持听得到app的相关内容(确实提高了我的认知)

2018年,也给自己定个小目标:

  • 继续深入学习JavaScript核心技术
  • 以分享技术的形式,巩固理论知识
  • 英语,我的老大难,每天坚持翻译英文资料(哪怕只有一句)
  • 参加开源项目,提升自己

最后:祝愿祖国,繁荣强大!

理解HTTP之Content-Type

About

查看Restful API 报头插件:Chrome插件REST Console,以及发送Restful API工具:Chrome插件POST Man

在HTTP 1.1规范中,HTTP请求方式有OPTIONS、GET、HEAD、POST、PUT、DELETE、TRACE、CONNECT

通常我们用的只有GET、POST,然而对于Restful API规范来说,请求资源要用PUT方法,删除资源要用DELETE方法。

例如发送个DELETE包:

http://example.com/my/resource?id=12345

那么通过id就能获取到信息,这个包只有header,并不存在body,下面讨论几个包含body的发包的body传输格式。

Content-Type

Content-Type用于指定内容类型,一般是指网页中存在的Content-Type,Content-Type属性指定请求和响应的HTTP内容类型。如果未指定 ContentType,默认为text/html。

在nginx中有个配置文件mime.types,主要是标示Content-Type的文件格式。

下面是几个常见的Content-Type:

  1. text/html
  2. text/plain
  3. text/css
  4. text/javascript
  5. application/x-www-form-urlencoded
  6. multipart/form-data
  7. application/json
  8. application/xml

前面几个都很好理解,都是html,css,javascript的文件类型,后面四个是POST的发包方式。

application/x-www-form-urlencoded

application/x-www-form-urlencoded是常用的表单发包方式,普通的表单提交,或者js发包,默认都是通过这种方式,

比如一个简单地表单:

1
2
3
4
5
<form enctype="application/x-www-form-urlencoded" action="http://homeway.me/post.php" method="POST">
<input type="text" name="name" value="homeway">
<input type="text" name="key" value="nokey">
<input type="submit" value="submit">
</form>

那么服务器收到的raw header会类似:

1
2
3
4
5
6
7
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4,gl;q=0.2,de;q=0.2
Cache-Control:no-cache
Connection:keep-alive
Content-Length:17
Content-Type:application/x-www-form-urlencoded

那么服务器收到的raw body会是,name=homeway&key=nokey,在php中,通过$_POST就可以获得数组形式的数据。

multipart/form-data

multipart/form-data用在发送文件的POST包。

这里假设我用python的request发送一个文件给服务器:

1
2
3
4
5
6
7
data = {
"key1": "123",
"key2": "456",
}
files = {'file': open('index.py', 'rb')}
res = requests.post(url="http://localhost/upload", method="POST", data=data, files=files)
print res

通过工具,可以看到我发送的数据内容如下:

1
2
3
4
5
6
7
8
9
10
11
POST http://www.homeway.me HTTP/1.1
Content-Type:multipart/form-data; boundary=------WebKitFormBoundaryOGkWPJsSaJCPWjZP

------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key2"
456
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="key1"
123
------WebKitFormBoundaryOGkWPJsSaJCPWjZP
Content-Disposition: form-data; name="file"; filename="index.py"

这里Content-Type告诉我们,发包是以multipart/form-data格式来传输,另外,还有boundary用于分割数据。

当文件太长,HTTP无法在一个包之内发送完毕,就需要分割数据,分割成一个一个chunk发送给服务端,

那么–用于区分数据快,而后面的数据633e61ebf351484f9124d63ce76d8469就是标示区分包作用。

text/xml

微信用的是这种数据格式发送请求的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST http://www.homeway.me HTTP/1.1
Content-Type: text/xml

<?xml version="1.0"?>
<resource>
<id>123</id>
<params>
<name>
<value>homeway</value>
</name>
<age>
<value>22</value>
</age>
</params>
</resource>

php中$_POST只能读取application/x-www-form-urlencoded数据,$_FILES只能读取multipart/form-data类型数据,

那么,要读取text/xml格式的数据,可以用:

file=fopen(‘php://input′,‘rb′);file=fopen(‘php://input′,‘rb′); data = fread(file,length);fclose(file,length);fclose(file);

或者

$data = file_get_contents(‘php://input’);

application/json

通过json形式将数据发送给服务器,一开始,我尝试通过curl,给服务器发送application/json格式包,

然而我收到的数据如下:

————————–e1e1406176ee348a Content-Disposition: form-data; name=”nid” 2 ————————–e1e1406176ee348a Content-Disposition: form-data; name=”uuid” cf9dc994-a4e7-3ad6-bc54-41965b2a0dd7 ————————–e1e1406176ee348a Content-Disposition: form-data; name=”access_token” 956731586df41229dbfec08dd5d54eedb98d73d2 ————————–e1e1406176ee348a–

后来想想明白了,HTTP通信中并不存在所谓的json,而是将string转成json罢了,也就是,application/json可以将它理解为text/plain,普通字符串。

之所以出现那么多乱七八糟的——-应该是php数组传输进去,存在的转换问题吧(我目前能想到的原因)。

本文出自 夏日小草,转载请注明出处:http://homeway.me/2015/07/19/understand-http-about-content-type/

深入理解JavaScript系列—全面解析Module模式

简介

Module模式是JavaScript编程中一个非常通用的模式,一般情况下,大家都知道基本用法,本文尝试着给大家更多该模式的高级用法。

首先我们来看看Module模式的基本特征:

  1. 模块化,可重用
  2. 分装了变量和函数,和全局的namespace不接触,松耦合
  3. 只暴露可用public的方法,其他私有方法全部隐藏

关于Module模式,最早是由YUI的成员Eric Miraglia在4年前提出了这个概念,我们将从一个简单的例子来解释一下基本的用法(如果你已经非常熟悉了,请忽略这一节)。

基本用法

先看下最简单的实现,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
var Calculator = function (eq) {
//这里可以声明私有成员

var eqCtl = document.getElementById(eq);

return {
// 暴露公开的成员
add: function (x, y) {
var val = x + y;
eqCtl.innerHTML = val;
}
};
};

我们可以通过如下方式来调用:

1
2
var claculator = new Calculator('eq');
claculator.add(2,2);

大家可能看到了,每次用的时候都要new一下,也就是说,每个实例在内存中都是一份copy,如果你必须要传参数或者没有一些特殊苛刻的要求的话,我们可以在最后一个}后面加上一个括号,来达到自执行的目的,这样改实例在内存中只会存在一份copy,不过在展示他的优点之前,我们还是先来看看这个模式的基本使用方法吧。

匿名闭包

匿名闭包是让一切成为可能的基础,而这也是JavaScript最好的特性,我们来创建一个最简单的闭包函数,函数内部的代码一直存在于闭包内,在整个运行周期内,该闭包都能保证内部的代码处于私有状态。

1
2
3
4
(function () {
// ... 所有的变量和function都在这里声明,并且作用域也只能在这个匿名闭包里
// ...但是这里的代码依然可以访问外部全局的对象
}());

注意,匿名函数后面的括号,这是JavaScript语言所要求的,因为如果你不声明的话,JavaScript解释器默认是声明一个function函数,有括号,就是创建一个函数表达式,也就是自执行,用的时候不用和上面那样在new了,当然你也可以这样来声明:

1
(function () {/* 内部代码 */})();

不过我们推荐使用第一种方式,关于函数自执行,我后面会有专门一篇文章进行详解,这里就不多说了。

引用全局变量

JavaScript有一个特性叫做隐式全局变量,不管一个变量有没有用过,JavaScript解释器反向遍历作用域链来查找整个变量的var声明,如果没有找到var,解释器则假定该变量是全局变量,如果该变量用于了赋值操作的话,之前如果不存在的话,解释器则会自动创建它,这就是说在匿名闭包里使用或创建全局变量非常容易,不过比较困难的是,代码比较难管理,尤其是阅读代码的人看着很多区分哪些变量是全局的,哪些是局部的。

不过,好在在匿名函数里我们可以提供一个比较简单的替代方案,我们可以将全局变量当成一个参数传入到匿名函数然后使用,相比隐式全局变量,它又清晰又快,我们来看一个例子:

1
2
3
(function ($, YAHOO) {
// 这里,我们的代码就可以使用全局的jQuery对象了,YAHOO也是一样
} (jQuery, YAHOO));

现在很多类库里都有这种使用方式,比如jQuery源码。

不过,有时候可能不仅仅要使用全局变量,而是也想声明全局变量,如何做呢?我们可以通过匿名函数的返回值来返回这个全局变量,这也就是一个基本的Module模式,来看一个完整的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var blogModule = (function () {
var my = {}, privateName = "博客园";

function privateAddTopic(data) {
// 这里是内部处理代码
}

my.Name = privateName;
my.AddTopic = function (data) {
privateAddTopic(data);
};

return my;
} ());

上面的代码声明了一个全局变量blogModule,并且带有2个可访问的属性:blogModule.AddTopic和blogModule.Name,除此之外,其它代码都在匿名函数的闭包里保持着私有状态。同时根据上面传入全局变量的例子,我们也可以很方便地传入其它的全局变量。

高级用法

上面的内容对大多数用户已经很足够了,但我们还可以基于此模式延伸出更强大,易于扩展的结构,让我们一个一个来看。

扩展

Module模式的一个限制就是所有的代码都要写在一个文件,但是在一些大型项目里,将一个功能分离成多个文件是非常重要的,因为可以多人合作易于开发。再回头看看上面的全局参数导入例子,我们能否把blogModule自身传进去呢?答案是肯定的,我们先将blogModule传进去,添加一个函数属性,然后再返回就达到了我们所说的目的,上代码:

1
2
3
4
5
6
var blogModule = (function (my) {
my.AddPhoto = function () {
//添加内部代码
};
return my;
} (blogModule));

这段代码,看起来是不是有C#里扩展方法的感觉?有点类似,但本质不一样哦。同时尽管var不是必须的,但为了确保一致,我们再次使用了它,代码执行以后,blogModule下的AddPhoto就可以使用了,同时匿名函数内部的代码也依然保证了私密性和内部状态。

松耦合扩展

上面的代码尽管可以执行,但是必须先声明blogModule,然后再执行上面的扩展代码,也就是说步骤不能乱,怎么解决这个问题呢?我们来回想一下,我们平时声明变量的都是都是这样的:

1
var cnblogs = cnblogs || {} ;

这是确保cnblogs对象,在存在的时候直接用,不存在的时候直接赋值为{},我们来看看如何利用这个特性来实现Module模式的任意加载顺序:

1
2
3
4
5
6
var blogModule = (function (my) {

// 添加一些功能

return my;
} (blogModule || {}));

通过这样的代码,每个单独分离的文件都保证这个结构,那么我们就可以实现任意顺序的加载,所以,这个时候的var就是必须要声明的,因为不声明,其它文件读取不到哦。

紧耦合扩展

虽然松耦合扩展很牛叉了,但是可能也会存在一些限制,比如你没办法重写你的一些属性或者函数,也不能在初始化的时候就是用Module的属性。紧耦合扩展限制了加载顺序,但是提供了我们重载的机会,看如下例子:

1
2
3
4
5
6
7
8
9
var blogModule = (function (my) {
var oldAddPhotoMethod = my.AddPhoto;

my.AddPhoto = function () {
// 重载方法,依然可通过oldAddPhotoMethod调用旧的方法
};

return my;
} (blogModule));

通过这种方式,我们达到了重载的目的,当然如果你想在继续在内部使用原有的属性,你可以调用oldAddPhotoMethod来用。

克隆与继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
var blogModule = (function (old) {
var my = {},
key;

for (key in old) {
if (old.hasOwnProperty(key)) {
my[key] = old[key];
}
}

var oldAddPhotoMethod = old.AddPhoto;
my.AddPhoto = function () {
// 克隆以后,进行了重写,当然也可以继续调用oldAddPhotoMethod
};

return my;
} (blogModule));

这种方式灵活是灵活,但是也需要花费灵活的代价,其实该对象的属性对象或function根本没有被复制,只是对同一个对象多了一种引用而已,所以如果老对象去改变它,那克隆以后的对象所拥有的属性或function函数也会被改变,解决这个问题,我们就得是用递归,但递归对function函数的赋值也不好用,所以我们在递归的时候eval相应的function。不管怎么样,我还是把这一个方式放在这个帖子里了,大家使用的时候注意一下就行了。

跨文件共享私有对象

通过上面的例子,我们知道,如果一个module分割到多个文件的话,每个文件需要保证一样的结构,也就是说每个文件匿名函数里的私有对象都不能交叉访问,那如果我们非要使用,那怎么办呢? 我们先看一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var blogModule = (function (my) {
var _private = my._private = my._private || {},

_seal = my._seal = my._seal || function () {
delete my._private;
delete my._seal;
delete my._unseal;

},

_unseal = my._unseal = my._unseal || function () {
my._private = _private;
my._seal = _seal;
my._unseal = _unseal;
};

return my;
} (blogModule || {}));

任何文件都可以对他们的局部变量_private设属性,并且设置对其他的文件也立即生效。一旦这个模块加载结束,应用会调用 blogModule._seal()”上锁”,这会阻止外部接入内部的_private。如果这个模块需要再次增生,应用的生命周期内,任何文件都可以调用_unseal() ”开锁”,然后再加载新文件。加载后再次调用 _seal()”上锁”。

子模块

最后一个也是最简单的使用方式,那就是创建子模块

1
2
3
4
5
6
blogModule.CommentSubModule = (function () {
var my = {};
// ...

return my;
} ());

尽管非常简单,我还是把它放进来了,因为我想说明的是子模块也具有一般模块所有的高级使用方式,也就是说你可以对任意子模块再次使用上面的一些应用方法。

总结

上面的大部分方式都可以互相组合使用的,一般来说如果要设计系统,可能会用到松耦合扩展,私有状态和子模块这样的方式。另外,我这里没有提到性能问题,但我认为Module模式效率高,代码少,加载速度快。使用松耦合扩展允许并行加载,这更可以提升下载速度。不过初始化时间可能要慢一些,但是为了使用好的模式,这是值得的。

转载地址:http://www.cnblogs.com/TomXu/archive/2011/12/30/2288372.html

深入理解JavaScript系列—编写高质量JavaScript代码的基本要点

良好的代码编写习惯,是减少bug,以及提升代码的可读性、扩展性都是非常有效的,这系列是在看汤姆大叔深入理解JavaScript系列做的一下总结。

书写可维护的代码(Writing Maintainable Code)

修改bug的代价是沉重的,随着时间的推移,这代价也会随之加重,要想了解历史遗留的某个bug你需要:

  • 花时间学习和理解这个问题
  • 花时间了解怎么解决这个问题

还有,对于大项目来说,修复bug和写代码的不是同一个人,这代价可想而知,谁都想学习新技能,有谁愿意一直围着一堆bug,因此可维护代码是非常有必要的,可维护代码意味着:

  • 可读性
  • 一致性
  • 可预测性
  • 看上去想一个人写的

最小全局变量(Minimizing Globals)

全局变量的问题在于,你的JavaScript应用程序和web页面上的所有代码都共享了这些全局变量,当在程序的两个不同部分定义了同名但不同作用的全局变量的时候,命名冲突在所难免。

web页面包含不是该页面开发者所写的代码也是比较常见的,比如:

  • 第三方的JavaScript库
  • 广告方的脚本代码
  • 第三方用户跟踪和分析脚本的代码

在这里注意声明变量却忘记使用var关键字,会隐式的创建全局变量。

单var形式(Single var Pattern)

在函数顶部使用单var语句是比较有用的一种形式,其好处在于:

  • 提供单一的地方去寻找功能所需要的所有局部变量
  • 防止变量在定义之前使用的逻辑错误
  • 帮助你记住声明的是局部变量,因此减少了全局变量
  • 少代码
1
2
3
4
5
6
7
8
9
function func() {
var a = 1,
b = 2,
sum = a + b,
myobject = {},
i,
j;
// function body...
}

for循环(for Loops)

1
2
3
for (var i = 0; i < myarray.length; i++) {
// 使用myarray[i]做点什么
}

这种循环的不足蜘蛛在于每次循环的时候数组的长度都要去获取一下,这回降低你的代码的执行效率,尤其当maarray不是数组,而是个HTMLCollection对象的时候。

HTMLCollection指的是DOM方法的返回对象,例如:

1
2
3
document.getElementsByName()
document.getElementsByClassName()
document.getElementsByTagName()

复杂的网页dom的层级是很多的,获取长度就意味着你要实时查询DOM,而DOM的操作一般都是比较昂贵的。

这就是为什么当你循环获取值时,缓存数组或集合的长度是比较好的形式,正如下面代码显示的:

1
2
3
for (var i = 0, max = myarray.length; i < max; i++) {
// 使用myarray[i]做点什么
}

这样,在循环过程中,只检索一次长度值。

for-in循环(for-in Loops)

for-in循环应该用在非数组对象的遍历上,使用for-in进行循环也被称之为“枚举”。

从技术上讲,你可以使用for-in循环数组(因为JavaScript中数组也是对象),但这是不推荐,因为如果数组对象已经被自定义的功能增强,就可能发生逻辑错误。另外,在for-in中,属性列表的顺序是不能被保证的。所以最好数组使用正常的for循环,对象使用for-in循环。

有个很重要的hasOwnProperty()方法,当遍历对象属性的时候可以过滤掉从原型链上的属性。

思考下面一段代码:

1
2
3
4
5
6
7
8
9
10
11
12
// 对象
var man = {
hands: 2,
legs: 2,
heads: 1
};

// 在代码的某个地方
// 一个方法添加给了所有对象
if (typeof Object.prototype.clone === "undefined") {
Object.prototype.clone = function () {};
}

在这个例子中,我们定义了字面量对象man,然后在对象原型上增加了名为clone的方法。此原型链是实时的,这就意味着所有的对象自动可以访问新的方法。
为了避免枚举man的时候出现clone方法。你需要应用hasOwnProerty()方法过滤原型属性。如果不做过滤,会导致clone函数显示出来,大多数情况下这是不希望出现。

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
// 1.
// for-in 循环
for (var i in man) {
if (man.hasOwnProperty(i)) { // 过滤
console.log(i, ":", man[i]);
}
}
/* 控制台显示结果
hands : 2
legs : 2
heads : 1
*/
// 2.
// 反面例子:
// for-in loop without checking hasOwnProperty()
for (var i in man) {
console.log(i, ":", man[i]);
}
/*
控制台显示结果
hands : 2
legs : 2
heads : 1
clone: function()
*/

另外一种使用hasOwnProperty()的形式是取消Object.property上的方法:

1
2
3
4
5
for (var i in man) {
if (Object.prototype.hasOwnProperty.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
}

其好处在于man对象重新定义hasOwnProperty情况下避免命名冲突。也避免了长属性对象的所有方法,你可以使用局部变量缓存它。

1
2
3
4
5
6
var i, hasOwn = Object.prototype.hasOwnProperty;
for (i in man) {
if (hasOwn.call(man, i)) { // 过滤
console.log(i, ":", man[i]);
}
}

严格来说,不使用hasOwnProperty()并不是一个错误。根据任务以及你对代码的自信程度,你可以跳过它以提高些许的循环速度。但是当你对当前对象内容(和其原型链)不确定的时候,添加hasOwnProperty()更加保险些。

(不)扩展内置原型((Not) Augmenting Built-in Prototypes)

扩增构造函数的prototype属性是个很强大的增加功能的方法,但有时候它太强大了。

增加内置的构造函数原型(如Object(), Array(), 或Function())挺诱人的,但是这严重降低了可维护性,因为它让你的代码变得难以预测。使用你代码的其他开发人员很可能更期望使用内置的 JavaScript方法来持续不断地工作,而不是你另加的方法。

另外,属性添加到原型中,可能会导致不使用hasOwnProperty属性时在循环中显示出来,这会造成混乱。

因此,不增加内置原型是最好的。你可以指定一个规则,仅当下面的条件均满足时例外:

  • 可以预期将来的ECMAScript版本或是JavaScript实现将一直将此功能当作内置方法来实现。例如,你可以添加ECMAScript 5中描述的方法,一直到各个浏览器都迎头赶上。这种情况下,你只是提前定义了有用的方法。
  • 如果您检查您的自定义属性或方法已不存在——也许已经在代码的其他地方实现或已经是你支持的浏览器JavaScript引擎部分。
  • 你清楚地文档记录并和团队交流了变化。

如果这三个条件得到满足,你可以给原型进行自定义的添加,形式如下:

1
2
3
4
5
if (typeof Object.protoype.myMethod !== "function") {
Object.protoype.myMethod = function () {
// 实现...
};
}

避免隐式类型转换(Avoiding Implied Typecasting)

JavaScript的变量在比较的时候会隐式类型转换。这就是为什么一些诸如:false == 0 或 “” == 0 返回的结果是true。为避免引起混乱的隐含类型转换,在你比较值和表达式类型的时候始终使用===和!==操作符。

1
2
3
4
5
6
7
8
9
var zero = 0;
if (zero === false) {
// 不执行,因为zero为0, 而不是false
}

// 反面示例
if (zero == false) {
// 执行了...
}

还有另外一种思想观点认为==就足够了===是多余的。例如,当你使用typeof你就知道它会返回一个字符串,所以没有使用严格相等的理由。然而,JSLint要求严格相等,它使代码看上去更有一致性,可以降低代码阅读时的精力消耗。(“==是故意的还是一个疏漏?”)

parseInt()下的数值转换(Number Conversions with parseInt())

使用parseInt()你可以从字符串中获取数值,该方法接受另一个基数参数,这经常省略,但不应该。当字符串以”0″开头的时候就有可能会出问 题,例如,部分时间进入表单域,在ECMAScript 3中,开头为”0″的字符串被当做8进制处理了,但这已在ECMAScript 5中改变了。为了避免矛盾和意外的结果,总是指定基数参数。

1
2
3
4
var month = "06",
year = "09";
month = parseInt(month, 10);
year = parseInt(year, 10);

此例中,如果你忽略了基数参数,如parseInt(year),返回的值将是0,因为“09”被当做8进制(好比执行 parseInt( year, 8 )),而09在8进制中不是个有效数字。

替换方法是将字符串转换成数字,包括:

1
2
+"08" // 结果是 8
Number("08") // 8

这些通常快于parseInt(),因为parseInt()方法,顾名思意,不是简单地解析与转换。但是,如果你想输入例如“08 hello”,parseInt()将返回数字,而其它以NaN告终。

编码规范(coding Conventions)

建立和遵循编码规范是很重要的,这让你的代码保持一致性,可预测,更易于阅读和理解。一个新的开发者加入这个团队可以通读规范,理解其它团队成员书写的代码,更快上手干活。

许多激烈的争论发生会议上或是邮件列表上,问题往往针对某些代码规范的特定方面(例如代码缩进,是Tab制表符键还是space空格键)。如果你是 你组织中建议采用规范的,准备好面对各种反对的或是听起来不同但很强烈的观点。要记住,建立和坚定不移地遵循规范要比纠结于规范的细节重要的多。

命名规范(Naming Conventions)

另一种方法让你的代码更具可预测性和可维护性是采用命名规范。这就意味着你需要用同一种形式给你的变量和函数命名。

下面是建议的一些命名规范,你可以原样采用,也可以根据自己的喜好作调整。同样,遵循规范要比规范是什么更重要。

以大写字母写构造函数(Capitalizing Constructors)

JavaScript并没有类,但有new调用的构造函数:

1
var adam = new Person();

因为构造函数仍仅仅是函数,仅看函数名就可以帮助告诉你这应该是一个构造函数还是一个正常的函数。

命名构造函数时首字母大写具有暗示作用,使用小写命名的函数和方法不应该使用new调用:

1
2
function MyConstructor() {...}
function myFunction() {...}

分隔单词(Separating Words)

当你的变量或是函数名有多个单词的时候,最好单词的分离遵循统一的规范,有一个常见的做法被称作“驼峰(Camel)命名法”,就是单词小写,每个单词的首字母大写。

对于构造函数,可以使用大驼峰式命名法(upper camel case),如MyConstructor()。对于函数和方法名称,你可以使用小驼峰式命名法(lower camel case),像是myFunction(), calculateArea()和getFirstName()。

要是变量不是函数呢?开发者通常使用小驼峰式命名法,但还有另外一种做法就是所有单词小写以下划线连接:例如,first_name, favorite_bands, 和 old_company_name,这种标记法帮你直观地区分函数和其他标识——原型和对象。

ECMAScript的属性和方法均使用Camel标记法,尽管多字的属性名称是罕见的(正则表达式对象的lastIndex和ignoreCase属性)。

其它命名形式(Other Naming Patterns)

有时,开发人员使用命名规范来弥补或替代语言特性。

例如,JavaScript中没有定义常量的方法(尽管有些内置的像Number, MAX_VALUE),所以开发者都采用全部单词大写的规范来命名这个程序生命周期中都不会改变的变量,如:

1
2
3
// 珍贵常数,只可远观
var PI = 3.14,
MAX_WIDTH = 800;

还有另外一个完全大写的惯例:全局变量名字全部大写。全部大写命名全局变量可以加强减小全局变量数量的实践,同时让它们易于区分。

另外一种使用规范来模拟功能的是私有成员。虽然可以在JavaScript中实现真正的私有,但是开发者发现仅仅使用一个下划线前缀来表示一个私有属性或方法会更容易些。考虑下面的例子:

1
2
3
4
5
6
7
8
9
10
11
12
var person = {
getName: function () {
return this._getFirst() + ' ' + this._getLast();
},

_getFirst: function () {
// ...
},
_getLast: function () {
// ...
}
};

在此例中,getName()就表示公共方法,部分稳定的API。而_getFirst()和_getLast()则表明了私有。它们仍然是正常的公共方法,但是使用下划线前缀来警告person对象的使用者这些方法在下一个版本中时不能保证工作的,是不能直接使用的。注意,JSLint有些不鸟下划线前缀,除非你设置了noman选项为:false。

下面是一些常见的_private规范:

使用尾下划线表示私有,如name_和getElements_()
使用一个下划线前缀表_protected(保护)属性,两个下划线前缀表示private (私有)属性
Firefox中一些内置的变量属性不属于该语言的技术部分,使用两个前下划线和两个后下划线表示,如:
proto__和parent

注释(Writing Comments)

你必须注释你的代码,即使不会有其他人向你一样接触它。通常,当你深入研究一个问题,你会很清楚的知道这个代码是干嘛用的,但是,当你一周之后再回来看的时候,想必也要耗掉不少脑细胞去搞明白到底怎么工作的。

很显然,注释不能走极端:每个单独变量或是单独一行。但是,你通常应该记录所有的函数,它们的参数和返回值,或是任何不寻常的技术和方法。要想到注 释可以给你代码未来的阅读者以诸多提示;阅读者需要的是(不要读太多的东西)仅注释和函数属性名来理解你的代码。例如,当你有五六行程序执行特定的任务, 如果你提供了一行代码目的以及为什么在这里的描述的话,阅读者就可以直接跳过这段细节。没有硬性规定注释代码比,代码的某些部分(如正则表达式)可能注释 要比代码多。

最重要的习惯,然而也是最难遵守的,就是保持注释的及时更新,因为过时的注释比没有注释更加的误导人。

关于作者(About the Author )

Stoyan Stefanov是Yahoo!web开发人员,多个O’Reilly书籍的作者、投稿者和技术评审。他经常在会议和他的博客www.phpied.com上发表web开发主题的演讲。Stoyan还是smush.it图片优化工具的创造者,YUI贡献者,雅虎性能优化工具YSlow 2.0的架构设计师。

本文转自:http://www.cnblogs.com/TomXu/archive/2011/12/28/2286877.html
英文原文:http://net.tutsplus.com/tutorials/javascript-ajax/the-essentials-of-writing-high-quality-javascript/

Node.js module.exports与exports

折腾Node.js有些日子了,下面将陆陆续续记录下使用Node.js的一些细节。

熟悉Node.js的童鞋都知道,Node.js作为服务器端的javascript运行环境,它使用npm作为通用的包管理工具,npm遵循CommonJS规范定义了一套用于Node.js模块的约定,关于npm实现Node.js模块的更多细节请细读深入Node.js的模块机制,这里简单讲下书写Node.js代码时module.exportsexports的区别。

在浏览器端js里面,为了解决各模块变量冲突等问题,往往借助于js的闭包把所有模块相关的代码都包装在一个匿名函数里。而Node.js编写模块相当的自由,开发者只需要关注requireexportsmodule等几个变量就足够,而为了保持模块的可读性,很推荐把不同功能的代码块都写成独立模块,减少各模块耦合。开发者可以在“全局”环境下任意使用var申明变量(不用写到闭包里了),通过exports暴露接口给调用者。
我们经常看到类似export.xxx = yyy或者module.exports = xx这样的代码,可实际在通过require函数引入模块时会出现报错的情况,这是什么原因导致的呢?

Node.js在模块编译的过程中会对模块进行包装,最终会返回类似下面的代码:

1
2
3
(function (exports, require, module, __filename, __dirname) {
// module code...
});

其中,module就是这个模块本身,require是对Node.js实现查找模块的模块Module._load实例的引用,filename和dirname是Node.js在查找该模块后找到的模块名称和模块绝对路径,这就是官方API里头这两个全局变量的来历。
关于module.exportsexports的区别,了解了下面几点之后应该就完全明白:

模块内部大概是这样:

1
exports = module.exports = {};

  • exports是module.exports的一个引用
  • require引用模块后,返回给调用者的是module.exports而不是exports
  • exports.xxx,相当于在导出对象上挂属性,该属性对调用模块直接可见
  • exports=相当于给exports对象重新赋值,调用模块不能访问exports对象及其属性
  • 如果此模块是一个类,就应该直接赋值module.exports,这样调用者就是一个类构造器,可以直接new实例
    客官如果看明白咋回事儿了下面的内容可以忽略:)

假如有模块a.js代码如下:

1
2
exports.str = 'a';
exports.fn = function() {};

对a模块的调用:

1
2
3
var a = require('./a');
console.log(a.str);
console.log(a.fn());

这样用是对的,如果改造a如下:

1
2
exports.str = 'a';
exports = function fn() {};

在调用a模块时自然没用fn属性了。

再改造下a模块:

1
2
exports.str = 'a';
module.exports = function fn() {};

这时a模块其实就是fn函数的引用,也就是说可以require(‘./a’)()这样使用,而同时不再有str属性了。

下面直接导出一个类:

1
module.exports = function A() {};

调用:

1
2
var A = require('./a');
var a = new A();

总结下,有两点:

  1. 对于要导出的属性,可以简单直接挂到exports对象上
  2. 对于类,为了直接使导出的内容作为类的构造器可以让调用者使用new操作符创建实例对象,应该把构造函数挂到module.exports对象上,不要和导出属性值混在一起
    最后,不用再纠结module.exportsexorts什么时候该用哪个了吧~

原文地址:https://github.com/chemdemo/chemdemo.github.io/issues/2

git 使用规范流程

git 使用规范流程

团队开发,需要遵循一个git使用规范,方便项目的协调和维护。

git操作需遵循以下步骤:

  • 新建分支
  • 提交分支commit
  • 编写提交信息
  • 与主干同步
  • 合并commit
  • 推送到远程仓库
  • 发出Pull Request

新建分支

每次开发新功能都应该新建一个单独的分支。

1
2
3
4
5
6
# 获取主干最新代码
$ git checkout master
$ git pull

# 新建一个开发分支myfeature
$ git checkout -b myfeature

第二步: 提交分支commit

分支修改后,就可以提交commit了。

1
2
3
$ git add .
$ git status
$ git commit --verbose

git status 命令,用来查看发生变动的文件。

git commit 命令的verbose参数,会列出 diff 的结果。

编写提交信息

使用标识符区别提交信息

  • feat:新功能(feature)
  • fix:修补bug
  • docs:文档(documentation)
  • style: 格式(不影响代码运行的变动)
  • refactor:重构(即不是新增功能,也不是修改bug的代码变动)
  • test:增加测试
  • chore:构建过程或辅助工具的变动

例如:

1
fix:修改了某某bug

这只是简单的commit message规范,更加详细的请转到

与主干同步

分支的开发过程中,要经常与主干保持同步。

1
2
$ git fetch origin
$ git rebase origin/master

合并commit

分支开发完成后,很可能有一堆commit,但是合并到主干的时候,往往希望只有一个(或最多两三个)commit,这样不仅清晰,也容易管理。
那么,怎样才能将多个commit合并呢?这就要用到 git rebase 命令。

1
$ git rebase -i origin/master

git rebase命令的i参数表示互动(interactive),这时git会打开一个互动界面,进行下一步操作。

下面采用Tute Costa的例子,来解释怎么合并commit。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pick 07c5abd Introduce OpenPGP and teach basic usage
pick de9b1eb Fix PostChecker::Post#urls
pick 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

# Rebase 8db7e8b..fa20af3 onto 8db7e8b
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
#
# These lines can be re-ordered; they are executed from top to bottom.
#
# If you remove a line here THAT COMMIT WILL BE LOST.
#
# However, if you remove everything, the rebase will be aborted.
#
# Note that empty commits are commented out

上面的互动界面,先列出当前分支最新的4个commit(越下面越新)。每个commit前面有一个操作命令,默认是pick,表示该行commit被选中,要进行rebase操作。

4个commit的下面是一大堆注释,列出可以使用的命令。

  • pick:正常选中
  • reword:选中,并且修改提交信息;
  • edit:选中,rebase时会暂停,允许你修改这个commit
  • squash:选中,会将当前commit与上一个commit合并
  • fixup:与squash相同,但不会保存当前commit的提交信息
  • exec:执行其他shell命令

上面这6个命令当中,squash和fixup可以用来合并commit。先把需要合并的commit前面的动词,改成squash(或者s)。

1
2
3
4
pick 07c5abd Introduce OpenPGP and teach basic usage
s de9b1eb Fix PostChecker::Post#urls
s 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

这样一改,执行后,当前分支只会剩下两个commit。第二行和第三行的commit,都会合并到第一行的commit。提交信息会同时包含,这三个commit的提交信息。

1
2
3
4
5
6
7
8
9
10

# This is a combination of 3 commits.
# The first commit's message is:
Introduce OpenPGP and teach basic usage

# This is the 2nd commit message:
Fix PostChecker::Post#urls

# This is the 3rd commit message:
Hey kids, stop all the highlighting

如果将第三行的squash命令改成fixup命令。

1
2
3
4
pick 07c5abd Introduce OpenPGP and teach basic usage
s de9b1eb Fix PostChecker::Post#urls
f 3e7ee36 Hey kids, stop all the highlighting
pick fa20af3 git interactive rebase, squash, amend

运行结果相同,还是会生成两个commit,第二行和第三行的commit,都合并到第一行的commit。但是,新的提交信息里面,第三行commit的提交信息,会被注释掉。

1
2
3
4
5
6
7
8
9
# This is a combination of 3 commits.
# The first commit's message is:
Introduce OpenPGP and teach basic usage

# This is the 2nd commit message:
Fix PostChecker::Post#urls

# This is the 3rd commit message:
# Hey kids, stop all the highlighting

Pony Foo提出另外一种合并commit的简便方法,就是先撤销过去5个commit,然后再建一个新的。

1
2
3
4
$ git reset HEAD~5
$ git add .
$ git commit -am "Here's the bug fix that closes #28"
$ git push --force

squash和fixup命令,还可以当作命令行参数使用,自动合并commit。

1
2
$ git commit --fixup
$ git rebase -i --autosquash

推送到远程仓库

合并commit后,就可以推送当前分支到远程仓库了。

1
$ git push --force origin myfeature

git push命令要加上force参数,因为rebase以后,分支历史改变了,跟远程分支不一定兼容,有可能要强行推送。

转载地址

nginx-proxy使用说明

nginx-proxy使用说明

这是使用docker-gen自动化的Docker容器的nginx代理,减少手动配置nginx。

GitHub地址里面有具体的使用教程。

我的项目使用docker部署并使用nginx-proxy做的反向代理,在使用过程中碰到两个问题:

  • 如何在nginx-proxy中使用SSL
  • 在代理多个nginx项目的情况下,如何对指定的项目进行https访问

我在阿里云CA证书中心给我的域名 申请了免费版的SSL https://rnode.me,该证书针对单域名有效,因此我只希望我的主域名通过https访问,其他二级域名通过http访问。

现在针对以上两个问题做个总结。

如何在nginx-proxy中使用SSL

yaml配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
a-nginx:
image: daocloud.io/daocloud/nginx-proxy:alpine
privileged: false
restart: always
ports:
- 80:80
- 443:443
volumes:
- /root/nginx/certs:/etc/nginx/certs
- /root/nginx/conf.d:/etc/nginx/conf.d
- /var/run/docker.sock:/tmp/docker.sock:ro
environment:
- VIRTUAL_PORT=443
- VIRTUAL_PROTO=https

在这里重点配置certs证书位置,还要注意的是证书后缀为crt,如果不是可以进行证书转换,并配置VIRTUAL_PORT=443VIRTUAL_PROTO=https

以上配置就开启了https访问,但由于nginx-proxy HTTPS_METHOD属性默认开启redirect,会导致所有被代理的所有项目都通过https访问,但由于单一证书的原因,只有主域名可以正常访问,其他域名访问异常。

在这里至于要给nginx项目添加配置如下:

1
2
3
4
5
6
7
8
9
10
myblog:
image: daocloud.io/tranren/myblog:master-b63956b
privileged: false
restart: always
ports:
- '80'
environment:
- HTTPS_METHOD=nohttps
- LETSENCRYPT_HOST=blog.rnode.me
- VIRTUAL_HOST=blog.rnode.me

关键一句 HTTPS_METHOD=nohttps哈哈大问题解决了,其他nginx项目也一样,添加该配置即可。

react native系列——环境搭建

安装步骤参考reactnative.cn,由于window环境下安装比较麻烦,下面我将进行逐步安装。
根据reactnative.cn的提示第一步安装Chocolatey工具,不知道什么原因安装失败了,如图:

Chocolatey只是一个包管理器,其安装的目的就是为了很方便的安装必备软件(python2、node),安装失败的同学,我们手动安装。

python 安装及配置

  1. 下载地址

  2. 开始安装……安装成功如下:

  3. 配置环境变量

  4. 验证

node 安装及配置

  1. 下载地址
  2. 开始安装……安装成功如下:
  3. 验证

安装完node后建议设置npm镜像以加速后面的过程(或使用科学上网工具)。注意:不要使用cnpm!cnpm安装的模块路径比较奇怪,packager不能正常识别!

1
2
npm config set registry https://registry.npm.taobao.org --global
npm config set disturl https://npm.taobao.org/dist --global

Yarn、React Native的命令行工具(react-native-cli)

Yarn是Facebook提供的替代npm的工具,可以加速node模块的下载。React Native的命令行工具用于执行创建、初始化、更新项目、运行打包服务(packager)等任务。

1
npm install -g yarn react-native-cli

安装完yarn后同理也要设置镜像源:

1
2
yarn config set registry https://registry.npm.taobao.org --global
yarn config set disturl https://npm.taobao.org/dist --global

android studio安装

  1. 下载地址安装之前必须安装jdk!jdk!jdk!,具体安装步骤参照这里
  2. 安装结束,并打开会看到以下图:

  3. 下一步,如图:

  4. 检查已安装的组件,尤其是模拟器和HAXM加速驱动。
  5. 一直下一步会看到以下界面,点击完成:
  6. 安装完成后,在Android Studio的欢迎界面中选择Configure | SDK Manager
  7. 在SDK Platforms窗口中,选择Show Package Details,然后在Android 6.0 (Marshmallow)中勾选Google APIsAndroid SDK Platform 23Intel x86 Atom System ImageIntel x86 Atom_64 System Image以及Google APIs Intel x86 Atom_64 System Image
  8. 在SDK Tools窗口中,选择Show Package Details,然后在Android SDK Build Tools中勾选Android SDK Build-Tools 23.0.1(必须包含有这个版本。当然如果其他插件需要其他版本,你可以同时安装其他多个版本)。然后还要勾选最底部的Android Support Repository
  9. 开始安装以上所选内容:

ANDROID_HOME环境变量

到这里为止基本的环境就安装结束了,在这里强烈建议一下,在Android studio新建一个android项目,开发工具会检测你的android开发环境,如图:

该问题同样也会影响react native 在android端的调试,点击标记的区域安装相关环境,直到该区域没有蓝色警告,如图:

到此ardroid基本环境安装结束。

Genymotion安装

比起Android Studio自带的原装模拟器,Genymotion是一个性能更好的选择,但它只对个人用户免费。

  1. 下载和安装Genymotion(genymotion需要依赖VirtualBox虚拟机,下载选项中提供了包含VirtualBox和不包含的选项,请按需选择)。
  2. 打开Genymotion。如果你还没有安装VirtualBox,则此时会提示你安装。
  3. 创建一个新模拟器并启动。
  4. 启动React Native应用后,可以按下F1来打开开发者菜单。

在启动模拟器的时候报错了,如图:

实测之后发现,在genymotion安装过程中,会有VirtualBox的安装过程,其版本是5.0.28,
我们安装较新的版本 下载地址

哒哒哒……安装成功了,小伙伴们有木有???

测试安装

1
2
3
react-native init AwesomeProject
cd AwesomeProject
react-native run-android

运行成功图如下:

【完】

react native系列——介绍

react native是什么?

来自百度百科的介绍

React Native (简称RN)是Facebook于2015年4月开源的跨平台移动应用开发框架,是Facebook早先开源的UI框架 React 在原生移动应用平台的衍生产物,目前支持iOS和安卓两大平台。RN使用Javascript语言,类似于HTML的JSX,以及CSS来开发移动应用,因此熟悉Web前端开发的技术人员只需很少的学习就可以进入移动应用开发领域。

react native能干什么?

React Native使你能够在Javascript和React的基础上获得完全一致的开发体验,构建世界一流的原生APP。

React Native着力于提高多平台开发的开发效率 —— 仅需学习一次,编写任何平台。(Learn once, write anywhere)

说白了就是一次开发编译成android、ios两个版本。

react native实现机制

简单的讲就是建立js与native之间的桥梁(bridge),将js的行为映射到native,最终的操作都是native的操作,这和webapp有本质的区别。
具体的实现机制请看这里

几种app开发的比较

     web app hybrid app react native app native app
原生功能体验 良好 接近原生 优秀
渲染性能 较快 非常快
是否支持设备底层访问 不支持 支持 支持 支持
网络要求 依赖网络 支持离线(资源存本地) 支持离线 支持离线
更新复杂度 低(服务端直接更新) 较低(可以进行资料包更新) 较低(可以进行资源包更新) 高(几乎通过应用商店更新)
编程语言 html5+css3+javascript html5+css3+javascript 主要使用js Android(Java),iOS(OC/Swift)
社区资源 丰富(大量前端资料) 有局限性(不同的Hybrid相互独立) 丰富(统一的活跃社区) 丰富(Android,iOS单独学习)
上手难度 简单(写一次,支持不同的平台访问) 简单(写一次,运行任何平台) 中等(学习一次,运行任何平台) 难(不同平台需要单独学习)
开发周期 较短 中等
开发成本 便宜 较为便宜 中等 昂贵
跨平台 所有H5浏览器 Android,iOS,h5浏览器 Android、iOS 不跨平台
APP发布 web服务器 App Store App Store App Store

详情参考这里

react native 整套组件库

react native常用组件

下一篇:react native 基础环境搭建

【完】

package.json中^符号的含义

版本号 x.y.z :其中z 表示一些小的bugfix, 更改z的号,

y表示一些大的版本更改,比如一些API的变化

x表示一些设计的变动及模块的重构之类的,会升级x版本号

在package.json里面dependencies依赖包的版本号前面的符号有两种,一种是~,一种是^。

~,^的区别是:
~的意思是匹配最近的小版本 比如~1.2.3将会匹配所有的1.2.x版本,但不匹配1.3.0, 1.2.0 <= ~1.2.3 <1.3.0
^的意思是最近的一个大版本 比如^1.2.3 将会匹配 所有 1.x.x 包括1.3.0 但不包括2.0 1.0.0 <= ^1.2.3 < 1.x.x