“如何创建JavaScript”;“类”;将方法添加到原型并使用';这';正确地

“如何创建JavaScript”;“类”;将方法添加到原型并使用';这';正确地,javascript,methods,prototype,this,Javascript,Methods,Prototype,This,我一直被教导在JavaScript中模拟类的正确方法是在将成为类的函数之外向原型添加方法,如下所示: function myClass() { this.myProp = "foo"; } myClass.prototype.myMethod = function() { console.log(this); } myObj = new myClass(); myObj.myMethod(); 我的方法中的this解决了全局窗口对象的问题 我试过做var=thistrick

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

function myClass()
{
    this.myProp = "foo";
}

myClass.prototype.myMethod = function()
{
    console.log(this);
}

myObj = new myClass();
myObj.myMethod();
我的方法中的
this
解决了全局窗口对象的问题

我试过做
var=this
trick Koch提到,但由于我的方法在我的类之外,所以我的
变量不再在范围内。也许我只是没有完全理解它

有没有一种方法可以在JavaScript中创建一个类,在这个类中,每个实现都不会重新创建方法,并且
这个
始终指向对象

编辑:

上面的简化代码可以工作,但我已经多次声明了与上面完全相同的“类”,当我调用
myObj.myMethod()
时,它会作为
窗口
对象返回。我已经阅读了我能找到的关于
这个
的所有解释,比如我链接到的解释,但仍然不理解为什么有时会发生这个问题。有没有想过代码可以像上面那样编写,而
这个
是指
窗口

以下是我目前遇到的问题,但当我将其简化为上面的几行时,我不再有问题:

HTML文件:

<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>
main.js

var dataObj = new DataUnifier();

$(document).ready(function ev_document_ready() {
    dataObj.startAutoUpdating(5);
}
我删掉了一些本不重要的代码,但也许确实重要。当页面加载并调用dataObj.startAutoUpdating(5)时,它在this.stopAutoUpdate()处中断;行,因为此
引用了
窗口
对象。就我所见(根据提供的链接),
这个
应该引用DataUnifier对象。我已经阅读了很多关于
这个
关键字的资料,不明白为什么我一直遇到这个问题。我不使用内联事件注册。是否有任何原因导致这种格式的代码出现此问题


编辑2:对于那些有类似问题的人,请参阅本Mozilla文档页面下半页的“该问题:

我使用下面的样式:

function MyClass(){
    var privateFunction = function(){

    }

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

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

另外,你还有另一个好的模式

但是,如果您想更改对函数的
this
关联的引用,请使用,您可以查看将全局
this
更改为对象的方法。

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

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

function myClass() {
    var that = {},
        myMethod = function() {
            console.log(that);
        };
    that.myMethod = myMethod;
    return that;
}
在youtube上观看他的演讲。
答案

问题不在于
this.stopAutoUpdate(),它具有:

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

var self = this;
setInterval(function(){ self.getUpdates(); }, interval * 1000);
在任何现代发动机中,您都可以使用更好的:

如果您首先选择绑定,也可以在旧引擎中使用
bind


了解问题

我建议您阅读和,以便更好地理解

请注意,当您正常调用函数时,不使用bind、call或apply,那么
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'

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

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}
var MyClass = defclass({
    constructor: function () {
        this.myProp = "foo";
    },
    myMethod: function () {
        console.log(this.myProp);
    }
});
使用
defclass
功能,您可以定义
MyClass
,如下所示:

function defclass(prototype) {
    var constructor = prototype.constructor;
    constructor.prototype = prototype;
    return constructor;
}
var MyClass = defclass({
    constructor: function () {
        this.myProp = "foo";
    },
    myMethod: function () {
        console.log(this.myProp);
    }
});
顺便说一句,你的实际问题不在于课程。这是您调用此的方式。从
setTimeout
获取更新:

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;
        }
    }
});
简洁,不是吗?如果您需要继承,请查看

编辑:如注释中所述,将附加参数传递到
setTimeout
setInterval
在小于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的InternetExplorer版本上有条件地执行,因此它是完全不引人注目的。只需在所有其他脚本之前包含它,您的代码就可以在每个浏览器上运行。

Scope与
无关;所以
Function.prototype.apply()
与作用域无关,它与被调用函数中的
this
值有关。这是初学者常见的误解。您(现在已更正)的风格有一个缺点,即每次调用构造函数时,您都在创建(此处:两个)新的
函数
实例。现在,要创建一百个
myClass
实例,您需要创建三百个对象并为它们保留堆内存。如果您真的需要实例私有部分,这种模式是有意义的;在所有其他情况下,这都是过激的,并构成了巨大的开销。顺便说一句,按照惯例,构造函数的标识符以大写开头。我不这样认为