How to watch complex objects and their changes in JavaScript? [duplicate] - javascript

This question already has answers here:
Watch for object properties changes in JavaScript [duplicate]
(3 answers)
How to watch for array changes?
(10 answers)
Object.watch() for all browsers?
(9 answers)
Closed 4 years ago.
I'm trying to find similar functionality to AngularJS's $watch function (as defined here) which allows for 'watching of complex objects' and their changes. To the best of my knowledge, I understand it as being able to watch changes to variables within the object even if they themselves are also within an object (within the object being watched).
I wish to have this same 'watchability' in native JavaScript (or JQuery) but I can't seem to find anything. I know of Object.watch() and the Polyfill as found here but I'm pretty certain this only does reference checking or only watches the 'immediate' variables within the object and not anything that is nested so to speak and does not check properties 'deep' inside the object.
Does anyone know of any library, functions, anything that could help me to provide this 'deep watching' capability? Or even help me to understand Object.watch() a bit better if it does in-fact provide what I'm wanting?
I am creating a real-time music application and want to have a 'deep' watch on the instrument so I can see if any of its variables, parameters, etc.. change so I can sync it to the server and other clients.
Any help would be greatly appreciated, thanks!

As #Booster2ooo mention you can use Proxy object to observe the changes, you can use something like this:
function proxify(object, change) {
// we use unique field to determine if object is proxy
// we can't test this otherwise because typeof and
// instanceof is used on original object
if (object && object.__proxy__) {
return object;
}
var proxy = new Proxy(object, {
get: function(object, name) {
if (name == '__proxy__') {
return true;
}
return object[name];
},
set: function(object, name, value) {
var old = object[name];
if (value && typeof value == 'object') {
// new object need to be proxified as well
value = proxify(value, change);
}
object[name] = value;
change(object, name, old, value);
}
});
for (var prop in object) {
if (object.hasOwnProperty(prop) && object[prop] &&
typeof object[prop] == 'object') {
// proxify all child objects
object[prop] = proxify(object[prop], change);
}
}
return proxy;
}
and you can use this fuction like this:
object = proxify(object, function(object, property, oldValue, newValue) {
console.log('property ' + property + ' changed from ' + oldValue +
' to ' + newValue);
});
...

You shouldn't use Object.watch
Warning: Generally you should avoid using watch() and unwatch() when possible. These two methods are implemented only in Gecko, and they're intended primarily for debugging use. In addition, using watchpoints has a serious negative impact on performance, which is especially true when used on global objects, such as window. You can usually use setters and getters or proxies instead. See Browser compatibility for details. Also, do not confuse Object.watch with Object.observe.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/watch
I'd rather have a look at Proxies:
The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
And maybe MutationObserver if the DOM in implied:
MutationObserver provides developers with a way to react to changes in a DOM. It is designed as a replacement for Mutation Events defined in the DOM3 Events specification.
https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
Explore & enjoy :)

jcubic's answer is solid, but unfortunately it will not work with nested objects.
I published a library on GitHub (Observable Slim) that will allow you to observe/watch for changes that occur to a object and any nested children of that object. It also has a few extra features:
Reports back to a specified callback whenever changes occur.
Will prevent user from trying to Proxy a Proxy.
Keeps a store of which objects have been proxied and will re-use existing proxies instead of creating new ones (very significant performance implications).
Written in ES5 and employs a forked version of the Proxy polyfill so it can be deployed in older browsers fairly easily and support Array mutation methods.
It works like this:
var test = {testing:{}};
var p = ObservableSlim.create(test, true, function(changes) {
console.log(JSON.stringify(changes));
});
p.testing.blah = 42; // console: [{"type":"add","target":{"blah":42},"property":"blah","newValue":42,"currentPath":"testing.blah",jsonPointer:"/testing/blah","proxy":{"blah":42}}]
Please feel free to take a look and hopefully contribute as well!

Related

How to store a weak reference to an object? [duplicate]

