React-Todos

最近学完React的最基本概念,闲下来的时候就自己写了一个Todo-List的小应用。这里做个简略的说明,给想好好学React的新手看。

开始之前


学习前提

这里我用了webpackb做了babel和JSX预处理和模块打包。所以对React和一些ES2015(ES6)的语法要有一定的了解。我相信学习ES2015绝对是划算的,因为它是Js的规范。这里给出学习的地方,阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015

功能需求

最后的实际效果:

效果图

我们需要做到的功能有:

  • 可以在最上面的input里,使用回车来添加任务。
  • 在中间的任务列表里,由checkbox来控制任务的状态。
  • 已完成的任务有一个line-through的样式。
  • 当鼠标移到每一个任务时,都会出现删除按钮提供删除。
  • 在底部有一个全选按钮,用于控制所有的任务状态。
  • 还有已完成与总数的显示。
  • 可以清空已完成的任务。

项目下载

上面就是一个Todo-List最基本的功能,而我们这次就是用React实现上述功能。例子在我的github上可以download下来,可以用作参考:React-Todos

阅读全文 »

webpack体验

最近在看许多React的资料,发现了大部分的项目都是用webpack行模块化管理的工具。这次也是借着写了一个React-Todos的小应用,对webPack最基本实用的功能体验了一番,顺带做个小记录。

为什么用webpack


CommonJs与AMD

在一开始,我们先讲一下它和以往我们所用的模块管理工具有什么不一样。在最开始的阶段,Js并没有这些模块机制,各种Js到处飞,得不到有效妥善的管理。后来前端圈开始制定规范,最耳熟能详的是CommonJs和AMD。

CommonJs是应用在NodeJs,是一种同步的模块机制。它的写法大致如下:

1
2
3
4
5
var firstModule = require("firstModule");

//your code...

module.export = anotherModule

AMD的应用场景则是浏览器,异步加载的模块机制。require.js的写法大致如下:

1
2
3
4
5
define(['firstModule'], function(module){

//your code...
return anotherModule
})

其实我们单比较写法,就知道CommonJs是更为优秀的。它是一种同步的写法,对Human友好,而且代码也不会繁琐臃肿。但更重要的原因是,随着npm成为主流的JavaScript组件发布平台,越来越多的前端项目也依赖于npm上的项目,或者自身就会发布到npm平台。所以我们对如何可以使用npm包中的模块是我们的一大需求。所以browserify工具就出现了,它支持我们直接使用require()的同步语法去加载npm模块。

当然我们这里不得不说的是,ES2015(ES6)里也有了自己的模块机制,也就是说ES6的模块机制是官方规定的,我们通过babel(一种6to5的编译器)可以使用比较多的新特性了,包括我们提到的模块机制,而它的写法大致如下:

1
2
3
4
5
import {someModule} from "someModule";

// your codes...

export anotherModule;

当然上面的写法只是最基本的,还有其他的不同加载模块的写法,可以看一下阮一峰老师的ECMAScript 6 入门或者babel的相关文档Learn ES2015

阅读全文 »

异步编程之co——源码分析

异步编程系列教程:

  1. (翻译)异步编程之Promise(1)——初见魅力
  2. 异步编程之Promise(2):探究原理
  3. 异步编程之Promise(3):拓展进阶
  4. 异步编程之Generator(1)——领略魅力
  5. 异步编程之Generator(2)——剖析特性
  6. 异步编程之co——源码分析

如何使用co


大家如果能消化掉前面的知识,相信这一章的分析也肯定是轻轻松松的。我们这一章就来说说,我们之前一直高调提到的co库。co库,它用Generator和Promise相结合,完美提升了我们异步编程的体验。我们首先看看如何使用co的,我们仍旧以之前的读取Json文件的例子看看:

1
2
3
4
5
6
// 注意readFile已经是Promise化的异步API 
co(function* (){
var filename = yield readFile('hello3.txt', 'utf-8');
var json = yield readFile(filename, 'utf-8');
return JSON.parse(json).message;
}).then(console.log, console.error);

大家看上面的代码,甚至是可以使用同步的思维,不用去理会回调什么鬼的。我们readFile()得到filename,然后再次readFile()得到json,解析完json后输出就结束了,非常清爽。大家如果不相信的话,可以使用原生的异步api尝试一下,fs.readFile()像上面相互有依赖的,绝对恶心!

我们可以看到,仅仅是在promise化的异步api前有个yield标识符,就可以使co完美运作。上一篇我们也假想过co的内部是如何实现的,我们再理(fu)顺(zhi)一次:

  1. 我们调用遍历器的next()得到该异步的promise对象
  2. 在promise对象的then()中的resolve对数据进行处理
  3. 把处理后的数据作为参数res传入next(res),继续到下一次异步操作
  4. 重复2,3步骤。直到迭代器的done: true,结束遍历。

如果不清楚我们上面说过的Generator遍历器或promise对象的,可以先放一放这篇文章,从之前的几篇看起。

进入co的世界


获得遍历器

co的源码包括注释和空行仅仅才240行,不能再精简!我们抽出其中主要的代码来进行分析。

1
2
3
4
5
6
7
8
9
10
11
function co(gen) {
var ctx = this; // context

// return a promise
return new Promise(function(resolve, reject) {
if (typeof gen === 'function') gen = gen.call(ctx); // 调用构造器来获得遍历器
if (!gen || typeof gen.next !== 'function') return resolve(gen);

//...下面代码暂时省略...
})
}

