Koa源码阅读笔记(1) -- co

本笔记共四篇
Koa源码阅读笔记(1) – co
Koa源码阅读笔记(2) – compose
Koa源码阅读笔记(3) – 服务器の启动与请求处理
Koa源码阅读笔记(4) – ctx对象

起因

在7月23号时,我参加了北京的NodeParty。其中第一场演讲就是深入讲解Koa。
由于演讲只有一个小时,讲不完Koa的原理。于是在听的时候觉得并不是很满足,遂开始自己翻看源代码。
而Koa1是基于ES6的generator的。其在Koa1中的运行依赖于co。
正好自己之前也想看co的源代码,所以趁着这个机会,一口气将其读完。

co

关于co,其作者的介绍很是简单。

The ultimate generator based flow-control goodness for nodejs (supports thunks, promises, etc)

而co的意义,则在于使用generator函数,解决了JavaScript的回调地狱问题

源码解读

co的源代码十分简洁,一共才两百余行。而且里面注释到位,所以阅读起来的难度还是不大的。
co的核心代码如下(已加上自己的注释):

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
/**
* Execute the generator function or a generator
* and return a promise.
*
* @param {Function} fn
* @return {Promise}
* @api public
*/
function co(gen) {
var ctx = this;
var args = slice.call(arguments, 1)
// we wrap everything in a promise to avoid promise chaining,
// which leads to memory leak errors.
// see https://github.com/tj/co/issues/180
return new Promise(function(resolve, reject) {
// 启动generator函数。
if (typeof gen === 'function') gen = gen.apply(ctx, args);
// 如果gen不存在或者gen.next不是函数(非generator函数)则返回空值
if (!gen || typeof gen.next !== 'function') return resolve(gen);
onFulfilled();
/**
* @param {Mixed} res
* @return {Promise}
* @api private
*/
function onFulfilled(res) {
var ret;
try {
// ret = gen.next return的对象
// gen.next(res),则是向generator函数传参数,作为yield的返回值
/**
* yield句本身没有返回值,或者说总是返回undefined。
* next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
* [next方法的参数](http://es6.ruanyifeng.com/#docs/generator#next方法的参数)
*/
ret = gen.next(res);
} catch (e) {
return reject(e);
}
// 在这儿,每完成一次yield,便交给next()处理
next(ret);
}
/**
* @param {Error} err
* @return {Promise}
* @api private
*/
function onRejected(err) {
var ret;
try {
ret = gen.throw(err);
} catch (e) {
return reject(e);
}
next(ret);
}
/**
* Get the next value in the generator,
* return a promise.
*
* @param {Object} ret
* @return {Promise}
* @api private
*/
function next(ret) {
// 如果这个generator函数完成了,返回最终的值
// 在所有yield完成后,调用next()会返回{value: undefined, done: true}
// 所以需要手动return一个值。这样最后的value才不是undefined
if (ret.done) return resolve(ret.value);
// 未完成则统一交给toPromise函数去处理
// 这里的ret.value实际是 yield 后面的那个(对象|函数|值) 比如 yield 'hello', 此时的value则是 'hello'
var value = toPromise.call(ctx, ret.value);
// 这里value.then(onFulfilled, onRejected),实际上已经调用并传入了 onFulfilled, onRejected 两个参数。
// 因为非这些对象,无法调用then方法。也就无法使用onFulfilled
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
});
}
/**
* Convert a `yield`ed value into a promise.
*
* @param {Mixed} obj
* @return {Promise}
* @api private
*/
function toPromise(obj) {
if (!obj) return obj;
if (isPromise(obj)) return obj;
if (isGeneratorFunction(obj) || isGenerator(obj)) return co.call(this, obj);
if ('function' == typeof obj) return thunkToPromise.call(this, obj);
if (Array.isArray(obj)) return arrayToPromise.call(this, obj);
if (isObject(obj)) return objectToPromise.call(this, obj);
return obj;
}

co的运行机制

看完了源代码,对generator函数有更深的理解,也理解了co的运行机制。

自动执行generator

首先解决的问题则是自动执行generator函数是如何实现的。
这儿的核心部分则在于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function co(gen) {
if (typeof gen === 'function') gen = gen.apply(ctx, args);
onFulfilled();
function onFulfilled(res) {
var ret;
try {
ret = gen.next(res);
} catch (e) {
return reject(e);
}
next(ret);
}
function next(ret) {
if (ret.done) return resolve(ret.value);
var value = toPromise.call(ctx, ret.value);
if (value && isPromise(value)) return value.then(onFulfilled, onRejected);
return onRejected(new TypeError('You may only yield a function, promise, generator, array, or object, '
+ 'but the following object was passed: "' + String(ret.value) + '"'));
}
}

这儿,在给co传入一个generator函数后,co会将其自动启动。然后调用onFulfilled函数。
onFulfilled函数内部,首先则是获取next的返回值。交由next函数处理。
next函数则首先判断是否完成,如果这个generator函数完成了,返回最终的值。
否则则将yield后的值,转换为Promise
最后,通过Promise的then,并将onFulfilled函数作为参数传入。

1
2
3
if (value && isPromise(value)) {
return value.then(onFulfilled, onRejected);
}

而在generator中,yield句本身没有返回值,或者说总是返回undefined
而next方法可以带一个参数,该参数就会被当作上一个yield语句的返回值。
同时通过onFulfilled函数,则可以实现自动调用。
这也就能解释为什么co基于Promise。且能自动执行了。

co.wrap的运行机制

首先,先放上co.wrap的源代码:

1
2
3
4
5
6
7
8
co.wrap = function (fn) {
createPromise.__generatorFunction__ = fn;
return createPromise;
function createPromise() {
// arguments是createPromise()这个函数传入的。
return co.call(this, fn.apply(this, arguments));
}
};

使用方法也很简单:

1
2
3
4
5
6
7
8
var fn = co.wrap(function* (val) {
console.log('this is fn')
return yield Promise.resolve(val);
});
fn(true).then(function (val) {
});

然而在这里,我差点想破了脑袋。一直不理解,但执行co.call(this, fn.apply(this, arguments));这一句时,为什么fn没有实际运行,控制台也没有输出'this is fn'的提示信息。百思不得其解。
然后在苦思冥想,写了好几个demo后,才发现了问题所在。
因为co.wrap()需要传入一个generator函数。而generator函数在运行时时不会自动执行的。
这一点,阮一峰的《ECMAScript 6入门》中有提及。

不同的是,调用Generator函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象。需要手动调用它的next()方法。

而剩下的步骤,就是把这个对象传入co,开始自动执行。

结语

co的源代码读取来不难,但其处理方式却令人赞叹。
而且generator函数的使用,对ES7中的Async/Await的产生,起了关键作用。
正如其作者TJ在co的说明文档中所说的那样:

Co is a stepping stone towards ES7 async/await.

虽然说我没用过co,只使用过Async/Await
但如今的Async/Await,使用babel,启用transform-async-to-generator插件,转译后,也是编译为generator函数。
所以了解一下,还是有好处的。而且阅读co的源代码,是阅读koa1源码的必经之路。


前端路漫漫,且行且歌。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

Sass和Atom与CSS学习

起因

在五月初的时候,我停止了博客的更新。当时还发了篇博文《备战期末考试,暑期再见~》
停止更新的原因在里面也说的很清楚,一是因为要备战期末考试(结果自然是全部通过),二则是因为可写的话题越来越窄。基础知识大抵了解,中高级知识了解不多。处于很尴尬的位置。
但背后真正的原因,却是因为自己前端的学习处于一个迷茫期。让我出现了无文章可写,没有动力的情况。
因为当时HTML/CSS/JavaScript的基础知识大抵掌握的还行,同时在JavaScript的学习和应用上也越来越得心应手。Node.js与ES6的出现更是拯救了自己之前那混乱不堪的代码。而CSS的学习,却出现了毫无寸进的现象。

CSS学习的停滞

CSS学习的停滞,大概出现在三月底四月初那时候。
对于这个我有很深的印象。因为当时JavaScript还有大量可学习的资源,也能找到前进的方向。
但当时对于CSS,我当时完全就是懵的。学习时,纷繁复杂的细节与浏览器兼容性让我无暇顾及其他,很有可能谷歌浏览器上是正常的,火狐浏览器就出了问题。甚至于可能Linux下开发时是正常的,但是Windows下就出了问题。
同时,由于自己在写的一个内网项目,规模不断的在膨胀,从而在CSS中出现了不可控制的状况。这个时候全局样式冲突,复用性差,维护难度增加等问题纷至沓来。自己也对如何更好的写CSS,CSS命名规范等问题的解决方案产生了需求。

迷茫期

从三月底四月初,一直到这七月下半旬,CSS学习的停滞期足足持续了四个月。
中间看过大牛的文章,读过《CSS权威指南》《CSS揭秘》等图书,也尝试过Sass, Stylus等预处理工具。却发现自己写CSS代码时愈发吃力。
从一开始能用各种方式布局,到后期则成为了只知道使用flex的“废人”。然后使用flex多了,发了这种布局方式的局限性也很强。遂又开始了苦苦的思考。中途也求助过大牛,当然人家也没理我。于是依然只能自己思考。
还有非常重要的一点,则是写代码时所用的编辑器Sublime Text,对CSS3,Sass等新特性的支持太差。没有snnipets,代码高亮来写CSS,很大一部分时间都得浪费在思考写的东西是不是对的。
这种东西,看起来是成为大牛的“必经之路”,实际上只是自己在一边烧脑,还一边安慰自己这是学习的行为。毕竟这年头用记事本写代码的,十之八九是情非得已。

项目重新开始

七月中旬,之前写的办公项目决定推倒重来。重新开发3.0版。
当然,这次重构并不是简单的儿戏。而是基于项目情况,自身能力,各方要求。从而做出的选择。同时项目仓库也从开源中国转移至Coding.net。
当决定推倒重来的时候,其实心里很矛盾,一方面是由于参与人员能力的成长,项目会做的更加好。另一方面则是对自己几千行代码,瞬间废弃,所带来的不舍。
下图就是开发这个项目的commit次数。
2016-07-26_14:50:57.jpg
但最后,还是决定重新开始了。七月十六,一切推倒重来。
2016-07-26_14:54:14.jpg

曙光

在项目一开始,我就一直追着设计师,和他谈。希望他的风格能统一化。从而方便前端的代码复用。
同时尝试采取快速迭代的方式,在完成办公系统的核心部分之前,不对其进行大的更改。

Sass

2016-07-26_14:43:21.jpg
项目中的预处理器采取的是Sass。至于他的好处我就不说了,虽然之前也用,但是只是用来简单的嵌套元素。现在回想,觉得当时的行为也算是暴殄天物。
新项目中,模仿了mint-ui的写法。采用了mixin与变量的方式来构造CSS。
2016-07-26_15:02:45.jpg
而在实际的使用中,由于Vue的scoped属性,保证了每个组件的的CSS不会影响其它组件。同时得益于设计风格的统一,大大加快了开发速度。可维护性则提高了,之前的问题也迎刃而解。

Atom

2016-07-26_15:06:14.jpg
另一方面,不得不提的这是Atom编辑器。其对CSS3,Sass等语言的支持,要远远超过Sublime Text。
以下是Atom与Sublime的比较。选择的都是Sass语言,但是对于flex等新属性,Sublime几乎没有支持。
2016-07-26_15:11:24.jpg
2016-07-26_15:12:02.jpg
虽然也能自己写snnipets,但是考虑到后期会加入无限多的新属性,自己也无暇顾及,所有稍微思考了一下,就放弃了这个选项。

结语

Sass与Atom双管齐下的情况下,不仅解决了之前自己的问题,也大大的提高了开发速度。
而对于CSS学习的问题,很多时候觉得更是一种心理的症结。我不担心自己学不会,也不怕自己写得多。但确实害怕自己像个人形打字机一般,无意义的堆积,复制,粘贴代码。最后还因为混乱,从而让写的代码整个崩塌,无可维护。
所以到后期,自己会看一些编写CSS的方法论,但是一直没有找到什么应对当前项目需求的解决方法,所以对于CSS的心情也日渐厌烦,毕竟一些重复的代码写上十几遍,确实能把人写吐了。而如果其中某个颜色一旦更改,大把大把的时间都得浪费在这种本就可以避免的重构上。
不过吃一堑长一智,这个坑估计我现在不踩,后期也会踩到。


收拾收拾,前端的小船重新扬帆起航~
前端路漫漫,且行且歌。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

备战期末考试,暑期再见~

起因

距离宣布更新博客已经两个月了,一共写了6篇博客。
然后就进入考试月,得准备期末考试的复习了。
至于写博客,说实话,可写的话题越来越窄。基础知识大抵了解,中高级知识了解不多。处于很尴尬的位置。

暑期

博客预计恢复更新的时间,应该在暑期。那时候时间多,且自己暑期会去北京闪银奇异实习,能学习到大量的前端知识,相信对于我是一个很大的提高。
那时候再来更新,相信对自己和对各位都会有帮助,而非单纯的凑凑字数啥的。

结语

就酱~

山不在高,有仙则灵。
水不在深,有龙则灵。
文不在多,有助则灵。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

深入理解JavaScript类数组

起因

写这篇博客的起因,是我在知乎上回答一个问题时,说自己在学前端时把《JavaScript高级程序设计》看了好几遍。
于是在评论区中,出现了如下的对话:
对话

天啦噜,这话说的,宝宝感觉到的,是满满的恶意啊。还好自己的JavaScript基础还算不错,没被打脸。(吐槽一句:知乎少部分人真的是恶意度爆表,整天想着打别人的脸。都是搞技术的,和善一点不行吗…………)

不过这个话题也引起了我的注意,问了问身边很多前端同学关于数组与类数组的区别。他们都表示不太熟悉,所以决定写一篇博客,来分享我对数组与类数组的理解。

什么是类数组

类数组的定义,只有一条:
有length属性。

这儿有三个典型的JavaScript类数组例子。

  1. DOM方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 获取所有div
let arrayLike = document.querySelectorAll('div')
console.log(Object.prototype.toString.call(arrayLike)) // [object NodeList]
console.log(arrayLike.length) // 127
console.log(arrayLike[0])
// <div id="js-pjax-loader-bar" class="pjax-loader-bar"></div>
console.log(Array.isArray(arrayLike)) // false
arrayLike.push('push')
// Uncaught TypeError: arrayLike.push is not a function(…)

是的,这个arrayLike的 NodeList,有length,也能用数组下标访问,但是使用Array.isArray测试时,却告诉我们它不是数组。直接使用push方法时,当然也会报错。
但是,我们可以借用类数组方法:

1
2
3
4
5
6
let arr = Array.prototype.slice.call(arrayLike, 0)
console.log(Array.isArray(arr)) // true
arr.push('push something to arr')
console.log(arr[arr.length - 1]) // push something to arr

不难看出,此时的arrayLike在调用数组原型方法时,返回值已经转化成数组了。也能正常使用数组的方法。

  1. 类数组对象
1
2
3
4
5
6
7
8
9
10
11
12
let arrayLikeObj = {
length: 2,
0: 'This is Array Like Object',
1: true
}
console.log(arrayLikeObj.length) // 2
console.log(arrayLikeObj[0]) // This is Array Like Object
console.log(Array.isArray(arrayLikeObj)) // false
let arrObj = Array.prototype.slice.call(arrayLikeObj, 0)
console.log(Array.isArray(arrObj)) // true

这个例子也很好理解。一个对象,加入了length属性,再用Array的原型方法处理一下,摇身一变成为了真的数组。

  1. 类数组函数

这个应该算是最好玩,也是最迷惑人的类数组对象了。

1
2
3
4
5
6
7
8
9
let arrayLikeFunc1 = function () {}
console.log(arrayLikeFunc1.length) // 0
let arrFunc1 = Array.prototype.slice.call(arrayLikeFunc1, 0)
console.log(arrFunc1, arrFunc1.length) // ([], 0)
let arrayLikeFunc2 = function (a, b) {}
console.log(arrayLikeFunc2.length) // 2
let arrFunc2 = Array.prototype.slice.call(arrayLikeFunc2, 0)
console.log(arrFunc2, arrFunc2.length) // ([undefined × 2], 2)

可以看出,函数也有length属性,其值等于函数要接收的参数。

注:不适用于ES6的rest参数。具体原因和表现这儿就不再阐述了,不属于本文讨论范围。可参见 《rest参数 - ECMAScript 6 入门》。另外arguments在ES6中,被rest参数代替了,所以这儿不作为例子。

而length属性大于0时,如果转为数组,则数组里的值会是undefined。个数等于函数length的长度。

类数组的实现原理

类数组的实现原理,主要有以下两点:
第一点是JavaScript的“万物皆对象”概念。
第二点则是JavaScript支持的“鸭子类型”。

首先,从第一点开始解释。

万物皆对象

万物皆对象具体解释如下:

在JavaScript中,“一切皆对象”,数组和函数本质上都是对象,就连三种原始类型的值——数值、字符串、布尔值——在一定条件下,也会自动转为对象,也就是原始类型的“包装对象”。

而另外一个要点则是,所有对象都继承于Object。所以都能调用对象的方法,比如使用点和方括号访问属性。
比如说,这样的:

1
2
3
4
let func = function() {}
console.log(func instanceof Object) // true
func[0] = 'I\'m a func'
console.log(func[0]) // 'I\'m a func'

鸭子类型

万物皆对象具体解释如下:

如果它走起来像鸭子,而且叫起来像鸭子,那么它就是鸭子。

比如说上面举的类数组例子,虽然他们是对象/函数,但是只要有length属性,能当数组用,那么他们就是数组。
是什么,不是什么对鸭子类型来说,一点也不重要。能做什么,才是鸭子类型的核心。(谢谢nightre大大的指正)

但是,在这儿,还是有些迷糊的。为什么使用call/apply借用数组方法就能处理这些类数组呢?

探秘V8

一开始,我也对这个犯迷糊啊。直到我去Github上,看到了谷歌V8引擎处理数组的源代码。
地址在这儿:v8/array.js
作为讲述,我们在这里引用push的源代码(方便讲述,删除部分。slice的比较长,但是原理一致):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// Appends the arguments to the end of the array and returns the new
// length of the array. See ECMA-262, section 15.4.4.7.
function ArrayPush() {
// 获取要处理的数组
var array = TO_OBJECT(this);
// 获取数组长度
var n = TO_LENGTH(array.length);
// 获取函数参数长度
var m = arguments.length;
for (var i = 0; i < m; i++) {
// 将函数参数push进数组
array[i+n] = arguments[i];
}
// 修正数组长度
var new_length = n + m;
array.length = new_length;
// 返回值是数组的长度
return new_length;
}

是的,整个push函数,并没有涉及是否是数组的问题。只关心了length。而因为其对象的特性,所以可以使用方括号来设置属性。

这也是万物皆类型和鸭子类型最生动的体现。

总结

JavaScript中的类数组的特殊性,是由其“万物皆类型”和“鸭子类型”决定的,而浏览器引擎底层的实现,更是佐证了这一点。
而先前说我的那位同学,因为只是知道类数组的几种表现和用法,并且想通过apply来打我脸,证明我根本没有仔细看书。这种行为不仅不友善,而且学习效率也不高。
因为,知其然而不知其所以然是不可取的。特别是发现很多这种例子,就得学会归纳总结。(感谢winter老师的演讲:一个前端的自我修养,教会我很多东西。)。
很多时候,深入看看源代码也会让你对这个理解的更透彻。将来就算是蹦出一百种类数组,也能知道是怎么回事儿。

最后,还是开头那句话:“都是搞技术的,和善一点不行吗?有问题就好好交流,不要总想着打别人脸啊…………”


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

Always bet on F2E

这篇文章应该五一就发出来的,但是写到一半,和室友出去浪了。还剩下最后一点,在五月七号给补完了。

起因

学习编程和前端的路上,有过欢笑有过迷茫。
庆幸的是,我从15年3月份开始学编程,到15年10月份确定以编程作为我大学的方向,只花了6个月。因为编程使我感到快乐。
更庆幸的是,我从15年10月份到16年5月份,花了7个月。确定把前端作为编程的方向。因为这能让我的优势得到最大的发挥,而且,我喜欢前端啊。

我所理解的前端

昨天和一位前端的前辈聊天,他看了看我的博客,和我说:“你只要坚持下去,毕业之后一定会在前端有所建树的。”
当然,他指的前端可能是HTML,CSS,JavaScript。跑在浏览器的前端,基于JavaScript的前端。但是,不是我所理解的前端。

我所理解的前端,正如维基百科给出的定义一般:

在软件架构和程序设计领域,前端是软件系统中直接和用户交互的部分。

也正如我现在干的事情一般,一半时间写Node.js,一半时间写前端。

没有常青树

作为一名公共管理学院的文科生,阅读过许多历史学与管理学的书籍。对这些书籍的理解,就有一条:“没有常青树”
在软件开发的领域,时局瞬息万变。树立起没有常青树的概念是很重要的,君且看万古时空,多少王朝沉浮。也看硅谷,曾经的王者雅虎,如今也不得挂牌出售。

因为没有常青树,所以不愿把自己局限于仅仅只是跑在浏览器的前端,基于JavaScript的前端。
所以我更加认同:“前端是软件系统中直接和用户交互的部分”

虽然目前JavaScript是王者地位,自己也很喜欢它。但WebAssembly的出现,VR/AR的发展,总有一天,情势会变化的。
无论那一天依然是JavaScript登顶,或者是别的替代了它。

我都喜欢做前端啊,直接与用户交互的,决定用户体验好坏的前端,需要懂得计算机基础,也要理解设计、交互、产品、后台知识的前端啊。

Always bet on F2E

曾经看过一个slides,最后有这么一些话:
Always bet on js
这些话是Brendan Eich,也就是JavaScript之父说的。

这也是我想说的话:Always bet on F2E.
可能有人会说,这样会不会太过偏激,知识面狭窄?

我喜欢编程,因为写代码就是一件很开心的事情。只是更加偏向于前端。
于是我决定了,这就是我编程学习的方向,不再迷茫Java好还是C#的语法优雅又或是XXX的发展前途广。
因为有用户交互的地方,就有前端。而语言只是其实现方式而已。

So,always bet on F2E


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

从零组装新工具 - Koa2

整个项目已开源于Github,项目地址:koa2-easy在线Demo:

起因

作为一个前端,Node.js算是必备知识之一。同时因为自己需要做一些后台性的工作,或者完成一个小型应用。所以学习了Node的Express框架,用于辅助和加速开发。

不过当初自己对Express的学习和了解,并不是很深入。要求也仅仅是停留在能发送静态文件,构建后台API,与数据库完成简单交互而已。所以当初自己选用Express时,靠的是Express 应用生成器,相当于Express的最佳实践。
在使用了一段时间之后,被Express的“回调地狱”,“自定义程度不高”等问题所困扰,于是决定更换至新的框架。

在选择框架时,遵循了自己学习新技术的原则:

要么找值得学习的,深入学习并理解。要么找适合当前业务,能快速解决问题的。不要在具体某某某个技术上纠结太久。

这句话也是自己看余果大大的《Web全栈工程师的自我修养》这本书的体会。

选择Koa

在上面原则的指导下,很容易的就找到了一款符合自己需求的框架:Koa。
Koa因为应用了ES6的生成器语法,所以非常优雅的解决了Node.js的回调地狱问题。
比如说这样的Ajax代码,看起来就比回调函数的写法优雅很多。

1
2
3
4
5
6
7
8
9
10
11
function* main() {
var result = yield request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}
function request(url) {
makeAjaxCall(url, function(response){
it.next(response);
});
}

例子来源: Generator 函数

虽然yield的写法有点奇怪,但还是可以接受的。

选择Koa2

同时在Koa的github首页中,看到了Koa2。
Koa2应用了ES7的Async/Await来替代Koa1中的生成器函数与yield。
所以上一段代码的main函数,在Koa2里长这样:

1
2
3
4
5
async function main() {
var result = await request("http://some.url");
var resp = JSON.parse(result);
console.log(resp.value);
}

使用了Async/Await后,整段代码是变的更加好看的。

理解Koa的中间件

在一开始学习Koa时,是不太理解Koa的中间件级联这个概念的。
就是下图这玩意。
中间件级联

这个算是Koa的核心概念了,不理解这个,只能安安心心继续用Express。

还好自己平时爱去看各种开发大会的视频,来提升自己的眼界。所以昨晚正好在慕课网看到了《阿里D2前端技术论坛——2015融合》的大会视频,便开心的点开学习。
而第一篇《用 Node.js 构建海量页面渲染服务——by 不四》讲的就有Koa框架,还梳理了Koa的中间件级联这个概念。
在不四前辈介绍完Koa的中间件级联后,我发现自己好像理解了。
配合着自己之前学习的ES6知识,才发现原来是这样。
在这儿我贴一段代码和自己的理解,有兴趣的同学可以看一看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var koa = require('koa');
var app = koa();
// x-response-time
app.use(function *(next){
// 首先启动第一个中间件,记录下时间
var start = new Date;
// 进入中间件,并等待返回。
yield next;
// 返回后,代表操作已完成,记录结束时间并输出。
var ms = new Date - start;
this.set('X-Response-Time', ms + 'ms');
});
// response
app.use(function *(){
// 最后一个中间件,将body写成'Hello World'
this.body = 'Hello World';
});
app.listen(3000);

整个的流程,会是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
.middleware1 {
// (1) do some stuff
.middleware2 {
// (2) do some other stuff
.middleware3 {
// (3) NO next yield !
// this.body = 'hello world'
}
// (4) do some other stuff later
}
// (5) do some stuff lastest and return
}

至此,学习Koa的最后一个难关,也被攻克了。

从零组装Koa

因为对Express的学习和使用,知道了自己对于后台框架的真实需求。
所以这回决定不用Koa generator之内的工具,而是自己从零开始,组装一个适合自己的Koa框架。
基于Koa2,使用Async/Await,符合自己需求……
想想就是很美好的事情呀。

梳理需求

首先要做的,自然就是梳理自己的需求。看看到底需要什么东西。
于是翻出自己前两个月在使用的Express框架,确定了以下要点。

  1. 路由,创建Rest Api
  2. 发送静态HTML文件
  3. 设置静态文件目录
  4. 发送和读取JSON数据
  5. 渲染模板
  6. 使用ES6语法完成工作

实现需求

具体的实现部分,这儿就不再赘述了。就是去github和npm上,寻找一个一个的包并组装在一起了而已。
整个项目的亮点就在于:完全符合个人需求,并且使用ES6来完成工作。对我个人而言,用ES6不仅看起来爽,也能提升我的工作效率。

总结

这周因为胃肠炎,好像也没做啥事情……最大的事儿也只是组装了个Koa框架。
因为养病的原因,只能每天看看开发者大会的视频。因为肚子时不时的抽一下,真的很影响工作啊……

今天感觉好了一点,希望病情早日康复~
就酱~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

Vuex源码阅读笔记

笔记中的Vue与Vuex版本为1.0.21和0.6.2,需要阅读者有使用Vue,Vuex,ES6的经验。

起因

俗话说得好,没有无缘无故的爱,也没有无缘无故的恨,更不会无缘无故的去阅读别人的源代码。
之所以会去阅读Vuex的源代码,是因为在刚开始接触Vuex时,就在官方文档的Actions部分,看到这么一句:

1
2
3
4
5
6
7
8
9
10
// the simplest action
function increment (store) {
store.dispatch('INCREMENT')
}
// a action with additional arguments
// with ES2015 argument destructuring
function incrementBy ({ dispatch }, amount) {
dispatch('INCREMENT', amount)
}

上面的Action还好说,能看懂,但是下面使用ES6写法的Action是什么鬼呀喂(摔!)
虽然知道有解构赋值,但是那个{ dispatch }又是从哪儿冒出来的呀喂!明明我在调用时,没有传这个参数呀!
之前因为赶项目进度,所以抱着能用就行的态度,也就没管那么多。如今有了空闲时间,必须好好钻研一下呀。
而钻研最好的方式,就是阅读Vuex的源代码。这样就能弄清楚,那个{ dispatch }到底从哪儿冒出来的。

Vuex源代码简介

Vuex的源代码量挺少的,加起来也才600行不到,但是其中大量使用了ES6的语法,且部分功能(如Vuex初始化)使用到了Vue。所以读起来还是有些费劲的。
整个Vuex的源代码,核心内容包括两部分。一部分是Store的构造函数,另一部分则是Vuex的初始化函数。
而刚才问题的答案,就在第二部分。

问题场景还原

首先要介绍的,就是Vuex在Vue项目中的初始化。这儿贴一段代码:
首先是Vuex中,我写的Actions源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
// global/Vuex/action.js
export const getMe = ({ dispatch }) => {
/**
* 异步操作,获取用户信息,并存入Vuex的state中
*/
res.user.get_me()
.then(data => {
dispatch('GET_ME', data)
})
.catch(err => {
console.log(err)
})
}

这个则是顶层组件,调用store的地方。由于Vuex的特点,store只需要在最顶层的组件声明一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div id="wrapper">
<router-view></router-view>
</div>
</template>
<script type="text/javascript">
import store from './Vuex/store.js'
export default {
store
}
</script>

接下来则是组件中,则是实际调用Vuex的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// index.vue
import { getMe } from './../global/Vuex/action'
export default {
vuex: {
actions: {
getMe
},
getters: {
// 从state中获取信息
user: state => state.user
}
},
ready() {
// 开始获取用户信息
this.getMe()
}
}

在这儿,可以很明显的看出,我在使用this.getMe()时,是没有任何参数的。但是在getMe函数的定义中,是需要解构赋值出{dispatch}的。
就好比说这个:

1
2
3
4
5
6
function getX({ x }) {
console.log(x)
}
getX({ x: 3, y: 5 })
// 3

你得传入相应的参数,才能进行解构赋值。
同时,我注意到在Vuex的Actions调用,需要在Vue的options的Vuex.actions中先声明,之后才能使用。
那么,一定是Vuex对这个Action动了手脚。(逃)
而动手脚的代码,就存在于Vuex源代码的override.js中。这个文件,是用于初始化Vuex的。

Vuex的初始化

override.js中,有个vuexInit的函数。看名字就知道,这是拿来初始化Vuex的。
在代码开头,有这么一句:

1
2
3
4
5
6
const options = this.$options
const { store, vuex } = options
// 感觉解构赋值真的很棒,这样写能省很多时间。
// 下面的是老写法
// const store = options.store
// const vuex = options.vuex

在这儿,用于是在Vue中调用,所以this指向Vue,而this.$options则是Vue的配置项。
也就是写Vue组件时的:
export default {……一些配置}
这里,就把Vue配置项的store和vuex抽离出来了。

搜寻store

接下来,则看到了Vuex源代码的精妙之处:

1
2
3
4
5
6
// store injection
if (store) {
this.$store = store
} else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}

