JS和OOP:滥用“that = this”模式

JS and OOP: abuse of `that = this` pattern

本文关键字:this 模式 that OOP 滥用 JS      更新时间:2023-09-26

我是JS世界的新手。

我习惯用QT(构建UI的伟大工具!)编写UI。有了QT,我正在为每个元素做一个类:如果我有一个带有一些元素的表,我有一个用于每个元素的类和一个用于表的类(也可能用于行)。每个类都包含数据和操作其"DOM"的方法。

现在在html中,我正在使用一些包含在div上的空骨架的ejs文件,我正在编写一个通过jquery操纵它的类。

例如:

userslist.ejs:

<script type="text/javascript">
function UsersList(path) {
    this.path = path;
    $('#userList > tbody').empty();
    this.loadAjax();
}
UsersList.prototype.loadAjax = function() {
    var that = this;
    $.getJSON(this.path, function(users) {
        that.fillUsers(users);
    });
};
UsersList.prototype.fillUsers = function(users) {
    var that = this;
    users.forEach(function(user){
        var tr = that.buildTr(user);
        $('#userList > tbody:last').append(tr);    
    });
};
UsersList.prototype.buildTr = function(user) {
    ...
};
</script>

<h1>Users list</h1>
<table id="userList">
    <thead>
        <tr>
            <th>#</th>
            <th>Name</th>
            <th>Control</th>
        </tr>
    </thead>
    <tbody></tbody>
</table>

使用JavaScript的回调编码方式(以及一般的jquery dom操作),我很容易丢失this关键字来引用类方法和类属性(即使我在嵌套回调中获得一些控制)。我发现自己真的滥用了var that = this;的东西…我在类的几乎每个方法中都有它。

我怎样才能避免这种情况?我喜欢面向对象,但我很好奇探索这个新的流行的js,一些功能特性和异步无处不在。

在JS中处理这个问题的正确方法是什么?我怎么能避免写var that = this;在每个方法每次我使用回调?

当你在javascript (events, ajax)中使用异步编程时,上下文通常是事件的上下文,所以你失去了对象的上下文。

var that = this模式,你甚至更经常读为var self = thisvar $this = this,允许在方法中添加闭包,以便重新建立上下文。

这里有另一个方法,它将在调用对象方法时强制上下文为this:

UsersList.prototype.loadAjax = function() {
    var that = this;
    $.getJSON(this.path, this.fillUsers.bind(this));
};

您可以使用.bind来保存上下文。较新版本的JavaScript (ES6)用新的箭头符号解决了这个问题。此外,有些函数接受上下文参数。

下面是ES5的解决方案:

UsersList.prototype.fillUsers = function(users) {
    users.forEach(function(user){
        var tr = this.buildTr(user);
        $('#userList > tbody:last').append(tr);    
    }, this); // note the context parameter
};
以下是ES6的解决方案:
UsersList.prototype.fillUsers = function(users) {
    users.forEach((user) => { // arrow notation
        var tr = this.buildTr(user);
        $('#userList > tbody:last').append(tr);    
    }); 
};
当然,ES3的方法应该是使用普通的for循环而不是forEach。

对于$.getJSON, jQuery的$.ajax也接受一个上下文参数:

$.ajax({
  url: this.path,
  context: this
}).done(function(users) {
    this.fillUsers(users);
});

或者,带ES6箭头:

$.getJSON(this.path).done((users) =>  this.fillUsers(users));

您可能已经发现,"经典"oop在JavaScript中看起来不太好,并且您可能希望在代码与经典语言不同的地方分发。

您可以创建一个将上下文作为参数传递的装饰器(Python也是如此),这样您就可以在嵌套作用域中访问它。还有return this用于链接:

function fluent(f) {
  return function() {
    f.apply(this, [this].concat([].slice.call(arguments)))
    return this
  }
}
function Ary(xs) {
  this.xs = xs
}
Ary.prototype.method = fluent(function(self, a, b) {
  self.xs.forEach(function(x) {
    console.log(self.xs, x + a + b)
  })
})
var a = new Ary([1,2,3])
a.method(1, 2)
//[1, 2, 3] 4
//[1, 2, 3] 5
//[1, 2, 3] 6

另一个答案是使用jQuery的.proxy。一个例子:

UsersList.prototype.loadAjax = function() {
    $.getJSON(this.path, $.proxy(function(users) {
        //jQuery.proxy ensures that 'this' referring to the object you intend to refer to, i.e. the object passed in to the second argument of .proxy
        this.fillUsers(users);
    }), this);  //Note we pass 'this' to .proxy
};

我认为这个类似的问题有助于说明:$(this)内部AJAX成功不工作

老实说,我不会试图避免这种方法。也许你可以使用.bind(...), .call(...).apply(...),或者过度设计你的代码来摆脱var that = this;的编码。

但是,毕竟,var that = this比其他解决方案执行得更好,因为您使用,并且没有额外的开销来访问代表当前对象的this引用。

最好的论点是假设 JavaScript以这种方式工作,我非常确定这将是您项目中不太重要的问题。

你说

:

在JS中处理这个问题的正确方法是什么?我怎样才能避免写var that = this;在每个方法中,每次我使用回调?

答案是你的做法是正确的。无论如何,我会看看bind, callapply函数,因为有些情况下它们比var that = this;工作得更好。