JiM-W

keep Moving


  • 首页

  • 分类

  • 关于

  • 归档

  • 标签

React-router Foundation

发表于 2017-05-09 | 分类于 javascript

参考此文章,具体的demo以及相关配置都有,大家可以直接看这个即可,我写的只是为了让自己理解深刻

react-router-tutorial

1 React-Router用来配合React来使用,对于不同的组件,根据不同的url地址进行不同的组件的渲染

还是从本质上先大概了解下Router和Route分别是什么

1
2
3
4
import { Router, Route, hashHistory } from 'react-router'
console.log('Router',Router);
console.log('Route',Route);
1
2
3
4
5
6
7
8
9
10
Router function (props, context, updater) {
// This constructor is overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
if (process.env.NODE_ENV !== 'production…
Route function (props, context, updater) {
// This constructor is overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
if (process.env.NODE_ENV !== 'production…

可以看出,它们也是一般的组件创建函数,然后通过类似于下面的方法进行调用即可

1
2
3
4
5
<Router history={hashHistory}>
<Route path="/" component={App}/>
<Route path='/about' component={About}/>
<Route path='/repo' component={Repo}/>
</Router>

最终实现的功能就会根据不同的路由渲染不同的组件

2 如上实现根据不同的url地址的实现方案,需要我们在地址栏手动输入相关地址,才可以访问,这个时候Link组件

1
2
import {Link} from 'react-router'
console.log('Link',Link);
1
2
3
4
5
Link function (props, context, updater) {
// This constructor is overridden by mocks. The argument is used
// by mocks to assert on what gets mounted.
if (process.env.NODE_ENV !== 'production…

其实我们发现 Router Route Link等也都是一些组件而已,实现的功能就是根据不同的url地址进行不同组件的渲染.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import React from 'react'
import {Link} from 'react-router'
console.log('Link',Link);
export default React.createClass({
render() {
return (
<div>
<h1>React Router Tutorial</h1>
<ul role = 'nav'>
<li><Link to='/about'>About</Link></li>
<li><Link to='/repo'>Repo</Link></li>
</ul>
</div>
)
}
})

Link组件的作用和我们平常用的<a>标签的作用大同小异 .

同样我们也可以封装一个Link ,该link可以有一些样式,以后在这个组件就可以被复用.

1
2
3
4
5
6
7
8
9
// modules/NavLink.js
import React from 'react'
import { Link } from 'react-router'
export default React.createClass({
render() {
return <Link {...this.props} activeClassName="active"/>
}
})

3 React-router的工作 原理其实 就是根据url地址栏不同的地址,然后可以渲染不同的组件

在路由中

1
this.props.children

至关重要,理解它就算基本上理解了路由的基本原理

先贴上参考链接吧 xianyulaodi

App组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
const App=React.createClass({
render() {
return (
<div className="mp_wrap bui_wrap">
{/**主屏幕**/}
<div className="mp_pagebox_home">
{/**这里面的内容会被子路由给代替**/}
{this.props.children}
{/**注意这个this.props.children**/}
{/**公共页脚**/}
<div className="mp_page_footer">
<Footer />
</div>
{/**公共页脚**/}
</div>
{/**主屏幕**/}
</div>
)
}
});

路由的配置

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
render((
<Router>
<Route path="/" component={App}>
<IndexRoute component={Index} />
<Route path="/type/:typeName" component={Type} />
<Route path="/mall" component={Mall}>
<Route path="type/:typeName" component={Type} />
</Route>
<Route path="/my" component={My}>
<IndexRoute component={MyNav} />
<Route path="userCenter" component={MyUserCenter} />
<Route path="memberClub" component={MemberClub} />
</Route>
<Route path="/circle" component={Circle}>
<IndexRoute component={CircleType} />
<Route path="tip/:tipName" component={CircleTip} />
<Route path="say" component={CircleSay} />
</Route>
</Route>
</Router>
), document.getElementById('index'))
1
2
3
{/**这里面的内容会被子路由给代替**/}
{this.props.children}
{/**注意这个this.props.children**/}

在这里面 this.props.children会更具不同的url地址加载不同的组件

  • 默认加载的组件是Index

  • 如果url 是 /my ,那么就会加载 My组件

  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    这里是my.js文件,也就是my组件
    var Content= React.createClass({
    render(){
    return (
    <div className = 'myWrap'>
    {this.props.children}
    </div>
    )
    }
    })
  • my组件在渲染的时候,默认渲染的是MyNav组件,

    1
    2
    3
    4
    5
    <Route path="/my" component={My}>
    <IndexRoute component={MyNav} />
    <Route path="userCenter" component={MyUserCenter} />
    <Route path="memberClub" component={MemberClub} />
    </Route>

    如果url地址栏是/my/userCenter,则会加载MyUserCenter组件

    所以要理解路由的匹配原理

    嵌套关系:

    中文网的解释:React Router 使用路由嵌套的概念来让你定义 view 的嵌套集合,当一个给定的 URL 被调用时,整个集合中(命中的部分)都会被渲染。

    嵌套路由被描述成一种树形结构。React Router 会深度优先遍历整个路由配置来寻找一个与给定的 URL 相匹配的路由。

    什么意思呢。上面的入口文件中,可以看到,其他路由都是最外层那个app(也就是这个 <Route path="/" component={App}>)的子路由,其他路由都是嵌套在这里面。

    当url变化是,它里面的{this.props.children}都会替换,也就是所谓的整个集合的命中部分都会被渲染。

    路径语法:

    路由路径是匹配一个(或一部分)URL 的 一个字符串模式。大部分的路由路径都可以直接按照字面量理解,除了以下几个特殊的符号:

  • :paramName – 匹配一段位于 /、? 或 # 之后的 URL。 命中的部分将被作为一个参数

    • () – 在它内部的内容被认为是可选的
    • * – 匹配任意字符(非贪婪的)直到命中下一个字符或者整个 URL 的末尾,并创建一个 splat 参数
    1
    2
    3
    <Route path="/hello/:name"> // 匹配 /hello/michael 和 /hello/ryan
    <Route path="/hello(/:name)"> // 匹配 /hello, /hello/michael 和 /hello/ryan
    <Route path="/files/*.*"> // 匹配 /files/hello.jpg 和 /files/path/to/hello.jpg

    如果一个路由使用了相对路径,那么完整的路径将由它的所有祖先节点的路径和自身指定的相对路径拼接而成。使用绝对路径可以使路由匹配行为忽略嵌套关系。

4 路由中的参数传递

render/app.js中

注意此时的 : 冒号后面的变量就可 以通过 this.props.params

1 通过如下这种方式定义变量

1
<Route path="/type/:typeName" component={Type} />

上面这行route配置 , : typeName是一个变量 , 对于不同的变量值 , 在Type中该变量值可以通过

1
this.props.params

来获取.

Type组件中

1
2
3
4
5
6
const { typeName } = this.props.params
<h2>{typeName}</h2>
<Link to="/type/饼干">
<p className="icon icon1"></p>
<p className="bui_ta_c bui_tc_gray">饼干</p>
</Link>

当我们点击Link标签的时候,h2中既可以获取到此时

return小结

发表于 2017-05-08 | 分类于 javascript

1 函数执行的时候,如果遇到return,则终止当前函数的执行,直接返回return后面的结果

2 for(var k in obj ) { } 循环,只有在obj被定义的情况下,才会执行for后面的代码块

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
<script>
var obj = {name:'Jhon',age:13};
function f1(obj){
console.log('2');
for (var k in obj ){
//如果obj为定义,则for语句后面的代码块不会执行
return false ;
}
//如果obj是一个对象,下面的代码不会执行,函数在执行的时候,遇到return就会直接退出当前函数执行,返回return的值
console.log('11');
return true ;
cconsole.log('1');
}
var a = f1(obj);//传入obj
var b = f1();//不传值,默认undefined
console.log(a);//false
console.log(b);//true
function isEmptyObject(obj) {
for (var k in obj) {
return false;
}
return true;
}
</script>

Redux applyMiddleware源码

发表于 2017-05-05 | 分类于 redux

今天是周日,昨天玩了一天,嗯,该放松还是得放松嘛,今天来学习下.

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
38
39
40
41
42
43
import compose from './compose'
/**
* Creates a store enhancer that applies middleware to the dispatch method
* of the Redux store. This is handy for a variety of tasks, such as expressing
* asynchronous actions in a concise manner, or logging every action payload.
*
* See `redux-thunk` package as an example of the Redux middleware.
*
* Because middleware is potentially asynchronous, this should be the first
* store enhancer in the composition chain.
*
* Note that each middleware will be given the `dispatch` and `getState` functions
* as named arguments.
*
* @param {...Function} middlewares The middleware chain to be applied.
* @returns {Function} A store enhancer applying the middleware.
*/
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
/*
// 例如,chain 为 [M3, M2, M1],而 compose 是从右到左进行“包裹”的
// 那么,M1 的 dispatch 参数为 store.dispatch(见【降低逼格写法】的【锚点-2】)
// 往后,M2 的 dispatch 参数为 M1 的中间件处理逻辑哥(见【降低逼格写法】的【锚点-3】)
// 同样,M3 的 dispatch 参数为 M2 的中间件处理逻辑哥
// 最后,我们得到串联后的中间件链:M3(M2(M1(store.dispatch)))
*/
return {
...store,
dispatch
}
}
}

由于目前对这个源码理解还是不够透彻,索性拿别人的作为记录得了,后期自己深入研究在自己写demo.

下面这个就是一个简单的中间件

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
const printStateMiddleware = ({ getState }) => next => action => {
console.log('state before dispatch', getState())
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
-------------------------------------------------
function printStateMiddleware(middlewareAPI) { // 记为【锚点-1】,中间件内可用的 API
return function (dispatch) { // 记为【锚点-2】,传入上级中间件处理逻辑(若无则为原 store.dispatch)
// 下面记为【锚点-3】,整个函数将会被传到下级中间件(如果有的话)作为它的 dispatch 参数
return function (action) { // <---------------------------------------------- 这货就叫做【中间件处理逻辑哥】吧
console.log('state before dispatch', middlewareAPI.getState())
var returnValue = dispatch(action) // 还记得吗,dispatch 的返回值其实还是 action
console.log('state after dispatch', middlewareAPI.getState())
return returnValue // 继续传给下一个中间件作为参数 action
}
}
}

如果觉得代码有点晦涩难懂,可以进行下babel转化为ES5 如下:

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
Object.defineProperty(exports, "__esModule", {
value: true
});
var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
exports.default = applyMiddleware;
function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, preloadedState, enhancer) {
var store = createStore(reducer, preloadedState, enhancer);
var _dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
};
chain = middlewares.map(function (middleware) {
return middleware(middlewareAPI);
});
_dispatch = compose.apply(undefined, _toConsumableArray(chain))(store.dispatch);
return _extends({}, store, {
dispatch: _dispatch
});
};
};
}

先从整体上把控下Redux所谓的API都有什么 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Redux 有五个 API,分别是:
createStore(reducer, [initialState])
combineReducers(reducers)
applyMiddleware(...middlewares)
bindActionCreators(actionCreators, dispatch)
compose(...functions)
createStore 生成的 store 有四个 API,分别是:
getState()
dispatch(action)
subscribe(listener)
replaceReducer(nextReducer)

2 通过源码分析下applyMiddleware这个函数做了什么工作:

其实就是在dispatch一个action和执行reducer之前,增加了一些异步的操作

2.1 返回一个新的store创建器

首先返回一个函数,该函数接受Redux.createStore这个API作为参数,也就是说

1
applyMiddleware(...middleWares) = function (createStore){ }
1
var enhancedCreateStore = Redux.applyMiddleware(...Middlewares)(Redux.createStore);

上面这行代码返回的结果也是一个函数,而这个函数被赋值给enhancedCreateStore

正常的创建store的函数

1
var store = Redux.createStore(reducer, preloadedState, enhancer);

增强createStore之后的创建store函数

1
var store = enhancedCreateStore(reducer, preloadedState, enhancer);

2.2 新的store创建器返回的结果增加了原来的dispatch函数功能,从源码中可以看出返回的值和最基础的createStore一样返回的是一个下面的对象:

1
2
3
4
5
6
7
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}