Is there any way in JavaScript to create a "weak reference" to another object? Here is the wiki page describing what a weak reference is. Here is another article that describes them in Java. Can anyone think of a way to implement this behavior in JavaScript?
Update: Since July, 2020 some implementations (Chrome, Edge, Firefox and Node.js) has had support for WeakRefs as defined in the WeakRefs proposal, which is a "Stage 3 Draft" as of December 16, 2020.
There is no language support for weakrefs in JavaScript. You can roll your own using manual reference counting, but not especially smoothly. You can't make a proxy wrapper object, because in JavaScript objects never know when they're about to be garbage-collected.
So your ‘weak reference’ becomes a key (eg. integer) in a simple lookup, with an add-reference and remove-reference method, and when there are no manually-tracked references anymore then entry can be deleted, leaving future lookups on that key to return null.
This is not really a weakref, but it can solve some of the same problems. It's typically done in complex web applications to prevent memory leakage from browsers (typically IE, especially older versions) when there is a reference loop between a DOM Node or event handler, and an object associated with it such as a closure. In these cases a full reference-counting scheme may not even be necessary.
When running JS on NodeJS, you may consider https://github.com/TooTallNate/node-weak.
Update: September 2019
It is not possible to use weak references yet, but most likely soon it will be possible, as WeakRefs in JavaScript are Work In Progress. Details below.
Proposal
Proposal in now in Stage 3 which means that it has complete specification and that further refinement will require feedback from implementations and users.
The WeakRef proposal encompasses two major new pieces of functionality:
Creating weak references to objects with the WeakRef class
Running user-defined finalizers after objects are garbage-collected, with the FinalizationGroup class
Use cases
A primary use for weak references is to implement caches or mappings holding large objects, where it’s desired that a large object is not kept alive solely because it appears in a cache or mapping.
Finalization is the execution of code to clean up after an object that has become unreachable to program execution. User-defined finalizers enable several new use cases, and can help prevent memory leaks when managing resources that the garbage collector doesn't know about.
Source and further reading
https://github.com/tc39/proposal-weakrefs
https://v8.dev/features/weak-references
2021 Update
WeakRef is now implemented in Chrome, Edge, and Firefox. Still waiting on Safari and some other holdouts.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakRef
May 2021 Update
It's now available on Safari thus all major browsers. See above.
Just for reference; JavaScript doesn't have it, but ActionScript 3 (which is also ECMAScript) does. Check out the constructor parameter for Dictionary.
Finally they are here. Not yet implemented in browsers, but soon to be.
https://v8.dev/features/weak-references
True weak references, no, not yet (but browser makers are looking at the subject). But here is an idea on how to simulate weak references.
You could build a cache which you drive your objects through. When an object is stored, the cache keeps a prediction of how much memory the object will take up. For some items, like storing images, this is straight forward to work out. For others this would be more difficult.
When you need an object, you then ask the cache for it. If the cache has the object, it is returned. If it is not there, then the item is generated, stored, and then returned.
The weak references are simulated by the cache removing items, when the total amount of predicted memory reaches a certain level. It will predict which items are least used based on how often they are retrieved, weighted by how long ago they were taken out. A 'calculation' cost could also be added, if the code that creates the item is passed into the cache as a closure. This would allow the cache to keep items which are very expensive to build or generate.
The deletion algorithm is key, because if you get this wrong then you could end up removing the most popular items. This would cause terrible performance.
As long as the cache is the only object with permanent references to the objects stored, then the above system should work pretty well as an alternative to true weak references.
Using a caching mechanism to emulate a weak reference, as JL235 suggested above, is reasonable. If weak references would exist natively, you would observe a behavior like this:
this.val = {};
this.ref = new WeakReference(this.val);
...
this.ref.get(); // always returns val
...
this.val = null; // no more references
...
this.ref.get(); // may still return val, depending on already gc'd or not
Whereas with a cache you would observe:
this.val = {};
this.key = cache.put(this.val);
...
cache.get(this.key); // returns val, until evicted by other cache puts
...
this.val = null; // no more references
...
cache.get(this.key); // returns val, until evicted by other cache puts
As a holder of a reference, you should not make any assumptions about when it refers to a value, this is no different using a cache
the proposal and some details https://github.com/tc39/proposal-weakrefs
Typescript copy/paste version
export class IterableWeakMap<T extends Object, V> {
weakMap = new WeakMap();
refSet = new Set<WeakRef<T>>();
finalizationGroup = new FinalizationRegistry(IterableWeakMap.cleanup);
static cleanup({ set, ref }: { set: Set<WeakRef<Object>>; ref: WeakRef<Object> }) {
set.delete(ref);
}
constructor(iterable?: Iterable<[T, V]>) {
if (!iterable) return;
for (const [key, value] of iterable) {
this.set(key, value);
}
}
set(key: T, value: V) {
const ref = new WeakRef<T>(key);
this.weakMap.set(key, { value, ref });
this.refSet.add(ref);
this.finalizationGroup.register(key, { set: this.refSet, ref }, ref);
}
get(key: T) {
const entry = this.weakMap.get(key);
return entry && entry.value;
}
delete(key: T) {
const entry = this.weakMap.get(key);
if (!entry) {
return false;
}
this.weakMap.delete(key);
this.refSet.delete(entry.ref);
this.finalizationGroup.unregister(entry.ref);
return true;
}
*[Symbol.iterator]() {
for (const ref of this.refSet) {
const key = ref.deref();
if (!key) continue;
const { value } = this.weakMap.get(key);
yield [key, value];
}
}
entries() {
return this[Symbol.iterator]();
}
*keys() {
for (const [key] of this) {
yield key;
}
}
*values() {
for (const [, value] of this) {
yield value;
}
}
}
EcmaScript 6 (ES Harmony) has a WeakMap object. Browser support amongst modern browsers is pretty good (the last 3 versions of Firefox, chrome and even an upcoming IE version support it).
http://www.jibbering.com/faq/faq_notes/closures.html
ECMAScript uses automatic garbage collection. The specification does not define the details, leaving that to the implementers to sort out, and some implementations are known to give a very low priority to their garbage collection operations. But the general idea is that if an object becomes un-referable (by having no remaining references to it left accessible to executing code) it becomes available for garbage collection and will at some future point be destroyed and any resources it is consuming freed and returned to the system for re-use.
This would normally be the case upon exiting an execution context. The scope chain structure, the Activation/Variable object and any objects created within the execution context, including function objects, would no longer be accessible and so would become available for garbage collection.
Meaning there are no weak ones only ones that no longer become available.

