【问题标题】:Super-simple animation engine in javascript/jqueryjavascript/jquery中的超简单动画引擎
【发布时间】:2012-12-05 12:14:09
【问题描述】:

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

我写了一个简单的jsFiddle

我有一些疑问:

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

  2. jquery deferred object 在这种情况下可以帮助我吗?也许有一种更简单有效的方法来用延迟对象编写它。我需要可以在任何东西上调用的东西(动画、预加载完成、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!");           
    }
}

【问题讨论】:

  • 始终将相关代码和标记发布在问题本身中,不要只是链接。 meta.stackexchange.com/questions/118392/…
  • 我认为在 jsfiddle 上更清楚,谢谢
  • @nkint:两者兼而有之。 :-)
  • 在变量中保留对this(当前上下文)的引用,由您将this 分配给该变量的函数的返回值使用该变量是一个闭包。 var 是函数作用域的局部变量,所以它应该在函数返回后进行 GC,但是因为返回值(在构造函数的情况下:实例)没有超出作用域,并且引用了 var,所以它是保存在内存中,但只能由最初返回的对象访问。因此,该变量与脚本的其余部分关闭,因此:closure

标签: javascript jquery animation jquery-deferred


【解决方案1】:

更新:
我仔细看了你的小提琴。有几件事需要修复。您在构造函数中定义所有方法。只是不要这样做:每次创建实例时,都会一遍又一遍地创建相同的函数。这只是 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上找到方法,而是'将扫描原型并恢复正常服务
我确实希望这个答案不会太混乱,但我非常很累,所以如果是:对不起

【讨论】:

  • 但与类变量 i 相同。无论如何,您将如何编写类似的东西?或者更好,写这个的最好方法是什么?
  • @nkint:JS 没有类,但是prototypes,所以把prototypes 当作类。只需在Engine.finish 之间添加一个.prototype,这就是你需要做的,真的。然而,我很少再使用构造函数了,为了支持the module pattern,我会用我的“原型”方法创建一个闭包,并返回一个对象文字并返回它,但是扩展闭包会将我已经很庞大的答案变成一个书
  • 很清楚!谢谢!无论如何我已经解决了“var self = this;”图案。无论如何,您帖子中的超级有用信息!谢谢!
  • @nkint: var self = this;,嗯,听起来你正在使用模块模式。这总是一个好兆头;),欢迎来到理智和良好实践的世界。尽管有一个小小的建议:self 是 Workers 中的循环引用,并且在某些引擎上是全局对象。也许使用_selfthat 来避免混淆?
  • @nkint: 哦,关于延迟对象的事情:不,这不是前进的方向......即使你确实设法解决了这个问题(使用自定义事件和你有什么),它只会减慢 Engine 对象的速度,这是您在处理引擎时必须避免的。如果您确实想在这里尝试 jQuery,我认为$.when 更合适
【解决方案2】:

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

【讨论】:

  • 他不是在定义静态变量,而是在定义 Engine 函数对象的属性,这恰好是一个构造函数。函数是对象,就像任何其他对象一样,并且可以具有属性
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多