0

对于类选择器,上下文 $(this) 似乎在某些情况下会发生变化。我花了很多时间尝试调试下面的 javascript,但我并不擅长:

$.fn.specialbutton = function(callback) {
    $(this).bind('click', function() { // $(this) is the a.button, as expected
        $.ajax({
            url: 'ajax.php',
            data: callback(); // context pitfall 
        }).done(function(response) {
            $(this).html(response); // context changed to something unknown
        })
    });
}

$('a.button').specialbutton(function() {
    // context pitfall 
    return {
        id: $(this).data('id'); // $(this) is not what you expect
    };
});

最终我认为解决方案是保存上下文并使用回调的显式调用:

$.fn.specialbutton = function(callback) {
    $(this).bind('click', function() { // $(this) is the a.button, as expected

        var $this = $(this); // save the context;

        $.ajax({
            url: 'ajax.php',
            data: callback.call($this); // explicitly specify context
        }).done(function(response) {
            $this.html(response); // reuse the saved context
        })
    });
}

$('a.button').specialbutton(function() {
    // context pitfall 
    return {
        id: $(this).data('id'); // $(this) is what was specified in prototype
    };
});

语境变化的规律和原因是什么?这是设计特点还是限制?这似乎不会影响选择器是否是 HTML id 选择器,即$('a#button'). 编写上述代码的更好或常见方法是什么?

4

3 回答 3

2

简单的答案是它不会改变上下文。或者更确切地说,它与它如何改变它的上下文是一致的。当您在该done方法中时,该方法特定于ajax您正在进行的调用。因此,this在这种情况下,绝对意味着与处理程序this内部不同的东西submit

如果您在提交处理程序之外进行了 ajax 调用,您希望this是什么?

总结一下:this根据定义是上下文相关的。当您更改上下文时(例如进行ajax调用以及该对象的内部方法/回调的情况), 的含义this肯定会改变。如果您需要先前的this上下文值,则需要跟踪this来自更高层闭包的先前值,这正是您在设置$this.

于 2013-05-14T17:15:33.393 回答
1

context( this) 将根据函数的定义和/或执行方式而改变。

更好的写法是:

$.fn.specialbutton = function(callback) {
    this.bind('submit', function() { // $(this) is the a.button, as expected
        $.ajax({
            url: 'ajax.php',
            data: callback.call(this), // explicitly specify context
            context: this, // explicitly specify context
        }).done(function(response) {
            this.html(response); // the context is now correct
        })
    });
}

$('a.button').specialbutton(function() {
    // context pitfall 
    return {
        id: this.data('id'); // this is the element that was submitted
    };
});

注意,锚标签没有提交事件,所以你的代码没有多大意义......

于 2013-05-14T17:17:46.280 回答
1

既然提到的文章帮助了你的答案,我试着从文章中提到重点,

在 JavaScript 中,绑定总是显式的,并且很容易丢失,因此使用 this 的方法不会在所有情况下都引用正确的对象,除非你强制它这样做。总的来说,JavaScript 中的绑定并不是一个困难的概念,但它经常被 JavaScripters 忽略或掩盖,从而导致混乱。

例子:

var john = {
  name: 'John',
  greet: function(person) {
    alert("Hi " + person + ", my name is " + this.name);
  }
};
john.greet("Mark");

//=> "Hi Mark, my name is John"

再一次,

var john = {
  name: 'John',
  greet: function(person) {
    alert("Hi " + person + ", my name is " + this.name);
  }
};
var fx = john.greet;
fx("Mark");
// => "Hi Mark, my name is " 

这是 JavaScript 绑定中最重要的一个问题——我将其称为“绑定丢失”。每当您通过引用而不是直接通过其所有者对象访问方法时,都会发生这种情况。该方法失去了它的隐式绑定,并且 this 停止引用它的所有者对象并返回到它的默认值,在这种情况下是 window (所以如果 window 那时有一个 name 属性,它将被使用)。

识别绑定敏感的代码模式

绑定敏感代码模式涉及传递方法引用,这通常通过两种可能的方式发生:要么将方法作为值分配,要么将方法作为参数传递(这本质上是相同的,当你认为关于它)。

显式绑定

那么我们该如何解决呢?我们显式地绑定——也就是说,当 this 被调用时,我们显式地声明 this 将在方法中指向什么。我们如何做到这一点?JavaScript 为我们提供了两个选项:应用和调用。

var fx = john.greet;
fx.apply(john, ["Mark"]);
// => "Hi Mark, my name is John" 

var fx = christophe.greet;
fx.call(christophe, "Mark");
// => "Hi Mark, my name is John"

所以我们想要的是一种持久绑定方法的方法,这样我们就可以得到一个绑定的方法引用。实现这一点的唯一方法需要我们将原始方法包装在另一个方法中,该方法将执行应用调用。这是一个尝试:

function createBoundedWrapper(object, method) {
  return function() {
    return method.apply(object, arguments);
  };
}

var john = {
  name: 'John',
  greet: function(person) {
    alert("Hi " + person + ", my name is " + this.name);
  }
};
var fx = createBoundedWrapper(john, john.greet);

fx("Mark");
// => "Hi Mark, my name is John" 

如果你对 JavaScript 不太感兴趣,上面的代码可能会让你有点困惑。这里的想法是使用给定的对象和方法(可能属于所述对象)调用 createBoundedWrapper 将产生一个全新的函数(我们要返回的匿名函数)。

你应该绑定吗?

既然我们已经了解了绑定的细节,那么需要强调的是,有时候绑定是多余的。具体来说,有一种代码模式可以通过使用词法闭包来替换绑定,从而获得显着的性能收益。(如果你不清楚什么是闭包,不要惊慌。)

模式如下:方法中的一些代码依赖于通过引用传递给工作的匿名函数。该匿名函数需要访问周围方法的 this 关键字。例如,假设我们在数组中有每个迭代器,再次考虑以下代码:

// ...
  processItems: function() {
    this.items.each(function(item) {
      // Process item…
      this.markItemAsProcessed(item);
    });
  },
// ...

匿名方法可以被包裹起来,然后createBoundedWrapper内部this将指向外部范围的this.

然而,这样的代码并不像看起来那么好。我们看到实现这样的“绑定引用”需要我们将原始方法包装在一个匿名函数中,这意味着调用绑定方法引用会导致两个方法调用:我们的匿名包装器和原始方法。如果几乎所有语言都有一件事,那就是方法调用的成本很高。

在这种情况下,我们可以在定义和调用错误函数(我们作为参数传递给每个函数的匿名方法)的同一代码位置访问原始的、所需的 this 关键字。我们可以简单地将正确的 this 引用保存在一个局部变量中,并在我们的迭代函数中使用它:

// ...
  processItems: function() {
    var that = this;
    this.items.each(function(item) {
      // Process item
      that.markItemAsProcessed(item);
    });
  },
// ...

外卖点

回顾一下:

Any member access must be qualified with the object it pertains to, even when it is this.
Any sort of function reference (assigning as a value, passing as an argument) loses the function’s original binding.
JavaScript provides two equivalent ways of explicitly specifying a function’s binding when calling it: apply and call.
Creating a “bound method reference” requires an anonymous wrapper function, and a calling cost. In specific situations, leveraging closures may be a better alternative.

希望这可以帮助。

于 2013-05-17T08:40:27.647 回答