只不过dispatch函数增加了一些功能而已.

1
2
3
4
return {
...store,
dispatch
}

以上分析就是applyMiddleware函数所做的一些工作.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
原 createStore ————
│
↓
return enhancer1(createStore)(reducer, preloadedState, enhancer2)
|
├———————→ createStore 增强版 1
│
↓
return enhancer2(createStore1)(reducer, preloadedState, enhancer3)
|
├———————————→ createStore 增强版 1+2
│
↓
return enhancer3(createStore1+2)(reducer, preloadedState, applyMiddleware(m1,m2,m3))
|
├————————————————————→ createStore 增强版 1+2+3
│
↓
return appleMiddleware(m1,m2,m3)(createStore1+2+3)(reducer, preloadedState)
|
├——————————————————————————————————→ 生成最终增强版 store

3 走一个小的功能看下具体的效果

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
<!DOCTYPE html>
<html>
<head>
<script src="./redux.min.js"></script>
</head>
<body>
<script>
/** Action Creators */
function inc() {
return { type: 'INCREMENT' };
}
function dec() {
return { type: 'DECREMENT' };
}
function reducer(state, action) {
state = state || { counter: 0 };
switch (action.type) {
case 'INCREMENT':
return { counter: state.counter + 1 };
case 'DECREMENT':
return { counter: state.counter - 1 };
default:
return state;
}
}
function printStateMiddleware(middlewareAPI) {
return function (dispatch) {
return function (action) {
console.log('dispatch 前:', middlewareAPI.getState());
var returnValue = dispatch(action);
console.log('dispatch 后:', middlewareAPI.getState(), '\n');
return returnValue;
};
};
}
var enhancedCreateStore = Redux.applyMiddleware(printStateMiddleware)(Redux.createStore);
var store = enhancedCreateStore(reducer);
store.dispatch(inc());
store.dispatch(inc());
store.dispatch(dec());
</script>
</body>
</html>
控制台输出:
dispatch 前:{ counter: 0 }
dispatch 后:{ counter: 1 }
dispatch 前:{ counter: 1 }
dispatch 后:{ counter: 2 }
dispatch 前:{ counter: 2 }
dispatch 后:{ counter: 1 }