解构赋值并不是一定成功的,如果store在options中不存在,那么store就会是undefined。但是我们需要找store。
于是Vuex提供了向父级(Vue中的功能)寻找store的功能。不难看出,这儿父级的$store如果不存在,那么其实他也会到自己的父级去寻找。直到找到为止。
就想一条锁链一样,一层一层的连到最顶部store。所以在没有找到时,Vuex会给你报个错误。

1
2
3
4
5
6
7
8
// 声明了Vuex但没有找到store时的状况
if (vuex) {
if (!this.$store) {
console.warn(
'[vuex] store not injected. make sure to ' +
'provide the store option in your root component.'
)
}

对Vuex声明的内容,进行改造

接下来,则是对Vuex声明的内容,进行改造。
首先的是获取Vuex对象的内容:

1
let { state, getters, actions } = vuex

同时,在这儿还看到了对过时API的处理。感觉算是意料之外的惊喜。

1
2
3
4
5
6
7
8
9
10
// handle deprecated state option
// 如果使用state而不是getters来获取Store的数据,则会提示你state已经过时的,你需要使用新的api。
// 但是,这儿也做了兼容,确保升级时服务不会挂掉。
if (state && !getters) {
console.warn(
'[vuex] vuex.state option will been deprecated in 1.0. ' +
'Use vuex.getters instead.'
)
getters = state
}

接下来,则是对getters和actions的处理:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// getters
if (getters) {
options.computed = options.computed || {}
for (let key in getters) {
defineVuexGetter(this, key, getters[key])
}
}
// actions
if (actions) {
options.methods = options.methods || {}
for (let key in actions) {
options.methods[key] = makeBoundAction(this.$store, actions[key], key)
}
}

可以看出,在这儿对getters和actions都进行了额外处理。
在这儿,我们讲述actions的额外处理,至于getters,涉及了过多的Vue,而我不是很熟悉。等我多钻研后,再写吧。

Actions的改造

对整个Actions的改造,首先是Vuex的检测:

1
2
3
4
5
6
7
8
// actions
if (actions) {
// options.methods是Vue的methods选项
options.methods = options.methods || {}
for (let key in actions) {
options.methods[key] = makeBoundAction(this.$store, actions[key], key)
}
}

在这儿,我们一点一点的剖析。可以看出,所有的actions,都会被makeBoundAction函数处理,并加入Vue的methods选项中。
那么看来,makeBoundAction函数就是我要找的答案了。
接下来贴出makeBoundAction函数的源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* Make a bound-to-store version of a raw action function.
*
* @param {Store} store
* @param {Function} action
* @param {String} key
*/
function makeBoundAction(store, action, key) {
if (typeof action !== 'function') {
console.warn(`[vuex] Action bound to key 'vuex.actions.${key}' is not a function.`)
}
return function vuexBoundAction(...args) {
return action.call(this, store, ...args)
}
}

事情到这儿,其实已经豁然明朗了。
我在Vuex中传入的actions,实际会被处理为vuexBoundAction,并加入options.methods中。
在调用这个函数时,实际上的action会使用call,来改变this指向并传入store作为第一个参数。而store是有dispatch这个函数的。
那么,在我传入{dispatch}时,自然而然就会解构赋值。
这样的话,也形成了闭包,确保action能访问到store。

结语

今天应该算是解决了心中的一个大疑惑,还是那句话:

没有无缘无故的爱,也没有无缘无故的恨,更没有无缘无故冒出来的代码。

整个源代码读下来一遍,虽然有些部分不太理解,但是对ES6和一些代码的使用的理解又加深了一步。比如这回就巩固了我关于ES6解构赋值的知识。而且还收获了很多别的东西。总而言之,收获颇丰~
最后的,依然是那句话:前端路漫漫,且行且歌。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

ES6学习之解构赋值

本文选自我在SegmentFault的#21天阅读分享#中,所记录的两篇笔记。
因为对自己帮助较大,所以分享在此。

起因

前两天在项目中,需要应用到vuex(类似redux的状态管理工具)。而vuex中,关于变量的赋值是ES6中的解构赋值。
恰巧今天在看犀牛书时,也看到了关于解构赋值的介绍,所以今天准备专门学习解构赋值。

解构赋值

之前我们声明变量,是这样的:

1
2
var one = 1;
var two = 2;

这种变量声明的方式,写的少了还好说。写多了,却会感觉繁琐。也容易出错。
而ES6中,关于解构赋值的写法,是这样的:

1
2
3
4
5
var [one, two] = [1, 2]
console.log(one)
// 1
console.log(two)
// 2

这样的话,一次性就命名了两个变量。
但只是这样的话,功能其实是不够用的。
结构赋值还支持如下的形式:

1
2
3
var [one,,three,] = [1,2,3,4]
console.log(three)
// 3

这种方式,可以留空位,从而是变量赋值达到精准的要求。
在阮一峰老师的ES6文档中,关于解构赋值有这么一句:

解构赋值可以方便地将一组参数与变量名对应起来。

1
2
3
4
5
6
7
// 参数是一组有次序的值
function f([x, y, z]) { ... }
f([1, 2, 3])
// 参数是一组无次序的值
function f({x, y, z}) { ... }
f({z: 3, y: 2, x: 1})

现在看来,只是把传入的dispatch参数,给解构赋值了。

对象的解构赋值

先写一个demo。

1
2
3
4
5
6
7
8
var { foo, bar } = {
foo: "Hi i'm foo",
bar: "Hi i'm bar"
}
console.log(foo)
// "Hi i'm foo"
console.log(bar)
// "Hi i'm bar"

对象的属性没有次序,变量必须与属性同名,才能取到正确的值。
在这儿,我设置了foo和bar,自然就取到了相应的变量。
但是如果要名字不一样呢?

1
2
3
4
5
6
7
8
var { foo: Anotherfoo, bar: Anotherbar } = {
foo: "Hi i'm foo",
bar: "Hi i'm bar"
}
console.log(Anotherfoo)
"Hi i'm foo"
console.log(Anotherbar)
"Hi i'm bar"

这儿相当于把获取到的foo值,赋值给Anotherfoo。从而达到变量名不同也能变量赋值的效果。
这部分的机制,就借用阮一峰老师的话语:

这实际上说明,对象的解构赋值是下面形式的简写

1
var { foo: foo, bar: bar } = { foo: "aaa", bar: "bbb" };

也就是说,对象的解构赋值的内部机制,是先找到同名属性,然后再赋给对应的变量。真正被赋值的是后者,而不是前者。

1
2
3
var { foo: baz } = { foo: "aaa", bar: "bbb" };
baz // "aaa"
foo // error: foo is not defined

上面代码中,真正被赋值的是变量baz,而不是模式foo

同时之前在使用vuex中,对这一句话很不理解:

1
2
3
4
5
6
7
8
const vm = new Vue({
vuex: {
getters: { ... },
actions: {
plus: ({ dispatch }) => dispatch('INCREMENT')
}
}
})

2016.04.16更新

在看到Vuex源代码时,发现有这么一部分:

1
2
3
4
5
6
7
constructor ({
state = {},
mutations = {},
modules = {},
middlewares = [],
strict = false
} = {})

感兴趣的,是函数中,指定了变量的默认参数并进行了变量解构赋值,但给整个参数又指定了默认值。
于是手写了一个demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
function getInfo({
a = '我是默认参数A',
b = '我是默认参数B'
} = {}) {
console.log(a, b)
}
getInfo({ a: 'A被覆盖了', b: 'B被覆盖了' })
// A被覆盖了 B被覆盖了
getInfo({})
// 我是默认参数A 我是默认参数B
getInfo()
// 我是默认参数A 我是默认参数B

也就是说,这种写法,当函数未传入覆盖默认值的参数,则默认参数将被解构赋值。从而保证默认参数100%得到使用。
而不会出现下面,没有传入参数时报错的现象。

1
2
3
4
function getInfo({……一些默认参数} = {}) {}
getAnotherInfo()
// Uncaught TypeError: Cannot match against 'undefined' or 'null'.

嵌套对象的解构

感觉,这应该是解构赋值中最实用的部分了(个人认为)。
因为经常套数据,所以也经常需要把变量的数据取出,转成变量。写多了的话,也是感觉很繁琐的。
而ES6,提供了一种全新的解决方式。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var json = {
'name': 'Lxxyx',
'info': {
'age': 19,
'subject': 'HRM'
}
}
var {
name,
info: {
age,
subject
}
} = json
name // "Lxxyx"
age //19
subject //"HRM"

如果直接写变量,代表把相应变量赋值。
如果加个:号,则表示操作符。表示要去这里面找变量。
如果不理解的话,自己写一遍demo也就理解了。

结尾

前端路漫漫,且行且歌~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

前端基础HTTP知识学习之HTTP状态码

起因

之前我在我的2016,寒假前端学习计划中,发现自己对HTTP的知识很不熟悉。而作为一个前端,应该对HTTP知识了如指掌。
这也是三月份我在找实习的过程中,和几家公司的CTO交流中,他们给我的指点与建议。很感激他们对我的帮助,让我了解到自己知识的漏洞。
在补习HTTP相关知识的时候,我手头上有两本书,一本是《图解HTTP》,另外一本是《HTTP权威指南》。
在这儿比较推荐前端初学者,去看《图解HTTP》这本书。
《图解HTTP》
就是图里这本书,整本书深入浅出,书里面的图示非常多。所以整本书读下来非常轻松,却又能恰好掌握HTTP的基础知识。所以我在读的时候,一口气记了十几页的笔记。

那学习HTTP知识,最大的好处就是当前后端交互出错时,至少知道是前端的问题还是后台的问题。这样能省下很多扯皮的时间。(对于我来说)

HTTP与前端

既然作为HTTP的基础知识,我们这儿就不介绍HTTP的起源,发展,与TCP/IP啥啥啥的关系了,有兴趣的同学可以自己看《图解HTTP》或者《计算机网络》这本书。
作为前端,最多接触的HTTP知识是什么?我认为,是状态码。
作为一个前端,使用Ajax完成前后端交互,通过Chrome控制台的Network来看资源加载情况等事情,简直和家常便饭一样。
比如说,看到 404 就知道这个资源没能加载出来,而看到200就知道这次加载稳了。
曾经的我,对于HTTP实际应用的大概了解,就只有这些了。但是这些还不够呀。所以在看完《图解HTTP》时,就过来分享一些我HTTP知识的盲点,希望能对大家有所帮助。

HTTP常见状态码分类

大家都知道,在进行请求时,会产生各种各样的HTTP状态码,所以在这儿,首先要介绍的就是HTTP的常见状态码。
最常见的,就是HTTP 2XX - 5XX了。
这儿,我列了一个表格,来描述

HTTP状态码 状态码 原因解释
2xx 成功 请求正常处理完毕
3xx 重定向 需要附加操作
4xx 客户端错误 服务器无法处理请求
5xx 服务器错误 服务器处理请求出错

关于各种状态码的意义,在这儿就一目了然了,比如说,发生4XX时,一般是前端的锅。发生5XX时则是后台的锅。
当要设置页面重定向时,应该设置3XX的状态码,而不是在原网页上使用

1
location.href = 'http://xxxxx'

这种方式。(别笑,真的见过这样的……)

常见状态码

这儿,我将会记下几个常见的状态码,虽然《图解HTTP》这本书提供了14个常见的HTTP状态码,但是对我来说,可能知识盲点和常见的也就那几个。
所以这儿就着重介绍他们,如果想了解详细的,各位可以自行借阅或购买《HTTP权威指南》这本书,绝对满足你的需求。

2XX - 成功

在这儿,首先介绍的是HTTP 200,200的意思很好理解,就是客户端的请求已被处理完毕。

204 - No Content

这个呢,代表服务器请求已被成功处理,但是返回的结果中,是不带实体信息的。

206 - Partial Content

表示范围请求,既返回的实体信息只是一部分。比如,返回某张照片的下半部分,某个文本的最后几行。用得好的话,可以大大节省网络传输的流量,达到指哪打哪的效果。

3XX - 重定向

这部分,其实是有点绕的。不过仔细看看,想一想也能很好的分辨。

301 - 永久重定向

永久重定向,告诉客户端以后应从新地址访问。同时书签(如果有的话)也应该更换为新地址。

302 - 临时重定向

告诉客户端我换了位置了,但你还可以用这个地址来访问。现在基本重定向,返回的都是302状态码。

303 - 临时重定向 - POST请求改为GET

对于POST请求,它表示请求已经被处理,客户端可以接着使用GET方法去请求Location里的URI。

307 - 临时重定向 - POST请求不变

对于POST请求,表示请求还没有被处理,客户端应该向Location里的URI重新发起POST请求。

304 - Not Modified

这个算是很重要的状态码,常见于网站的缓存处理中。
这儿,我摘抄了维基百科的解释:

如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。

304的作用,还是很大的。简单点说就是:我给你发送请求,查看我本地这个资源是否是最新的,如果是,服务器就返回304。浏览器从本地读取缓存,而不用去服务器上请求资源。
想深入了解的话,可以阅读这篇文章: [译]理解HTTP/304响应

4XX - 客户端错误

这部分属于客户端错误,也就是说如果出错了,很大可能是前端的锅。

400 - Bad Request

该状态码表示请求报文存在语法错误,需要修改。

401 - Unauthorized

代表请求未经认证,不给过。

403 - Forbidden

服务器拒绝请求,但是不想告诉你理由。

404 - Not Found

哈哈,这个就是大名鼎鼎的HTTP 404 Not Found 状态码。
这个状态码有两层意思,第一层是请求的资源未找到,第二层则是和HTTP 403一样,服务器拒绝请求,但是不想告诉你理由。

5XX - 服务器错误

这部分属于服务器错误,也就是说如果出错了,则是后台的锅。

500 - Internal Server Error

Internal Server Error翻译成中文就是服务器处理请求出错。也就是后台那儿出了问题,所以发送了500给你。
这种时候,放心的找后台报BUG去吧。

503 - Service Unavailable

这个状态码,则是告诉你现在服务器暂时无法处理你的请求,你待会儿再试试。
当然,出现这种问题,一般也可以直接找后台。

总结

今天所写的HTTP状态码,只是HTTP整个知识的一部分,但却很重要。熟悉常见的HTTP状态码,能帮我们快速定位问题,同时如果你是后台,给出正确的HTTP状态码,也方便自己将来DEBUG。

话说这儿得吐槽一句,那就是七牛的图片上传工具不太方便。在Windows下还好,有个图形界面,自动监控文件夹内容就自动上传的。
但是在Mac和Linux下只有纯命令行界面的工具。很是不方便。于是在写这篇博客时,自己用Gulp写了个小工具,专门配合七牛的Node.js SDK,实现自动监控文件变化并上传。
怎么说,既然选择做程序员,那么能让电脑帮你完成的,就不要动手啦。
况且,这个博客从配置完成的那一天起,我就基本没有折腾过。因为只是想安安静静的写博客而已,目前HEXO和这个主题的效果就挺好的。博客博客,最重要的还是里面的内容。

就酱~


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款

博客将于近期恢复更新

停滞的一个月

在02-25,我写下了《寒假前端学习总结》。直到今天,整整一个多月的时间,没有去更新博客。在这儿对关注我博客的各位,说声抱歉。
因为所处的学期是大二下学期,课程超级多。而且因为刚开学,学生会和工作室两头的事情也忙得不可开交。
所以整整一个月,都是忙过来的。
不过这一个月,倒也不是全无收获。

二十一天阅读分享

虽然在三月份,一篇博客都没写,但是我也没有停止学习。
在三月初参加了前端早读课与Segmentfault的二十一天阅读分享活动。要求连续二十一天,每天都写一篇技术笔记。
很高兴的是我坚持下来了,Segmentfault的奖品就是我一直想买的那本《ES6 标准入门》。所以算是有所收获。
近期会把一些自己写的比较好的笔记誊抄到博客里。
这是我的笔记链接。

Lxxyx 的笔记

校招与暑期实习

三月份还有不得不提的一件事,就是校招与暑期实习了。

校招

因为大二结束,就会退出现在的工作室。相当于没有项目可以做,练手的机会也很少,所以想着找份实习。
因为是大二,只有暑期能去实习。所以时间上算是比较尴尬的,也很少会有公司去招聘一个大二的实习生(总觉得技术不够或者不靠谱等等)。
第一个想到的就是BAT这种大公司,因为只有这种大公司会招聘暑期实习生。所以很高兴的找了阿里的一个前辈帮我内推,幸运的是内推申请过了。
衰的是在三月四号进行了第一轮面试,在今天,简历的状态依然是在评估中。据消息是已经挂了。
====> 4.9号更新 确实,已经挂了。

暑期实习

其实中间还是有些慌乱的,因为想着如果暑期找不到相应的前端实习,自己只能继续闭门造车了。还好,在对路上申请实习,获得了三个实习机会。
于是选择了最喜欢的一家,约定好暑期去实习。两轮电话面试,CTO和另外一个前辈给我的感觉都非常好。
怎么说呢,应该是以进入一家这样的公司为荣吧。

博客将于近期恢复更新

这是今天的正题,博客这种东西,不一直写下去其实是没有意义的。
今后会比较侧重前端相关的知识,和一些自己学习的感悟。
希望能帮助到大家,也算是给自己的知识备个份,需要的时候也能方便的查阅。
好些天没有写博客,说实话写这篇博客的时候自己的脑子是不连贯的。
至于写博客的好处,可以参见阮一峰老师的文章:

为什么要写Blog?

就酱,吃早餐去啦。


如果你觉得我的文章对你有帮助,请支持我。让我写出更好的文章。

支付宝付款 微信付款