Alternative methods for extending object.prototype when using jQuery

Some time ago I tried to extend Object.prototype... I was surprised when later I saw errors in the console which comes from jQuery file. I tried to figured out what is wrong and of course I found information that extending Object.prototype is a "evil", "you shouldn't do that because JS is dynamic language and your code will not work soon" and information that jQuery will now add hasOwnProperty method to their for in loops.
Because I didn't want to leave jQuery, I drop the idea about extending Object.prototype.
Till now. My project getting bigger and I am really annoyed because I have to repeat many times some parts of the code. Below is a bit of the structure which I am using in my projects:
charts.js:
CHARTS = {
_init: function () {
this.monthlyChart();
/*
*
* more propertys goes here
*
*/
return this;
},
monthlyChart: function () {
//create my chart
return {
update: function () {
// update chart
}
};
}()
/*
*
* more propertys goes here
*
*/
}._init;
dashboard.js
NAVBAR = {
_init: function () {
/*
*
* more propertys goes here
*
*/
return this;
},
doSomething: function(){
$(document).ready(function(){
$('.myButton').on('click', function(){
var data = [];
// calling property from charts.js
CHARTS.monthlyChart.update(data);
});
});
}
}._init
As I mentioned project is really big now - it's over 40 js files and some of them has a few thousands line of code. It is really annoying that I have to repeat _init section every time, as well as I many functions I have to repeat $(document).ready && $(window).load.
I tried to find another solution for my problem. I tried to create class with init property (more you can find here) but I this solution forced me to add another "unnecessary" piece of the code to every file and accessing other file object property makes it to complicated too (return proper objects everywhere etc). As advised in the comment I started reading about getters and setters in JS.
After all I created something like that:
//Auto initialization
if (typeof $document === 'undefined') {
var $document = $(document),
$window = $(window),
$body = $('body');
}
Object.defineProperty(Object.prototype, '_init', {
get: function () {
// if object has no property named `_init`
if (!this.hasOwnProperty('_init')) {
for (var key in this) {
// checking if name of property does starts from '_' and if it is function
if (this.hasOwnProperty(key) && key[0] === '_' && typeof this[key] === 'function') {
if (key.indexOf('_ready_') > -1) {
//add function to document ready if property name starts from '_ready_'
$document.ready(this[key].bind(this));
} else if (key.indexOf('_load_') > -1) {
//add function to window load if property name starts from '_load_'
$window.load(this[key].bind(this));
} else {
// else execute function now
this[key].bind(this)();
}
}
}
return this;
}
}
});
and my object:
var DASHBOARD = {
_runMe: function(){
},
_ready_runMeOnReady: function(){
},
_load_runMeOnLoad: function(){
},
iAmAString: ''
}._init
It seems that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.
Also I'm trying to understand how it works in details. Theoretically I defined property for the Object.prototype by defineProperty, without assigning value to it. Somehow it doesn't cause any errors in jQuery fore in loop, why? Does that mean that property _init is not defined at some point or at all because I am defined only getter of it?
Any help will be appreciated :)
By not including the descriptor in Object.defineProperty(obj, prop, descriptor) JavaScript defaults all the Boolean descriptor attributes to false. Namely
writable, enumerable, and configurable. Your new property is hidden from the for in iterators because your _init property is enumerable:false.
I am not a fan of JQuery so will not comment on why in regard to JQuery
There is no absolute rule to adding properties to JavaScript's basic type and will depend on the environment that your code is running. Adding to the basic type will add it to the global namespace. If your application is sharing the namespace with 3rd party scripts you can potentially get conflicts, causing your code or the third party code or both to fail.
If you are the only code then conflicts will not be an issues, but adding to object.prototype will incur an addition overhead on all code that uses object.
I would strongly suggest that you re examine the need for a global _init. Surely you don't use it every time you need a new object. I am a fan of the add hock approach to JavaScript data structures and try to keep away from the formal OOP paradigms
Your question in fact contains two questions.
It seams that this solution works with jQuery. But is it safe to use? I don't see any problem the code can cause and I don't see any further problems that it may cause. I will be really happy if somebody will tell me why I shouldn't use this solution.
First of all, there are three main reasons to avoid modification of built-in prototypes.
For-in loops
There is too much code using for-in loop without hasOwnProperty check. In your case that is jQuery code that does not perform check.
Solutions
Don't use for-in loop without .hasOwnProperty check.
Doesn't apply in this case because it's third-party code and you can't modify it.
for-in loop traverses only enumerable keys.
You have used that solution. Object.defineProperty creates non-enumerable properties by default (ECMAScript 5.1 specification)
Not supported by IE8.
Conflicts
There is risk of property name. Imagine that you use jQuery plugin that checks for existence of ._init property on objects - and it can lead to subtle and hard to debug bugs. Names prefixed with underscore are widely used in modern JavaScript libraries for indicating private properties.
Encapsulation violation (bad design)
But you have worser problem. Definining global ._init property suggests that every object have universal initialization logic. It breaks encapsulation, because your objects don't have full control over their state.
You can't rely on presence of _init method due to this. Your coworkers can't implement their own class with
Alternative designs
Global initializer
You can create global function initialize and wrap all your objects that require initialization in it.
Decouple view and logic
Your objects should not merge logic and view in one object (it violates single responsibility principle) and you are victim of spaghetti code.
Moreover - object initialization should not bind it to DOM, some controller objects should be a proxy between your logic and display.
It can be good idea to inspect how popular client-side MVC frameworks have solved this problem (Angular, Ember, Backbone) have solved this problem.
Is it safe to use getters and setters?
Yes. But if you only support IE9+.
Is it safe to modify Object.prototype?
No. Create another object to inherit all of your application objects from.
Why extending basic JavaScript objects is eval evil?
Because EVERY SINGLE object created on the webpage where your script is loaded will inherit that property or method.
There is a lot cons like collisions and performance overhead if you do it that way.
There is a lot of ways to make it better, let me show you the one I use.
// Here we create the base object:
var someBaseObject = {};
someBaseObject.someMethod = function () {
// some code here
}
someBaseObject.someProperty = "something";
// And inherit another object from the someBaseObject
someObject = Object.create(someBaseObject);
someObject.someAnotherMethod = function () {
// some code here
}
This approach allow us to leave the Object prototype alone, and build a prototype chain where someObject inherits from someBaseObject, and someBaseObject inherits from Object.
The only thing I want to say by this post: leave base objects alone and build your own, so you will have much less headache.
Note: Object.create is supported in IE9+. Here is shim for IE8 and lower by Douglas Crockford:
if (typeof Object.create !== 'function') {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}

