Copyright 2012 Kap IT (http://www.kapit.fr/)
-
-Licensed under the Apache License, Version 2.0 (the 'License');
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
-
-Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an 'AS IS' BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
- Author : François de Campredon (http://francois.de-campredon.fr/), | |
Object.observe Shim | |
See The harmony proposal page | (function (global) {
- 'use strict'; |
Utilities | |
setImmediate shim used to deliver changes records asynchronously
-use setImmediate if available | var setImmediate = global.setImmediate || global.msSetImmediate,
- clearImmediate = global.clearImmediate || global.msClearImmediate;
- if (!setImmediate) { |
fallback on setTimeout if not | setImmediate = function (func, args) {
- return setTimeout(func, 0, args);
- };
- clearImmediate = function (id) {
- clearTimeout(id);
- };
- } |
WeakMap | var PrivateMap;
- if (typeof WeakMap !== 'undefined') { |
use weakmap if defined | PrivateMap = WeakMap;
- } else { |
else use ses like shim of WeakMap | /* jshint -W016 */
- var HIDDEN_PREFIX = '__weakmap:' + (Math.random() * 1e9 >>> 0),
- counter = new Date().getTime() % 1e9,
- mascot = {};
-
- PrivateMap = function () {
- this.name = HIDDEN_PREFIX + (Math.random() * 1e9 >>> 0) + (counter++ + '__');
- };
-
- PrivateMap.prototype = {
- has: function (key) {
- return key && key.hasOwnProperty(this.name);
- },
-
- get: function (key) {
- var value = key && key[this.name];
- return value === mascot ? undefined : value;
- },
-
- set: function (key, value) {
- Object.defineProperty(key, this.name, {
- value : typeof value === 'undefined' ? mascot : value,
- enumerable: false,
- writable : true,
- configurable: true
- });
- },
-
- 'delete': function (key) {
- return delete key[this.name];
- }
- };
-
-
- var getOwnPropertyName = Object.getOwnPropertyNames;
- Object.defineProperty(Object, 'getOwnPropertyNames', {
- value: function fakeGetOwnPropertyNames(obj) {
- return getOwnPropertyName(obj).filter(function (name) {
- return name.substr(0, HIDDEN_PREFIX.length) !== HIDDEN_PREFIX;
- });
- },
- writable: true,
- enumerable: false,
- configurable: true
- });
- } |
Internal Properties | |
An ordered list used to provide a deterministic ordering in which callbacks are called.
-Corresponding Section in ECMAScript wiki | var observerCallbacks = []; |
This object is used as the prototype of all the notifiers that are returned by Object.getNotifier(O).
-Corresponding Section in ECMAScript wiki | var NotifierPrototype = Object.create(Object.prototype); |
Used to store immediate uid reference | var changeDeliveryImmediateUid; |
Used to schedule a call to _deliverAllChangeRecords | function setUpChangesDelivery() {
- clearImmediate(changeDeliveryImmediateUid);
- changeDeliveryImmediateUid = setImmediate(_deliverAllChangeRecords);
- }
-
- Object.defineProperty(NotifierPrototype, 'notify', {
- value: function notify(changeRecord) {
- var notifier = this;
- if (Object(notifier) !== notifier) {
- throw new TypeError('this must be an Object, given ' + notifier);
- }
- if (!notifier.__target) {
- return;
- }
- if (Object(changeRecord) !== changeRecord) {
- throw new TypeError('changeRecord must be an Object, given ' + changeRecord);
- }
-
-
- var type = changeRecord.type;
- if (typeof type !== 'string') {
- throw new TypeError('changeRecord.type must be a string, given ' + type);
- }
-
- var changeObservers = changeObserversMap.get(notifier);
- if (!changeObservers || changeObservers.length === 0) {
- return;
- }
- var target = notifier.__target,
- newRecord = Object.create(Object.prototype, {
- 'object': {
- value: target,
- writable : false,
- enumerable : true,
- configurable: false
- }
- });
- for (var prop in changeRecord) {
- if (prop !== 'object') {
- var value = changeRecord[prop];
- Object.defineProperty(newRecord, prop, {
- value: value,
- writable : false,
- enumerable : true,
- configurable: false
- });
- }
- }
- Object.preventExtensions(newRecord);
- _enqueueChangeRecord(notifier.__target, newRecord);
- },
- writable: true,
- enumerable: false,
- configurable : true
- });
-
- Object.defineProperty(NotifierPrototype, 'performChange', {
- value: function performChange(changeType, changeFn) {
- var notifier = this;
- if (Object(notifier) !== notifier) {
- throw new TypeError('this must be an Object, given ' + notifier);
- }
- if (!notifier.__target) {
- return;
- }
- if (typeof changeType !== 'string') {
- throw new TypeError('changeType must be a string given ' + notifier);
- }
- if (typeof changeFn !== 'function') {
- throw new TypeError('changeFn must be a function, given ' + changeFn);
- }
-
- _beginChange(notifier.__target, changeType);
- var error, changeRecord;
- try {
- changeRecord = changeFn.call(undefined);
- } catch (e) {
- error = e;
- }
- _endChange(notifier.__target, changeType);
- if (typeof error !== 'undefined') {
- throw error;
- }
-
- var changeObservers = changeObserversMap.get(notifier);
- if (changeObservers.length === 0) {
- return;
- }
-
- var target = notifier.__target,
- newRecord = Object.create(Object.prototype, {
- 'object': {
- value: target,
- writable : false,
- enumerable : true,
- configurable: false
- },
- 'type': {
- value: changeType,
- writable : false,
- enumerable : true,
- configurable: false
- }
- });
- if (typeof changeRecord !== 'undefined') {
- for (var prop in changeRecord) {
- if (prop !== 'object' && prop !== 'type') {
- var value = changeRecord[prop];
- Object.defineProperty(newRecord, prop, {
- value: value,
- writable : false,
- enumerable : true,
- configurable: false
- });
- }
- }
- }
-
- Object.preventExtensions(newRecord);
- _enqueueChangeRecord(notifier.__target, newRecord);
-
- },
- writable: true,
- enumerable: false,
- configurable : true
- }); |
Implementation of the internal algorithm 'BeginChange'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _beginChange(object, changeType) {
- var notifier = Object.getNotifier(object),
- activeChanges = activeChangesMap.get(notifier),
- changeCount = activeChangesMap.get(notifier)[changeType];
- activeChanges[changeType] = typeof changeCount === 'undefined' ? 1 : changeCount + 1;
- } |
Implementation of the internal algorithm 'EndChange'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _endChange(object, changeType) {
- var notifier = Object.getNotifier(object),
- activeChanges = activeChangesMap.get(notifier),
- changeCount = activeChangesMap.get(notifier)[changeType];
- activeChanges[changeType] = changeCount > 0 ? changeCount - 1 : 0;
- } |
Implementation of the internal algorithm 'ShouldDeliverToObserver'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _shouldDeliverToObserver(activeChanges, acceptList, changeType) {
- var doesAccept = false;
- if (acceptList) {
- for (var i = 0, l = acceptList.length; i < l; i++) {
- var accept = acceptList[i];
- if (activeChanges[accept] > 0) {
- return false;
- }
- if (accept === changeType) {
- doesAccept = true;
- }
- }
- }
- return doesAccept;
- } |
Map used to store corresponding notifier to an object | var notifierMap = new PrivateMap(),
- changeObserversMap = new PrivateMap(),
- activeChangesMap = new PrivateMap(); |
Implementation of the internal algorithm 'GetNotifier'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _getNotifier(target) {
- if (!notifierMap.has(target)) {
- var notifier = Object.create(NotifierPrototype); |
we does not really need to hide this, since anyway the host object is accessible from outside of the
-implementation. we just make it unwritable | Object.defineProperty(notifier, '__target', { value : target });
- changeObserversMap.set(notifier, []);
- activeChangesMap.set(notifier, {});
- notifierMap.set(target, notifier);
- }
- return notifierMap.get(target);
- } |
map used to store reference to a list of pending changeRecords
-in observer callback. | var pendingChangesMap = new PrivateMap(); |
Implementation of the internal algorithm 'EnqueueChangeRecord'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _enqueueChangeRecord(object, changeRecord) {
- var notifier = Object.getNotifier(object),
- changeType = changeRecord.type,
- activeChanges = activeChangesMap.get(notifier),
- changeObservers = changeObserversMap.get(notifier);
-
- for (var i = 0, l = changeObservers.length; i < l; i++) {
- var observerRecord = changeObservers[i],
- acceptList = observerRecord.accept;
- if (_shouldDeliverToObserver(activeChanges, acceptList, changeType)) {
- var observer = observerRecord.callback,
- pendingChangeRecords = [];
- if (!pendingChangesMap.has(observer)) {
- pendingChangesMap.set(observer, pendingChangeRecords);
- } else {
- pendingChangeRecords = pendingChangesMap.get(observer);
- }
- pendingChangeRecords.push(changeRecord);
- }
- }
- setUpChangesDelivery();
- } |
map used to store a count of associated notifier to a function | var attachedNotifierCountMap = new PrivateMap(); |
Remove reference all reference to an observer callback,
-if this one is not used anymore.
-In the proposal the ObserverCallBack has a weak reference over observers,
-Without this possibility we need to clean this list to avoid memory leak | function _cleanObserver(observer) {
- if (!attachedNotifierCountMap.get(observer) && !pendingChangesMap.has(observer)) {
- attachedNotifierCountMap.delete(observer);
- var index = observerCallbacks.indexOf(observer);
- if (index !== -1) {
- observerCallbacks.splice(index, 1);
- }
- }
- } |
Implementation of the internal algorithm 'DeliverChangeRecords'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _deliverChangeRecords(observer) {
- var pendingChangeRecords = pendingChangesMap.get(observer);
- pendingChangesMap.delete(observer);
- if (!pendingChangeRecords || pendingChangeRecords.length === 0) {
- return false;
- }
- try {
- observer.call(undefined, pendingChangeRecords);
- }
- catch (e) { }
-
- _cleanObserver(observer);
- return true;
- } |
Implementation of the internal algorithm 'DeliverAllChangeRecords'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | function _deliverAllChangeRecords() {
- var observers = observerCallbacks.slice();
- var anyWorkDone = false;
- for (var i = 0, l = observers.length; i < l; i++) {
- var observer = observers[i];
- if (_deliverChangeRecords(observer)) {
- anyWorkDone = true;
- }
- }
- return anyWorkDone;
- }
-
-
- Object.defineProperties(Object, { |
Implementation of the public api 'Object.observe'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | 'observe': {
- value: function observe(target, callback, accept) {
- if (Object(target) !== target) {
- throw new TypeError('target must be an Object, given ' + target);
- }
- if (typeof callback !== 'function') {
- throw new TypeError('observer must be a function, given ' + callback);
- }
- if (Object.isFrozen(callback)) {
- throw new TypeError('observer cannot be frozen');
- }
-
- var acceptList;
- if (typeof accept === 'undefined') {
- acceptList = ['add', 'update', 'delete', 'reconfigure', 'setPrototype', 'preventExtensions'];
- } else {
- if (Object(accept) !== accept) {
- throw new TypeError('accept must be an object, given ' + accept);
- }
- var len = accept.length;
- if (typeof len !== 'number' || len >>> 0 !== len || len < 1) {
- throw new TypeError('the \'length\' property of accept must be a positive integer, given ' + len);
- }
-
- var nextIndex = 0;
- acceptList = [];
- while (nextIndex < len) {
- var next = accept[nextIndex];
- if (typeof next !== 'string') {
- throw new TypeError('accept must contains only string, given' + next);
- }
- acceptList.push(next);
- nextIndex++;
- }
- }
-
-
- var notifier = _getNotifier(target),
- changeObservers = changeObserversMap.get(notifier);
-
- for (var i = 0, l = changeObservers.length; i < l; i++) {
- if (changeObservers[i].callback === callback) {
- changeObservers[i].accept = acceptList;
- return target;
- }
- }
-
- changeObservers.push({
- callback: callback,
- accept: acceptList
- });
-
- if (observerCallbacks.indexOf(callback) === -1) {
- observerCallbacks.push(callback);
- }
- if (!attachedNotifierCountMap.has(callback)) {
- attachedNotifierCountMap.set(callback, 1);
- } else {
- attachedNotifierCountMap.set(callback, attachedNotifierCountMap.get(callback) + 1);
- }
- return target;
- },
- writable: true,
- configurable: true
- }, |
Implementation of the public api 'Object.unobseve'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | 'unobserve': {
- value: function unobserve(target, callback) {
- if (Object(target) !== target) {
- throw new TypeError('target must be an Object, given ' + target);
- }
- if (typeof callback !== 'function') {
- throw new TypeError('observer must be a function, given ' + callback);
- }
- var notifier = _getNotifier(target),
- changeObservers = changeObserversMap.get(notifier);
- for (var i = 0, l = changeObservers.length; i < l; i++) {
- if (changeObservers[i].callback === callback) {
- changeObservers.splice(i, 1);
- attachedNotifierCountMap.set(callback, attachedNotifierCountMap.get(callback) - 1);
- _cleanObserver(callback);
- break;
- }
- }
- return target;
- },
- writable: true,
- configurable: true
- }, |
Implementation of the public api 'Object.deliverChangeRecords'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | 'deliverChangeRecords': {
- value: function deliverChangeRecords(observer) {
- if (typeof observer !== 'function') {
- throw new TypeError('callback must be a function, given ' + observer);
- }
- while (_deliverChangeRecords(observer)) {}
- },
- writable: true,
- configurable: true
- }, |
Implementation of the public api 'Object.getNotifier'
-described in the proposal.
-Corresponding Section in ECMAScript wiki | 'getNotifier': {
- value: function getNotifier(target) {
- if (Object(target) !== target) {
- throw new TypeError('target must be an Object, given ' + target);
- }
- if (Object.isFrozen(target)) {
- return null;
- }
- return _getNotifier(target);
- },
- writable: true,
- configurable: true
- }
-
- });
-
-})(typeof global !== 'undefined' ? global : this);
-
- |