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 相关文章推荐
iframe 自适应高度[在IE6 IE7 FF下测试通过]
Apr 13 Javascript
JQuery datepicker 使用方法
May 20 Javascript
JQuery.Ajax之错误调试帮助信息介绍
Jul 04 Javascript
利用JavaScript实现新闻滚动效果(实例代码)
Nov 27 Javascript
js函数在frame中的相互调用详解
Mar 03 Javascript
AngularJs Understanding the Controller Component
Sep 02 Javascript
DOM 事件的深入浅出(二)
Dec 05 Javascript
jquery uploadify如何取消已上传成功文件
Feb 08 Javascript
js 作用域和变量详解
Feb 16 Javascript
jquery手机触屏滑动拼音字母城市选择器的实例代码
Dec 11 jQuery
bootstrap fileinput插件实现预览上传照片功能
Jan 23 Javascript
javascript中的数据类型检测方法详解
Aug 07 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代码
2012/06/08 PHP
PHP字符串的编码问题的详细介绍
2013/04/27 PHP
php读取纯真ip数据库使用示例
2014/01/26 PHP
PHP中使用CURL获取页面title例子
2015/01/07 PHP
jquery 图片 上一张 下一张 链接效果(续篇)
2010/04/20 Javascript
基于Jquery的将DropDownlist的选中值赋给label的实现代码
2011/05/06 Javascript
CodeMirror2 IE7/IE8 下面未知运行时错误的解决方法
2012/03/29 Javascript
关于IE中getElementsByClassName不能用的问题解决方法
2013/08/26 Javascript
理解jquery事件冒泡
2016/01/03 Javascript
JQuery给select添加/删除节点的实现代码
2016/04/26 Javascript
nodejs实现发出蜂鸣声音(系统报警声)的方法
2017/01/18 NodeJs
使用 NodeJS+Express 开发服务端的简单介绍
2017/04/07 NodeJs
jquery 校验中国身份证号码实例详解
2017/04/11 jQuery
JavaScript初学者必看“new”
2017/06/12 Javascript
原生JS与jQuery编写简单选项卡
2017/10/30 jQuery
JavaScript中import用法总结
2019/01/20 Javascript
Nodejs核心模块之net和http的使用详解
2019/04/02 NodeJs
基于Vue+ElementUI的省市区地址选择通用组件
2019/11/20 Javascript
Vue解析剪切板图片并实现发送功能
2020/02/04 Javascript
js事件机制----捕获与冒泡机制实例分析
2020/05/22 Javascript
用Python实现QQ游戏大家来找茬辅助工具
2014/09/14 Python
TensorFlow平台下Python实现神经网络
2018/03/10 Python
python素数筛选法浅析
2018/03/19 Python
Pycharm 操作Django Model的简单运用方法
2018/05/23 Python
解决Pycharm界面的子窗口不见了的问题
2019/01/17 Python
python 制作简单的音乐播放器
2020/11/25 Python
canvas因为图片资源不在同一域名下而导致的跨域污染画布的解决办法
2019/01/18 HTML / CSS
写好自荐信的要点
2013/11/06 职场文书
应届毕业生求职自荐书
2014/01/03 职场文书
技校毕业生个人学习的自我评价
2014/02/21 职场文书
医学生临床实习自我评价
2014/03/07 职场文书
销售岗位职责范本
2014/06/12 职场文书
驳回起诉民事裁定书
2015/05/19 职场文书
干部外出学习心得体会
2016/01/18 职场文书
如何利用map实现Nginx允许多个域名跨域
2021/03/31 Servers
Redis Cluster集群动态扩容的实现
2021/07/15 Redis