Underscore 源码学习(六)

关于 Underscore 的 mixin 方法。
首先先看个例子:

1
2
3
4
5
6
7
8
9
10
function Panel() {
consoe.log(this, this instanceof Panel);
if(this instanceof Panel) {
return this;
} else {
return new Panel();
}
}
a = Panel(); // Window false
b = new Panel(); // Panel{} true

当函数作为构造器使用时,函数内的 this 执行被新建的对象。当函数被调用时,函数内的 this 则为被调用的对象,在这里是 Window

1
2
3
4
5
var _ = function(obj) {
if (obj instanceof _) return obj;
if (!(this instanceof _)) return new _(obj);
this._wrapped = obj;
};

同样的,如果我们使用下面方法调用:

1
var under = _();

第二个条件成立,所以新建一个 _ 对象后返回,注意这里是再次调用这个函数。
如果我们这样调用:

1
var under = new _();

就好像上面第二次调用一样,这时候就构造了 under 这个对象,如果传入了参数 obj,则把 obj 存入 under 这个对象的 _wrapped 属性中。
Underscore 提供了一个 OO 的调用方法,即:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var chainResult = function(instance, obj) {
return instance._chain ? _(obj).chain() : obj;
};
_.mixin = function(obj) {
// 遍历obj中的函数
_.each(_.functions(obj), function(name) {
// 避免原型链查找,提升性能
var func = _[name] = obj[name];
_.prototype[name] = function() {
// 把wrapped作为数组第一个参数(context),其余传参push到这个数组中
var args = [this._wrapped];
push.apply(args, arguments);
// 如果this是一个_实例,则使用func调用的结果来新建_实例后返回以供继续链式调用
// 如果this不是一个_实例,则直接返回func调用的结果
return chainResult(this, func.apply(_, args));
};
});
return _;
};
// 把Underscore对象mixin化,这样就可以直接在_上调用方法
_.mixin(_);

当然我们还可以把自己写的方法通过 mixin 加入到 Underscore 对象中。

阅读更多

Underscore 源码学习(五)

Underscore 的函数大部分还是挺好理解的,感觉过一遍就行了,不过今天看到两个函数感觉还是挺有意思的,并且也挺常用。这两个函数就是 throttle 和 debounce。

