1.类型检测
typeof有时返回值不合理,比如RegExp对象返回object,测试代码:
var regex = /^what$/i; regex = new RegExp(‘^what$‘); alert(typeof regex);
instanceof在页面有多个frame时用不了,来自不同frame的对象instanceof返回false
可以用Object.prototype.toString.call(value) === ‘[object Array/Function...]’来做类型检查,也可以用来区分原生对象和自定义对象,例如:
[object JSON]//原生JSON对象 [object Object]//自定义JSON对象
注意:IE中以COM对象形式实现的函数对象toString不会返回[object Function]
2.作用域安全的构造函数
用new操作符调用构造函数会给新创建的对象添加属性,而直接调用构造函数会给全局对象window添加属性
为了避免污染全局作用域,可以用如下构造函数:
/* 可能会污染全局作用域 function Student(name){ this.name = name; } */ //作用域安全的构造函数 function Student(name){ if(this instanceof Student){ this.name = name; } else{ return new Student(name); } }
上面的构造函数能够避免直接调用构造函数给全局对象意外添加属性,但用这种方式实现继承可能会出现问题,类型检查可能导致继承失败
3.惰性载入(避免重复分支检测)
- 在第一次执行分支检测时,覆盖原有函数,例如:
function detect(){ if(...){ detect = function(){ // } } else if(...){ detect = function(){ // } } else... }
- 可以用匿名函数立即执行并返回匿名函数来实现惰性载入,例如:
var detect = (function(){ if(...){ return function(){ // } } else if(...){ return function(){ // } } else... })();
第一种方式第一次调用时损失性能,以后调用不会损失性能;第二种方式在第一次调用时也不会损失性能,因为把时耗放到了第一次载入代码时
4.函数绑定(指定执行环境)
可以用下面的函数给函数指定执行环境并创造新函数:
function bind(fun, context){ return function(){ return fun.apply(context, arguments); } }
可以方便地根据已有函数生成新函数,[IE9+]有原生的bind方法,例如var newFun = fun.bind(obj);
注意:函数绑定存在内存消耗多,执行慢的缺点
5.函数柯里化(也叫函数套用,允许指定一些参数)
创建柯里化函数的通用方式:
function curry(fun){ var args = Array.prototype.slice.call(arguments, 1);//去掉第一个参数fun,得到给定的参数值 return function(){ var innerArgs = Array.prototype.slice.call(arguments);//把内部arguments对象转换为数组(为了用concat方法) var finalArgs = args.concat(innerArgs);//拼接参数列表 return fun.apply(null, finalArgs);//把拼接的参数列表传给fun } }
或者增强bind方法实现柯里化:
function bind(fun, context){ var args = Array.prototype.slice.call(arguments, 2);//去掉前2个参数 return function(){ var innerArgs = Array.prototype.slice.call(arguments);//同curry var finalArgs = args.concat(innerArgs);//同curry return fun.apply(context, finalArgs);//指定执行环境和参数 } }
注意:柯里化和bind都存在额外开销,不要滥用
6.防篡改对象
- 不可扩展对象(不能添加新属性)
var obj = {a : 1, b : 2}; alert(Object.isExtensible(obj));//true Object.preventExtensions(obj);//把obj设置为不可扩展 alert(Object.isExtensible(obj));//false obj.c = 3; alert(obj.c);//undefined
注意:设置不可扩展操作无法撤销(改不回来),设置之后无法添加新属性,但可以修改/删除原有属性
- 密封对象(只能修改现有属性,无法删除或添加)
var obj = {a : 1, b : 2}; Object.seal(obj);//设置密封对象 alert(Object.isSealed(obj));//true obj.c = 3; alert(obj.c);//undefined delete obj.a;//严格模式下报错 alert(obj.a);//1
- 冻结对象(只读,访问器属性可写)
var obj = {a : 1, b : 2}; Object.freeze(obj);//设置密封对象 alert(Object.isFrozen(obj));//true obj.a = 3; alert(obj.a);//1
上面的实现都是ES5新增的部分,浏览器支持性未知,本机测试[IE8-]不支持,Chrome和FF支持
7.函数节流
把耗时的大任务分割成小块,用setTimeout控制执行
优点:提高了页面响应速度
缺点:逻辑连贯性没了,实现难度增大,而且不易实现事务控制,因为完整事务被拆开了
8.观察者模式
用自定义事件可以实现观察者模式:
function EventTarget(){ this.handlers = {}; } EventTarget.prototype = { constructor: EventTarget, addHandler: function(type, handler){ if (typeof this.handlers[type] == "undefined"){ this.handlers[type] = []; } this.handlers[type].push(handler); }, fire: function(event){ if (!event.target){ event.target = this; } if (this.handlers[event.type] instanceof Array){ var handlers = this.handlers[event.type]; for (var i=0, len=handlers.length; i < len; i++){ handlers[i](event); } } }, removeHandler: function(type, handler){ if (this.handlers[type] instanceof Array){ var handlers = this.handlers[type]; for (var i=0, len=handlers.length; i < len; i++){ if (handlers[i] === handler){ break; } } handlers.splice(i, 1); } } };
用法如下:
function handleMessage(event){ alert("Message received: " + event.message); } //创建新对象 var target = new EventTarget(); //添加事件处理器 target.addHandler("message", handleMessage); //触发事件 target.fire({ type: "message", message: "Hello world!"}); //删除事件处理器 target.removeHandler("message", handleMessage); //再次触发事件,应该没有事件处理器 target.fire({ type: "message", message: "Hello world!"});