I recently upgraded our project's jQuery file from 1.4.2 to 1.4.4 and it appears that as of 1.4.3 the way we have been using jQuery.data has stopped working.
We have this code:
var events = $(window).data('events');
if (events.scroll)
if (!events.scroll.include(handler))
$(window).scroll(handler);
the purpose is to prevent this particular handler from being bound multiple times.
In 1.4.2, this works fine. In 1.4.4, events is undefined.
function handler() {
//do something
}
$(document).ready(function(){
$(window).scroll(handler);
$('div#test').scroll(handler);
$(window).data('events') -> undefined
$('div#test').data('events') -> Object
});
What changed with this API? How should I list events for window?
I have changed the first line to this:
var events = $(window).data('__events__').events;
a bit messy-looking, but the ability to wire events to plain objects is compelling.
There was a change in jQuery 1.4.3+ for event types, to avoid object name collisions, for window (or any other plain object) use the key "__events__" instead, like this:
var events = $(window).data('__events__');
The same __events__ key is used for any objects that don't have a .nodeType property (which window doesn't, so it's treated like a plain object here).
To be clear that this was a conscious, intentional change, it's included in the jQuery 1.4.3 release notes:
JavaScript Objects
A number of changes were made to when .data() is used on JavaScript objects (or, more accurately, anything that isn’t a DOM node). To start whenever you set data on a JavaScript object the data is set directly on the object – instead of going into the internal data object store. Additionally events that are attached to objects are put in a new __events__ property that is actually a function. This was done to allow events to be attached directly to an object, be garbage collected when the object is collected, and not be serialized by a JSON serializer. These changes should make jQuery’s data and event systems much more useful on JavaScript objects.
The basic API still seems to work.
However, it doesn't seem to work on the window.
So, the API for accessing jQuery-assigned events hasn't really changed; it just no longer applies to the window. That doesn't exactly sound like an intentional decision, and the 1.4.3 -> 1.4.4 changelog makes no mention of it.
Sounds like a bug, and it might have to do with the recent changes to data now being able to access HTML5 data- attributes. Consider filing a ticket for it :/
Related
I'm trying to build a simple binding framework in JavaScript. I want to have a model with getters and setters that keeps everything consistent, i.e. updating one property can affect others and then JavaScript that then binds that to fields in the page.
The problem I'm having is I need to add some extra code to the setters on the model when I bind so that updating a property executes code that's already in the setter to keep the model consistent and then update the bound input(s).
I thought about renaming the existing property and adding a new property with the original name which could set the original property and update the input but I can't see a way to rename the field.
Is there any way to either rename a property with a getter/setter, modify a setter or get the code in a setter so I can copy that?
Alternatively, is there any other way to achieve what I'm trying to do?
I'm trying to keep separation between business logic (the model) and the UI (the html) without requiring the person writing the model to have to think about the UI / binding at all.
I also don't want to use any of the big libraries like Angular or Knockout as they're a lot of code to include in the page for pretty limited requirements, plus this is a project we've been developing / maintaining for 20 years+ so we don't want to be using a library that has a history of massive breaking changes (Angular).
We currently need to support IE10/11 and modern versions of Chrome / Edge / Firefox / Safari (iOS and Mac). However, if there's something that doesn't support all of these, we're open to doing a "nice" way with a "nasty" fallback until we can drop IE support.
If it makes any difference, we are using TypeScript to write the JavaScript.
Edit:
Since submitting my question, I've found this. It says you can rename a property with:
Object.defineProperty(o, new_key, Object.getOwnPropertyDescriptor(o, old_key));
delete o[old_key];
This is working for renaming simple variable type properties but not for renaming properties with getters and setters. I'm not sure why, although my properties with getters / setters return false when I do hasOwnProperty on them.
Edit2:
Turns out that TypeScript was adding the properties against the prototype instead of the object and this is why they weren't accessible. I called Object.getOwnPropertyDescriptor(o.__proto__, old_key) instead and this gave me the descriptor I needed.
I've found that you can edit / extend the setter by doing the following:
var propDescriptor = Object.getOwnPropertyDescriptor(model, key);
propDescriptor.set = function (value) {
setter.call(model, value);
//Do extra stuff here
}
Object.defineProperty(model, key, propDescriptor);
This means that the property still maintains consistency while allowing you to extend it to update the bound input field.
Is there a tool for Node.js or the browser whereby I can find out which objects hold a reference to object X?
Right now I am using Backbone for front-end development and even though I remove views there still seem to be references to them afterwards.
The reason I suspect this behavior in the first place is because I am using plugin/addons for Backbone debugging in Chrome and Mozilla.
This does make me wonder if perhaps these programs themselves are the ones holding references to the Backbone objects!
First of all,Sadly there is no way to do that.
You can check who calls a function and object which specific variable holds as reference though.
It's not because of Backbone/Node.js but Javascript itself.
When you substitute object/Array, javascript only passes target memory address to the variable.
But I assume it's highly possible that the reason why you are having memory leak problem is not because of references from another variables but event handlers which is often seen in Backbone uses(also knowns as "zombie view")
Once you set events handler in a View, You need to make sure all events are unset before you actually delete the view(.remove()) unless You are using only listenTo for Backbone events and this.$el for jQuery events.
Because events set via listenTo and this.$el are automatically removed by Backbone Core when you remove a View.
And events set by Model.on or global jQuery$ would not be so.
So Please check your whole code whether You are using .on or global jQuery Object to set events, in the case You have, replace them into listenTo or this.$el.on or manually unset them Before You remove them.
I am using Adobe Flash CC to create EaselJS output manipulating the HTML5 canvas. However there seems to be a massive oversight in the ability to pass parameters to event listeners.
The issue is that I offer multiple animation outputs for compatibility, one using EaselJS and one using Raphael, however the interface that controls these remains the same, these are plain HTML elements with data stored in attributes that I wish to call functions written in the Flash IDE by triggering events and passing parameters.
I could easily find ways to avoid using EventDispatcher such as registering them with the root object and calling them directly with custom handlers. However I would rather keep my Flash IDE output as universally compatible as possible and not pollute object's namespaces I have little control over. I consider it a bad design pattern to write code in the Flash IDE that won't work without external aid.
Is there a way to pass parameters from an EaselJS event dispatcher to the listening events? I know on() provides a data parameter but that is useless as I need to pass different parameters to the same event depending upon user interaction.
You can put any properties on an Event object that you want. When you dispatch an event, you can use a string like "complete" (which is better if you don't need parameters, since it won't generate an object if there are no listeners). If you have parameters, create a new createjs.Event, and put whatever properties on it that you want:
var event = new createjs.Event("complete");
event.time = new Date();
this.dispatchEvent(event);
Then you can inspect the event object in your handler.
myObject.addEventListener("complete", handler); // add a listener
function handler(event) {
console.log(event.time);
}
Hope that helps!
The Garmin Communicator API operates through a browser plugin that is exposed to JS from an <object> tag embedded in the HTML body.
I'm trying to find any undocumented methods/properties of this object as I build the GWT-Garmin-API. Working with their JS API source I can see the official methods, but I want to find any other methods/props. So far I cannot find a way to list these from a reference to the Object element in the page.
No debugger I use shows any such props. I was hoping there might be some Object reflection kungfu I don't know about. Thanks.
Update:
Example can be found at the Garmin Hello Device example.
From the console, iterate across the object you'll find from the following:
var plugin = document.getElementsByTagName('object')[0];
for(var prop in plugin) {
console.log( prop );
}
However this will not find plugin methods like plugin.Unlock(), which you can easily call from the same console line.
No debugger I use shows any such props
Then there is no such thing, assuming those host objects are not implemented as Proxies.
Your approach of enumerating properties with a for-in-loop (and even heavier weapons such as Object.getOwnPropertyNames and Object.getPrototypeOf) is flawed, as anything visible like that would be shown in your debugger.
If you really want to find "hidden" properties (I'm very sure there are none), you would need to brute-force test all possible property names. Or have a look into their source, which might be hidden from you if it's a host object.
In general, if you have a reference to object in javascript, you can loop over the properties and methods of that object using:
for(var property in object) {
var value = object[property];
console.log(property + ' = ' + value);
}
Given the source code you linked, you could also try iterating over the prototypes of some of the Garmin classes, like:
for(var property in Garmin.DevicePlugin.prototype) {
//...
}
If it doesn't show up when you iterate in one of these ways, it means that the property is not exposed to javascript. "Callable" methods that that don't show up (like plugin.unlock()) are defined within the plugin itself. (When you call a method like this, you can think of it like passing a message from javascript directly into the implementation of the plugin.) The only way I know to find a "list" of these methods is to have access to the source code of the plugin that you are using. There is no way for javascript to ask for this list, unless the plugin has specifically implemented something to enable that kind of functionality.
I am a little confused how jQuery stores data with .data() functions.
Is this something called expando?
Or is this using HTML5 Web Storage although I think this is very unlikely?
The documentation says:
The .data() method allows us to attach data of any type to DOM elements in a way that is safe from circular references and therefore from memory leaks.
As I read about expando, it seems to have a risk of memory leak. Unfortunately my skills are not enough to read and understand jQuery code itself, but I want to know how jQuery stores such data by using data().
http://api.jquery.com/data/
Basically jQuery holds the information you store/retrieve with data(name, value)/data(name) and remove with removeData(name) in an internal javascript object named cache. The rest is just a bit of javascript magic to make it work and keep all the associations right.
Oh and to answer the first part of your question. Neither is it expando nor HTML5 WebStorage.
To correct myself I think jQuery does use expando in one occasion. It sets one attribute on those elements you used data() on to store information to them. The attribute name looks like this
"jQuery" + now() //e.g. jQuery1268647073375
btw. now() is an internal function which returns (new Date).getTime()
and the value is an UUID generated by jQuery.
This way jQuery later on can retrieve the correct associated data from it's internal cache.
So if you are concerned about expando in IE, where I recall you can't delete them, then the leak should be minimal as jQuery only uses 1 expando per element you store data on. Unless you call data() on literally 1000s of elements I see no memory problems
Function data in jQuery.fn.extend is using this statement to save provided variable:
jQuery.cache[ id ][ name ] = data;
jQuery.cache is just a standard object, defined as cache: {}, inside jQuery namespace.
So answering your question - I believe jQuery stores data in standard, internal JavaScript object called cache.
Oh, and regarding your memory leak question - I really don't know. If JavaScript has some troubles storing references to DOM elements in standard JS object this might be an issue.
Also check out the metadata plugin - it extracts metadata from a DOM Element and returns it as an object (discussed in comments here).
Its put into cache by the browser locally much like a cookie
from jquery uncompressed:
if ( data !== undefined ) {
thisCache[ name ] = data;
}