Giter Club home page Giter Club logo

observe-js's Introduction

Analytics

Learn the tech

Why observe-js?

observe-js is a library for observing changes in JavaScript data. It exposes a high-level API and uses Object.observe if available, and otherwise performs dirty-checking. observe-js requires ECMAScript 5.

Observable

observe-js implements a set of observers (PathObserver, ArrayObserver, ObjectObserver, CompoundObserver, ObserverTransform) which all implement the Observable interface:

{
  // Begins observation. Value changes will be reported by invoking |changeFn| with
  // |opt_receiver| as the target, if provided. Returns the initial value of the observation.
  open: function(changeFn, opt_receiver) {},

  // Report any changes now (does nothing if there are no changes to report).
  deliver: function() {},

  // If there are changes to report, ignore them. Returns the current value of the observation.
  discardChanges: function() {},

  // Ends observation. Frees resources and drops references to observed objects.
  close: function() {}
}

PathObserver

PathObserver observes a "value-at-a-path" from a given object:

var obj = { foo: { bar: 'baz' } };
var defaultValue = 42;
var observer = new PathObserver(obj, 'foo.bar', defaultValue);
observer.open(function(newValue, oldValue) {
  // respond to obj.foo.bar having changed value.
});

PathObserver will report a change whenever the value obtained by the corresponding path expression (e.g. obj.foo.bar) would return a different value.

PathObserver also exposes a setValue method which attempts to update the underlying value. Setting the value does not affect notification state (in other words, a caller sets the value but does not discardChanges, the changeFn will be notified of the change).

observer.setValue('boo');
assert(obj.foo.bar == 'boo');

Notes:

  • If the path is ever unreachable, the value is considered to be undefined (unless you pass an overriding defaultValue to new PathObserver(...) as shown in the above example).
  • If the path is empty (e.g. ''), it is said to be the empty path and its value is its root object.
  • PathObservation respects values on the prototype chain

ArrayObserver

ArrayObserver observes the index-positions of an Array and reports changes as the minimal set of "splices" which would have had the same effect.

var arr = [0, 1, 2, 4];
var observer = new ArrayObserver(arr);
observer.open(function(splices) {
  // respond to changes to the elements of arr.
  splices.forEach(function(splice) {
    splice.index; // the index position that the change occurred.
    splice.removed; // an array of values representing the sequence of removed elements
    splice.addedCount; // the number of elements which were inserted.
  });
});

ArrayObserver also exposes a utility function: applySplices. The purpose of applySplices is to transform a copy of an old state of an array into a copy of its current state, given the current state and the splices reported from the ArrayObserver.

AraryObserver.applySplices = function(previous, current, splices) { }

ObjectObserver

ObjectObserver observes the set of own-properties of an object and their values.

var myObj = { id: 1, foo: 'bar' };
var observer = new ObjectObserver(myObj);
observer.open(function(added, removed, changed, getOldValueFn) {
  // respond to changes to the obj.
  Object.keys(added).forEach(function(property) {
    property; // a property which has been been added to obj
    added[property]; // its value
  });
  Object.keys(removed).forEach(function(property) {
    property; // a property which has been been removed from obj
    getOldValueFn(property); // its old value
  });
  Object.keys(changed).forEach(function(property) {
    property; // a property on obj which has changed value.
    changed[property]; // its value
    getOldValueFn(property); // its old value
  });
});

CompoundObserver

CompoundObserver allows simultaneous observation of multiple paths and/or Observables. It reports any and all changes in to the provided changeFn callback.

var obj = {
  a: 1,
  b: 2,
};

var otherObj = { c: 3 };

var observer = new CompoundObserver();
observer.addPath(obj, 'a');
observer.addObserver(new PathObserver(obj, 'b'));
observer.addPath(otherObj, 'c');
var logTemplate = 'The %sth value before & after:';
observer.open(function(newValues, oldValues) {
  // Use for-in to iterate which values have changed.
  for (var i in oldValues) {
    console.log(logTemplate, i, oldValues[i], newValues[i]);
  }
});

