在原型中进行事件绑定是否明智,为什么它的上下文会丢失?

Is it wise to do event binding in the prototype & why is the context of this lost?

在为网页创建模块化 JavaScript 代码时,我经常需要将事件附加到例如纽扣。

采用以下 示例代码 ,通常在 AMD 模块中找到:

define(function(require) {

    var MyObject = function(ele) {
        this.ele = ele;
    }

    MyObject.prototype = {
        myfunct: function() {
            console.log('myfunct called');
        }
    }

    return MyObject;

});

稍后在页面上我会做:

$(document).ready(function() {
    var button = $('#button'),
        myobj = new MyObject(button);

    button.click(function() {
        myobj.myfunct();
    });
});

这行得通,但我觉得还是有点不干净。

例如,我需要在全局命名空间中创建至少一个变量来将一个函数绑定到一个按钮。此外,当页面上有许多 JavaScript 强大的交互时,代码会变得混乱——这是我最初想通过使用模块化 JavaScript 来解决的问题。

这就是为什么我的想法是在原型内部绑定事件:

var MyObject = function(ele) {
    self = this;
    this.element = ele; 

    this.init();
}

MyObject.prototype = {
    init: function() {        
        $(this.element).click(function() {
           self.myfunct();
        });
    },
    myfunct: function() {
        console.log('myfunct called');
    }
}

这样,模块外的代码将如下所示:

$(document).ready(function() {
    var button = $('#button'),
        myobj = new MyObject(button);

});

将事件绑定移动到原型中是否明智?如果是这样,我这样做的方式可以吗,或者有没有办法使用 init()?

此外,我注意到当页面上有两个按钮时,一些上下文会丢失 – self 总是指的是 MyObj 的最后一个实例。

为什么会这样——我认为使用 self = this; 可以阻止上下文?

Fiddle

我们从第二个问题开始。您的代码的问题是您将 self 声明为全局变量,因为您使用 forgot/omitted var 关键字。因此,当您创建两个或多个实例时,最后一个实例会覆盖之前的实例,并且所有点击事件中的 self 都指向最后一个实例。

检查固定码。请注意,您必须将 var self = this 移动到 init 方法,因为现在它是局部变量:

var MyObject = function(ele) {
    this.element = ele; 
    this.init();
}

MyObject.prototype = {
    init: function() {        
        var self = this;
        $(this.element).click(function() {
           self.myfunct();
        });
    },
    myfunct: function() {
        console.log('myfunct called');
    }
}

关于第一个问题,没关系,是你的设计,没有什么问题。 init 方法中的绑定事件确实更清晰。

按照您的方式进行事件绑定并没有错。你失去范围的原因是因为你做了

self = this;

在构造函数中将 self 创建为全局变量。因此,每次调用构造函数时,它都会将 self 设置为该实例。

要修复它,请在您的 init 函数中将 self 设置为局部变量:

MyObject.prototype = {
  init: function() {
    var self = this;  // <-- this is the fix      
    $(this.element).click(function() {
       self.myfunct();
    });
} 

好的,首先是 self 的情况。

这一行:

self = this;

您正在创建一个名为 self 的全局变量,每次调用构造函数时都会覆盖该变量。如果您使用严格模式,这很容易被检测到。此外,如果您正确使用局部变量,您的原型将不知道 self 是什么,因此您尝试在原型中使用 self 已损坏。

我认为你的两种方法都存在问题:

  • 第一种方法需要太多手动工作在您的 MyObject 类型之外
  • 第二种方法(如果它工作正常)将事件附加到按钮作为调用构造函数的副作用。这会让使用您的 API 的人感到困惑,因为人们希望构造函数创建一个对象,而不是修改其他现有对象。

作为补救措施,我建议采用以下方法:

var MyObject = function(ele) {
    this.element = ele; 
}

MyObject.prototype = {
    attachEvents: function() {        
        var self = this;
        $(this.element).click(function() {
           self.myfunct();
        });
    },
    myfunct: function() {
        console.log('myfunct called');
    }
};

$(document).ready(function() {
    var button = $('#button'),
        myobj = new MyObject(button);

    myobj.attachEvents();
});

这需要实例化 MyObject 的人执行一个额外的步骤,但它清楚地传达了将事件附加到 myobj 的封装元素的意图。它也不需要有人使用 MyObject 来执行您的第一种方法的复杂操作。