Is there Proxy-object polyfill available google chrome?

Is this even possible? How about other browsers? Any estimates when es6 will be "ready" and rolled out?
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy
btw. https://github.com/tvcutsem/harmony-reflect Proxy does not work with current chrome (36.0.n)
You could use Object.defineProperty and Object.observe to to simulate a Proxy. I started to wonder how much functionality a polyfill could support, so I wrote an implementation (you can see it at gist.github.com/mailmindlin/640e9d707ae3bd666d70).
I was able to emulate all of the features of Proxy that didn't rely on operator overloading, whic isn't possible in JavaScript as of now.
However, you can get the get, set, and a few others working. You can use getters and setters to mirror the target object's properties:
for (var property in target)
Object.defineProperty(proxy, property, {
get: function() {
if ('get' in handler)
return handler.get(target, property, proxy);
else
return target[property];
},
set: function(value) {
if ('set' in handler)
handler.set(target, property, value, proxy);
else
target[property] = value;
}});
The only problem with that is that the getters and setters only apply to properties that were defined in for the target when the proxy was initialized, and the delete operator won't work (If you delete a property on the target object, the proxy will still enumerate it as a property; if you delete a property on the proxy, nothing will happen to the object).
To fix that, you can use Object.observe which will be called on any change to either object. A quick check on caniuse.com shows that Object.observe is available on Chrome and Opera. If you really need support for Proxy on another browser, you can poll the target and proxy objects, to check if any properties have been created or destroyed:
var oldKeys = Object.keys(proxy);
setInterval(function() {
var keys = Object.keys(proxy);
for(var i in keys)
if(!oldKeys.includes(keys[i]))
//Trigger code for a new property added
for(var i in oldKeys)
if(!keys.includes(oldKeys[i]))
//trigger code for a deleted property
oldKeys = keys;
//repeat for target object
}, 100);
If you desperately need more features of the proxy, you can try overriding methods such as Object.defineProperty and Object.getOwnPropertyDescriptor, but that might create compatibility issues with other scripts, depending on how you do it.
In short, you can do most of what you'll probably need to use Proxy for with a polyfill. As far as Google adding it to their browser, I have no idea. It apparently used to be part of the V8 engine, but it was removed because of security problems (that nobody elaborates on), as far as I could tell based on this thread.
I have created babel plugin whic allows you to do this but it comes with huge performance impact (for each property access) - it is more education example.
https://github.com/krzkaczor/babel-plugin-proxy
Here is one created by the Google Chrome team:
https://github.com/GoogleChrome/proxy-polyfill
It's not a full implementation, though.
Update: Although my answer provides a partial solution, mailmindlin's solution proves that my main point is false: you can create a polyfill for Proxy.
No, you can't. Because Proxys rely on a special (new) behavior of several language syntax elements — namely the . operator and the [index] operator — it cannot be emulated by a polyfill.
The only way do it is to change the syntax that you use. For example, if you wanted to uppercase all string properties via a proxy, you could create a "proxy" object like so:
var object = ...
var proxy = {
get: function proxyGet(key) {
var res = object[key];
if (typeof res === "string") {
res = res.toUpperCase();
}
return res;
}
}
But, then you would still have to call it differently:
proxy.get("myVar");
instead of
object.myVar;
or
proxy.myVar
which is what the new Proxy syntax supports.
Note: You could almost create a polyfill that worked only for methods, by enumerating the function properties of the object, and creating a proxy function on the proxy object for each one of these properties; however, this would not work for non-function properties, since you can't dynamically affect the way they are accessed.