ObserverTransform

ObserverTransform is used to dynamically transform observed value(s).

var obj = { value: 10 };
var observer = new PathObserver(obj, 'value');
function getValue(value) { return value * 2 };
function setValue(value) { return value / 2 };

var transform = new ObserverTransform(observer, getValue, setValue);

// returns 20.
transform.open(function(newValue, oldValue) {
  console.log('new: ' + newValue + ', old: ' + oldValue);
});

obj.value = 20;
transform.deliver(); // 'new: 40, old: 20'
transform.setValue(4); // obj.value === 2;

ObserverTransform can also be used to reduce a set of observed values to a single value:

var obj = { a: 1, b: 2, c: 3 };
var observer = new CompoundObserver();
observer.addPath(obj, 'a');
observer.addPath(obj, 'b');
observer.addPath(obj, 'c');
var transform = new ObserverTransform(observer, function(values) {
  var value = 0;
  for (var i = 0; i < values.length; i++)
    value += values[i]
  return value;
});

// returns 6.
transform.open(function(newValue, oldValue) {
  console.log('new: ' + newValue + ', old: ' + oldValue);
});

obj.a = 2;
obj.c = 10;
transform.deliver(); // 'new: 14, old: 6'

Path objects

A path is an ECMAScript expression consisting only of identifiers (myVal), member accesses (foo.bar) and key lookup with literal values (arr[0] obj['str-value'].bar.baz).

Path.get('foo.bar.baz') returns a Path object which represents the path. Path objects have the following API:

{
  // Returns the current value of the path from the provided object. If eval() is available,
  // a compiled getter will be used for better performance. Like PathObserver above, undefined
  // is returned unless you provide an overriding defaultValue.
  getValueFrom: function(obj, defaultValue) { },

  // Attempts to set the value of the path from the provided object. Returns true IFF the path
  // was reachable and set.
  setValueFrom: function(obj, newValue) { }
}

Path objects are interned (e.g. assert(Path.get('foo.bar.baz') === Path.get('foo.bar.baz'));) and are used internally to avoid excessive parsing of path strings. Observers which take path strings as arguments will also accept Path objects.

About delivery of changes

observe-js is intended for use in environments which implement Object.observe, but it supports use in environments which do not.

If Object.observe is present, and observers have changes to report, their callbacks will be invoked at the end of the current turn (microtask). In a browser environment, this is generally at the end of an event.

If Object.observe is absent, Platform.performMicrotaskCheckpoint() must be called to trigger delivery of changes. If Object.observe is implemented, Platform.performMicrotaskCheckpoint() has no effect.

observe-js's People

Contributors

addyosmani avatar ajklein avatar arv avatar capaj avatar dfreedm avatar ebidel avatar funkmonkey avatar hartca avatar inetufo avatar mangini avatar mkedwards avatar mohanaravind avatar nevir avatar olsondev avatar peterwmwong avatar rafaelw avatar samduvall avatar vicb avatar zero-is-one avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

observe-js's Issues

CPU thrashing computed properties without Object.observe

Without chrome://flags/#enable-javascript-harmony, this example simply doesn't work and chews 100% CPU in Chrome, and chews 100% CPU in Firefox + continually eats memory.

aside: also exposes a PITA regarding whitespace in templates.

<!DOCTYPE html>
<html lang="en">
  <head>
    <title>polymer cpu thrashing test</title>
  </head>
  <body>
    <template id="view" bind>
      <section class="path-segments">
        <template repeat="{{ file, key in pathSegments}}" bind><template if="{{ file.href != path }}" bind><a href="?path={{file.href}}">{{file.text}}/</a></template><template if="{{ file.href == path }}" bind>{{file.text}}</template></template>
      </section>
      <input value="{{ path }}" />
    </template>
    <script src="/platform.js"></script>
    <script>
      window.addEventListener('WebComponentsReady', function() {
        var model = {path: '/path/to/file'}
        Object.defineProperty(model, "pathSegments", {
          get: function() {
            return this.path.split('/').map(function(item, index, segments) {
              return {
                text: item,
                href: segments.slice(0, index + 1).join('/')
              }
            })
          } 
        })
        var viewEl = document.querySelector('#view')
        viewEl.bindingDelegate = new PolymerExpressions()
        viewEl.model = model
      })
    </script>
  </body>
 </html>

