《JavaScript高级程序设计 第三版》学习笔记 (十三)高级函数

来源:互联网 发布:sql统计表行数 编辑:程序博客网 时间:2024/04/29 06:58

1.安全的原生类型检验

  js的原生类型,可以用typeof判断,但有时会失效。比如typeof array返回的不是Array,而是Object;再比如老版本IE,会将function识别为Object。另外一个判断类型的是instanceof,它能够在对象的原型链中查找构造函数,但这种方法对于原生类型的判断也会出问题,因为某些原生构造函数用户是可以覆盖的,比如Array和JSON。请看下面的例子:
[javascript] view plain copy
  1. function Array(){  
  2.     this.type="new Array";  
  3.     this.length=7;    
  4. }  
  5.   
  6. var a=new Array();  
  7. alert(a.length);//7  
  8. alert(typeof a);//object  
  9. alert(a instanceof Array);//true  
  10.   
  11. var b=[1,2,3];  
  12. alert(b.length);//3  
  13. alert(b[1]);//2  
  14. alert(typeof b);//object  
  15. alert(b instanceof Array);//false  
这是个很有意思的例子,我们覆盖了Array的构造函数,然后创建了一个不是Array的对象a,但instanceof把它识别成了Array,相反,本来是Array的b,却误判成其他。
  解决这一个问题的方法是利用toString。原生类型调用toString后会返回诸如"[object array]"的字符串,即便构造函数被恶意覆盖了,原生类型的toString方法是不会被覆盖的。
[javascript] view plain copy
  1. function Array(){this.length=7;}  
  2. var a=1;  
  3. var b=true;  
  4. var c="string";  
  5. var d=[];  
  6. var e=new Array();  
  7.   
  8. console.log(Object.prototype.toString.call(a));//[object Number]   
  9. console.log(Object.prototype.toString.call(b));//[object Boolean]  
  10. console.log(Object.prototype.toString.call(c));//[object String]  
  11. console.log(Object.prototype.toString.call(d));//[object Array]  
  12. console.log(Object.prototype.toString.call(e));//[object Object]  
注意:在老IE中以COM对象实现的函数,返回的是Object

2.作用域安全的构造函数

  在说对象创建的时候,我们给出了很多构造对象的方法,后来在说继承的时候,也使用了那些方法。但那些构造对象的方法都是不安全的。
[javascript] view plain copy
  1. function Person(name,age){  
  2.     this.name=name;  
  3.     this.age=age;     
  4. }  
  5.   
  6. var p1=new Person("Brain",18);  
  7. var p2=Person("Brain",18);  
  8. alert(p1.name);//Brain  
  9. alert(p2);//undefined  
  10. alert(window.name);//Brain  
上面例子里,有一个构造函数Person,p1是使用正确调用方式创建出来的对象,p2则使用了不正确的调用。这种不正确的调用有一个非常严重的副作用,就是在Person的执行空间中加入了name属性和age属性,当前的执行空间是window。解决这个问题的思路是让构造函数new和不new的运行结果是一样的,这在之前有一个解决方案,现在提出第二个解决方案。
[javascript] view plain copy
  1. function Person(name,age){  
  2.     if(this instanceof Person){  
  3.         this.name=name;  
  4.         this.age=age;  
  5.     }else{  
  6.         return new Person(name,age);  
  7.     }  
  8. }  
  9.   
  10. var p1=new Person("Brain",18);  
  11. var p2=Person("TOM",18);  
  12. alert(p1.name);//Brain  
  13. alert(p2.name);//TOM  
  14. alert(window.name);//""  

3.懒惰载入函数

  在写跨浏览器的函数时,难免要判断浏览器是否支持某功能。比如在IE8-的浏览器中绑定事件,是不能用addEventListener的,只能用attachEvent,然后就有了一下的函数:
[javascript] view plain copy
  1. function addListener(element,type,func){  
  2.     if(typeof element.addEventListener == "function"){  
  3.         console.log("addEventListener");  
  4.         element.addEventListener(type.toLocaleLowerCase(),func,false);    
  5.     }else{  
  6.         console.log("attachEvent");  
  7.         element.attachEvent("on"+type,func);  
  8.     }  
  9. }  
  10. addListener(document.getElementById("box1"),"Click",function(){});  
  11. addListener(document.getElementById("box2"),"Click",function(){});  
  12. /*控制台输出 
  13. addEventListener  
  14. addEventListener  
  15. */  
这样做看似不错,但往往这个函数要调用很多次,每次都要进行if判断,这明显会降低效率。于是,可以把函数重写:
[javascript] view plain copy
  1. function addListener(element,type,func){  
  2.     if(typeof element.addEventListener == "function"){  
  3.         console.log("addEventListener");  
  4.         element.addEventListener(type.toLocaleLowerCase(),func,false);  
  5.         addListener=function(e,t,f){  
  6.             console.log("override addEventListener");  
  7.             e.addEventListener(t.toLocaleLowerCase(),f);      
  8.         }     
  9.     }else{  
  10.         console.log("attachEvent");  
  11.         element.attachEvent("on"+type,func);  
  12.         addListener=function(e,t,f){  
  13.             console.log("override attachEvent");  
  14.             e.attachEvent("on"+t,f);  
  15.         }  
  16.     }  
  17. }  
  18. addListener(document.getElementById("box1"),"Click",function(){});  
  19. addListener(document.getElementById("box2"),"Click",function(){});  
  20. /*控制台输出 
  21. addEventListener  
  22. override addEventListener  
  23. */  

4.函数绑定

  有些时候,我们希望某个函数的执行,一直呆在事先规定好的执行环境中,而且这个函数可以由事件触发,这种需求是很常见的。比如下面这个例子,通过对象名称调用对象的方法,结果如我们预期;但如果想通过按钮点击触发showMsg,alert出来的确实undefined,和我们的预期相左。
[javascript] view plain copy
  1. var handle={  
  2.     message:"handle mesasge",  
  3.     showMsg:function(event){  
  4.         alert(this.message);  
  5.     }  
  6. }  
  7. handle.showMsg();//handle message  
  8. btn.addEventListener("click",handle.showMsg);  
不符合预期的原因,前面几篇博客已经说过了:handle.showMsg是一个函数的指针,把函数指针添加到按钮的click事件中,函数的执行空间不再是handle,而是btn,btn没有message属性,因此提示undefined。解决这一问题的思路是创建闭包:
[javascript] view plain copy
  1. btn.addEventListener("click",function(event){  
  2.     handle.showMsg(event);  
  3. });  
这次给btn的click事件绑定了一个闭包,闭包中直接通过handle调用了showMsg,符合我们的预期了。这样写虽然能解决问题,但扩展性不是很好。我们真正要达到的目的,是让某个函数永远在某个执行空间中执行。这样,自然会想到函数的apply方法。于是可以创建这样一个闭包。
[javascript] view plain copy
  1. function bind(func,content){  
  2.     return function(){  
  3.         return func.apply(content,argument);  
  4.     }  
  5. }  
bind函数传入两个参数,一个是待执行函数,一个是待执行函数的执行环境。bind函数返回一个闭包,闭包执行时,通过apply方式调用待执行函数,并为其指定执行空间。使用时,只要做如下变换即可:
[javascript] view plain copy
  1. btn.addEventListener("click",bind(handle.showMsg,handle)); 
0 0
原创粉丝点击