javascript中的Function.prototye.bind


Posted in Javascript onJune 25, 2015

函数绑定(Function binding)很有可能是你在开始使用JavaScript时最少关注的一点,但是当你意识到你需要一个解决方案来解决如何在另一个函数中保持this上下文的时候,你真正需要的其实就是 Function.prototype.bind(),只是你有可能仍然没有意识到这点。

第一次遇到这个问题的时候,你可能倾向于将this设置到一个变量上,这样你可以在改变了上下文之后继续引用到它。很多人选择使用 self, _this 或者 context 作为变量名称(也有人使用 that)。这些方式都是有用的,当然也没有什么问题。但是其实有更好、更专用的方式。

我们真正需要解决的问题是什么?
在下面的例子代码中,我们可以名正言顺地将上下文缓存到一个变量中:

var myObj = {
 
  specialFunction: function () {
 
  },
 
  anotherSpecialFunction: function () {
 
  },
 
  getAsyncData: function (cb) {
    cb();
  },
 
  render: function () {
    var that = this;
    this.getAsyncData(function () {
      that.specialFunction();
      that.anotherSpecialFunction();
    });
  }
};
 
myObj.render();

如果我们简单地使用 this.specialFunction() 来调用方法的话,会收到下面的错误:

Uncaught TypeError: Object [object global] has no method 'specialFunction'
我们需要为回调函数的执行保持对 myObj 对象上下文的引用。 调用 that.specialFunction()让我们能够维持作用域上下文并且正确执行我们的函数。 然而使用 Function.prototype.bind() 可以有更加简洁干净的方式:

render: function () {
 
  this.getAsyncData(function () {
 
    this.specialFunction();
 
    this.anotherSpecialFunction();
 
  }.bind(this));
 
}

我们刚才做了什么?
.bind()创建了一个函数,当这个函数在被调用的时候,它的 this 关键词会被设置成被传入的值(这里指调用bind()时传入的参数)。因此,我们传入想要的上下文,this(其实就是 myObj),到.bind()函数中。然后,当回调函数被执行的时候, this 便指向 myObj 对象。

如果有兴趣想知道 Function.prototype.bind() 内部长什么样以及是如何工作的,这里有个非常简单的例子:

Function.prototype.bind = function (scope) {
  var fn = this;
  return function () {
    return fn.apply(scope);
  };
}

还有一个非常简单的用例:

var foo = {
  x: 3
}
 
var bar = function(){
  console.log(this.x);
}
 
bar(); 
// undefined
 
var boundFunc = bar.bind(foo);
 
boundFunc(); 
// 3

我们创建了一个新的函数,当它被执行的时候,它的 this 会被设置成 foo —— 而不是像我们调用 bar() 时的全局作用域。

浏览器支持
Browser Version support
Chrome 7
Firefox (Gecko) 4.0 (2)
Internet Explorer 9
Opera 11.60
Safari 5.1.4
正如你看到的,很不幸,Function.prototype.bind 在IE8及以下的版本中不被支持,所以如果你没有一个备用方案的话,可能在运行时会出现问题。

幸运的是,Mozilla Developer Network(很棒的资源库),为没有自身实现 .bind() 方法的浏览器提供了一个绝对可靠的替代方案:

if (!Function.prototype.bind) {
 Function.prototype.bind = function (oThis) {
  if (typeof this !== "function") {
   
// closest thing possible to the ECMAScript 5 internal IsCallable function
   throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
  }
 
  var aArgs = Array.prototype.slice.call(arguments, 1), 
    fToBind = this, 
    fNOP = function () {},
    fBound = function () {
     return fToBind.apply(this instanceof fNOP && oThis
                 ? this
                 : oThis,
                aArgs.concat(Array.prototype.slice.call(arguments)));
    };
 
  fNOP.prototype = this.prototype;
  fBound.prototype = new fNOP();
 
  return fBound;
 };
}

适用的模式

在学习技术点的时候,我发现有用的不仅仅在于彻底学习和理解概念,更在于看看在手头的工作中有没有适用它的地方,或者比较接近它的的东西。我希望,下面的某些例子能够适用于你的代码或者解决你正在面对的问题。

CLICK HANDLERS(点击处理函数)
一个用途是记录点击事件(或者在点击之后执行一个操作),这可能需要我们在一个对象中存入一些信息,比如:

var logger = {
  x: 0,    
  updateCount: function(){
    this.x++;
    console.log(this.x);
  }
}

我们可能会以下面的方式来指定点击处理函数,随后调用 logger 对象中的 updateCount() 方法。

document.querySelector('button').addEventListener('click', function(){
  logger.updateCount();
});

但是我们必须要创建一个多余的匿名函数,来确保 updateCount()函数中的 this 关键字有正确的值。

我们可以使用如下更干净的方式:

document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));
我们巧妙地使用了方便的 .bind() 函数来创建一个新的函数,而将它的作用域绑定为 logger 对象。

SETTIMEOUT
如果你使用过模板引擎(比如Handlebars)或者尤其使用过某些MV*框架(从我的经验我只能谈论Backbone.js),那么你也许知道下面讨论的关于在渲染模板之后立即访问新的DOM节点时会遇到的问题。

假设我们想要实例化一个jQuery插件:

var myView = {
 
  template: '/* 一个包含 <select /> 的模板字符串*/',
 
  $el: $('#content'),
 
  afterRender: function () {
    this.$el.find('select').myPlugin();
  },
 
  render: function () {
    this.$el.html(this.template());
    this.afterRender();
  }
}
 
