更新:
我仔细看了你的小提琴。有几件事需要修复。您在构造函数中定义所有方法。只是不要这样做:每次创建实例时,都会一遍又一遍地创建相同的函数。这只是 total 矫枉过正。这里是a working fiddle, where all methods are prototype properties。
特别是有一行导致了一些问题:
$(this).animate({width:200}, getRandomInt(500, 3000), engine.oncomplete);
在这里,您将 reference 作为回调传递给 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
无论如何,这就是它所需要的。 Here's some more info on the matter - I think.
还有一些事情可以稍微调整一下,但总的来说,我认为你的进展顺利。请记住:方法最好远离构造函数。如果第二个选项有点模糊(它是,IMO)。
我已经向自己保证,这将是我将添加到这个答案中的最后一个链接:我在memory-leaks in JavaScript 上问过一个问题,这是我的另一篇庞大的帖子,但看看一些代码 sn-ps:看看函数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 所引用的任何对象的 prototype,在这种情况下:它引用了一个函数。该函数使用new关键字调用,所以JS会创建一个对象。为此,它需要原型(以及很多原型)。这个特定函数(构造函数)的 prototype 没有定义任何属性,因此 JS 在链中寻找下一个原型,Object 原型。该原型的所有方法和属性都被传递给新实例(试试myEngine.toString(); 和Object.prototype.toString === myEngine.toString,您会发现它们共享相同的方法)。
请注意,这并不完全正确/准确,甚至不是您从构造函数创建新对象时发生的所有事情的 10%,但它可能会帮助您了解一些即将发生的事情:
假设您希望每个新实例都有一个 finish 方法,并且 - 如您所知 - 所有新 Engine 实例都继承 Object.prototype 中的方法,您可能会这样做:
Object.prototype.finish = function(){};//!!
它肯定会起作用,但它也会起作用,然后:someArray.finish!是的,数组是对象,函数、对 DOM 元素的引用以及你拥有的东西也是如此。所以不要更改 Object 原型!
相反,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}
因此,每当创建新实例时,都会为该新实例分配原型中定义的任何属性。结果,一个方法可以创建一次(而不是在构造函数中,它为每个实例创建一个新函数),所有实例都将共享该方法。 but refer to MDN for more details
就这一点: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也会做同样的伎俩,只不过不是在data上找到方法,而是'将扫描原型并恢复正常服务。
我确实希望这个答案不会太混乱,但我非常很累,所以如果是:对不起