javascript函数中的3个高级技巧


Posted in Javascript onSeptember 22, 2016

前面的话 

函数对任何一门语言来说都是一个核心的概念,在javascript中更是如此。前面曾以深入理解函数系列的形式介绍了函数的相关内容,本文将再深入一步,介绍函数的3个高级技巧  

技巧一:作用域安全的构造函数

构造函数其实就是一个使用new操作符调用的函数 

function Person(name,age,job){
  this.name=name;
  this.age=age;
  this.job=job;
}
var person=new Person('match',28,'Software Engineer');
console.log(person.name);//match

如果没有使用new操作符,原本针对Person对象的三个属性被添加到window对象 

function Person(name,age,job){
  this.name=name;
  this.age=age;
  this.job=job;
}     
var person=Person('match',28,'Software Engineer');
console.log(person);//undefined
console.log(window.name);//match

window的name属性是用来标识链接目标和框架的,这里对该属性的偶然覆盖可能会导致页面上的其它错误,这个问题的解决方法就是创建一个作用域安全的构造函数 

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 person=Person('match',28,'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'
var person= new Person('match',28,'Software Engineer');
console.log(window.name); // ""
console.log(person.name); //'match'

但是,对构造函数窃取模式的继承,会带来副作用。这是因为,下列代码中,this对象并非Polygon对象实例,所以构造函数Polygon()会创建并返回一个新的实例 

function Polygon(sides){
  if(this instanceof Polygon){
    this.sides=sides;
    this.getArea=function(){
      return 0;
    }
  }else{
    return new Polygon(sides);
  }
}
function Rectangle(wifth,height){
  Polygon.call(this,2);
  this.width=this.width;
  this.height=height;
  this.getArea=function(){
    return this.width * this.height;
  };
}
var rect= new Rectangle(5,10);
console.log(rect.sides); //undefined

如果要使用作用域安全的构造函数窃取模式的话,需要结合原型链继承,重写Rectangle的prototype属性,使它的实例也变成Polygon的实例 

function Polygon(sides){
  if(this instanceof Polygon){
    this.sides=sides;
    this.getArea=function(){
      return 0;
    }
  }else{
    return new Polygon(sides);
  }
}
function Rectangle(wifth,height){
  Polygon.call(this,2);
  this.width=this.width;
  this.height=height;
  this.getArea=function(){
    return this.width * this.height;
  };
}
Rectangle.prototype= new Polygon();
var rect= new Rectangle(5,10);
console.log(rect.sides); //2

技巧二:惰性载入函数

因为各浏览器之间的行为的差异,我们经常会在函数中包含了大量的if语句,以检查浏览器特性,解决不同浏览器的兼容问题。比如,我们最常见的为dom节点添加事件的函数 

function addEvent(type, element, fun) {
  if (element.addEventListener) {
    element.addEventListener(type, fun, false);
  }
  else if(element.attachEvent){
    element.attachEvent('on' + type, fun);
  }
  else{
    element['on' + type] = fun;
  }
}

每次调用addEvent函数的时候,它都要对浏览器所支持的能力进行检查,首先检查是否支持addEventListener方法,如果不支持,再检查是否支持attachEvent方法,如果还不支持,就用dom0级的方法添加事件。这个过程,在addEvent函数每次调用的时候都要走一遍,其实,如果浏览器支持其中的一种方法,那么他就会一直支持了,就没有必要再进行其他分支的检测了。也就是说,if语句不必每次都执行,代码可以运行的更快一些。

解决方案就是惰性载入。所谓惰性载入,指函数执行的分支只会发生一次

有两种实现惰性载入的方式 

【1】第一种是在函数被调用时,再处理函数。函数在第一次调用时,该函数会被覆盖为另外一个按合适方式执行的函数,这样任何对原函数的调用都不用再经过执行的分支了

我们可以用下面的方式使用惰性载入重写addEvent() 

function addEvent(type, element, fun) {
  if (element.addEventListener) {
    addEvent = function (type, element, fun) {
      element.addEventListener(type, fun, false);
    }
  }
  else if(element.attachEvent){
    addEvent = function (type, element, fun) {
      element.attachEvent('on' + type, fun);
    }
  }
  else{
    addEvent = function (type, element, fun) {
      element['on' + type] = fun;
    }
  }
  return addEvent(type, element, fun);
}

在这个惰性载入的addEvent()中,if语句的每个分支都会为addEvent变量赋值,有效覆盖了原函数。最后一步便是调用了新赋函数。下一次调用addEvent()时,便会直接调用新赋值的函数,这样就不用再执行if语句了

但是,这种方法有个缺点,如果函数名称有所改变,修改起来比较麻烦 

【2】第二种是声明函数时就指定适当的函数。 这样在第一次调用函数时就不会损失性能了,只在代码加载时会损失一点性能

以下就是按照这一思路重写的addEvent()。以下代码创建了一个匿名的自执行函数,通过不同的分支以确定应该使用哪个函数实现 

var addEvent = (function () {
  if (document.addEventListener) {
    return function (type, element, fun) {
      element.addEventListener(type, fun, false);
    }
  }
  else if (document.attachEvent) {
    return function (type, element, fun) {
      element.attachEvent('on' + type, fun);
    }
  }
  else {
    return function (type, element, fun) {
      element['on' + type] = fun;
    }
  }
})();

技巧三:函数绑定

在javascript与DOM交互中经常需要使用函数绑定,定义一个函数然后将其绑定到特定DOM元素或集合的某个事件触发程序上,绑定函数经常和回调函数及事件处理程序一起使用,以便把函数作为变量传递的同时保留代码执行环境 

<button id="btn">按钮</button>
<script>      
  var handler={
    message:"Event handled.",
    handlerFun:function(){
      alert(this.message);
    }
  };
btn.onclick = handler.handlerFun;
</script>

上面的代码创建了一个叫做handler的对象。handler.handlerFun()方法被分配为一个DOM按钮的事件处理程序。当按下该按钮时,就调用该函数,显示一个警告框。虽然貌似警告框应该显示Event handled,然而实际上显示的是undefiend。这个问题在于没有保存handler.handleClick()的环境,所以this对象最后是指向了DOM按钮而非handler

可以使用闭包来修正这个问题 

<button id="btn">按钮</button>
<script>      
var handler={
  message:"Event handled.",
  handlerFun:function(){
    alert(this.message);
  }
};
btn.onclick = function(){
  handler.handlerFun();  
}
</script>

当然这是特定于此场景的解决方案,创建多个闭包可能会令代码难以理解和调试。更好的办法是使用函数绑定
一个简单的绑定函数bind()接受一个函数和一个环境,并返回一个在给定环境中调用给定函数的函数,并且将所有参数原封不动传递过去 

function bind(fn,context){
  return function(){
    return fn.apply(context,arguments);
  }
}

这个函数似乎简单,但其功能是非常强大的。在bind()中创建了一个闭包,闭包使用apply()调用传入的函数,并给apply()传递context对象和参数。当调用返回的函数时,它会在给定环境中执行被传入的函数并给出所有参数 

<button id="btn">按钮</button>
<script> 
function bind(fn,context){
  return function(){
    return fn.apply(context,arguments);
  }
}     
var handler={
  message:"Event handled.",
  handlerFun:function(){
    alert(this.message);
  }
};
btn.onclick = bind(handler.handlerFun,handler);
</script>

ECMAScript5为所有函数定义了一个原生的bind()方法,进一步简化了操作。

只要是将某个函数指针以值的形式进行传递,同时该函数必须在特定环境中执行,被绑定函数的效用就突显出来了。它们主要用于事件处理程序以及setTimeout()和setInterval()。然而,被绑定函数与普通函数相比有更多的开销,它们需要更多内存,同时也因为多重函数调用稍微慢一点,所以最好只在必要时使用。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
js汉字排序问题 支持中英文混排,兼容各浏览器,包括CHROME
Dec 20 Javascript
JS实现带鼠标效果的头像及文章列表代码
Sep 27 Javascript
图解js图片轮播效果
Dec 20 Javascript
使用JQuery选择HTML遍历函数的方法
Sep 17 Javascript
如何提高javascript加载速度
Dec 26 Javascript
使用vue.js2.0 + ElementUI开发后台管理系统详细教程(一)
Jan 21 Javascript
javascript中apply/call和bind的使用
Feb 15 Javascript
JavaScript实现翻页功能(附效果图)
Feb 16 Javascript
React Native预设占位placeholder的使用
Sep 28 Javascript
Angular4绑定html内容出现警告的处理方法
Nov 03 Javascript
解决npm安装Electron缓慢网络超时导致失败的问题
Feb 06 Javascript
详解webpack 入门与解析
Apr 09 Javascript
JavaScript省市区三级联动菜单效果
Sep 21 #Javascript
Angular2 环境配置详细介绍
Sep 21 #Javascript
JS实现鼠标滑过显示边框的菜单效果
Sep 21 #Javascript
JS 动态判断PC和手机浏览器实现代码
Sep 21 #Javascript
详解AngularJs中$resource和restfu服务端数据交互
Sep 21 #Javascript
AngularJS通过$http和服务器通信详解
Sep 21 #Javascript
JavaScript 拖拽实例代码
Sep 21 #Javascript
You might like
php生成EXCEL的东东
2006/10/09 PHP
PHP根据两点间的经纬度计算距离
2014/10/31 PHP
示例详解Laravel的注册重构
2016/08/14 PHP
JS是否可以跨文件同时控制多个iframe页面的应用技巧
2007/12/16 Javascript
javascript中的prototype属性实例分析说明
2010/08/09 Javascript
MooBox 基于Mootools的对话框插件
2012/01/20 Javascript
ajax上传时参数提交不更新等相关问题
2012/12/11 Javascript
jQuery学习笔记(3)--用jquery(插件)实现多选项卡功能
2013/04/08 Javascript
raphael.js绘制中国地图 地图绘制方法
2014/02/12 Javascript
jquery调取json数据实现省市级联的方法
2015/01/29 Javascript
Js+php实现异步拖拽上传文件
2015/06/23 Javascript
jquery validate.js表单验证入门实例(附源码)
2015/11/10 Javascript
JS图片等比例缩放方法完整示例
2016/08/03 Javascript
ionic由于使用了header和subheader导致被遮挡的问题的两种解决方法
2016/09/22 Javascript
nodeJS服务器的创建和重新启动的实现方法
2018/05/12 NodeJs
详解mpvue开发小程序小总结
2018/07/25 Javascript
Node.js搭建WEB服务器的示例代码
2018/08/15 Javascript
js纯前端实现腾讯cos文件上传功能的示例代码
2019/05/14 Javascript
Vue-CLI项目中路由传参的方式详解
2019/09/01 Javascript
微信小程序如何播放腾讯视频的实现
2019/09/20 Javascript
Vue简单实现原理详解
2020/05/07 Javascript
基于leaflet.js实现修改地图主题样式的流程分析
2020/05/15 Javascript
[41:20]2014 DOTA2华西杯精英邀请赛 5 24 NewBee VS DK
2014/05/26 DOTA
[03:45]Newbee战队出征西雅图 决战2016国际邀请赛
2016/08/02 DOTA
Python 拷贝对象(深拷贝deepcopy与浅拷贝copy)
2008/09/06 Python
python的tkinter布局之简单的聊天窗口实现方法
2014/09/03 Python
高效使用Python字典的清单
2018/04/04 Python
分享Python切分字符串的一个不错方法
2018/12/14 Python
python 多线程串行和并行的实例
2019/02/22 Python
python3爬虫GIL修改多线程实例讲解
2020/11/24 Python
Linux常见面试题
2013/03/18 面试题
2014年便民服务中心工作总结
2014/12/20 职场文书
公司股份转让协议书范本
2015/01/28 职场文书
大四学生个人总结
2015/02/15 职场文书
经典哲理警句:志不真则心不热,心不热则功不贤
2019/11/14 职场文书
导游词之太行山青龙峡
2020/01/14 职场文书