1、安全的类型检测
-
JavaScript 内置的类型检测机制并非完全可靠。事实上,发生错误否定及错误肯定的情况也不在少数。例如:
- Safari(直至第4 版)在对正则表达式应用typeof 操作符时会返回"function"。
- instanceof 操作符在存在多个全局作用域的情况下,存在问题。
- 浏览器开始原生支持JSON对象后,是开发人员很难确定页面中的JSON对象到底是不是原生的。
-
解决方法:
- 在任何值上调用Object 原生的toString()方法,都会返回一个
[object NativeConstructorName]
格式的字符串。每个类在内部都有一个[[Class]]
属性,这个属性中就指定了上述字符串中的构造函数名。
alert(Object.prototype.toString.call(value)); //"[object Array]"
- 由于原生数组的构造函数名与全局作用域无关,因此使用toString()就能保证返回一致的值。利用这一点,可以创建如下函数。
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对象。Object的toString()方法不能检测非原生构造函数的构造函数名。因此,开发人员定义的任何构造函数都将返回[object Object]。
var isNativeJSON = window.JSON && Object.prototype.toString.call(JSON) =="[object JSON]";
- 在任何值上调用Object 原生的toString()方法,都会返回一个
2、作用域安全函数
- 创建一个构造函数Person,使用this 对象给三个属性赋值:name、age 和job。
function Person(name, age, job){
this.name = name;
this.age = age;
this.job = job;
}
- 当和new操作符连用时,则会创建一个新的Person 对象,同时会给它分配这些属性。
var person = new Person("Nicholas", 29, "Software Engineer");
alert(person.name); //"Nicholas"
- 在当没有使用new操作符来调用该构造函数的情况上。由于该this对象是在运行时绑定的,所以直接调用Person(),this 会映射到全局对象window 上,导致错误对象属性的意外增加。
var person = Person("Nicholas", 29, "Software Engineer");
alert(window.name); //"Nicholas"
alert(window.age); //29
alert(window.job); //"Software Engineer"
- 解决方案:创建一个作用域安全的构造函数。作用域安全的构造函数在进行任何更改前,首先确认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);
}
}
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"
- 注意:实现这个模式后,你就锁定了可以调用构造函数的环境。如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏。
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(5, 10);
alert(rect.sides); //undefined
- 如果构造函数窃取结合使用原型链或者寄生组合则可以解决这个问题。
Rectangle.prototype = new Polygon();
var rect = new Rectangle(5, 10);
alert(rect.sides); //2
3、惰性载入函数
- 因为浏览器之间行为的差异,多数JavaScript代码包含了大量的if语句,将执行引导到正确的代码中,如果if 语句不必每次执行,那么代码可以运行地更快一些。解决方案就是称之为惰性载入的技巧。
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
return new XMLHttpRequest();
} else if (typeof ActiveXObject != "undefined"){
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){
//跳过
}
}
}
return new ActiveXObject(arguments.callee.activeXString);
} else {
throw new Error("No XHR object available.");
}
}
- 惰性载入表示函数执行的分支仅会发生一次。
- 有两种实现惰性载入的方式,第一种就是在函数被调用时再处理函数。在第一次调用的过程中,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了。
function createXHR(){
if (typeof XMLHttpRequest != "undefined"){
createXHR = function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
createXHR = function(){
/**省略**/
};
} else {
createXHR = function(){
throw new Error("No XHR object available.");
};
}
return createXHR();
}
- 第二种实现惰性载入的方式是在声明函数时就指定适当的函数。
var createXHR = (function(){
if (typeof XMLHttpRequest != "undefined"){
return function(){
return new XMLHttpRequest();
};
} else if (typeof ActiveXObject != "undefined"){
return function(){
/**省略**/
};
} else {
return function(){
throw new Error("No XHR object available.");
};
}
})();
4、函数绑定
- 函数绑定要创建一个函数,可以在特定的this 环境中以指定参数调用另一个函数。
- JavaScript 库实现了一个可以将函数绑定到指定环境的函数,一个简单的bind()函数接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去。语法如下:
function bind(fn, context){
return function(){
return fn.apply(context, arguments);
};
}
var handler = {
message: "Event handled",
handleClick: function(event){
alert(this.message + ":" + event.type);
}
};
var btn = document.getElementById("my-btn");
EventUtil.addHandler(btn, "click", bind(handler.handleClick, handler));
- ECMAScript 5 为所有函数定义了一个原生的bind()方法,进一步简单了操作。它们主要用于事件处理程序以及 setTimeout() 和 setInterval()。
EventUtil.addHandler(btn, "click", handler.handleClick.bind(handler));
5、函数柯里化
- 函数柯里化,用于创建已经设置好了一个或多个参数的函数,基本方法与函数绑定相同:使用闭包返回一个函数。两者的区别在于,当函数被调用时,返回的函数还需要设置一些参数。
function add(num1, num2){
return num1 + num2;
}
function curriedAdd(num2){
return add(5, num2);
}
alert(add(2, 3)); //5
alert(curriedAdd(3)); //8
- 柯里化函数通常由以下步骤动态创建:调用另一个函数并为它传入要柯里化的函数和必要参数。创建柯里化函数的通用方式如下。
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()函数可以按以下方式应用。
function add(num1, num2){
return num1 + num2;
}
var curriedAdd = curry(add, 5);
alert(curriedAdd(3)); //8
var curriedAdd = curry(add, 5, 12);
alert(curriedAdd()); //17
好好学习
网友评论