2

我需要一个简单的引擎来告诉我一些动画(但也可以是 ajax 调用或任何具有某种“onComplete”功能的东西)何时完成,我的意思是,当所有注册的动画都完成时。

我写了一个简单的jsFiddle

我有一些疑问:

  1. 为什么我不能写this.iorthis.finish但我必须使用Engine.ior Engine.finish?有没有更好的方法来编写这样一个简单的类?我试图在这里记录一下,但仍然不清楚。这个内部引擎是一个 div,因为它在内部被调用,$("#somediv").bind('click', function() { })但我不知道在对象引擎上使用静态 (??) 变量是否是处理它的正确方法。多亏了这个答案,我已经解决了“这种自我重复”(我怎么能调用这个方法?)

  2. 在这种情况下,jquery延迟对象可以帮助我吗?也许有一种更简单有效的方法来用延迟对象编写它。我需要一些我可以在任何东西上调用的东西(动画,在预加载时完成,ajax)..

希望很清楚..任何处理类似情况的建议或链接表示赞赏!

最后的代码

现在是最后一个代码:

engine = new Engine(); 

$("#clickme").bind('click', function() {
    $(".bar").each(function() {
        engine.add()
        $(this).animate({width:200}, getRandomInt(500, 3000), 
                        engine.oncomplete);                      
    });
});

// animation engine
function Engine() {   
    var self = this;
    this.i = 0;
    this.add = function() {
        self.i++;
    }
    this.oncomplete = function() {
        self.i--;
        if(self.i<=0){
            self.finish();
        }
    }     
    this.finish = function() {
        alert("done!");           
    }
}

​</p>

4

2 回答 2

1

更新:
我仔细看了你的小提琴。有几件事需要修复。您在构造函数中定义所有方法。只是不要这样做:每次创建实例时,都会一遍又一遍地创建相同的函数。这完全是矫枉过正。这是一个工作小提琴,其中所有方法都是原型属性

特别是有一条线导致了一些问题:

$(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,而是扫描原型,恢复正常服务
我确实希望这个答案不会太混乱,但是我很累,所以如果是:对不起

于 2012-12-05T12:52:39.857 回答
0

解决点 1:通过使用 Engine.i 和 Engine.finish,您正在定义静态字段。您可能想要使用 Engine.prototype.i 和 Engine.prototype.finish。

于 2012-12-05T12:28:33.200 回答