钩子是编程惯用的一种手法,用来解决一种或多种特殊情况的处理。
简单来说,钩子就是适配器原理,或者说是表驱动原理,我们预先定义了一些钩子,在正常的代码逻辑中使用钩子去适配一些特殊的属性,样式或事件,这样可以让我们少写很多 else if 语句。
如果还是很难懂,看一个简单的例子,举例说明 hook 到底如何使用:
现在考公务员,要么靠实力,要么靠关系,但领导肯定也不会弄的那么明显,一般都是暗箱操作,这个场景用钩子实现再合理不过了。
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
|
// 如果不用钩子的情况 // 考生分数以及父亲名 function examinee(name, score, fatherName) { return { name: name, score: score, fatherName: fatherName }; } // 审阅考生们 function judge(examinees) { var result = {}; for ( var i in examinees) { var curExaminee = examinees[i]; var ret = curExaminee.score; // 判断是否有后门关系 if (curExaminee.fatherName === 'xijingping' ) { ret += 1000; } else if (curExaminee.fatherName === 'ligang' ) { ret += 100; } else if (curExaminee.fatherName === 'pengdehuai' ) { ret += 50; } result[curExaminee.name] = ret; } return result; } var lihao = examinee( "lihao" , 10, 'ligang' ); var xida = examinee( 'xida' , 8, 'xijinping' ); var peng = examinee( 'peng' , 60, 'pengdehuai' ); var liaoxiaofeng = examinee( 'liaoxiaofeng' , 100, 'liaodaniu' ); var result = judge([lihao, xida, peng, liaoxiaofeng]); // 根据分数选取前三名 for ( var name in result) { console.log( "name:" + name); console.log( "score:" + score); } |
可以看到,在中间审阅考生这个函数中,运用了很多 else if 来判断是否考生有后门关系,如果现在业务场景发生变化,又多了几名考生,那么 else if 势必越来越复杂,往后维护代码也将越来越麻烦,成本很大,那么这个时候如果使用钩子机制,该如何做呢?
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
|
// relationHook 是个钩子函数,用于得到关系得分 var relationHook = { "xijinping" : 1000, "ligang" : 100, "pengdehuai" : 50, // 新的考生只需要在钩子里添加关系分 } // 考生分数以及父亲名 function examinee(name, score, fatherName) { return { name: name, score: score, fatherName: fatherName }; } // 审阅考生们 function judge(examinees) { var result = {}; for ( var i in examinees) { var curExaminee = examinees[i]; var ret = curExaminee.score; if (relationHook[curExaminee.fatherName] ) { ret += relationHook[curExaminee.fatherName] ; } result[curExaminee.name] = ret; } return result; } var lihao = examinee( "lihao" , 10, 'ligang' ); var xida = examinee( 'xida' , 8, 'xijinping' ); var peng = examinee( 'peng' , 60, 'pengdehuai' ); var liaoxiaofeng = examinee( 'liaoxiaofeng' , 100, 'liaodaniu' ); var result = judge([lihao, xida, peng, liaoxiaofeng]); // 根据分数选取前三名 for ( var name in result) { console.log( "name:" + name); console.log( "score:" + score); } |
可以看到,使用钩子去处理特殊情况,可以让代码的逻辑更加清晰,省去大量的条件判断,上面的钩子机制的实现方式,采用的就是表驱动方式,就是我们事先预定好一张表(俗称打表),用这张表去适配特殊情况。当然 jQuery 的 hook 是一种更为抽象的概念,在不同场景可以用不同方式实现。
看看 jQuery 里的表驱动 hook 实现,$.type 方法:
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 (window, undefined) { var // 用于预存储一张类型表用于 hook class2type = {}; // 原生的 typeof 方法并不能区分出一个变量它是 Array 、RegExp 等 object 类型,jQuery 为了扩展 typeof 的表达力,因此有了 $.type 方法 // 针对一些特殊的对象(例如 null,Array,RegExp)也进行精准的类型判断 // 运用了钩子机制,判断类型前,将常见类型打表,先存于一个 Hash 表 class2type 里边 jQuery.each( "Boolean Number String Function Array Date RegExp Object Error" .split( " " ), function (i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); jQuery.extend({ // 确定JavaScript 对象的类型 // 这个方法的关键之处在于 class2type[core_toString.call(obj)] // 可以使得 typeof obj 为 "object" 类型的得到更进一步的精确判断 type: function (obj) { if (obj == null ) { return String(obj); } // 利用事先存好的 hash 表 class2type 作精准判断 // 这里因为 hook 的存在,省去了大量的 else if 判断 return typeof obj === "object" || typeof obj === "function" ? class2type[core_toString.call(obj)] || "object" : typeof obj; } }) })(window); |
这里的 hook 只是 jQuery 大量使用钩子的冰山一角,在对 DOM 元素的操作一块,attr 、val 、prop 、css 方法大量运用了钩子,用于兼容 IE 系列下的一些怪异行为。在遇到钩子函数的时候,要结合具体情境具体分析,这些钩子相对于表驱动而言更加复杂,它们的结构大体如下,只要记住钩子的核心原则,保持代码整体逻辑的流畅性,在特殊的情境下去处理一些特殊的情况:
1
2
3
4
5
6
7
8
9
|
var someHook = { get: function (elem) { // obtain and return a value return "something" ; }, set: function (elem, value) { // do something with value } } |
从某种程度上讲,钩子是一系列被设计为以你自己的代码来处理自定义值的回调函数。有了钩子,你可以将差不多任何东西保持在可控范围内。