一、高级函数
1. 安全的类型检测
JavaScript 内置的类型检测机制并非完全可靠。
问题:
typeof 操作符,有一些无法预知的行为,经常会导致检测数据类型时得到不靠谱的结果。Safari(直至第 4 版)在对正则表达式应用 typeof 操作符时会返回 “function”。
instanceof 操作符在存在多个全局作用域(像一个页面包含多个 frame)的情况下,也是问题多多。
在检测某个对象到底是原生对象还是开发人员自定义的对象的时候,也会有问题。
解决方法:在任何值上调用 Object 原生的 toString()方法,都会返回一个[object NativeConstructorName]格式的字符串。
Object 的 toString()方法不能检测非原生构造函数的构造函数名。开发人员定义的任何构造函数都将返回[object Object]。
Object.prototype.toString.call(value) // "[object Array]"
检测类型:
function isArray(value){
return Object.prototype.toString.call(value) == "[object Array]";
}
function isFunction(value){
return Object.prototype.toString.call(value) == "[object Function]";
}
function isRegExp(value){
return Object.prototype.toString.call(value) == "[object RegExp]";
}
// 检测原生 JSON 对象
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) == "[object JSON]";
注意:对于在 IE 中以 COM 对象形式实现的任何函数,isFunction() 都将返回 false,因为它们并非原生的 JavaScript 函数。
2. 作用域安全的构造函数
构造函数其实就是一个使用 new 操作符调用的函数。当使用 new 调用时,构造函数内用到的 this 对象会指向新创建的对象实例。
问题:当没有使用 new 操作符来调用构造函数的情况。由于 this 对象是在运行时绑定的,所以直接调用 Person(),this 会映射到全局对象 window 上,导致错误对象属性的意外增加。
解决方法:创建一个作用域安全的构造函数。
function Person(name, age, job){
if (this instanceof Person){ // 检查this对象是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"
关于作用域安全的构造函数的贴心提示:实现这个模式后,你就锁定了可以调用构造函数的环境。如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏。
3. 惰性载入函数
惰性载入表示函数执行的分支仅会发生一次。
实现惰性载入的两种方式:
1)在函数被调用时再处理函数。
在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支。
2)在声明函数时就指定适当的函数。
第一次调用函数时就不会损失性能,而在代码首次加载时会损失一点性能。
惰性载入函数的优点:只在执行分支代码时牺牲一点儿性能。两种方式都能避免执行不必要的代码,哪个更合适根据具体需求而定。
4. 函数绑定
函数绑定要创建一个函数,可以在特定的 this 环境中以指定参数调用另一个函数。
该技巧常常和回调函数与事件处理程序一起使用,以便在将函数作为变量传递的同时保留代码执行环境。
一个简单的 bind() 函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。
语法:
function bind(fn, context){
return function(){
return fn.apply(context, arguments);
}
ECMAScript 5 为所有函数定义了一个原生的 bind() 方法,进一步简单了操作,不用再自己定义 bind() 函数。
使用方法:fn.bind(context) // 传入作为this值的对象
支持原生 bind() 方法的浏览器:IE9+、Firefox 4+ 和 Chrome。
适用场景:要将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行。
主要用于:事件处理程序以及 setTimeout() 和 setInterval()。
注意:被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。
5. 函数柯里化
与函数绑定紧密相关的主题是函数柯里化(function currying),它用于创建已经设置好了一个或多个参数的函数。
函数柯里化的基本方法和函数绑定是一样的:使用一个闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些传入的参数。
柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。
创建柯里化函数的通用方式:
// 这个curry()函数的主要工作就是将被返回函数的参数进行排序
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()的第一个参数是要进行柯里化的函数,其他参数是要传入的值。
// 这个函数并没有考虑到执行环境,所以调用apply()时第一个参数是null。
调用:
function add(num1, num2){
return num1 + num2;
}
// 创建了第一个参数绑定为5的add()的柯里化版本
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8
// 柯里化的add()函数两个参数都提供了
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);
};
}
ECMAScript 5 的 bind() 方法也实现函数柯里化,只要在 this 的值之后再传入另一个参数即可。
fn.bind(context, arg));
JavaScript 中的柯里化函数和绑定函数提供了强大的动态函数创建功能。
使用 bind() 还是 curry() 要根据是否需要 object 对象响应来决定。
它们都能用于创建复杂的算法和功能,当然两者都不应滥用,因为每个函数都会带来额外的开销。
上一篇:45-JavaScript高级程序设计-跨域
下一篇:47-JavaScript高级程序设计-高级技巧2防篡改对象&高级定时器