理解 JavaScript 绑定 ()

总结:

       函数绑定可能是你开始使用 JavaScript 时最不关心的问题,但是当你意识到你需要一个解决方案来解决如何在另一个函数中保持this的上下文的问题时,你可能没有意识到你实际上需要的是 Function.prototype.bind()。

我们实际上想要解决什么问题?

 

 1 var myObj = {
 2 
 3     specialFunction: function () {
 4 
 5     },
 6 
 7     anotherSpecialFunction: function () {
 8 
 9     },
10 
11     getAsyncData: function (cb) {
12         cb();
13     },
14 
15     render: function () {
16         var that = this;
17         this.getAsyncData(function () {
18             that.specialFunction();
19             that.anotherSpecialFunction();
20         });
21     }
22 };
23 
24 myObj.render();

如果我们将函数调用保留为this.specialFunction(),那么我们会收到以下错误:

Uncaught TypeError: Object [object global] has no method 'specialFunction'

我们需要保留myObj调用回调函数时引用对象的上下文调用that.specialFunction()使我们能够维护该上下文并正确执行我们的函数。但是,这可以通过使用Function.prototype.bind().

让我们重写我们的例子:

 1 render: function () {
 2 
 3     this.getAsyncData(function () {
 4 
 5         this.specialFunction();
 6 
 7         this.anotherSpecialFunction();
 8 
 9     }.bind(this));
10 
11 }

 

我们刚刚做了什么?

好吧,.bind()只需创建一个新函数,该函数在调用时将其this关键字设置为提供的值。因此,我们将所需的上下文this(即myObj传递.bind()函数。然后,当回调函数被执行时,this引用myObj.

 

如果您有兴趣Function.prototype.bind()了解它的外观及其内部功能,这里有一个非常简单的示例:

1 Function.prototype.bind = function (scope) {
2     var fn = this;
3     return function () {
4         return fn.apply(scope);
5     };
6 }

这是一个非常简单的用例:

 1 var foo = {
 2     x: 3
 3 }
 4 
 5 var bar = function(){
 6     console.log(this.x);
 7 }
 8 
 9 bar(); // undefined
10 
11 var boundFunc = bar.bind(foo);
12 
13 boundFunc(); // 3

我们创建了一个新函数,在执行时将其this设置为foo- 而不是全局范围,如我们调用bar();

浏览器支持

 

 正如您所看到的,不幸的Function.prototype.bind是,Internet Explorer 8 及以下版本不支持它,因此如果您尝试在没有后备的情况下使用它,则会遇到问题。

幸运的是,Mozilla 开发者网络是一个很好的资源,如果浏览器没有实现原生方法,它提供了一个坚如磐石的替代方案.bind()

 1 if (!Function.prototype.bind) {
 2   Function.prototype.bind = function (oThis) {
 3     if (typeof this !== "function") {
 4       // closest thing possible to the ECMAScript 5 internal IsCallable function
 5       throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
 6     }
 7 
 8     var aArgs = Array.prototype.slice.call(arguments, 1),
 9         fToBind = this,
10         fNOP = function () {},
11         fBound = function () {
12           return fToBind.apply(this instanceof fNOP && oThis
13                                  ? this
14                                  : oThis,
15                                aArgs.concat(Array.prototype.slice.call(arguments)));
16         };
17 
18     fNOP.prototype = this.prototype;
19     fBound.prototype = new fNOP();
20 
21     return fBound;
22   };
23 }

使用模式 

单击处理程序

一种用途是跟踪可能需要我们将信息存储在对象中的点击(或在点击后执行操作),如下所示:

1 var logger = {
2     x: 0,
3     updateCount: function(){
4         this.x++;
5         console.log(this.x);
6     }
7 }

我们可以像这样分配点击处理程序,然后updateCount()在我们的logger对象中调用

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

但是我们不得不创建一个不必要的匿名函数来允许this关键字在updateCount()函数中正确存在

这可以整理,像这样:

document.querySelector('button').addEventListener('click', logger.updateCount.bind(logger));

我们已经使用巧妙方便的.bind()函数来创建一个新函数,然后设置要绑定到logger对象的作用域

设置超时

如果您曾经使用过模板引擎(例如 Handlebars),或者尤其是某些 MV* 框架(我只能从经验谈起 Backbone.js),那么您可能会意识到渲染模板时出现的问题,但是想在渲染调用后立即访问新的 DOM 节点。

假设我们尝试实例化一个 jQuery 插件:

 1 var myView = {
 2 
 3     template: '/* a template string containing our <select /> */',
 4 
 5     $el: $('#content'),
 6 
 7     afterRender: function () {
 8         this.$el.find('select').myPlugin();
 9     },
10 
11     render: function () {
12         this.$el.html(this.template());
13         this.afterRender();
14     }
15 }
16 
17 myView.render();

您可能会发现它有效——但并非总是如此。这就是问题所在。这是一场激烈的竞赛:无论发生什么,先到达那里的人都会获胜。有时是渲染,有时是插件的实例化。

现在,有些人不知道,我们可以对setTimeout().

稍微重写一下,一旦 DOM 节点出现,我们就可以安全地实例化我们的 jQuery 插件:

 1 //
 2 
 3     afterRender: function () {
 4         this.$el.find('select').myPlugin();
 5     },
 6 
 7     render: function () {
 8         this.$el.html(this.template());
 9         setTimeout(this.afterRender, 0);
10     }
11 
12 //

但是,我们将收到.afterRender()无法找到该函数的可信消息

那么,我们所做的就是将我们.bind()的混合在一起:

 1 //
 2 
 3     afterRender: function () {
 4         this.$el.find('select').myPlugin();
 5     },
 6 
 7     render: function () {
 8         this.$el.html(this.template());
 9         setTimeout(this.afterRender.bind(this), 0);
10     }
11 
12 //

现在,我们的afterRender()函数将在正确的上下文中执行。

使用 QUERYSELECTORALL 进行更整洁的事件绑定 

该DOM API一旦它包括这样的有用的方法,如显著改善querySelectorquerySelectorAll以及classListAPI,仅举几例的很多。

但是,到目前为止,还没有真正的方法可以将事件以本机方式添加到 a NodeList中。所以,我们最终forEachArray.prototype to 循环中窃取了函数,如下所示:

1 Array.prototype.forEach.call(document.querySelectorAll('.klasses'), function(el){
2     el.addEventListener('click', someFunction);
3 });

不过,与我们的朋友一起,我们可以做得更好.bind()

1 var unboundForEach = Array.prototype.forEach,
2     forEach = Function.prototype.call.bind(unboundForEach);
3 
4 forEach(document.querySelectorAll('.klasses'), function (el) {
5     el.addEventListener('click', someFunction);
6 });

我们现在有一个整洁的方法来循环我们的 DOM 节点。

结论 

如您所见,()可以巧妙地包含javascript 绑定函数用于许多不同的目的,以及整理现有代码。希望本概述为您提供了需要添加.bind()到您自己的代码中的内容(如有必要!)并利用转换this.

 

查看原文

posted @ 2021-09-18 16:31  一只灵活的胖子  阅读(104)  评论(0)    收藏  举报