函数是JavaScript中最有趣的部分之一。它们本质上十分简单和过程化,但也可以是非常复杂的,一些额外的功能可以使用闭包来实现,此外,由于所有函数都是对象,所以使用函数指针非常简单。接下来,描述集中在JavaScript中使用函数的高级方法。
一、安全的类型检测
问题:
1、typeof操作符,有一些无法预知的行为,经常会曹植检测数据类型时得到不靠谱的结果。比如在Safary对正则表达式应用typeof操作符时会返回function。
2、instanceOf 操作符在存在多个全局作用域的情况下,也有问题。
解决办法:
用Object.prototype.toString
在任何值上调用Object原生的toString()方法,都会返回一个[Object NativeConstructorName]格式的字符串。由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。
var arr = new Array ();
console.log(Object.prototype.toString.call(arr));
安全的类型检测:
//检测是否为数组
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]";
}
二、作用域安全的构造函数
问题:
我们都知道,当使用new调用构造函数时,构造函数内用到的this对象会指向新创建的对象实例,但是当没有使用new操作符来调用构造函数时,就会出现问题。因为构造函数的this对象是在运行时绑定的,如果直接调用构造函数,this会映射到全局对象window上。
例如:
function Person (name,age,job) {
this.name = name;
this.age = age;
this.job = job;
}
var person = Person("Jay",37,"star");
//console.log("Name:" + person.name);//报错,person没有name属性
console.log("Name:" + window.name);//Name:Jay
解决:
创建一个作用域安全的构造函数。
作用域安全的构造函数在进行任何更改之前,首先确认this对象是正确类型的实例。如果不是,那么会创建新的实例并返回。
如:
/*作用域安全的构造函数*/
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);
}
}
以上代码,构造函数中添加了一个检查并确保this对象是Person实例的if语句,这样不管是否使用new操作符,都会返回一个Person的新实例。
var person1 = new Person ("Jay",37,"star");
console.log(person1.name);//Jay
console.log(window.name);//""
var person2 = Person("Hanna",23,"star");
console.log(person2.name);//Hanna
构造函数窃取
以上的作用域安全的构造函数在构造函数窃取模式下,也会有问题。
function Polygon (sides) {
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function () {
return 0;
};
} else {
return new Polygon (sides);
}
}
function Rectangle (width,height) {
Polygon.call(this,2);
this.width = width;
this.height = height;
this.getArea = function () {
return this.width * this.height;
};
}
var rect = new Rectangle(10,15);
alert(rect.sides);//undefined!!!!
上述代码,Polygon构造函数是作用域安全的,但是Rectangle不是。Rectangle实例通过Polygon.call( )继承Polygon的sides属性,但是,由于Polygon是作用域安全的,this对象并非是Polygon的实例,所以会创建并返回一个新的Polygon对象。
解决:构造函数窃取+原型链
function Polygon (sides) {
if (this instanceof Polygon) {
this.sides = sides;
this.getArea = function () {
return 0;
};
} else {
return new Polygon (sides);
}
}
function Rectangle (width,height) {
Polygon.call(this,2);
this.width = width;
this.height = height;
this.getArea = function () {
return this.width * this.height;
};
}
Rectangle.prototype = new Polygon();//原型链
var rect = new Rectangle(10,15);
alert(rect.sides);
上述代码中,由于一个Rectangle实例同时也是一个Polygon实例,所以Polygon.call( )会照愿意执行。
三、惰性载入函数
惰性载入函数,表示函数执行的分支仅会发生一次,避免执行不必要的代码
其有两种方式实现:
1、在函数被调用时再处理函数。
第一次调用的过程中,该函数会覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。
使用惰性载入重写createXHR()
//惰性载入函数重写createXHR()
function createXHR(){
if (typeof XMLHttpRequest != "undefined") {
createXHR = function () {
return new XMLHttpRequest();//IE5以上版本直接创建XHR对象
};
} 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"];
var i;
var len =versions.length;
for (i = 0; i < len; i++) {
try{
new ActiveXObject(versions[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.");
};
}
}
if 语句的每个分支都会为createXHR变量赋值,有效覆盖了原有函数。
2、在声明函数时就指定适当的函数
这样,第一次调用函数时就不会损失性能了,而在代码首次加载时会损失一点性能。
//创建XHR对象(兼容IE5以前)
function createXHR(){
if (typeof XMLHttpRequest != "undefined") {
return function () {
return new XMLHttpRequest();//IE5以上版本直接创建XHR对象
};
} else if (typeof ActiveXObject != "undefined") {
return function () {
if (typeof arguments.callee.activeXString != "string") {
var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"];
var i;
var len =versions.length;
for (i = 0; i < len; i++) {
try{
new ActiveXObject(versions[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.")
}
}
}
以上代码的技巧就是,创建一个匿名、自执行的函数,用以确定应该使用哪一个函数实现。