4 自己实现一个最简单的中间件

1
2
3
4
5
6
7
function dispatchAndLog(store, action) {
console.log('dispatching', action)
store.dispatch(action)
console.log('next state', store.getState())
}
//
dispatchAndLog(store, addTodo('Use Redux'))
1
2
3
4
5
6
7
8
9
10
11
12
13
function logger(store) {
let next = store.dispatch
// 我们之前的做法:
// store.dispatch = function dispatchAndLog(action) {
return function dispatchAndLog(action) {
console.log('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
return result
}
}

参考

非常感谢作者能从源码的角度剖析文章.

Redux compose源码

发表于 2017-05-05 | 分类于 redux

1 先贴上源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Composes single-argument functions from right to left. The rightmost
* function can take multiple arguments as it provides the signature for
* the resulting composite function.
*
* @param {...Function} funcs The functions to compose.
* @returns {Function} A function obtained by composing the argument functions
* from right to left. For example, compose(f, g, h) is identical to doing
* (...args) => f(g(h(...args))).
*/
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

经过bable编译器编译之后

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
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.default = compose;
function compose() {
for (var _len = arguments.length, funcs = Array(_len), _key = 0; _key < _len; _key++) {
funcs[_key] = arguments[_key];
}
if (funcs.length === 0) {
return function (arg) {
return arg;
};
}
if (funcs.length === 1) {
return funcs[0];
}
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});
}

2 简化应用过程

1
2
3
4
5
6
7
8
9
10
11
12
// 由于 reduce / reduceRight 仅仅是方向的不同,因此下面用 reduce 说明即可
var arr = [1, 2, 3, 4, 5]
//封装的compose函数用的是这种reduce函数,没有传入初始值
var re1 = arr.reduce(function(total, i) {
return total + i
})
console.log(re1) // 15
var re2 = arr.reduce(function(total, i) {
return total + i
}, 100) // <---------------传入一个初始值
console.log(re2) // 115

3 reduce函数

