更新:
我仔细看了你的小提琴。有几件事需要修复。您在构造函数中定义所有方法。只是不要这样做:每次创建实例时,都会一遍又一遍地创建相同的函数。这完全是矫枉过正。这是一个工作小提琴,其中所有方法都是原型属性。
特别是有一条线导致了一些问题:
$(this).animate({width:200}, getRandomInt(500, 3000), engine.oncomplete);
在这里,您将对该方法的引用作为oncomplete
回调传递,但调用函数对象的上下文是ad-hoc确定的,因此这不再引用实例engine
,但这是一个简单的解决方法:只需使用包装函数:
$(this).animate({width:200}, getRandomInt(500, 3000), function()
{//this anonymous function will get some context
engine.oncomplete();//but invokes oncomplete in the context of engine, this references engine here
});
另一种方法是:一个var self
覆盖原型方法的闭包(就像你一样,也许是在不知不觉中创建的):
engine.oncomplete = (function(that,prototypeMethod)
{//argument is reference to engine object, and the original method, that we're overriding
{
return function()
{//new version of the method, you can do this right after the instance is created
//or even in the constructor
return prototypeMethod.apply(that,[]);//call in correct context
};
}(engine,engine.oncomplete));
$(this).animate({width:200}, getRandomInt(500, 3000),engine.oncomplete);
delete engine.oncomplete;//reset to prototype method, optional
无论哪种方式,这就是它所需要的。这是有关此事的更多信息-我认为。
还有一些事情可以稍微调整一下,但总的来说,我认为你进展顺利。请记住:方法最好远离构造函数。如果第二个选项有点模糊(它是,IMO)。
我已经向自己保证这将是我将添加到这个答案的最后一个链接:我已经问过一个关于JavaScript 内存泄漏的问题,这是我的另一篇庞大的帖子,但看看一些代码片段:看看函数reusableCallback
是如何被调用的,也看看我发布给我自己的问题的“答案”,有一个例子说明如何原生prototypes
可以使用。如果您感到无聊,或者如果您有兴趣:尝试阅读代码并找出this
在任何给定时间将参考的内容。如果你能解决这个问题,我认为你知道 JS 确定范围的基本原则是公平的。
你不能写this.finish
,仅仅因为this
没有一个名为finish
. 只是改变:
Engine.finish = function(){};
到
Engine.prototype.finish = function(){};
为什么?仅仅因为 JS 函数是一等对象(它们可以分配给变量,无论如何都可以引用函数对象,它们可以作为参数传递给函数,它们可以是函数的返回值,等等......)
只是尝试console.log(Engine instanceof Object);
,它会记录为真。所以你正在做的是这样的:
function Engine(){};
//is hoisted, and interpreted as:
var Engine = function(){};//<-- a function object, that is referenced by a var: Engine
Engine.finish = function(){};
//just like any object, assign a property to the object that Engine is referencing
但是,当您创建一个新的 Engine 对象时:
var myEngine = new Engine();
JS 检查正在引用的任何对象的原型Engine
,在这种情况下:它引用了一个函数。该函数使用new
关键字调用,因此 JS 将创建一个对象。为此,它需要原型(以及很多原型)。这个特定函数(构造函数)的原型没有定义任何属性,所以 JS 会在链中寻找下一个原型,Object
原型。该原型的所有方法和属性都被传递给新实例(尝试myEngine.toString();
and Object.prototype.toString === myEngine.toString
,您会注意到它们共享相同的方法)。
请注意,这并不完全正确/准确,甚至不是您从构造函数创建新对象时发生的所有事情的 10%,但它可能会帮助您了解一些即将发生的事情:
假设您希望每个新实例都有一个finish
方法,并且 - 如您所知 - 所有新 Engine 实例都继承 中的方法Object.prototype
,您可能会这样做:
Object.prototype.finish = function(){};//!!
它肯定会起作用,但它也会起作用,然后:someArray.finish
!是的,数组是对象,函数、对 DOM 元素的引用以及你拥有的东西也是如此。所以不要改变对象原型!
相反,JS 允许您在该特定构造函数的所有实例共享的原型级别上定义某些方法:
function Engine(){};//emtpy
Engine.prototype.i = 0;
var newEngine = new Engine();
console.log(newEngine.i);//0
var anotherEngine = newEngine();
newEngine.i = 1234;
console.log(anotherEngine.i);//0
console.log(newEngine.i);//1234
这将创建一个如下所示的基本原型链:
EngineInstance {list of instance properties}
||
====>Engine.prototype {list of properties that all Engine instances have}
||
====> Object.prototype {properties for all objects, Engine is an object, so it should have these, too}
因此,每当创建新实例时,都会为该新实例分配原型中定义的任何属性。结果,一个方法可以创建一次(而不是在构造函数中,它为每个实例创建一个新函数),所有实例都将共享该方法。但请参阅 MDN 了解更多详情
就这一点:JS 访问对象属性的方式与创建对象的方式相同,只是顺序相反:
调用时newEngine.finish
,JS 会首先查看该实例是否具有自己的finish
属性,如果没有,则Engine
检查原型,如果它也没有完成方法,JS 将继续通过原型链,直到找到所需的属性(一直到Object.prototype
),或者,如果对象原型没有该属性,它只会返回undefined
,这说明了一切,真的:这个属性是undefined
.
作为结果,您可以屏蔽原型方法/属性,只要您需要它们的行为与其默认行为不同,然后只需delete
修改方法即可。这是一个真实的例子:
function doAjax(data, url, method)
{
var xhr = makeAjaxInstance();
data.toString = function()
{
return JSON.stringify(this);
};
//some more stuff, but then:
xhr.send(data + '');//<-- add empty string forces implicit call to the toString method
delete data.toString;
}
doAjax('my/url',{foo:'bar'},'post');
JS 执行其通常的例程:检查toString
方法的实例并找到它。调用该函数,并发送一个整洁的 JSON 字符串。之后,该toString
方法被删除。
JS 在实例本身上搜索该属性。方法被删除了,所以原型的toString方法就不再被屏蔽了,下次调用的时候,JS也会做同样的伎俩,但不是在on上查找方法data
,而是扫描原型,恢复正常服务。
我确实希望这个答案不会太混乱,但是我很累,所以如果是:对不起