作用域安全的构造函数
function Person(name, age, job) {
if (this instanceof Person) {
this.name = name;
this.age = age;
this.job = job;
} else {
return new Person(name, age, job);
}
}
var person1 = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //""
alert(person1.name); //"Nicholas"
var person2 = new Person("Shelby", 34, "Ergonomist");
alert(person2.name); //"Shelby"
主要是防止以下情况发生:
没有通过new来创建Person对象
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"
惰性载入函数
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(versions[i]);
arguments.callee.activeXString = versions[i];
break;
} catch (ex) {
//skip
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
};
} else {
createXHR = function() {
throw new Error("No XHR object available.");
};
}
return createXHR();
}
当函数中有过多的if else的时候,使用此方法可以在下次函数调用的时候不要去判断了,提高了效率。
函数绑定
在点击的时候需要弹出message
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick);
实际上弹出的是undefined,因为this指向的作用域是btn的运行环境 而不是handler
解决办法:通过闭包实现,让handler对象去调用 handleClick()
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", function(event) {
handler.handleClick(event);
});
创建过多的闭包导致代码无法被调试,于是:
function bind(fn, context){
return function(){
return fn.apply(context, arguments);
};
}
在 bind() 中创建了一个闭包,闭包使用 apply() 调用传入的函数,并给 apply() 传递 context 对象和参数。注意这里使用的 arguments 对象是内部函数的,而非 bind() 的。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
ES5为每个函数提供了原生的bind方法
var handler = {
message: "Event handled",
handleClick: function(event) {
alert(this.message + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
函数柯里化
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数
简单版:
function curry(fn) {
var args = Array.prototype.slice.call(arguments, 1);
return function() {
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return fn.apply(null, finalArgs);
};
}
curry() 函数的主要工作就是将被返回函数的参数进行排序。 curry() 的第一个参数是要进行柯里化的函数,其他参数是要传入的值。为了获取第一个参数之后的所有参数,在 arguments 对象上调用了 slice() 方法,并传入参数 1 表示被返回的数组包含从第二个参数开始的所有参数。然后 args 数组包含了来自外部函数的参数。在内部函数中,创建了 innerArgs 数组用来存放所有传入的参数(又一次用到了 slice() )。有了存放来自外部函数和内部函数的参数数组后,就可以使用 concat() 方法将它们组合为 finalArgs ,然后使用 apply() 将结果传递给该函数。注意这个函数并没有考虑到执行环境,所以调用 apply() 时第一个参数是 null 。
function add(num1, num2) {
return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd()); //17
//需要被柯理化的函数只接收两个参数,所以传多了无效
函数柯里化还常常作为函数绑定的一部分包含在其中,构造出更为复杂的 bind() 函数。例如:
function bind(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);
};
}
当你想除了 event 对象再额外给事件处理程序传递参数时,这非常有用,例如:
var handler = {
message: "Event handled",
handleClick: function(name, event) {
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler, "my-btn"));
在这个更新过的例子中, handler.handleClick() 方法接受了两个参数:要处理的元素的名字和event 对象。作为第三个参数传递给 bind() 函数的名字,又被传递给了 handler.handleClick() ,handler.handleClick() 也会同时接收到 event 对象。这里有一个疑惑,event一般作为事件的第一个参数,这里第二个参数作为event,会接收到吗?
ECMAScript 5的 bind() 方法也实现函数 里化,只要在 this 的值之后再传入另一个参数即可。
var handler = {
message: "Event handled",
handleClick: function(name, event) {
alert(this.message + ":" + name + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler, "my-btn"));
高级定时器
JavaScript是单线程语言,事件处理程序,Ajax的回调函数都是在主线程中执行的,所以会有一个队列来管理这些程序的执行顺序,定时器中的执行代码是在间隔指定时间后添加到队列中的。
var btn = document.getElementById("my-btn");
btn.onclick = function(){
setTimeout(function(){
document.getElementById("message").style.visibility = "visible";
}, 250);
//其他代码
};
如果前面例子中的 onclick 事件处理程序执行了 300ms,那么定时器的代码至少要在定时器设置之后的 300ms后才会被执行。队列中所有的代码都要等到 JavaScript 进程空闲之后才能执行,而不管它们是如何添加到队列中的
重复的定时器
使用 setInterval() 创建的定时器确保了定时器代码规则地插入队列中。这个方式的问题在于,定时器代码可能在代码再次被添加到队列之前还没有完成执行,结果导致定时器代码连续运行好几次,而之间没有任何停顿。幸好,JavaScript 引擎够聪明,能避免这个问题。当使用 setInterval() 时,仅当没有该定时器的任何其他代码实例时,才将定时器代码添加到队列中
这种重复定时器的规则有两个问题:(1) 某些间隔会被跳过;(2) 多个定时器的代码执行之间的间隔可能会比预期的小。假设,某个 onclick 事件处理程序使用 setInterval() 设置了一个 200ms 间隔的重复定时器。 如果事件处理程序花了 300ms多一点的时间完成, 同时定时器代码也花了差不多的时间,就会同时出现跳过间隔且连续运行定时器代码的情况
这个例子中的第 1 个定时器是在 205ms 处添加到队列中的, 但是直到过了 300ms 处才能够执行。 当执行这个定时器代码时,在 405ms处又给队列添加了另外一个副本。在下一个间隔,即 605ms 处,第一个定时器代码仍在运行,同时在队列中已经有了一个定时器代码的实例。结果是,在这个时间点上的定时器代码不会被添加到队列中。结果在 5ms处添加的定时器代码结束之后,405ms 处添加的定时器代码就立刻执行。
为了避免这个问题,开始使用setTimeout链式调用:
setTimeout(function(){
//处理中
setTimeout(arguments.callee, interval);
}, interval);
demo:将一个div元素右移到200停止
setTimeout(function() {
var div = document.getElementById("myDiv");
left = parseInt(div.style.left) + 5;
div.style.left = left + "px";
if (left < 200) {
setTimeout(arguments.callee, 50);
}
}, 50);