这里我们需要关注的有两点:

  1. co函数最终返回的是一个Promise。
  2. 第6行代码,我们可以看到gen变量一开始就已经自身调用了。也就是gen从构造器变成了遍历器。
    阅读全文 »

异步编程之Generator(2)——剖析特性

异步编程系列教程:

  1. (翻译)异步编程之Promise(1)——初见魅力
  2. 异步编程之Promise(2):探究原理
  3. 异步编程之Promise(3):拓展进阶
  4. 异步编程之Generator(1)——领略魅力
  5. 异步编程之Generator(2)——剖析特性
  6. 异步编程之co——源码分析

Generator基础


继上一篇见识过其配合promise带来的超爽的异步编程体验,我想应该大部分同学都会想好好看一下,到底这个Generator是什么?接下来我们会对Generator的特性进行剖析,让我们对接下来学习co源码打个扎实的基础。

起源

我们首先得知道,Generator一开始并不是用来做异步编程的,是后来的大牛们挖掘了它的特性,让它在异步编程里大放异彩。其实Generator是生成遍历器的构造器,ES6定义了一个遍历器的接口Iterator。任何数据结构满足Iterator接口,都可以统一实现遍历操作。一步一步的调用next()或者for..of循环都可以遍历实现Iterator接口的数据结构。

我们简单说一下遍历对象的next()是怎样的:

  1. 第一次调用next()会直接指向第一个数据的位置,然后返回数据的信息。结构是这样的:{value: AnyType, done: Boolean}value属性是指该数据的值,done则是标志是否已经true,结束了。
  2. 再一次调用next()则指向下一个数据,返回相应的数据信息。
  3. 重复第二步,一直到数据结束,返回{value: undefined, done: true}。则表示遍历已经全部完成。

这就是Iterator最基本的实现,当然这里是很片面的,若要展开说,基本又是一大篇文章可以写。这里就直接给出阮一峰老师关于Iterator的文章:10. Iterator和for…of循环

定义

在我们知道了Generator生成的遍历对象是什么之后,我们看一下如何定义这样的Generator函数。对上一篇有印象的同学,应该记得函数标识符后面有一个诡异的星号function* ()。其实这个星号在括号前也是没关系的,这里我是参考了co源码的。我们一旦定义了一个带星号的函数之后,用这个构造器生成的对象在harmony模式里就成了Generator对象(下面我会称其为遍历器)。我们可以测试一下一段代码。

1
2
3
4
5
6
7
8
9
10
11
var toString = Object.prototype.toString;

var Generator = function* (){
yield "hello";
yield "world";
};

var gen = Generator(); // 可以省去new来创建对象

console.log(toString.call(Generator)); // [object Function]
console.log(toString.call(gen)); // [object Generator]

这样我们通过调用特殊定义的Generator构造器,生成一个遍历器([object Generator])。那我们要遍历的话必须得知道遍历的每个成员,yield就是用来定义遍历成员的。也就是说,遍历器进行遍历的时候会以yield为间隔,一个yield一个成员,不断往下走直到不存在下一个yield

在上面的例子中,就是第一次遍历到yield得到”hello”,第二次继续执行遍历操作到yield得到”world”,最后再执行就发现没有了,也就是done: true结束遍历。

接下来我们会详细说一下,遍历器是遍历的各种特性。

阅读全文 »

异步编程之Generator(1)——领略魅力

异步编程系列教程:

  1. (翻译)异步编程之Promise(1)——初见魅力
  2. 异步编程之Promise(2):探究原理
  3. 异步编程之Promise(3):拓展进阶
  4. 异步编程之Generator(1)——领略魅力
  5. 异步编程之Generator(2)——剖析特性
  6. 异步编程之co——源码分析

为何使用Generator


回顾一下我们之前学习的promise。我们巧妙利用了promise/deferred模式,用链式结构代替了嵌套回调的结构,大大缓解了回调地狱。我们再来看看之前我们举的那个异步串行队列的例子吧!假设我们有一个hello.txt,里面存了一个JSON文件的文件名,我们需要得到JSON文件的message属性的值。步骤如下:

  1. 读取hello.txt文件
  2. 得到JSON文件名,再次读取文件
  3. 得到JSON数据后,进行JSON解析
  4. 获得JSON的message属性

Promise链式调用

这个例子我们之前也是举过非常多次的,我们尝试使用Promise链式结构完成:

1
2
3
4
5
6
7
8
9
10
11
12
//这里的readFile已经是promise化的异步API
readFile('hello.txt', 'utf-8')
.then(function(filename){
return readFile(filename, 'utf-8');
})
.then(JSON.parse)
.then(function(data){
console.log(data.message);
})
.catch(function(err){
console.error(err.message);
});

这样一看下来,promise好像并没有多大问题,思维是线性的,而且错误处理也很友好。我们只需要把上一层执行后的结果通过then()传到下一步执行即可。嗯,但不得不说被链式结构束缚后,我们并没有得到一种酣畅淋漓的编程体验。

同步API

我们要写的爽,当然是要将异步编程得到同步编程的体验,这样我们直接使用同步API看一下是怎样的:

1
2
3
var filename = fs.readFileSync('hello.txt', 'utf-8');
var json = fs.readFileSync(filename, 'utf-8');
console.log(JSON.parse(json).message);

同步的写法清晰明了,而且更符合我们以往的编程习惯。但是同步API阻塞代码这个弊病会在Javascript的单线程执行中非常明显。我们到底有没有一种既可以非常接近同步编程的写法,又可以异步不阻塞代码执行呢?既然问出这种问题,答案当然是有的,就是今天的主角:Generator

阅读全文 »