读书笔记:javascript高级技巧(一)

 

一.安全的类型检测

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))

 

 

今天就到这里,接下来会研究 惰性载入函数及神秘的函数柯里化。

 

posted @ 2015-07-14 00:24  走在起点  阅读(265)  评论(0编辑  收藏  举报