如何创建一个JavaScript“类”,将方法添加到原型中,并正确使用“this”

How to create a JavaScript "class" that adds methods to prototype AND uses 'this' correctly

本文关键字:原型 this 添加 一个 JavaScript 创建 何创建 方法      更新时间:2023-09-26

我一直被教导在 JavaScript 中模拟类的正确方法是在将成为你的类的函数之外向原型添加方法,如下所示:

function myClass()
{
    this.myProp = "foo";
}
myClass.prototype.myMethod = function()
{
    console.log(this);
}
myObj = new myClass();
myObj.myMethod();

我一直遇到一个问题,即我的方法中的this解析为全局 Window 对象,正如在 quirksmode 上最好解释的那样。

我尝试过做 Koch 提到的var that = this;技巧,但由于我的方法在我的类之外,我的 that 变量不再在范围内。 也许我只是不完全理解它。

有没有办法在 JavaScript 中创建一个类,其中方法不会在每个实现中重新创建,并且this将始终指向该对象?

编辑:

上面的简化代码有效,但我有很多次像上面一样声明一个"类",当我调用myObj.myMethod()时,它会作为Window对象返回。 我已经阅读了我能找到的所有this解释,例如我链接到的解释,但仍然不明白为什么有时会发生此问题。 任何关于代码可以像上面一样编写的情况的想法,this会引用Window

这是我目前遇到问题的实现,但是当我像上面这样将其简化为几行时,我不再有问题:

文件:

<script type="text/javascript" src="./scripts/class.Database.js"></script>
<script type="text/javascript" src="./scripts/class.ServerFunctionWrapper.js"></script>
<script type="text/javascript" src="./scripts/class.DataUnifier.js"></script>
<script type="text/javascript" src="./scripts/main.js"></script>

.class。DataUnifier.js:

function DataUnifier()
{
    this._local = new Database();
    this._server = new ServerFunctionWrapper();
    this.autoUpdateIntervalObj = null;
}
DataUnifier.prototype.getUpdates = function()
{
    this._server.getUpdates(updateCommands)
    {
        console.log(updateCommands);
        if (updateCommands)
        {
            executeUpdates(updateCommands);
        }
    }
}
//interval is in seconds
DataUnifier.prototype.startAutoUpdating = function(interval)
{
    this.stopAutoUpdating();
    this.autoUpdateIntervalObj = setInterval(this.getUpdates,interval * 1000);
}
DataUnifier.prototype.stopAutoUpdating = function()
{
    if (this.autoUpdateIntervalObj !== null)
    {
        clearInterval(this.autoUpdateIntervalObj);
        this.autoUpdateIntervalObj = null;
    }
}

主.js

