JavaScript进阶技巧

归纳总结了JavaScript中的一些常用的进阶编程技巧,包括惰性载入函数函数柯里化函数节流函数防抖以及事件委托

惰性载入函数

惰性载入表示函数执行的分支仅会发生一次。有以下两种实现惰性载入的方式

在函数被调用时再处理函数

在第一次调用过程中,该函数会被覆盖为另一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。

以创建XHR对象的createXHR()函数为例

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 createXHR () {
if (typeof XMLHttpRequest != 'undefined') {
createXHR = function () {
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != 'undefined') {
createXHR = function () {
if (typeof arguments.callee.activeXString != 'string') {
var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'],
i,len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//...
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function() {
throw new Error('No XHR object available.');
};
}
return createXHR;
}

在这个惰性载入的 createXHR() 中,if语句的每一个分支都会为 createXHR 变量赋值,有效覆盖了原有的函数,最后一步就是调用新赋的函数,下次调用 createXHR() 时候,就会直接调用被分配的函数。

在声明函数时就指定适当的函数

第一次调用函数时不会损失性能,而在代码首次加载时会损失一点性能

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
var createXHR = (function createXHR () {
if (typeof XMLHttpRequest != 'undefined') {
return function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != 'undefined') {v
return function () {
if(typeof arguments.callee.activeXString != 'string') {
var versions = ['MSXML2.XMLHttp.6.0', 'MSXML2.XMLHttp.3.0', 'MSXML2.XMLHttp'],
i,len;
for (i = 0, len = versions.length; i < len; i++) {
try {
new ActiveXObject(version[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//...
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
return function () {
throw new Error('No XHR object available.');
};
}
})();

这种方法的技巧是创建一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。
惰性载入函数的优点是只在执行分支代码时牺牲一点性能。

函数柯里化

与函数绑定紧密相关的主题是函数柯里化,它用于创建已经设置好了一个或多个参数的函数(在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。)

通俗点讲,函数柯里化就是把函数完全变成「接受一个参数;返回一个值」的固定形式。

函数柯里化的基本方法是:使用一个闭包返回一个函数。
当函数被调用时,返回的函数还需要设置一些传入的参数。
柯里化函数通常由以下步骤动态创建:调用一个函数并为它传入要柯里化的函数和必要的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function curry (fn) {
var args = Array.prototype.slice.call(arguments, 1); // 这个arguments是外部的,即curry的参数
return function () {
var innerArgs = Array.prototype.slice.call(arguments); // 这个arguments是内部的,即curryAdd的参数
var finalArgs = args.concat(innerArgs); // 连接两个或多个数组
return fn.apply(null, finalArgs); // 没有考虑执行环境,所以调用apply时第一个参数是null
};
}
function add(num1,num2){
return num1+num2;
}
var curriedAdd = curry(add, 5); // 返回已设置第一个参数为5的函数
alert(curriedAdd(3)); // 8

结合函数柯里化的更复杂的bind()函数:

1
2
3
4
5
6
7
8
function curryBind (fn, context) {
var args = Array.prototype.slice.call(arguments, 2); // 给被绑定的函数的参数是从第三个开始
return function () {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(context, finalArgs);
};
}

可传入任意参数的,不传参数时输出结果的柯里化函数:

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
var currying = function (fn) {
var _args = [];
return function () {
// 参数为空时,计算结果
if (arguments.length === 0) {
return fn.apply(this, _args);
}
// 将参数保存
Array.prototype.push.apply(_args, [].slice.call(arguments));
return arguments.callee;
}
};
var multi = function () {
var total = 0;
for (var i = 0, len = arguments.length; i < len; i++) {
total += arguments[i];
}
return total;
};
var sum = currying(multi);
sum(100,200)(300);
sum(400);
console.log(sum()); // 1000

函数柯里化依赖于闭包的特性,来保存中间过程中输入的参数,最后统一处理参数

函数节流

在指定时间间隔内只执行一次任务,避免在短时间内多次操作或调用函数,常见操作:滚动滚动条,触发scroll事件

函数节流的实践 《JavaScript高级程序设计》中的方法:

1
2
3
4
5
6
function throttle (method , context) {
clearTimeout(method.tId);
method.tId = setTimeout(function () {
method.call(context);
}, 500);
}

知友@大板栗的方法:

1
2
3
4
5
6
7
8
9
10
11
function throttle(fn, interval = 300) {
let canRun = true;
return function () {
if (!canRun) return;
canRun = false;
setTimeout(() => {
fn.apply(this, arguments);
canRun = true;
}, interval);
};
}

函数防抖的实践

任务频繁触发的情况下,只有任务触发的间隔超过指定间隔的时候,任务才会执行。

1
2
3
4
5
6
7
8
9
function debounce (fn, interval = 300) {
let timeout = null;
return function () {
clearTimeout(timeout);
timeout = setTimeout(() => {
fn.apply(this, arguments);
}, interval);
};
}

事件委托

使用事件委托技术能让你避免对特定的每个节点添加事件监听器;相反,事件监听器是被添加到它们的父元素上。事件监听器会分析从子元素冒泡上来的事件,找到是哪个子元素的事件。

通俗的讲,事件就是onclick,onmouseover,onmouseout等,委托呢,就是让别人来做,这个事件本来是加在某些元素上的,然而你却加到别人身上来做,完成这个事件。
利用冒泡的原理,把事件加到父级上,触发执行效果。
主要优点就是:

将批量元素监听的任务通过一个监听实现,减少DOM的事件绑定;
监听动态生成子元素的事件触发。

这里要用到事件源:event 对象,事件源,不管在哪个事件中,只要你操作的那个元素就是事件源。
ie:window.event.srcElement
标准下:event.target

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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
</head>
<body>
<ul id="testUl">
<li>1</li>
<li>2</li>
<li>3</li>
<li>4</li>
<li>5</li>
</ul>
<input type="button" value="添加" id="addBtn">
<script>
var testUl = document.getElementById('testUl');
var addBtn = document.getElementById('addBtn');
addBtn.onClick = function () {
var li = document.createElement('li');
li.innerHtml = '6'
testUl.appendChild(li)
}
testUl.onClick = function (event) {
var event = event || window.event;
var target = event.target || event.srcElement;
if (target.nodeName.toLowerCase === 'li') {
console.log(target.innerHtml)
}
}
</script>
</body>
</html>

参考链接:
浅析 JavaScript 中的 函数 currying 柯里化
函数节流与函数防抖

坚持原创技术分享,您的支持将鼓励我继续创作!