一.安全的类型检测
javascript内置的类型检测机制并非完全可靠,由于浏览器或者作用域等原因,经常会发生错误。大家知道,在任何值调用toString()方法都会返回一个[object Native ConstructorName]格式的字符串,每个类内部都有一个[class]属性,这个属性就指定了上述字符串中的构造函数名。例如
var value=[1,2,3,4,5]
alert(Object.prototype.toString.call(value));//"[object Array]"
由此,我们可以创建函数来判断数据类型:
//示例 switch(Object.prototype.toString.call(value)){ case "[object Array]": alert(‘array‘); break; case "[object Function]": alert("function"); break; case "[object RegExp]": alert("regexp"); break; case "[object JSON]": alert("json") break }
二。作用域安全的构造函数:
我们来看下面的一个实例
function Person(name,age){
this.name=name;
this.age=age
}
//根据构造函数创建实例(正常情况应该是这样:var person=new Person("jame",29))
var person=Person("jame",29)
alert(person.name) //js报错,未定义
alert(person.age) //js报错,未定义
alert(window.name) //jame
由上可见,当我们没有使用new操作符调用构造函数Person时,this会映射到对象window上。这是由于this对象的晚绑定造成的,this对象是在运行时绑定的,所以直接调用Person(),构造函数座位普通函数调用,导致错误的发生。而且由于window的name属性是识别链接目标和frame的,所以这里对该属性的偶然覆盖可能会导致该页面上出现其他错误。这个问题的解决办法就是创建一个作用域安全的构造函数。如下:
function Person(name,age){ if(this instanceof Person){ //检测this对象是否为正确类型的实例,如若不是,那么创建新的对象并返回 this.name=name; this.age=age; }else{ return new Person(name,age) } } var person1=new Person("jame",28); alert(person1.name); //jame alert(person1.age); //28 alert(window.name) //‘‘ alert(window.age) //undefined
ps:实现这个模式后,同时就锁定了可以调用构造函数的环境,如果你使用构造函数窃取模式的继承且不使用原型链,那么这个继承很可能被破坏,如下:
1 function Polygon(sides){ 2 if(this instanceof Polygon){ 3 this.sides=sides; 4 this.getArea=function(){ 5 return 0; 6 } 7 }else{ 8 return new Polygon(sides); 9 } 10 } 11 function rect(w,h){ 12 Polygon.call(this,2); 13 this.width=w; 14 this.height=h; 15 this.getArea=function(){ 16 return this.width*this.height 17 } 18 } 19 var rect1=new rect(5,10) 20 alert(rect.sides) //undefined
按照以往理解,rect.sides的结果应为2.因为我们在rect函数的第一行用call方法调用了polygon的方法,可是结果为什么为undefined呢?
在这段代码中,Polygon构造函数的作用域是安全的,然而rect构造函数却不是。新创建一个rect实例后,这个实例应该通过Polygon。call来继承Polygon的sides属性。但是由于plygon函数的作用域是安全的,this对象并非polygon的实例,所以会创建并返回一个新的polygon对象。rect中的this并没有得到变化,同时polygon.call返回的值也没有用刀,所以rect实例中不会有sides属性。
解决办法如下:
function Polygon(sides){ if(this instanceof Polygon){ this.sides=sides; this.getArea=function(){ return 0; } }else{ return new Polygon(sides); } } function rect(w,h){ Polygon.call(this,2); this.width=w; this.height=h; this.getArea=function(){ return this.width*this.height } } rect.prototype=new Polygon() var rect1=new rect(5,10) alert(rect.sides) //2
注意代码中加粗的部分,这样一个rect实例同时也是一个Polygon实例,所以Polygon。call会按预定执行。
三。函数绑定
函数绑定要创建一个函数,可以在特定的this环境中以指定参数调用另一个函数。该技巧常常和回调函数与事件处理程序一起使用,以便在将函数座位变量传递的同时保留代码执行环境,请看下面例子:
1 var handler={ 2 message:"hello world", 3 handleClick:function(event){ 4 alert(this.message) 5 } 6 } 7 var btn=document.getElementById("my_btn"); 8 EventUtil.addHandler(btn,"click",handler.handleClick)//跨浏览器事件处理 9 var EventUtil={ 10 addHandler:function(element,type,handler){ 11 if(element.addEventListener){ 12 element.addEventListener(type,handler,false) 13 }else if(element.attachEvent){ 14 element.attachEvent("on"+type,handler) 15 }else { 16 element["on"+type]=handler 17 } 18 }, 19 removeHandler:function(element,type,handler){ 20 if(element.removeEventListener){ 21 element.removeEventListener(type,handler,false) 22 }else if(element.detachEvent){ 23 element.detachEvent("on"+type,handler) 24 }else { 25 element["on"+type]=null 26 } 27 }, 28 }
在这个例子中,我们对按钮绑定点击事件,当点击发生时,我们期望得到的是”hello world“,可是结果却为undefiend。这个问题在于没有保存handler.handleClick的环境,所以this对象指向Dom按钮而非handler,我们可以用闭包来修正此问题;如下:
var handler={ message:"hello world", handleClick:function(event){ alert(this.message) } } var btn=document.getElementById("my_btn"); EventUtil.addHandler(btn,"click",function(event){ handler.handleClick(event); })
这个解决方案在事件处理程序内使用了一个闭包直接调用handler.handleClick()。当然床架你多个闭包可能使代码变得难于理解和调试,因此很多javascript库实现了一个可以函数绑定到制定环境的函数,一般叫bind();一个简单的bind函数接手一个函数和环境,并返回一个在给定环境中调用给定函数的函数,并将所有参数是原封不动传递过去。语法如下:
function bind(fn,context){
return function(){
return fn.apply(context,arguments)
}
}
在bind中创建了闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数(参数是内部函数的并非bind()的)。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数。在ECMAScript 5中为所有函数定义了一个月uansheng的bind()方法,与上面bind()方法类似,都是要传入作为this值得对象。支持原生bind()方法的浏览器有IE9+,Firefox 4+,Chrome。只要是将某个函数指针以值得形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就凸显歘来了。然后被绑定函数与普通函数相比有更多的开销,他们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。
原生调用方法如下:
var handler={ message:"hello world", handleClick:function(event){ alert(this.message+":"+event.type) } } var btn=document.getElementById("my_btn"); EventUtil.addHandler(btn,"click",handler.handleClick.bind(handler))
今天就到这里,接下来会研究 惰性载入函数及神秘的函数柯里化。