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 相关文章推荐
IE和FireFox(FF)中js和css的不同
Apr 13 Javascript
Chrome中模态对话框showModalDialog返回值问题的解决方法
May 25 Javascript
基于jQuery的图片大小自动适应实现代码
Nov 17 Javascript
理清apply(),call()的区别和关系
Aug 14 Javascript
node.js中的fs.chmod方法使用说明
Dec 18 Javascript
JS实现表单验证功能(验证手机号是否存在,验证码倒计时)
Oct 11 Javascript
如何获取元素的最终background-color
Feb 06 Javascript
Vue声明式渲染详解
May 17 Javascript
Vue 将后台传过来的带html字段的字符串转换为 HTML
Mar 29 Javascript
vue router+vuex实现首页登录验证判断逻辑
May 17 Javascript
微信小程序中使用 async/await的方法实例分析
May 06 Javascript
基于原生JS封装的Modal对话框插件的示例代码
Sep 09 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
全国FM电台频率大全 - 10 江苏省
2020/03/11 无线电
php smarty截取中文字符乱码问题?gb2312/utf-8
2011/11/07 PHP
Thinkphp实现自动验证和自动完成
2015/12/19 PHP
Laravel与CI框架中截取字符串函数
2016/05/08 PHP
javascript之大字符串的连接的StringBuffer 类
2007/05/08 Javascript
jquery中ajax使用error调试错误的方法
2015/02/08 Javascript
JS实现CheckBox复选框全选、不选或全不选功能
2020/07/28 Javascript
深入解析JavaScript中的arguments对象
2016/06/12 Javascript
JavaScript生成带有缩进的表格代码
2016/06/15 Javascript
JS中正则表达式只有3种匹配模式(没有单行模式)详解
2016/07/28 Javascript
微信小程序 wxapp视图容器 view详解
2016/10/31 Javascript
jquery实现tab键进行选择后enter键触发click行为
2017/03/29 jQuery
深究AngularJS如何获取input的焦点(自定义指令)
2017/06/12 Javascript
vue axios 给生产环境和发布环境配置不同的接口地址(推荐)
2018/05/08 Javascript
使用angular-cli webpack创建多个包的方法
2018/10/16 Javascript
JS实现的点击按钮图片上下滚动效果示例
2019/01/28 Javascript
layui 表单标签的校验方法
2019/09/04 Javascript
vue实现点击追加选中样式效果
2019/11/01 Javascript
KnockoutJS数组比较算法实例详解
2019/11/25 Javascript
vue中使用router全局守卫实现页面拦截的示例
2020/10/23 Javascript
python实现的正则表达式功能入门教程【经典】
2017/06/05 Python
python使用for循环计算0-100的整数的和方法
2019/02/01 Python
pytorch下使用LSTM神经网络写诗实例
2020/01/14 Python
Python使用turtle库绘制小猪佩奇(实例代码)
2020/01/16 Python
NULL是什么,它是怎么定义的
2015/05/09 面试题
静态成员和非静态成员的区别
2012/05/12 面试题
岗位职责定义及内容
2013/11/08 职场文书
大学同学聚会邀请函
2014/01/29 职场文书
剪枝的学问教学反思
2014/02/07 职场文书
党支部公开承诺践诺书
2014/03/28 职场文书
学习经验演讲稿
2014/05/10 职场文书
2015小学毕业班工作总结
2015/07/21 职场文书
解决thinkphp6(tp6)在状态码500下不报错,或者显示错误“Malformed UTF-8 characters”的问题
2021/04/01 PHP
MySQL infobright的安装步骤
2021/04/07 MySQL
微信小程序APP的事件绑定以及传递参数时的冒泡和捕获
2022/04/19 Javascript
HttpClient实现表单提交上传文件
2022/08/14 Java/Android