Array.prototype.reduce(callback ,[initialValue]) (reduceRight函数和reduce函数类似,只不是从数组的最后反向开始迭代);数组的reduce方法向callback函数传递四个参数,分别是

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Parameters
callback
Function to execute on each value in the array, taking four arguments:
1 accumulator
The accumulated value previously returned in the last invocation of the callback, or initialValue, if supplied. (See below.)(如果initialValue提供了,那么第一次运行的时候,accumular值为initialValue,如果没有提供initialValue,那么accumular的值为数组中的第一个元素,currentValue为数组中的第二个元素,跳过第一个索引值)
2 currentValue
The current element being processed in the array.
3 currentIndex
The index of the current element being processed in the array. Starts at index 0, if an initialValue is provided, and at index 1 otherwise.
4 array
The array reduce was called upon.
5 initialValue
Optional. Value to use as the first argument to the first call of the callback.
Return value
The value that results from the reduction.

callback函数每次执行之后的结果作为第一个参数再次传递给callback函数

4 compose函数执行之后返回值是什么

1
2
3
4
5
return funcs.reduce(function (a, b) {
return function () {
return a(b.apply(undefined, arguments));
};
});

如上代码返回值是reduce函数执行后的结果

  • 如果是一个简单的数组进行reduce函数的运算,那么结果就很简单
1
2
3
4
5
6
var ret = [1,2,3].reduce(function(a,b){
return a+b ;
});
console.log(ret);//6
  • 如果是一个由函数组成的数组进行reduce函数的运算,那么返回的结果就是一个函数的链式调用
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
<body>
<script src='./redux.min.js'></script>
<script>
function func1(num){
console.log('func1获取参数',num);
return num + 1 ;
}
function func2(num){
console.log('func2获取参数',num);
return num + 1 ;
}
function func3(num){
console.log('func1获取参数',num);
return num + 3 ;
}
var ret = Redux.compose(func1,func2,func3);
console.dir(ret);//可以看出结果是一个函数 function anonymous(t)
var ret1 = Redux.compose(func1,func2,func3)(3) ;
console.log(ret1);
var ret2 = func1(func2(func3(3))); //这个就是compose函数最终返回的最简单的函数形态
console.log(ret2)
</script>

Redux createStore源码

发表于 2017-05-05 | 分类于 redux

###1 先贴上createStore源码

