My Little World

一些奇思妙想(二)

Jquery 链式调用原理

例子

1
$('div').eq(0).show().end().eq(1).hide();

jquery 通过更替this指向实现链式调用
实现

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
jQuery.fn = jQuery.prototype = {
pushStack: function( elems ) {
// Build a new jQuery matched element set
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
ret.prevObject = this;
// Return the newly-formed element set
return ret;
},
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
},
show: function() {
return showHide( this, true );
},
hide: function() {
return showHide( this );
},
end: function() {
return this.prevObject || this.constructor();
},

css: function( name, value ) {
return access( this, function( elem, name, value ) { 。。。。}, name, value, arguments.length > 1 );
}
}
function showHide( elements, show ) {
。。。。
return elements;
}
var access = function( elems, fn, key, value, chainable, emptyGet, raw ) {
var i = 0,
len = elems.length,
bulk = key == null;
if ( toType( key ) === "object" ) {
chainable = true;
for ( i in key ) {。。。。}
} else if ( value !== undefined ) {
chainable = true;
if ( !isFunction( value ) ) { 。。。。}
if ( bulk ) { 。。。。}
if ( fn ) { 。。。。。}
}
//当是链式调用时,返回对象
if ( chainable ) {
return elems;
}
// Gets
if ( bulk ) {
return fn.call( elems );
}

return len ? fn( elems[ 0 ], key ) : emptyGet;
};

当showHide,access传入this的时候,返回的即this,接着可以实现链式

promise then 原理

利用promise 对象去实现一系列功能的过程,可以理解为一个链式结构组成的过程
then 和 catch 函数的调用其实是对后续功能的收集
返回新的promise对象,区别存放每个阶段的执行结果
resolve 和 rejec 函数的调用为后续then/catch收集的函数进行定调,决定到底执行什么
resolve和reject函数执行过程中根据返回值,实现链式结构的拼接和后续函数的执行顺序处理
相关链接
代码实现

async await 原理

借助generator,通过封装promise,利用递归可以实现相同的效果

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
function asyncToGenerator(generatorFunc) {
return function() {
const gen = generatorFunc.apply(this, arguments)
return new Promise((resolve, reject) => {
function step(key, arg) {
let generatorResult
try {
generatorResult = gen[key](arg)
} catch (error) {
return reject(error)
}
const { value, done } = generatorResult
if (done) {
return resolve(value)
} else {
return Promise.resolve(value).then(
val => step('next', val), //递归
err => step('throw', err)
)
}
}
step("next")
})
}
}

KOA app.use next 原理

主要思想是递归实现
详见

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let index = -1
return dispatch(0) //从第一个中间件开始执行
function dispatch (i) {
//防止next 在一个中间件中被调用多次
if (i <= index) return Promise.reject(new Error('next() called multiple times'))
index = i
let fn = middleware[i] //拿到中间件
if (i === middleware.length) fn = next //如果中间件函数全部调用完,fn 赋值为next,next为undefined
if (!fn) return Promise.resolve() //fn为next=>undefined时成立,即执行到最后返回一个Promise.resolve() 可以继续写then
try {//try-catch 用于保证错误在promise的情况能够正常捕获
//执行中间件函数,传入ctx,next参数,next即dispatch.bind(null, i + 1),
//递归调用下一个中间件函数
//从这一步可以实现await next()时,先执行下一个中间件函数
//中间件有调用next,就利用await暂停当前中间件执行,开始执行下一个中间件
return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
} catch (err) {
return Promise.reject(err)
}
}

VUEROUTER next 原理

利用迭代器思想递归调用钩子函数
在vueRouter 的声明周期使用时,next函数会当做默认参数传递进去,如下

1
2
3
router.beforeEach((to, from, next) => {
next()
})

源码

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
function confirmTransition (route, onComplete, onAbort) {
var queue = [].concat(
// in-component leave guards
extractLeaveGuards(deactivated),
// global before hooks
this.router.beforeHooks, //beforeEach钩子函数传入的函数集合
extractUpdateHooks(updated),
activated.map(function (m) { return m.beforeEnter; }),
resolveAsyncComponents(activated)
);
this.pending = route;
var iterator = function (hook, next) {
if (this$1.pending !== route) {
return abort()
}
try {
hook(route, current, function (to) { 。。。。。。});
} catch (e) {
abort(e);
}
};
runQueue(queue, iterator, function () {
var postEnterCbs = [];
var isValid = function () { return this$1.current === route; };
var enterGuards = extractEnterGuards(activated, postEnterCbs, isValid);
var queue = enterGuards.concat(this$1.router.resolveHooks);
runQueue(queue, iterator, function () {。。。。});
});
}

可见 beforeEach 钩子函数被装在queue数组中,传递给了runQueue函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function runQueue (queue, fn, cb) {
var step = function (index) {
if (index >= queue.length) {
cb();
} else {
if (queue[index]) { //拿到生命周期函数传递给fn函数
fn(queue[index], function () {
step(index + 1);
});
} else {
step(index + 1);
}
}
};
step(0);
}

参数fn函数是 iterator

1
2
3
4
5
6
7
8
9
10
iterator = function (hook, next) {
if (this$1.pending !== route) {
return abort()
}
try {
hook(route, current, function (to) { 。。。。。。});
} catch (e) {
abort(e);
}
};

参数hook即钩子函数,来看传递给它的三个参数

1
hook(route, current, function (to) { 。。。。。。});

第三个参数即next

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function (to) {
if (to === false || isError(to)) {
// next(false) -> abort navigation, ensure current URL
this$1.ensureURL(true);
abort(to);
} else if (
typeof to === 'string' ||
(typeof to === 'object' &&
(typeof to.path === 'string' || typeof to.name === 'string'))
) {
// next('/') or next({ path: '/' }) -> redirect
abort();
if (typeof to === 'object' && to.replace) {
this$1.replace(to);
} else {
this$1.push(to);
}
} else {
// confirm transition and pass on the value
next(to);
}
}

这里面的next 指向参数

1
2
3
function () {
step(index + 1);
}

即当我们在钩子函数调用next不传递任何参数时,
只是在调用迭代器函数,递归进行下一个函数的执行