Substitute __proto__ of DOM Element Object

So basically I would like to extend a certain type of DOM elements by the following code:
var element = document.createElement("div");
var proto = Object.create(HTMLDivElement.prototype);
proto.newMethod = function() {console.log("Good.");};
proto.newConst = Math.PI / 2;
element.__proto__ = proto;
This code works in Chrome, Firefox and IE11 (IE10 not tested, but it will probably work), but I'm not sure whether it is proper JavaScript and whether it will continue to work in the future, because anyway this code is hacking DOM elements which is partially outside JavaScript. Could someone give explanation on how it works? I don't fully understand that, and I need to know if this method is robust. Thanks.
OK, to make things clearer, I know I should use Object.create() to specify prototype, but the real problem is that element objects are special and it's impossible to do that. The above code is more like a workaround, and this is why I'm asking this question.
Google's Polymer mutates __proto__ of DOM objects (code, line 259):
function implement(element, definition) {
if (Object.__proto__) {
element.__proto__ = definition.prototype;
} else {
customMixin(element, definition.prototype, definition.native);
element.__proto__ = definition.prototype;
}
}
So, should I trust this method because Google uses it?
From Mozilla Developer Network:
The __proto__ property is deprecated and should not be used. Object.getPrototypeOf should be used instead of the __proto__ getter to determine the [[Prototype]] of an object. Mutating the [[Prototype]] of an object, no matter how this is accomplished, is strongly discouraged, because it is very slow and unavoidably slows down subsequent execution in modern JavaScript implementations. However, Object.setPrototypeOf is provided in ES6 as a very-slightly-preferred alternative to the __proto__ setter.
In general, it is a bad practice to modify native prototypes like Array, String and even HTMLElement, details are described here, but if you control everything in the current context you can modify the prototypes by adding, on your own risk, some additional functional to achieve what you want. If you can guarantee that your code is not in conflict with some other code and the performance footprint is negligible then you are free to choose your path.
Your approach:
SomeHTMLElementInstance.__proto__ = newPrototype;
// or a general case like:
SomeHTMLElementPrototypeConstructor.prototype.newMethod = function () {
// Do something here
}
Recommended approach:
var SomeElementWrapper = function (someParams) {
this.container = document.createElement('SomeHTMLElement');
}
SomeElementWrapper.prototype.someMethod = function () {
// Do something with this.container without modifying its prototype
}