Cannot get observe to work

Hey guys,

I've been playing with this lib to see if I can use it as a polyfill. I've included the observe.js and in the console created a basic object:

var a = { moo: 0 };

Then added a path observer:

new PathObserver(a, 'moo', function() { console.log(arguments); });

Then updated the object:

a.moo = 2;

Nothing gets called. I've also tried multiple different ways of doing this but no luck. Can someone point me in the right direction here? :) thank you!

Prototype Array "view"

i.e. new ArrayView(array, filterFn, mapFn, sortFn)

ArrayView.prototype = {
proto: Array.prototype
}

Don't use native Object.observe for DOM objects' IDL properties

Object.observe does not trigger changes for IDL properties (e.g. offsetWidth, etc...) so when Observe-js is using the polyfill I can successfully bind to a HTMLElement's offsetWidth. But when Observe-js is using the native Object.observe it won't get any updates for offsetWidth as it doesn't trigger changes.

Is it possible to force these properties to use the polyfill?

Path is broken due to use of __proto__

This is broken in IE10.

Line 314

  Path.prototype = {
    __proto__: [],

This either needs to be wrapped in createObject or refactored to not use proto.

Here is createObject from compat.js

var createObject = ('__proto__' in {}) ?
    function(obj) { return obj; } :
    function(obj) {
      var proto = obj.__proto__;
      if (!proto)
        return obj;
      var newObject = Object.create(proto);
      Object.getOwnPropertyNames(obj).forEach(function(name) {
        Object.defineProperty(newObject, name,
                             Object.getOwnPropertyDescriptor(obj, name));
      });
      return newObject;
    };

Errors with security policy detection on Chrome Apps

On Chrome Apps the security policy detection fix from #26 doesn't seem to work and you get:

screenshot 2014-03-07 at 04 02 49

it seems Chrome Packaged Apps don't have document.securityPolicy defined.

screenshot 2014-03-07 at 04 05 47

Not seeing the same issue on our build with Mobile Chrome Apps, however, even though there is no document.securityPolicy there either.

Production Ready?

Hi @rafaelw, I'm wondering what's the status of this project? Is ChangeSummary kind of production-ready? I'd like to use it like a Object.observe shim. I understand I will still need to call .deliver() but this project seems to be more promising than any other shim I have seen so far.

Thanks again for this awesome project! Please let me know :D

send path to callback

i would like to receive the observed path in the path observer callback. this would make it easier for me to use a single callback to observe multiple paths

PathObserver.defineProperty should not clear the property value when closed

When an observer created by PathObserver.defineProperty is closed, currently it deletes the the bound property on the object.

This removes the property descriptor which links the property to the descriptor, but it also removes the current value of the property. It should do the former but not the latter.

consider ditching CallbackRouter

The correct pattern of use may be to use lots of little ChangeSummaries, instead of a single one with lots of observations. If that is ok, the CallbackRouter probably isn't neccessary

ArrayReduction does not update PathObservers

When items are added or removed from array being watched by ArrayReduction, all the PathObservers that watched items indexed AFTER the add/remove index are out of sync.

Let's look at an example...

var values = ['a','b','c'];
var calls = [];
var reduction = new ArrayReduction(
  values,
  '',
  function(sum,cur){
    sum.push(cur);
    return sum;
  },
  calls
);
calls.splice(0);

// Internal view of observers.path for each item by index:
// array index    observer.path
// -----------    -------------
// 0              0
// 1              1             <---- To be spliced
// 2              2

values.splice(1,1);
reduction.deliver();

// Internal view of observers.path for each item by index:
// array index    observer.path
// -----------    -------------
// 0              0
// 1              2             <---- INCORRECT PATH, SHOULD BE 1


// == EXPECTED ==
// > Calls to reduce function: ["a", "c"]
// === ACTUAL ===
// > Calls to reduce function: ["a", "c", "a", "c", undefined]
console.log('Calls to reduce function:', calls);

... Notice the difference in EXPECTED and ACTUAL output.
What appears to be happening is the PathObserver that was watching the [2] element (which is now the [1] element), incorrectly causes a another reduce() because it sees a change in value ('c' -> undefined).

I believe the problem is that ArrayReduction's handleSplice() does not loop over and update the paths in observers. https://github.com/rafaelw/ChangeSummary/blob/08cb2c69b5daba0cbd5e806820311a76ea279858/util/array_reduction.js#L56

Improve feedback from squelched exceptions

https://github.com/Polymer/observe-js/blob/master/src/observe.js#L367

There is more information available on ex then console.error emits to the console.

Changing it to something like this:

        console.error('Exception caught during observer callback: ', ex.stack || ex);

Gives a lot more useful information, on Chrome at least.

I didn't make a pull request because I didn't do any due diligence other than throw that change into my local checkout. I don't know what this does on any other browser, e.g., and I suspect Arv knows a lot about this topic.

@arv: btw, SD Polyfill has a similar issue where it rethrows a squelched error which loses the stack information. It could use the same treatment.

Need rigorous definitions for path and "value at path".

Cases to consider:
Path:
-empty string (currently a path with 0 property components)
-index operators, e.g. "foo[2].baz" (currently not supported)
Value at Path:
-empty string path (the value is the model itself)
-non-object model (valid, if path is non-empty, value is undefined)

Provide a helper for notifying when accessor properties have changed

When the the observe-js polyfill is used with accessor properties, changes are detected via dirty checking. Changes are not detected to accessors under Object.observe. The requirement in this case is that users call Object.getNotifier(object).notify(changes).

This should be sugared by observe-js such that there's a polyfill transparent way to do this, along the lines of:

https://github.com/Polymer/observe-js/blob/master/src/observe.js#L970

Object.observe and RFC6902

The Object.observe bits are in theory a good match for implementing an observer for the new IEFT JSON Patch standard (http://tools.ietf.org/html/rfc6902).

I created an implementation of it here. I use your bits in Chrome Canary when available and dirty checking for legacy browsers. However, I get a big performance drop when resorting to the obvious Object.observe implementation.

So my question is:
Should I expect the current implementation to be early work or should I investigate the performance of the Object.observe based version of JSON Patch?

As used in Polymer, dirty checking performance needs improvement

This issue is relevant only when Object.observe is unavailable.

The topeka app takes ~12ms to do a no-op dirty check in Safari and ~45ms on an iPad. This was determined by timing Platform.performMicrotaskCheckpoint within Polymer's dirty check interval when the app was idle. This jsperf suggests that dirty checking polymer's observed property getters is particularly slow. A quick test to comment out most of the work done in these getters improved the dirty check time by ~33%. Note also that Topeka has about 1200 observers, as reported by Observer._allObserversCount.

Observing push into existing array doesnt work

Tested with NodeJS 0.10.28

var someObject = {
  foo: []
};

observer = new ObjectObserver(someObject);

observer.open(function() {
  return console.log(arguments[0]);
});

someObject.foo.push('anotherMoo');

observer.deliver();

This wont deliver anything.


var someObject = {};

observer = new ObjectObserver(someObject);

observer.open(function() {
  return console.log(arguments[0]);
});

someObject.foo = []
someObject.foo.push('anotherMoo');

observer.deliver();

This delivers { foo: [ 'anotherMoo' ] } as added


I suspect this is due to the fact that Object.defineProperty wont trigger a set when pushing into an array. The only workaround I found is to use full diffing of objects using deep-diff

Update NPM package

It seems the NPM package is woefully out of date. Would it be possible to update and publish to NPM? Thanks.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.