throttle

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
_.throttle = function(func, wait, options) {
var timeout, context, args, result;
var previous = 0;
if (!options) options = {};
var later = function() {
// 更改previous即上一次执行时间为当前时间
previous = options.leading === false ? 0 : _.now();
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
var throttled = function() {
var now = _.now();
// 如果leading为false时禁用第一次首先执行,previous等于now(效果同已经执行过一次,所以第一次被禁用)
// 这个if语句只在第一次执行该函数的时候有效
if (!previous && options.leading === false) previous = now;
var remaining = wait - (now - previous);
context = this;
args = arguments;
// 超时处理和未到时的处理
if (remaining <= 0 || remaining > wait) {
// timeout不为null时清除掉并设置为null
if (timeout) {
clearTimeout(timeout);
timeout = null;
}
previous = now;
// 立即调用
result = func.apply(context, args);
if (!timeout) context = args = null;
} else if (!timeout && options.trailing !== false) { // 如果没有禁用最后一次执行
timeout = setTimeout(later, remaining); // remaining毫秒后执行later
}
// 返回调用的结果
return result;
};
throttled.cancel = function() {
clearTimeout(timeout);
previous = 0;
timeout = context = args = null;
};
return throttled;
};

其实一步步去理解也不难,就不细说这个方法了。这个方法有可选项设置,分别为 {leading: false} 和 {trailing: false}。

所以一般有四类情况:

  • 默认情况
    第一次调用时立即响应,之后每个周期内最多执行一次,周期内触发会产生定时执行使在上一次执行时间 preview 周期时间后再次执行。
  • 设置 leadingfalse
    同默认情况区别在于第一次调用不会立即执行而是等待周期时间后再次执行,如果在周期时间内触发,一样等待上一次执行时间 preview 周期时间后再执行。
  • 设置 trailingfalse
    最后周期内最多执行一次,但在周期时间内调用不会触发 timeout,只能在上一次 timeout 失效后调用才能生效并且此时调用将立即执行。
  • 设置 leadingtrailingfalse
    如果同时还设置 leadingfalse 的话,那么第一次调用不会立即执行而是等待周期时间后才执行,在这段时间内调用都不会有效果。

阅读更多

谈谈 React 和 Redux

刚回到家的时候学习了 React 和 Redux,现在才想来总结一下,不知道会不会忘的差不多了…本来是想写一个问卷发布系统的,使用 React 和 Redux 已经完成了基础的几个功能,但是那个代码量…用 Angular 写简直轻轻松松的好吧…然后就去重构项目还有现在学的 Underscore 去了。是时候重新回顾下了。
此处主要讲的是 Redux 。

关于 Redux

redux 是facebook 提出的 flux 架构的一种优秀实现;而且不局限于为 react 提供数据状态处理。它是零依赖的,可以配合其他任何框架或者类库一起使用。要想配合 react,还得引入 react-redux。

关于 Flux

那什么是 Flux 呢?见下图
flux-overview.png
Flux 可以分为四个部分:

  • View: 视图层
  • Action: 视图层触发的动作
  • Dispatcher: 派发器,用来接受 Actions, 执行回调函数
  • Store:数据层,用来存放应用的状态,其变更会触发 View 层更新

Flux 的最大特点就是单向流动,他的过程大概如下:

  1. 用户访问 View ,触发了动作 Action
  2. Dispatcher 收到 Action ,根据 Action 类别进行相应的处理,处理结束后要求 Store 更新
  3. Store 进行更新,通知 View 层刷新
  4. View 层收到通知更新页面

额,其实我没有用 Flux,不敢讲太多了,简单的说就是一种单项数据流动的解决方案吧。我是直接学 Redux,对 Flux 也就大概了解这么多了。

Redux 和 Flux

Redux 是 Flux 的一种实现,但他们又有所不同,在 Flux 中,Store 可以有多个,但 Redux 有且只能有一个 Store,Flux 中存在 Dispatcher,在 Redux 则没有这个,而是用 reducer 代替了。不多说了,直接往下说 Redux 吧,我快扯不下去 Flux 了=.=

理解Redux

Redux 由四部分组成:

  • Action
  • Reducer
  • Store
  • Views

我们结合具体的应用场景来看

Action

先从 Action 说起,一个 Action 是一个普通的对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const SET_PAPER_TITLE = 'SET_PAPER_TITLE';
export const ADD_QUESTION = 'ADD_QUESTION';
export const REMOVE_QUESTION = 'REMOVE_QUESTION';
export function setPaperTitle(newTitle) {
return {
type: SET_PAPER_TITLE,
value: newTitle
}
}
export function addQuestion(type) {
return {
type: ADD_QUESTION,
questionType: type
}
}
export function removeQuestion(questionId) {
return {
type: REMOVE_QUESTION,
questionId: questionId
}
}

type 属性是必须的,表示动作类别,其他的参数可以自定。
我们先不用管 Action 有什么用,后面会提到。

阅读更多

Underscore 源码学习(四)

终于看完了 Underscore 的集合部分了,看 Underscore 源码真的是长见识了,感觉真的受益匪浅。
但是集合里面方法也挺多的,我都不知道该拿哪些出来讲下,最近接触了 Redux,就说下 createRedux 这个方法吧,为后面讲 Redux 做个铺垫。
先看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
var createReduce = function(dir) {
var reducer = function(obj, iteratee, memo, initial) {
var keys = !isArrayLike(obj) && _.keys(obj),
length = (keys || obj).length,
index = dir > 0 ? 0 : length - 1;
if(!initial) {
memo = obj[keys ? keys[index] : index];
index += dir;
}
for(; index >= 0 && index < length; index += dir) {
var currentKey = keys ? keys[index] : index;
memo = iteratee(memo, obj[currentKey], currentKey, obj);
}
return memo;
};
return function(obj, iteratee, memo, context) {
var initial = arguments.length >= 3;
return reducer(obj, opitimizeCb(iteratee, context, 4), memo, initial);
};
};

这是 reduce 函数的工厂函数,用于生成一个 reducer ,dir 是决定方向用的。
我们从最后一个 return 开始看起,即

1
2
3
4
return function(obj, iteratee, memo, context) {
var initial = arguments.length >= 3;
return reducer(obj, opitimizeCb(iteratee, context, 4), memo, initial);
};

我们使用 reduce 的时候,如果没有指定 memo 值,这时候参数个数只有两个即 obj 和 iteratee,所以 initial 为 false 表示没有初始化。对于没初始的情况,就是增加了一个 if 语句里面的内容而已,作用是把第一个元素作为 memo 值。
接着就是有没有初始化都共用的部分了,通过一个 for 循环把 keys 遍历,并把相应的信息交给 iteratee 去处理,参数 memo 是上一次处理结果。遍历完后把最后的处理结果 memo 返回就完了。
这个函数派生了两个方法,即

1
2
_.reduce = _.foldl = _.inject = createReduce(1);
_.reduceRight = _.foldr = createReduce(-1);

只是方向不同而已。
举个例子方便理解些,例如:

1
2
3
var sum = _.reduce([1, 2, 3, 4, 5], function(accumulator, value, index, collection) {
return accmulator + value;
}, 0);

结果为 15 这个应该很明显,js 原生也有 reduce 方法,如下:

1
2
3
[1, 2, 3, 4, 5].reduce(function(left, right) {
return left + right;
});

阅读更多