Problems with extending regular Objects to support ES5 Array capabilities

I have long ago been willing to get the line between native Arrays and regular Objects totally blurred, not only extending Object with the same capabilities as Arrays got in ES5, but bundle up with my custom package of methods on both sides.
Couple of smart people thought about these paradigm changes. Like Angus Croll mentioned it in the article javascript-object-keys-finally:
"Moreover as the line between Arrays and regular Objects blurs (aided
by custom getters and setters) we’re likely to see a growth in generic
“array-like” objects which enjoy the best of both worlds – non-numeric
identifiers and access to the rich API set defined by Array.prototype.
EcmaScript 5 has apparently pre-empted this trend by introducing the
generic method, defined by one type but useable by any."
Along the way, he get things coded in the article:
extending-objects-with-javascript-getters
function extendAsArray(obj) {
if (obj.length === undefined || obj.__lookupGetter__('length')) {
var index = 0;
for (var prop in obj) {
if(!obj.__lookupGetter__(prop)) {
(function(thisIndex, thisProp) {
obj.__defineGetter__(thisIndex, function() {return obj[thisProp]});
})(index, prop)
index++;
}
};
obj.__defineGetter__("length", function() {return index});
}
return obj;
}
var myObj = {
left:50,
top:20,
width:10,
height:10
}
extendAsArray(myObj);
[].map.call(myObj,function(s){return s+' px'}).join(', ');
//"50px ,20px ,10px, 10px"
This approach is exceptionally interesting for me. However, it is also seemingly suffering a couple of serious problems!
How about extending the original myObj model with a couple of new properties?
Should we run extendAsArray on every property change to update it's concerning length property?
When a property changes, it's not just the length property that's relevant;
the array indices should also be updated, because an array-like property request definitely turns out to be undefined. So when
console.log(myObj.length) -> 4
myObj.zAxis=0
then
console.log(myObj[4]) // -> undefined!
console.log(myObj.length) // -> 4!
I have modified Angus' code accordingly, so it supports automatic update of length property on request:
function extendAsArray(obj) {
var index = 0;
for(var prop in obj){
if(!obj.__lookupGetter__(prop)){
(function(thisIndex, thisProp){
Object.defineProperty(obj, thisIndex, {
get: function(){return obj[thisProp]}
, enumerable: true
, configurable: true
, writeable: true
});
})(index, prop)
index++;
}
}
if(!obj.__lookupGetter__('length')){
Object.defineProperty(obj, 'length', {
get: function(){
return extendAsArray(obj);
}
, configurable: true
, writeable: true
});
return obj;
}
else{
return index;
}
}
The problem is: how do we updating the object's array indices together with its length property when a property is changed, added or removed?
Should I use Object.watch?
And there is still an unsolved question: how to interfere with my own unshimmed utility library, having made it also for Objects in a consistent way?
I am using the same codebase for both types: z.Object({}).mapEvery does the same as z.Object([]).mapEvery
Please avoid mentioning JQuery, and Underscore as well. I have got a comprehensive, custom list of methods for both types, and I am willing to use the standards completed possibly with my unshimmed ones, and I am not willing to refactor it!
I guess this is your question:
how do we updating the object's array indices together with its length property when a property is changed, added or removed?
You create methods to do it, so you essentially mimic the Object internal methods. I don't think you can do that with getters and setters, but I may be wrong about that.
The rest is more of a comment than an answer.
I have long ago been willing to get the line between native Arrays and regular Objects totally blurred
The line is already totally blurred. Arrays are Objects, the only thing that sets them apart is their special length property.
EcmaScript 5 has apparently pre-empted this trend by introducing the generic method,
ES5 didn't introduce generic methods, they have been in the language since ed 3 at least.
defined by one type but useable by any
Not at all, in fact ES5 is more restrictive. In ed 3, call and apply coerce the thisArg to an object using Object(*thisArg*), or substitute the global object if nothing was passed. Not so in ES5 which passes thisArg unmodified.
The restriction on using arrays as objects is related to convention, not the language itself. Most developers see a clear divide between when an object or array should be used. There are few cases where you really need to use an array like an object, but no doubt they exist. jQuery is an example of where an Object leverages Array properties, e.g. the elements collected by a selector are added as numeric properties and there is a length property that is the number of elements. That way generic array methods can be applied to jQuery objects (all in ed 3, by the way).
The Object.watch method is in JavaScrpit™, it's not part of ES5 so use with caution.
A major problem with creating your own version of built–in objects is that you'll probably end up wrapping every built–in method in a native one (like jQuery wraps every DOM method pretty much) and start setting getters and setters on every property, or end up with function calls to replace property access (e.g. jQuery's val, attr and prop methods). Rather tedious, and slow if performance matters.
Oh sorry, I mentioned jQuery… :-(
It just seems to be more sensible to design a library or framework to make the best use of the features that the language has, rather than trying to force it to do things it doesn't do well, or does not do natively.
But full marks for trying. :-)
There is a library watch.js out there, which is watching out for either property updates or new property addition as well.
try out!
It is working with setInterval, so it is not performance-friendly however.
When Harmony is out, we can do things that simply:
Object.observe(obj,Observer);
Check the spec for that:
Harmony
But, when later object extension is not in focus, i can freeze up the whole object upon initialization, not to be bothered with neither property changes nor property addition.
The code is changed accordingly:
extendAsArray = function z_extendAsArray(obj){
var index = 0;
for(var prop in obj){
if(!obj.__lookupGetter__(prop)){
(function(thisIndex, thisProp){
Object.defineProperty(obj, thisIndex, {
get: function(){return obj[thisProp]}
, enumerable: true
, configurable: true
, writeable: true
});
})(index, prop)
index++;
}
}
if(!obj.__lookupGetter__('length')){
Object.defineProperty(obj, 'length', {
value:index
});
if(!Object.freeze){
Object.defineProperty(Object, "freeze", {
enumerable: false
, configurable: false
, writable: false
, value: function (obj) {
var props = Object.getOwnPropertyNames(obj);
for(var i=0; i<props.length; i++){
var desc = Object.getOwnPropertyDescriptor(obj,props[i]);
if("value" in desc ){
desc.writable = false;
}
desc.configurable = false;
Object.defineProperty( obj, props[i], desc );
}
return Object.preventExtensions(obj);
}
});
}
Object.freeze(obj);
}
return obj;
};
Also i have found out what Angus Croll, who has been mentioned by the previous post has talked about it.
"Yes, we can make use of the equivalent functionality offered by well written libraries like underscore.js, but still we’re locked into non-standard, inverted signatures in which methods are static and objects are merely extra arguments – an ungainly arrangement for an instance-only language. At some point all supported browsers will be ES5 compliant, at which point the shimmed codebase can simply remove it’s shim library and carry on, while the unshimmed one must choose between a major refactor or a perpetually non-standard and static utility library."

Categories

Resources