看了源码我对redux的认识才开始清晰,现在终于明白,不看源码学框架就是没事儿找刺激啊,不知道底层如何实现,学起来就是举步维艰;

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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
import isPlainObject from 'lodash/isPlainObject'
import $$observable from 'symbol-observable'
/**
* These are private action types reserved by Redux.
* For any unknown actions, you must return the current state.
* If the current state is undefined, you must return the initial state.
* Do not reference these action types directly in your code.
*/
export const ActionTypes = {
INIT: '@@redux/INIT'
}
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState
}
/**
* Adds a change listener. It will be called any time an action is dispatched,
* and some part of the state tree may potentially have changed. You may then
* call `getState()` to read the current state tree inside the callback.
*
* You may call `dispatch()` from a change listener, with the following
* caveats:
*
* 1. The subscriptions are snapshotted just before every `dispatch()` call.
* If you subscribe or unsubscribe while the listeners are being invoked, this
* will not have any effect on the `dispatch()` that is currently in progress.
* However, the next `dispatch()` call, whether nested or not, will use a more
* recent snapshot of the subscription list.
*
* 2. The listener should not expect to see all state changes, as the state
* might have been updated multiple times during a nested `dispatch()` before
* the listener is called. It is, however, guaranteed that all subscribers
* registered before the `dispatch()` started will be called with the latest
* state by the time it exits.
*
* @param {Function} listener A callback to be invoked on every dispatch.
* @returns {Function} A function to remove this change listener.
*/
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
/**
* Dispatches an action. It is the only way to trigger a state change.
*
* The `reducer` function, used to create the store, will be called with the
* current state tree and the given `action`. Its return value will
* be considered the **next** state of the tree, and the change listeners
* will be notified.
*
* The base implementation only supports plain object actions. If you want to
* dispatch a Promise, an Observable, a thunk, or something else, you need to
* wrap your store creating function into the corresponding middleware. For
* example, see the documentation for the `redux-thunk` package. Even the
* middleware will eventually dispatch plain object actions using this method.
*
* @param {Object} action A plain object representing “what changed”. It is
* a good idea to keep actions serializable so you can record and replay user
* sessions, or use the time travelling `redux-devtools`. An action must have
* a `type` property which may not be `undefined`. It is a good idea to use
* string constants for action types.
*
* @returns {Object} For convenience, the same action object you dispatched.
*
* Note that, if you use a custom middleware, it may wrap `dispatch()` to
* return something else (for example, a Promise you can await).
*/
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
//最初始的值是false
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
//执行完try之后,还是将该值重置为false
isDispatching = false
}
const listeners = currentListeners = nextListeners
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* Replaces the reducer currently used by the store to calculate the state.
*
* You might need this if your app implements code splitting and you want to
* load some of the reducers dynamically. You might also need this if you
* implement a hot reloading mechanism for Redux.
*
* @param {Function} nextReducer The reducer for the store to use instead.
* @returns {void}
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const outerSubscribe = subscribe
return {
/**
* The minimal observable subscription method.
* @param {Object} observer Any object that can be used as an observer.
* The observer object should have a `next` method.
* @returns {subscription} An object with an `unsubscribe` method that can
* be used to unsubscribe the observable from the store, and prevent further
* emission of values from the observable.
*/
subscribe(observer) {
if (typeof observer !== 'object') {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
// When a store is created, an "INIT" action is dispatched so that every
// reducer returns their initial state. This effectively populates
// the initial state tree.
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
  • var store = createStore(reducer , preLoadState , enhancer) 定义一个store的时候可以传递reducer函数,state初始状态
    • 注意createStore之后,会先默认执行dispatch函数一次,看源码注意下这一点.通过这次默认执行dispatch函数,reducer函数就会执行,可以初始化state状态
    • dispatch函数执行的时候,由于刚创建store并没有通过subscribe绑定listeners,所以listeners在createStore的时候不会执行.
  • getState( )函数用来获取state下一个状态
  • dispatch(action)函数,
    • 用来根据不同的action,调用reducer函数改变state状态
    • 依次执行所有通过subscribe订阅到listeners数组中的函数;
    • 返回一个传入的action对象
  • subscribe( listener ) 用来向listeners数组中添加监听器
  • replaceReducer( nextReducer ) 替换 reducer 为 nextReducer
1
2
3
dispatch不同的action,然后触发reducer函数
定义reducer函数的时候,reducer函数接受两个参数,一个是state,一个是action,
因为reducer函数在执行的时候,是在dispatch函数内部执行,所以reducer函数的action来自于dispatch的参数action,reducer函数的state可以来自于自己,也可以来自于定义store的时候

2 我们来结合一个小栗子看下createStore在状态管理过程中所扮演的角色

2.1 还是老惯例,看下引入redux之后,文件中对象有什么

1
2
3
4
5
6
7
8
<body>
<script src='./redux.min.js'></script>
<script>
console.log(Redux);
console.log(Redux.createStore);
</script>
</body>
1
2
3
4
5
6
7
8
9
10
11
Object {__esModule: true,
createStore: function,
combineReducers: function,
bindActionCreators: function,
applyMiddleware: function…}
applyMiddleware:function o()
bindActionCreators:function u(t,e)
combineReducers:function i(t)
compose:function n()
createStore:function o(t,e)
__esModule:true

2.2 reducer函数的定义为什么总是第一个参数是state,第二个参数是action?

我们定义的reducer函数要返回一个reducer处理之后的state,赋值给currentState,然后通过getState就可以获取当前状态;

源码中有如下两行代码,可以看出

1
2
var currentReducer = reducer;
currentState = currentReducer(currentState, action);
  • 当我们创建一个store的时候,createStore接受两个参数,一个是reducer函数,一个是initialState初始状态,
  • 然后 var currentReducer = reducer ; var currentState = initialState;
  • 当我们调用dispatch的时候,dispatch执行的过程中,会调用reducer函数,reducer函数就是我们处理state的方法
  • dispatch函数中在调用reducer的时候,会向其中传递两个参数,一个是initialState对象,一个是action对象;dispatch函数执行后返回的是传入的action
  • 注意定义reducer的时候,必须要返回一个state状态,防治state状态的丢失.

2.3 初始化state的两种方式,

  • 通过createStore(reducer , initialState)
  • 通过reducer(state = initialState , action )
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
<body>
<script src='./redux.min.js'></script>
<script>
//action 创建器
function createActionOne (){
return {
type : 'INCREAMENT'
}
}
function createActionTwo(){
return {
type : 'DECREASE'
}
}
//reducer处理函数
function reducer (state = {count : 0 },action){
// state = state || {count : 0 }
switch (action.type) {
case "INCREAMENT" :
return {conut : state.count+1};
case "DECREASE" :
return {count : state.count-1};
default :
return state ;
}
}
//store状态管理器的创建
var store = Redux.createStore(reducer);
console.log( store.getState() );
store.dispatch(createActionOne());
console.log( store.getState() );
</script>
</body>
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
<body>
<script src='./redux.min.js'></script>
<script>
//action 创建器
function createActionOne (){
return {
type : 'INCREAMENT'
}
}
function createActionTwo(){
return {
type : 'DECREASE'
}
}
//reducer处理函数
function reducer (state ,action){
// state = state || {count : 0 }
switch (action.type) {
case "INCREAMENT" :
return {conut : state.count+1};
case "DECREASE" :
return {count : state.count-1};
default :
return state ;
}
}
//store状态管理器的创建
var initialState = {count : 10 }
var store = Redux.createStore(reducer,initialState);
console.log( store.getState() );
store.dispatch(createActionOne());
console.log( store.getState() );
</script>
</body>

3 ES6语法新特性 bable在线编译工具

ES6

1
2
3
4
5
6
7
8
9
10
11
function reducer (state = {count : 0 },action){
// state = state || {count : 0 }
switch (action.type) {
case "INCREAMENT" :
return {conut : state.count+1};
case "DECREASE" :
return {count : state.count-1};
default :
return state ;
}
}

经过转化为ES5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
"use strict";
function reducer() {
var state = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : { count: 0 };
var action = arguments[1];
// state = state || {count : 0 }
switch (action.type) {
case "INCREAMENT":
return { conut: state.count + 1 };
case "DECREASE":
return { count: state.count - 1 };
default:
return state;
}
}

4 参考

源码解读

ES6 import

发表于 2017-05-05 | 分类于 ES6

1 了解ES6 模块加载和Common.js模块加载的区别

1.1 Common.js语法中的模块加载加载得是引用的模块暴露出来的module.exports 整体

1.2 ES6的模块加载可以’静态加载,也就是说加载部分接口,而不是加载整个对象’

1
2
3
4
5
6
7
8
// CommonJS模块
let { stat, exists, readFile } = require('fs');
// 等同于
let _fs = require('fs');
let stat = _fs.stat;
let exists = _fs.exists;
let readfile = _fs.readfile;

上面代码的实质是整体加载fs模块(即加载fs的所有方法),生成一个对象(_fs),然后再从这个对象上面读取3个方法。这种加载称为“运行时加载”,因为只有运行时才能得到这个对象,导致完全没办法在编译时做“静态优化”。

1
2
// ES6模块
import { stat, exists, readFile } from 'fs';

ES6 模块不是对象,而是通过export命令显式指定输出的代码,再通过import命令输入。

上面代码的实质是从fs模块加载3个方法,其他方法不加载。这种加载称为“编译时加载”或者静态加载,即 ES6 可以在编译时就完成模块加载,效率要比 CommonJS 模块的加载方式高。当然,这也导致了没法引用 ES6 模块本身,因为它不是对象。

注意: ES6 模块之中,顶层的this指向undefined,即不应该在顶层代码使用this。

2 ES6模块的命令组成

主要有export和import两个命令组成;export用于规定模块对外的接口;import用于输入其他模块提供的功能接口

2.1 export

profile.js文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//第一种写法
export var name = 'JiM';
export function f(){
console.log('this is a func');
}
//-----------------------------------------------
//第二种写法
var name1 = 'Jhon' ;
function f1 (){
console.log('this is a func1');
}
export { name1,f1 } ;//注意暴露出去的写法必须用{}包起来

main.js

1
import {name ,name1 ,f,f1} from './profile.js';

最后,export命令可以出现在模块的任何位置,只要处于模块顶层就可以。如果处于块级作用域内,就会报错,下一节的import命令也是如此。这是因为处于条件代码块之中,就没法做静态优化了,违背了ES6模块的设计初衷。

export default 用来指定模块默认输出的接口,此时import该模块的就不需要{ } 括起来了,如下:

但是一定要注意,如果不是export default接口,那么导入该接口的时候,必须通过import { }的方式(这个小坑踩了好几次)

1
2
3
4
5
6
7
8
9
10
11
12
13
// 第一组
export default function crc32() { // 输出
// ...
}
import crc32 from 'crc32'; // 输入
// 第二组
export function crc32() { // 输出
// ...
};
import {crc32} from 'crc32'; // 输入

export default 也可以默认导出任何数据类型

1
export default {userData : null , msg : "login"}

export default命令用于指定模块的默认输出。显然,一个模块只能有一个默认输出,因此export default命令只能使用一次。所以,import命令后面才不用加大括号,因为只可能对应一个方法。

正是因为export default命令其实只是输出一个叫做default的变量,所以它后面不能跟变量声明语句。

2.2 import

import命令接受一堆大括号,用于引出模块中的接口,from后面接的是模块的路径,可以是相对路径,也可以是绝对路径,如果是模块标识,需要进行配置,告诉浏览器引擎如何加载该模块中的接口;

  • import命令具有声明提升功能,会提升到整个模块的头部
  • import命令是静态执行的,不能使用变量和表达式
1
2
3
foo();
import { foo } from 'my_module';

上面的代码不会报错,因为import的执行早于foo的调用。这种行为的本质是,import命令是编译阶段执行的,在代码运行之前。

1
2
3
4
5
6
7
8
9
10
11
12
13
// 报错
import { 'f' + 'oo' } from 'my_module';
// 报错
let module = 'my_module';
import { foo } from module;
// 报错
if (x === 1) {
import { foo } from 'module1';
} else {
import { foo } from 'module2';
}

由于import是静态执行,所以不能使用表达式和变量,这些只有在运行时才能得到结果的语法结构。

Redux combineReducers和bindActionCreators源码

发表于 2017-05-05 | 分类于 redux

1 combineReducers源码如下(核心)

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
function combineReducers(reducers) { //传入combineReducers函数的是一个对象,包括不同的reducer的key -value键值对
var reducerKeys = Object.keys(reducers);//reducerKeys是一个数组,是我们传入的reducers的keys组成的数组
var finalReducers = {}//创建一个空对象,用来存放我们传入的reducers的value的值组成的数组
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
//以上循环用于过滤传入的reducers,确保最后每个reducer都是一个函数
var finalReducerKeys = Object.keys(finalReducers)//我们传入的reducers数组确认是一个key -value(function)的对象
// 返回合成后的 reducer 返回的这个函数就是combineReducers函数的返回值.
//这个函数负责处理根据不同的action来进行state状态改变
//每次dispatch不同的action的时候,这个函数会被执行,执行的时候,所有的reducer树上的reducer都会执行,如果action对上了,则会执行响应的操作
return function combination(state = {}, action) {
var hasChanged = false
var nextState = {}
//以下循环通过不同的reducer对应的不同的key,处理state状态树上的对应的key值所对应的的子state,然后返回一个新的nextState对应的key-value组成的对象;
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key] // 获取当前子 state
//首先,当createStore的时候,就会直接执行这个reducer,将默认的state给到state状态树,然后第一次调用该reducer的时候,传入该reducer的state就是默认的state,然后第一次调用reducer返回新的state更新对应state状态树对应的节点;当第二次调用的时候,传入该reducer的state参数就是上次更新的state状态值,依次类推.
var nextStateForKey = reducer(previousStateForKey, action) // 执行各子 reducer 中获取子 nextState,每一个子reducer都会返回一个新的state
nextState[key] = nextStateForKey // 将子 nextState 挂载到对应的键名
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}

通过源码我们可以看出,当将一个合并后的reducers函数传入createStore的时候

1
2
3
4
5
6
const reducers = combineReducers({
todos,
visibilityFilter
})
const store = createStore(reducers)

因为当我们执行createStore函数的时候,会默认执行dispatch函数,而dispatch函数会执行reducers函数,在执行该函数的的过程中,我们可以看到,reducers函数的返回值会赋值给currentState,`

2 state和action运行的流程

无论是 dispatch 哪个 action,都会流通所有的 reducer, 看来,这样子很浪费性能,但 JavaScript 对于这种纯函数的调用是很高效率的,因此请尽管放心这也是为何 reducer 必须返回其对应的 state 的原因。否则整合状态树时,该 reducer 对应的键值就是 undefine

1
2
3
4
5
counterReducer(counter, action) -------------------- counter
↗ ↘
rootReducer(state, action) —→∑ ↗ optTimeReducer(optTime, action) ------ optTime ↘ nextState
↘—→∑ todo ↗
↘ todoListReducer(todoList,action) ----- todoList ↗

3 bindActionCreators源码核心如下

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
/* 为 Action Creator 加装上自动 dispatch 技能 */
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
/*
转为ES5如下
"use strict";
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(undefined, arguments));
};
}
*/
export default function bindActionCreators(actionCreators, dispatch) {
// 省去一大坨类型判断
var keys = Object.keys(actionCreators) //将对象中所有的key值集合到一个数组当中,返回一个数组
var boundActionCreators = {}
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
var actionCreator = actionCreators[key] //actionCreator[key]对应的是一个组件函数
if (typeof actionCreator === 'function') {
// 逐个装上自动 dispatch 技能
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) //该函数返回结果也是一个函数,如果返回的函数再次执行的话,那么返回的将是dispatch函数,进行action分发了
}
}
return boundActionCreators
}
1
2
3
4
5
6
7
8
9
10
<input id="todoInput" type="text" />
<button id="btn">提交</button>
<script>
$('#btn').on('click', function() {
var content = $('#todoInput').val() // 获取输入框的值
var action = addTodo(content) // 执行 Action Creator 获得 action
store.dispatch(action) // 手动显式 dispatch 一个 action
})
</script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<input id="todoInput" type="text" />
<button id="btn">提交</button>
<script>
// 全局引入 Redux、jQuery,同时 store 是全局变量
var actionsCreators = Redux.bindActionCreators( //bindActionCreators函数会返回一个boundActionCreators对象,该对象中
{ addTodo: addTodo },
store.dispatch // 传入 dispatch 函数
)
$('#btn').on('click', function() {
var content = $('#todoInput').val()
actionCreators.addTodo(content) // 它会自动 dispatch
})
</script>

DHTMLX框架浅析

发表于 2017-05-03 | 分类于 dhtmlx

###1

seajsuse实例应用与理解

发表于 2017-05-02 | 分类于 seajs

五一小长假已经过完,心里很愧疚,因为没有学习,没有学习就没有进步,没有进步就没有薪水,没有薪水我就无法生活,没有生活我就无法生存,没有生存我还能怎样拯救世界呢?为了世界和平,继续学习;嗯,开始节后第一博;回顾原理,夯实基础.

seajs.use

首先看下在没有这些工具的时候我们如何加载一个js文件的

1
2
3
4
5
6
var script = document.createElement('script');
script.setAttribute('src', 'example.js');
script.onload = function() {
console.log("script loaded!");
};
document.body.appendChild(script);

1 seajs.use语法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
seajs.use(id, callback?)
// 加载一个模块
seajs.use('./a');
// 加载一个模块,在加载完成时,执行回调
seajs.use('./a', function(a) {
a.doSomething();
});
// 加载多个模块,在加载完成时,执行回调
seajs.use(['./a', './b'], function(a, b) {
a.doSomething();
b.doSomething();
});

2 seajs.use模块加载在所在文件的用途:通过seajs.use引入的文件在当前页面都是可用的,相当于引入的js文件在当前页面执行(src引入js文件的原理一样)

考虑如下文件目录

1
2
3
4
5
6
7
8
9
10
11
12
13
F:
workspace
-sea.js
-jquery-1.12.4.js
-seajsTest
-module.js
-js
module1.js
module2.js
-css
common.css
-page
index.html

index.html

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<body>
<p id='test'>this is a test for seajsuse</p>
<script>
seajs.use(['../../jquery-1.12.4'],function(){
console.log($)
// seajs.use('../module');
})
setTimeout(function(){
console.log('1');
console.log($);
},200)
console.log($);
</script>
</body>

输出

1
2
3
Uncaught ReferenceError: $ is not defined // 最后一行代码输出
function ( selector, context ) {} //回调函数输出
function ( selector, context ) {} //定时器输出

这个时候要对函数的同步执行,异步执行以及回调函数有着清晰的认识:简单来说就是 同步 异步 回调

seajs.use的回调函数在依赖模块加载完毕之后触发执行.因为模块的加载时同步加载的,加载完成之后才会执行回调函数common.css也可以通过seajs.use引入当前页面

1
2
3
p {
font-size : 50px ;
}

index.html加上如下代码

1
2
3
4
<script>
seajs.use('../css/common.css')
</script>

可以发现字体被加上了css样式,字体变大了。

注意引入css文件的时候,一定要加上css后缀名,因为seajs不会为我们加上后缀,默认是 .js

a.加载css的时候一定要加后缀的

b.路径中有”?“的时候javascript文件的后缀不能省略

c.路径中是以”#”号结尾的文件也不可以省去后缀

3 seajs.use如果嵌套seajs.use,还是从原理层面去考虑,seajs.use依赖的模块也是可以直接调用外部seajs.use引用的模块数据(可以理解嵌套的使用script -src加载js文件)

module.js

1
$('#test').css('backgroundColor','red')

然后再浏览器中打开index.html文件,可以发现字体变红了,module.js可以使用$这样的jquery封装的代码

一个文件就是一个模块,通过seajs.use加载一个模块本质上就是通过script标签src属性引入一个文件而已,但是需要注意命名空间的问题

3.1 当这个模块使用define定义的时候,那么就不会污染全局,define定义的变量必须通过exports对象提供接口才能被外部访问

3.2 当这个模块不是通过define函数定义的时候,那么就会容易产生全局的变量污染,因为通过script标签引入的js文件会在全局执行;

module1.js

1
var foo = 'bar';

module2.js

1
2
3
4
5
6
define(function(require,exports,){
var foo1 = 'bar1';
exports.foo1 = foo1 ;
//通过模块可以直接操作DOM元素
document.getElementById('test').style.color = 'green';//字体会变成绿色
})

module.js

1
2
3
$('#test').css('backgroundColor','red');
console.log('module.js',foo);
console.log('module.js',foo1);

index.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
<body>
<p id='test'>this is a test for seajsuse</p>
<script>
seajs.use('../css/common.css')
</script>
<script>
seajs.use(['../../jquery-1.12.4','../js/module1','../js/module2'],function(a,b,c){
// console.log($);
console.log(arguments);//null null Object
seajs.use('../module');
console.log('seajs',foo);
console.log('seajs',c.foo1);
})
setTimeout(function(){
console.log('setTimeout',foo);
console.log('setTimeout',c.foo1);
},2000)
//define定义的变量不会污染全局 直接引入的js文件会污染全局
</script>
</body>

输出如下

1
2
3
4
5
6
7
-(3) [null, null, Object, callee: function, Symbol(Symbol.iterator): function]
-seajs bar
-seajs bar1
-module.js bar
-Uncaught ReferenceError: foo1 is not defined
-setTimeout bar
-Uncaught ReferenceError: c is not defined

4 module 模块的生命周期

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var STATUS = Module.STATUS = {
// 1 - The `module.uri` is being fetched
FETCHING: 1,
// 2 - The meta data has been saved to cachedMods
SAVED: 2,
// 3 - The `module.dependencies` are being loaded
LOADING: 3,
// 4 - The module are ready to execute
LOADED: 4,
// 5 - The module is being executed
EXECUTING: 5,
// 6 - The `module.exports` is available
EXECUTED: 6
}

5 详细源码可以查看(通过npm install seajs下载的资源包里面也会有响应的代码,有兴趣的可以看下里面的源码,以下是我参考的文章)

seajs源码解析1

seajs源码解析2

seajs

这篇总结的比较好

seajs源码实现

发表于 2017-04-28 | 分类于 seajs

1 seajs核心实现包括以下几个函数

use(id,callback) 入口函数

define(id?dep?factory) 模块定义函数

require(id) 模块加载函数

getModuleExports(module) 取得模块接口函数

2 代码实现

2.1. use(ids, callback)
use为程序启动的入口,主要干两件事:加载指定的模块待模块加载完成后,调用回调函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function use(ids, callback) {
if (!Array.isArray(ids)) ids = [ids];
Promise.all(ids.map(function (id) {
return load(myLoader.config.root + id);
})).then(function (list) {
callback.apply(global, list);// 加载完成, 调用回调函数
}, function (error) {
throw error;
});
}

use会调用load函数,这个函数的作用是根据模块的id,加载模块,并返回一个Promise对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function load(id) {
return new Promise(function (resolve, reject) {
var module = myLoader.modules[id] || Module.create(id); // 取得模块或者新建模块 此时模块正在加载或者已经加载完成
module.on("complete", function () {
var exports = getModuleExports(module);
resolve(exports);// 加载完成-> 通知调用者
})
module.on("error", reject);
})
}

2.2 define函数的实现

1
2
3
4
var factory = function(require, exports, module){
// some code
}
define(factory);

一个模块文件被浏览器下载下来后,并不会直接运行我们的模块定义代码,而是会首先执行一个define函数,这个函数会取得模块定义的源代码(通过函数的toString()函数来取得源代码),然后利用正则匹配找到依赖的模块(匹配require("dep.js")这样的字符串),然后加载依赖的模块,最后发射一个自定义事件complete,通知当前模块, 模块已经加载完成,此时,当前模块的就会调用与complete事件绑定的回调函数,完成与这个模块相关的任务,比如resolve与这个模块加载绑定的Promise。
具体实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function define(factory) {
var id = getCurrentScript();
id = id.replace(location.origin, "");
var module = myLoader.modules[id];
module.factory = factory;
var dependences = getDependcencs(factory);
if (dependences) {
Promise.all(dependences.map(function (dep) {
return load(myLoader.config.root + dep);
})).then(function () {
module.fire("complete"); // 依赖加载完成,通知模块。
}, function () {
module.fire("error");
});
} else {
module.fire("complete");//没有依赖,通知模块加载完成
}
}

2.3 require函数的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function require(id) {
var module = myLoader.modules[myLoader.config.root + id];
if (!module) throw "can not load find module by id:" + id;
else {
return getModuleExports(module); // 返回模块的对外接口。
}
}
function getModuleExports(module) {
if (!module.exports) {
module.exports = {};
module.factory(require, module.exports, module);
}
return module.exports;
}

参考

1234…20
JiM-W

JiM-W

keep fighting!

195 日志
52 分类
90 标签
© 2017 JiM-W
由 Hexo 强力驱动
主题 - NexT.Pisces