var dataObj = new DataUnifier();
$(document).ready(function ev_document_ready() {
    dataObj.startAutoUpdating(5);
}

我已经删除了一些无关紧要的代码,但也许确实如此。 当页面加载并调用 dataObj.startAutoUpdating(5( 时,它会在 this.stopAutoUpdating((;行,因为this引用Window对象。 据我所知(根据提供的链接(,this应该引用DataUnifier对象。 我已经阅读了许多关于this关键字的资源,但不明白为什么我一直遇到这个问题。 我不使用内联事件注册。 像这样格式化的代码有什么原因会出现这个问题吗?

编辑 2:对于那些有类似问题的人,请参阅此 Mozilla 文档页面页面中半途的"this问题":http://developer.mozilla.org/en-US/docs/Web/API/Window.setInterval

我最喜欢的定义类的方法如下:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}

使用 defclass 函数,您可以按如下方式定义MyClass

var MyClass = defclass({
    constructor: function () {
        this.myProp = "foo";
    },
    myMethod: function () {
        console.log(this.myProp);
    }
});

顺便说一句,您的实际问题不在于类。这是您从setTimeout调用this.getUpdates的方式:

this.autoUpdateIntervalObj = setInterval(this.getUpdates, interval * 1000);

相反,它应该是:

this.autoUpdateIntervalObj = setInterval(function (self) {
    return self.getUpdates();
}, 1000 * interval, this);

因此,您的DataUnifier类可以编写为:

var DataUnifier = defclass({
    constructor: function () {
        this._local = new Database;
        this._server = new ServerFunctionWrapper;
        this.autoUpdateIntervalObj = null;
    },
    getUpdates: function () {
        this._server.getUpdates(function (updateCommands) {
            console.log(updateCommands);
            if (updateCommands) executeUpdates(updateCommands);
        });
    },
    startAutoUpdating: function (interval) {
        this.stopAutoUpdating();
        this.autoUpdateIntervalObj = setInterval(function (self) {
            return self.getUpdates();
        }, 1000 * interval, this);
    },
    stopAutoUpdating: function () {
        if (this.autoUpdateIntervalObj !== null) {
            clearInterval(this.autoUpdateIntervalObj);
            this.autoUpdateIntervalObj = null;
        }
    }
});

简洁不是吗?如果您需要继承,请查看 augment .

编辑:正如注释中指出的那样,将其他参数传递给setTimeoutsetInterval在低于9的Internet Explorer版本中不起作用。以下填充程序可用于解决此问题:

<!--[if lt IE 9]>
    <script>
        (function (f) {
            window.setTimeout = f(window.setTimeout);
            window.setInterval = f(window.setInterval);
        })(function (f) {
            return function (c, t) {
                var a = [].slice.call(arguments, 2);
                return f(function () {
                    c.apply(this, a);
                }, t);
            };
        });
    </script>
<![endif]-->

由于代码仅在低于 9 的 Internet Explorer 版本上有条件地执行,因此完全不引人注目。只需在所有其他脚本之前包含它,您的代码就可以在每个浏览器上运行。

答案

问题不在于this.stopAutoUpdating();,而在于:

setInterval(this.getUpdates, interval * 1000);

当你将这样的函数传递给setInterval时,它将从事件循环中调用,不知道你在这里this。请注意,this与函数的定义方式无关,而与函数的调用方式有关。您可以通过传入匿名函数来绕过它:

var self = this;
setInterval(function(){ self.getUpdates(); }, interval * 1000);

在任何现代引擎中,您都可以使用更好的绑定:

setInterval(this.getUpdates.bind(this), interval * 1000);

如果您先填充bind,也可以在旧引擎中使用它。


了解问题

我建议您阅读有关电话并申请的信息,以便更好地理解。

请注意,当您正常调用函数而不使用 bind、调用或 apply 时,this将仅设置为从中调用函数的任何对象上下文(即,.之前的任何内容(。

希望这能帮助你理解我所说的this不是关于函数是如何定义的,而是关于它是如何被调用的。下面是一个示例,您可能不希望this工作,但它确实有效:

// This function is not defined as part of any object
function some_func() {
  return this.foo;
}
some_func(); // undefined (window.foo or an error in strict mode)
var obj = {foo: 'bar'};
// But when called from `obj`'s context `this` will refer to obj:
some_func.call(obj); // 'bar'
obj.getX = some_func;
obj.getX(); // 'bar'

您可能希望它工作但事实并非如此的示例,以及使其再次工作的几个解决方案:

function FooBar() {
  this.foo = 'bar';
}
FooBar.prototype.getFoo = function() {
  return this.foo;
}
var fb = new FooBar;
fb.getFoo(); // 'bar'
var getFoo = fb.getFoo;
// getFoo is the correct function, but we're calling it without context
// this is what happened when you passed this.getUpdates to setInterval
getFoo(); // undefined (window.foo or an error in strict mode)
getFoo.call(fb); // bar'
getFoo = getFoo.bind(fb);
getFoo(); // 'bar'

没有"正确"的方法来模拟类。您可以使用不同的模式。您可以坚持使用现在使用的那个。您发布的代码工作正常。或者您可以切换到另一种模式。

例如,道格拉斯·克罗克福德(Douglas Crockford(主张做这样的事情

function myClass() {
    var that = {},
        myMethod = function() {
            console.log(that);
        };
    that.myMethod = myMethod;
    return that;
}

在YouTube上观看他的演讲。http://youtu.be/6eOhyEKUJko?t=48m25s

我使用下面的样式:

function MyClass(){
    var privateFunction = function(){
    }
    this.publicFunction = function(){
        privateFunction();
    }
}

对我来说,这比使用原型方式要直观得多,但结合apply()方法,你会得到类似的结果。

此外,您还有另一种很好的模式,文字对象表示法,揭示模块模式

但是,如果要更改对函数关联this引用,请使用 Function.prototype.apply((,您可以在此示例中看到一种更改对象的全局this的方法。