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 相关文章推荐
页面只能打开一次Cooike如何实现
Dec 04 Javascript
js计算任意值之间随机数的方法
Jan 16 Javascript
jQuery结合CSS制作漂亮的select下拉菜单
May 03 Javascript
JavaScript中的substr()方法使用详解
Jun 06 Javascript
AngularJS 指令的交互详解及实例代码
Sep 14 Javascript
JavaScript同源策略和跨域访问实例详解
Apr 03 Javascript
vue下拉列表功能实例代码
Apr 08 Javascript
深入理解react 组件类型及使用场景
Mar 07 Javascript
vue中created和mounted的区别浅析
Aug 13 Javascript
JavaScript 几种循环方式以及模块化的总结
Sep 03 Javascript
微信小程序轮播图swiper代码详解
Dec 01 Javascript
JS中箭头函数与this的写法和理解
Jan 14 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.ini中添加extension=php_mysqli.dll指令的说明
2007/06/14 PHP
PHP实现的迷你漂流瓶
2015/07/29 PHP
PHP获取不了React Native Fecth参数的解决办法
2016/08/26 PHP
Js event事件在IE、FF兼容性问题
2011/01/01 Javascript
基于JavaScript 声明全局变量的三种方式详解
2013/05/07 Javascript
jquery图片放大功能简单实现
2013/08/01 Javascript
javascript jq 弹出层实例
2013/08/25 Javascript
浅谈JavaScript事件的属性列表
2015/03/01 Javascript
原生js与jQuery实现简单的tab切换特效对比
2015/07/30 Javascript
node.js+express制作网页计算器
2016/01/17 Javascript
探讨Vue.js的组件和模板
2017/10/27 Javascript
详解.vue文件解析的实现
2018/06/11 Javascript
vue 实现LED数字时钟效果(开箱即用)
2019/12/08 Javascript
JavaScript实现横版菜单栏
2020/03/17 Javascript
VSCode搭建React Native环境
2020/05/07 Javascript
Vue2.x和Vue3.x的双向绑定原理详解
2020/11/05 Javascript
JavaScript实现缓动动画
2020/11/25 Javascript
python模块smtplib实现纯文本邮件发送功能
2018/05/22 Python
把csv文件转化为数组及数组的切片方法
2018/07/04 Python
Python在图片中插入大量文字并且自动换行
2019/01/02 Python
flask 使用 flask_apscheduler 做定时循环任务的实现
2019/12/10 Python
使用python-pptx包批量修改ppt格式的实现
2020/02/14 Python
Python学习之路之pycharm的第一个项目搭建过程
2020/06/18 Python
利用HTML5 Canvas制作一个简单的打飞机游戏
2015/05/11 HTML / CSS
德国家用电器购物网站:Premiumshop24
2019/08/22 全球购物
艺术学院毕业生自我评价
2014/03/02 职场文书
信息技术毕业生自荐信范文
2014/03/13 职场文书
元宵节晚会主持人串词
2014/03/25 职场文书
申论倡议书范文
2014/05/13 职场文书
竞聘演讲稿开场白
2014/08/25 职场文书
毕业论文答辩开场白和答辩技巧
2015/05/27 职场文书
pytest进阶教程之fixture函数详解
2021/03/29 Python
python munch库的使用解析
2021/05/25 Python
OpenCV全景图像拼接的实现示例
2021/06/05 Python
Javascript的promise,async和await的区别详解
2022/03/24 Javascript
MySql中的json_extract函数处理json字段详情
2022/06/05 MySQL