myView.render();

你或许发现它能正常工作——但并不是每次都行,因为里面存在着问题。这是一个竞争的问题:只有先到达的才能获胜。有时候是渲染先到,而有时候是插件的实例化先到。【译者注:如果渲染过程还没有完成(DOM Node还没有被添加到DOM树上),那么find(‘select')将无法找到相应的节点来执行实例化。】

现在,或许并不被很多人知晓,我们可以使用基于 setTimeout() 的 slight hack来解决问题。

我们稍微改写一下我们的代码,就在DOM节点加载后再安全的实例化我们的jQuery插件:

afterRender: function () {
    this.$el.find('select').myPlugin();
  },
 
  render: function () {
    this.$el.html(this.template());
    setTimeout(this.afterRender, 0);    
  }

然而,我们获得的是 函数 .afterRender() 不能找到 的错误信息。

我们接下来要做的,就是将.bind()使用到我们的代码中:

//
 
  afterRender: function () {
    this.$el.find('select').myPlugin();
  },
 
  render: function () {
    this.$el.html(this.template());
    setTimeout(this.afterRender.bind(this), 0);    
  }
 
//

以上所述就是本文的全部内容了,希望大家能够喜欢。

Javascript 相关文章推荐
jquery图片延迟加载 前端开发技能必备系列
Jun 18 Javascript
原生js写的放大镜效果
Aug 22 Javascript
js获取和设置属性的方法
Feb 20 Javascript
用JS在浏览器中创建下载文件
Mar 05 Javascript
Node.js中的process.nextTick使用实例
Jun 25 Javascript
AngularJS入门教程之REST和定制服务详解
Aug 19 Javascript
AngularJS实现的生成随机数与猜数字大小功能示例
Dec 25 Javascript
js tab栏切换代码实例解析
Sep 03 Javascript
vue实现手机号码的校验实例代码(防抖函数的应用场景)
Sep 05 Javascript
详解从vue-loader源码分析CSS Scoped的实现
Sep 23 Javascript
vuex存储复杂参数(如对象数组等)刷新数据丢失的解决方法
Nov 05 Javascript
Vue 解决父组件跳转子路由后当前导航active样式消失问题
Jul 21 Javascript
jQuery实现为图片添加镜头放大效果的方法
Jun 25 #Javascript
jquery实现的缩略图预览滑块实例
Jun 25 #Javascript
使用javascript提交form表单方法汇总
Jun 25 #Javascript
Jquery结合HTML5实现文件上传
Jun 25 #Javascript
浅谈jQuery.easyui的datebox格式化时间
Jun 25 #Javascript
javascript学习笔记之函数定义
Jun 25 #Javascript
把Node.js程序加入服务实现随机启动
Jun 25 #Javascript
You might like
在MongoDB中模拟Auto Increment的php代码
2011/03/06 PHP
浅谈php serialize()与unserialize()的用法
2013/06/05 PHP
解决PHP4.0 和 PHP5.0类构造函数的兼容问题
2013/08/01 PHP
在CentOS系统上从零开始搭建WordPress博客的全流程记录
2016/04/21 PHP
PHP实现数据库的增删查改功能及完整代码
2018/04/18 PHP
PHP实现正则匹配所有括号中的内容
2018/06/22 PHP
js检测客户端不是firefox则提示下载
2007/04/07 Javascript
javascript 清除输入框中的数据
2009/04/13 Javascript
Jquery下:nth-child(an+b)的使用注意
2011/05/28 Javascript
jQuery链式操作如何实现以及为什么要用链式操作
2013/01/17 Javascript
表单提交前触发函数返回true表单才会提交
2014/03/11 Javascript
js+css实现导航效果实例
2015/02/10 Javascript
node.js 动态执行脚本
2016/06/02 Javascript
JavaScript数组去重由慢到快由繁到简(优化篇)
2016/08/26 Javascript
JQuery和HTML5 Canvas实现弹幕效果
2017/01/04 Javascript
Three.js中网格对象MESH的属性与方法详解
2017/09/27 Javascript
layui select获取自定义属性方法
2018/08/15 Javascript
微信小程序如何自定义table组件
2019/06/29 Javascript
Threejs实现滴滴官网首页地球动画功能
2020/07/13 Javascript
vue 路由meta 设置导航隐藏与显示功能的示例代码
2020/09/04 Javascript
Node 使用express-http-proxy 做api网关的实现
2020/10/15 Javascript
python二分法实现实例
2013/11/21 Python
python中精确输出JSON浮点数的方法
2014/04/18 Python
Python实现随机创建电话号码的方法示例
2018/12/07 Python
python3.6根据m3u8下载mp4视频
2019/06/17 Python
python 类的继承 实例方法.静态方法.类方法的代码解析
2019/08/23 Python
Python实现剪刀石头布小游戏(与电脑对战)
2019/12/31 Python
python 自定义异常和主动抛出异常(raise)的操作
2020/12/11 Python
工厂会计员职责
2014/02/06 职场文书
幼儿园庆六一活动方案
2014/03/06 职场文书
银行求职自荐信范文
2015/03/04 职场文书
2015年实习单位评语
2015/03/25 职场文书
高中班主任培训心得体会
2016/01/07 职场文书
Python-typing: 类型标注与支持 Any类型详解
2021/05/10 Python
python机器学习创建基于规则聊天机器人过程示例详解
2021/11/02 Python
Java Spring Boot 正确读取配置文件中的属性的值
2022/04/20 Java/Android