JS和OOP:滥用`that = this`模式

时间:2014-07-14 21:34:30

标签: javascript oop

我对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;的东西......我几乎在课堂上的每个方法中都有它。

我该如何避免这种情况?我喜欢OOP,但我很想探索这个新的时尚js,具有一些功能特性和无处不在的asynch。

在JS中处理这个问题的正确方法是什么?每次使用回调时,如何避免在每种方法中编写var that = this;

5 个答案:

答案 0 :(得分:2)

您可以使用.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中看起来并不太好,您可能希望分发代码与传统语言不同的地方。

答案 1 :(得分:2)

当您在javascript(events,ajax)中使用异步编程时,上下文通常是事件的上下文,因此您将丢失对象的上下文。

var that = this模式,您将更频繁地将其视为var self = thisvar $this = this允许在方法中添加闭包以重新建立上下文。

这是另一种方法,它将在调用它时强制上下文在object方法中:

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

答案 2 :(得分:1)

您可以创建一个将上下文作为参数传递的装饰器(ala 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

答案 3 :(得分:1)

其他答案的另一种选择是使用jQuery&#39; .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) inside of AJAX success not working

答案 4 :(得分:1)

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

但是,毕竟var that = this的效果优于其他解决方案,因为您使用 clousures 并且只需访问this就没有额外的开销表示当前对象的引用。

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

你说:

  

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

答案是你以正确的方式做事。无论如何,我会查看bindcallapply函数,因为在某些情况下,它们的工作效果优于var that = this;