Object.Observe

Table of Contents

  1. Introduction
    1. Problem
    2. Goals
    3. Solution
    4. Cross-cutting Concerns and Potential Implementation Challenges
  2. Examples
    1. Basic
    2. Array
    3. Synthetic Change Records
    4. Higher-level Change Records
  3. 1 Key Algorithms and Semantics
    1. 1.1 ChangeObserver and PendingChangeRecords
    2. 1.2 Observable changes to objects
    3. 1.3 The Notifier Object
    4. 1.4 Accepted Change Types
    5. 1.5 ActiveChanges and higher-level change types
    6. 1.6 Enqueuing changes to observers
    7. 1.7 End of turn delivery of change records
  4. 2 New Internal Properties, Objects and Algorithms
    1. 2.1 [[ObserverCallbacks]]
    2. 2.2 [[PendingChangeRecords]]
    3. 2.3 [[Notifier]]
    4. 2.4 Notifier Objects
      1. 2.4.1 Properties of the Notifier Prototype
        1. 2.4.1.1 %NotifierPrototype%.notify(changeRecord)
        2. 2.4.1.2 %NotifierPrototype%.performChange(changeType, changeFn)
    5. 2.5 GetNotifier(O)
    6. 2.6 BeginChange(O, changeType)
    7. 2.7 EndChange(O, changeType)
    8. 2.8 ShouldDeliverToObserver(activeChanges, acceptList, changeType)
    9. 2.9 EnqueueChangeRecord(O, changeRecord)
    10. 2.10 DeliverChangeRecords(C)
    11. 2.11 DeliverAllChangeRecords()
    12. 2.12 CreateChangeRecord(type, object, name, oldDesc, newDesc)
    13. 2.13 [[CreateSpliceChangeRecord]]
  5. 3 Public API Specification
    1. 3.1 Object.observe(O, callback, options = undefined)
    2. 3.2 Object.unobserve(O, callback)
    3. 3.3 Array.observe(O, callback, options = undefined)
    4. 3.4 Array.unobserve
    5. 3.5 Object.deliverChangeRecords
    6. 3.6 Object.getNotifier
  6. 4 Modifications to existing internal algorithms
    1. 4.1 9.1.6.3 ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current)
    2. 4.2 9.1.10 [[Delete]] (P)
    3. 4.3 9.1.4 [[PreventExtensions]] ()
    4. 4.4 9.1.2 `[[SetPrototypeOf]] (V)`
    5. 4.5 Changes to Array methods
      1. 4.5.1 Array.prototype.pop
      2. 4.5.2 Array.prototype.push
      3. 4.5.3 Array.prototype.shift
      4. 4.5.4 Array.prototype.splice
      5. 4.5.5 Array.prototype.unshift
    6. 4.6 Array Exotic Objects
      1. 4.6.1 [[DefineOwnProperty]] (P, Desc)
      2. 4.6.2 ArraySetLength Abstract Operation
  7. A Updates

Introduction

Problem

Declarative programming techniques provide leverage and power to developers. The web platform has pioneered many -- not the least of which is HTML. As the practice of application development evolves, it is important that primitives exist in the platform which allow developers to create and evolve robust declarative mechanisms independent of those provided directly by the platform.

A class of declarative mechanisms depends on discovering that ECMAScript values have changed. For example, UI frameworks often want to provide an ability to databind objects in a datamodel to UI elements. Likewise, domain objects, application logic and persistence strategies can often best be described in terms of constraints and relationships on data.

Today, ECMAScript code wishing to observe changes to objects typically either creates objects wrapping the real data or employs dirty-checking strategies for discovering changes. The first approach requires objects being observed to buy into the strategy, making the user model more complex and eroding composability of concerns. The second approach has poor algorithmic behavior, requiring work proportional to the number of objects observed to discover if any change has taken place. Both require increased working sets.

Goals

The desired characteristics of a solution are:

Solution

Object.observe allows for the direct observation of changes to ECMAScript objects. It allows an observer to receive a time-ordered sequence of change records which describe the set of changes which took place to the set of observed objects.

Changes to objects are directly observable and are described in terms of

A flexible system is provided via a "notifier" object associated with every observable object, which allows for:

Lastly, Array.observe allows for the efficient description of certain changes which may affect many index-valued properties as single "splice" change record.

Cross-cutting Concerns and Potential Implementation Challenges

Object.observe is a relatively significant and cross-cutting feature, as it

Examples

Basic


var records;
function observer(recs) {
  records = recs;
}

var obj = { id: 1 };
Object.observe(obj, observer);

obj.a = 'b';
obj.id++;
Object.defineProperty(obj, 'a', { enumerable: false });
delete obj.a;
Object.preventExtensions(obj);

Object.deliverChangeRecords(observer);
assertChangesAre(records, [
  { object: obj, type: 'add', name: 'a' },
  { object: obj, type: 'update', name: 'id', oldValue: 1 },
  { object: obj, type: 'reconfigure', name: 'a' },
  { object: obj, type: 'delete', name: 'a', oldValue: 'b' },
  { object: obj, type: 'preventExtensions', }
]);
    

Array


var basicChanges;
function basicObserver(recs) {
  basicChanges = recs;
}

var arrayChanges;
function arrayObserver(recs) {
  arrayChanges = recs;
}

var array = [1, 2, 3];
Object.observe(array, basicObserver);
Array.observe(array, arrayObserver);

array.push(4);
array.splice(2, 2);
array[5] = 'a';
array.length = 0;

Object.deliverChangeRecords(basicObserver);
assertChangesAre(basicChanges, [
  // push
  { object: array, type: 'add', name: '3' },
  { object: array, type: 'update', name: 'length', oldValue: 3 },
  // splice
  { object: array, type: 'delete', name: '3', oldValue: 4 },
  { object: array, type: 'delete', name: '2', oldValue: 3 },
  { object: array, type: 'update', name: 'length', oldValue: 4 },
  // index assignment
  { object: array, type: 'add', name: '5' },
  { object: array, type: 'update', name: 'length', oldValue: 2 },
  // length assignment
  { object: array, type: 'delete', name: '5', oldValue: 'a' },
  { object: array, type: 'delete', name: '1', oldValue: 2 },
  { object: array, type: 'delete', name: '0', oldValue: 1 },
  { object: array, type: 'update', name: 'length', oldValue: 6 },
]);

Object.deliverChangeRecords(arrayObserver);
assertChangesAre(arrayChanges, [
  // push
  { object: array, type: 'splice', index: 3, removed: [], addedCount: 1 },
  // splice
  { object: array, type: 'splice', index: 2, removed: [3, 4], addedCount: 0 },
  // index assignment
  { object: array, type: 'splice', index: 2, removed: [], addedCount: 4 },
  // length assignment
  { object: array, type: 'splice', index: 0, removed: [1, 2,,,,'a'], addedCount: 0 },
]);
    

Synthetic Change Records


function Circle(r) {
  var radius = r;

  var notifier = Object.getNotifier(this);
  function notifyAreaAndRadius(radius) {
    notifier.notify({
      type: 'update',
      name: 'radius',
      oldValue: radius
    })
    notifier.notify({
      type: 'update',
      name: 'area',
      oldValue: Math.pow(radius * Math.PI, 2)
    });
  }

  Object.defineProperty(this, 'radius', {
    get: function() {
      return radius;
    },
    set: function(r) {
      if (radius === r)
        return;
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });

  Object.defineProperty(this, 'area', {
    get: function() {
      return Math.power(radius * Math.PI, 2);
    },
    set: function(a) {
      r = Math.sqrt(a)/Math.PI;
      notifyAreaAndRadius(radius);
      radius = r;
    }
  });
}

var changes;
function observer(recs) {
  changes = recs;
}

var circle = new Circle(5);

Object.observe(circle, observer);

circle.radius = 10;
circle.area = 100;

Object.deliverChangeRecords(observer);
assertChangesAre(changes, [
  // 'radius' assignment
  { object: circle, type: 'update', name: 'radius', oldValue: 5 },
  { object: circle, type: 'update', name: 'area', oldValue: 246.74011002723395 },
  // 'area' assignment
  { object: circle, type: 'update', name: 'radius', oldValue: 10 },
  { object: circle, type: 'update', name: 'area', oldValue: 986.9604401089358 },
]);
    

Higher-level Change Records


function Square(x, y, width, height) {
  this.x = x;
  this.y = y;
  this.width = width;
  this.height = height;
}

Square.prototype = {
  scale: function(ratio) {
    Object.getNotifier(this).performChange('scale', () => {
      this.width *= ratio;
      this.height *= ratio;
      return {
        ratio: ratio
      };
    });
  },

  translate: function(dx, dy) {
    Object.getNotifier(this).performChange('translate', () => {
      this.x += dx;
      this.y += dy;
      return {
        dx: dx,
        dy: dy
      }
    });
  }
}

Square.observe = function(square, callback) {
  return Object.observe(square, callback, ['update', 'translate', 'scale']);
}

var basicChanges;
function basicObserver(recs) {
  basicChanges = recs;
}

var squareChanges;
function squareObserver(recs) {
  squareChanges = recs;
}

var square = new Square(0, 0, 10, 10);

Object.observe(square, basicObserver);
Square.observe(square, squareObserver);

square.translate(5, 5);
square.x = -5;
square.scale(2);

Object.deliverChangeRecords(basicObserver);
assertChangesAre(basicChanges, [
  // translate
  { object: square, type: 'update', name: 'x', oldValue: 0 },
  { object: square, type: 'update', name: 'y', oldValue: 0 },
  // assignment to 'x'
  { object: square, type: 'update', name: 'x', oldValue: 5 },
  // scale
  { object: square, type: 'update', name: 'width', oldValue: 10 },
  { object: square, type: 'update', name: 'height', oldValue: 10 },
]);

Object.deliverChangeRecords(squareObserver);
assertChangesAre(squareChanges, [
  // translate
  { object: square, type: 'translate', dx: 5, dy: 5 },
  // assignment to 'x'
  { object: square, type: 'update', name: 'x', oldValue: 5 },
  // scale
  { object: square, type: 'scale', ratio: 2 },
]);
    

1Key Algorithms and Semantics

1.1ChangeObserver and PendingChangeRecords

Conceptually, at the heart of the EMCAScript observation mechanism are two structures:

Object.observe enters a function into an object's associated set of ChangeObservers (while Object.unobserve removes it).

When a change takes place to object, a change record may be appended to the PendingChangeRecords of the functions which are registered in its associated ChangeObservers.

An observer will receive the changes, either asynchronously, at the end of the current turn, or synchronously, if it calls Object.deliverChangeRecords() with itself as an argument. In either case, IFF the function has pending changes, it will be invoked with the contents of its PendingChangeRecords as the only argument.

1.2Observable changes to objects

The vocabulary of observable changes to objects hews close to the object model and reflective APIs of ECMAScript.

Change records are enqueued to observers via the internal EnqueueChangeRecord algorithm.

1.3The Notifier Object

Every object has an associated Notifier object which can be retrieved via Object.getNotifier() as long as the object is not frozen. The Notifier has internal properties which reference the object's associated set of observers (ChangeObservers) as well as the set of changes which are currently being performed on the object (ActiveChanges).

The Notifier has public API (notify() and performChange()) which can be used to deliver "synthetic" change records to observers and describe higher-level changes.

1.4Accepted Change Types

An observer must only receive change records whose type matches one provided in the accept list, which is passed as the (optional) third argument to Object.observe (if the argument is not provided, the list defaults to the "intrinsic" set of change types).

An observation is conceptually a tuple: . Object.observe ensures that this tuple is represented as an observerRecord in the associated Notifier's ChangeObservers list.

1.5ActiveChanges and higher-level change types

The performChange() method of an object's associated Notifier provides a mechanism to describe a set of changes as a single (more compact) higher-level change type.

The set of change types being performed on an object is represented in the set of properties of the Notifier's BeginChange before, and EndChange after invoking the provided changeFn function.

1.6Enqueuing changes to observers

When a change is to be enqueued to an object's associated observers, the internal EnqueueChangeRecord algorithm tests whether each observer should receive the change by invoking ShouldDeliverToObserver.

ShouldDeliverToObserver returns true IFF an observer accepts the candidate change record's type, but accepts none of the change types currently being performed on the object (as represented in the ActiveChanges).

1.7End of turn delivery of change records

At the end of the current processing turn (or "Microtask"), DeliverAllChangeRecords is invoked, which continuously delivers pending change records to observers until there are none remaining.

The order in which observers are delivered to is maintained in the ObserverCallbacks list. The first time a function is used as an observer by providing it as an argument to Object.observe, it is appended to the end of ObserverCallbacks.

Delivery takes place by repeatedly iterating front-to-back through ObserverCallbacks, delivering to any observer who has pending change records, until no observer have pending change records.

2New Internal Properties, Objects and Algorithms

2.1[[ObserverCallbacks]]

There is now an ordered list, [[ObserverCallbacks]] which is shared per event queue. It is initially empty.

NoteThis list is used to provide a deterministic ordering in which callbacks are called.

2.2[[PendingChangeRecords]]

Every function now has a [[PendingChangeRecords]] internal slot which is an ordered list of ChangeRecords. It is initially empty.

NoteThis list gets populated with change records as the objects that this function is observing are mutated. It gets emptied when the change records are delivered.

2.3[[Notifier]]

Every object O now has a [[Notifier]] internal slot which is initially undefined.

NoteThis gets lazily initialized to a notifier object which is an object with the %NotifierPrototype% as its [[Prototype]].

2.4Notifier Objects

A Notifier Object is an object returned from Object.getNotifier. There is not a named constructor for Notifier Objects.

2.4.1Properties of the Notifier Prototype

All Notifier Objects inherit properties from the %NotifierPrototype% intrinsic object. The %NotifierPrototype% intrinsic object is an ordinary object and its [[Prototype]] internal slot is the %ObjectPrototype% intrinsic object (19.1.3). In addition, %NotifierPrototype% has the following properties:

2.4.1.1%NotifierPrototype%.notify(changeRecord)

  1. Let O be the this value.
  2. If Type(O) is not Object, throw a TypeError exception.
  3. If O does not have a [[Target]] internal slot return.
  4. Let type be Get(changeRecord, "type").
  5. ReturnIfAbrupt(type).
  6. If Type(type) is not string, throw a TypeError exception.
  7. Let target be the value of O's [[Target]] internal slot.
  8. Let newRecord be ObjectCreate(%ObjectPrototype%).
  9. Let desc be the PropertyDescriptor{[[Value]]: target, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  10. Let success be the result of calling the [[DefineOwnProperty]] internal method of newRecord passing "object" and desc as arguments.
  11. Assert: success is true.
  12. Let names be the result of calling the [[Enumerate]] internal method of changeRecord with no arguments.
  13. Repeat for each element N of names in List order,
    1. If N is not "object", then
      1. Let value be Get(changeRecord, N).
      2. ReturnIfAbrupt(value).
      3. Let desc be the PropertyDescriptor{[[Value]]: value, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
      4. Let success be the result of calling the [[DefineOwnProperty]] internal method of newRecord passing N and desc as arguments.
      5. Assert: success is true.
  14. Set the value of the [[Extensible]] internal slot of newRecord to false.
  15. Call EnqueueChangeRecord(target, newRecord).

2.4.1.2%NotifierPrototype%.performChange(changeType, changeFn)

  1. Let O be the this value.
  2. If Type(O) is not Object, throw a TypeError exception.
  3. If O does not have a [[Target]] internal slot return.
  4. 1. Let target be the value of O's [[Target]] internal slot.
  5. If Type(changeType) is not string, throw a TypeError exception.
  6. If IsCallable(changeFn) is false, throw a TypeError exception.
  7. Call BeginChange(target, changeType).
  8. Let changeRecord be the result of calling the [[Call]] internal method of changeFn, with undefined as thisArgument and an empty List as argumentsList.
  9. Call EndChange(target, changeType).
  10. ReturnIfAbrupt(changeRecord).
  11. Let changeObservers be the value of O's [[ChangeObservers]] internal slot.
  12. If changeObservers is empty, return.
  13. Let target be the value of O's [[Target]] internal slot.
  14. Let newRecord be ObjectCreate(%ObjectPrototype%).
  15. Let desc be the PropertyDescriptor{[[Value]]: target, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  16. Let success be the result of calling the [[DefineOwnProperty]] internal method of newRecord passing "object" and desc as arguments.
  17. Assert: success is true.
  18. Let desc be the PropertyDescriptor{[[Value]]: changeType, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  19. Let success be the result of calling the [[DefineOwnProperty]] internal method of newRecord passing "type" and desc as arguments.
  20. Assert: success is true.
  21. Let names be the result of calling the [[Enumerate]] internal method of changeRecord with no arguments.
  22. Repeat for each element N of names in List order,
    1. If N is not "object" and N is not "type", then
      1. Let value be Get(changeRecord, N).
      2. ReturnIfAbrupt(value).
      3. Let desc be the PropertyDescriptor{[[Value]]: value, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
      4. Let success be the result of calling the [[DefineOwnProperty]] internal method of newRecord passing N and desc as arguments.
      5. Assert: success is true.
  23. Set the value of the [[Extensible]] internal slot of newRecord to false.
  24. Call EnqueueChangeRecord(target, newRecord).

2.5GetNotifier(O)

When the abstract operation GetNotifier is called with Object O the following steps are taken:

  1. Let notifier be the value of O's [[Notifier]] internal slot.
  2. If notifier is undefined, then
    1. Let notifier be ObjectCreate(%NotifierPrototype%, ([[Target]], [[ChangeObservers]], [[ActiveChanges]])).
    2. Set notifier's [[Target]] internal slot to O.
    3. Set notifier's [[ChangeObservers]] internal slot to a new empty List.
    4. Set notifier's [[ActiveChanges]] internal slot to ObjectCreate(null).
    5. Set O's [[Notifier]] internal slot to notifier.
  3. return notifier.

2.6BeginChange(O, changeType)

When the abstract operation BeginChange is called with Object O and string changeType the following steps are taken:

  1. Assert: Type(O) is Object.
  2. Assert: Type(changeType) is String.
  3. Let notifier be GetNotifier(O).
  4. Let activeChanges be the value of notifier's [[ActiveChanges]] internal slot.
  5. Let changeCount be Get(activeChanges, changeType).
  6. ReturnIfAbrupt(changeCount).
  7. If changeCount is undefined, let changeCount be 1.
  8. Else, let changeCount be changeCount + 1.
  9. Let success be CreateDataProperty(activeChanges, changeType, changeCount).
  10. Assert: success is true.

2.7EndChange(O, changeType)

When the abstract operation EndChange is called with Object O and string changeType the following steps are taken:

  1. Assert: Type(O) is Object.
  2. Assert: Type(changeType) is String.
  3. Let notifier be GetNotifier(O).
  4. Let activeChanges be the value of notifier's [[ActiveChanges]] internal slot.
  5. Let changeCount be Get(activeChanges, changeType).
  6. ReturnIfAbrupt(changeCount).
  7. Assert: changeCount > 0.
  8. Let changeCount be changeCount - 1.
  9. Let success be CreateDataProperty(activeChanges, changeType, changeCount).
  10. Assert: success is true.

2.8ShouldDeliverToObserver(activeChanges, acceptList, changeType)

When the abstract operation ShouldDeliverToObserver is called with Object activeChanges, List acceptList and string changeType the following steps are taken:

  1. Let doesAccept be false.
  2. For each accept in acceptList, do
    1. Let activeChangeCount be Get(activeChanges, accept).
    2. ReturnIfAbrupt(activeChangeCount).
    3. If activeChangeCount > 0, return false.
    4. If accept is same string as changeType, then
      1. Let doesAccept be true .
  3. return doesAccept.

2.9EnqueueChangeRecord(O, changeRecord)

When the abstract operation EnqueueChangeRecord is called with Object O and change record changeRecord the following steps are taken:

  1. Assert: Type(O) is Object.
  2. Let notifier be GetNotifier(O).
  3. Let changeType be Get(changeRecord, "type").
  4. ReturnIfAbrupt(changeType).
  5. Let activeChanges be the value of notfier's [[ActiveChanges]] internal slot.
  6. Let changeObservers be the value of notifier's [[ChangeObservers]] internal slot.
  7. For each observerRecord in changeObservers, do
    1. Let deliver be ShouldDeliverToObserver(activeChanges, observerRecord.[[Accept]], changeType).
    2. If deliver is false, continue.
    3. Otherwise, let observer be observerRecord.[[Callback]].
    4. Let pendingRecords be the value of observer's [[PendingChangeRecords]] internal slot.
    5. If observerRecord.[[Skip]] is false, then
      1. Append undefined to the end of pendingRecords.
    6. Else,
      1. Append changeRecord to the end of pendingRecords.

2.10DeliverChangeRecords(C)

When the abstract operation DeliverChangeRecords is called with callback C, the following steps are taken:

  1. Let changeRecords be the value of C's [[PendingChangeRecords]] internal slot.
  2. Set C's [[PendingChangeRecords]] internal slot to a new empty List.
  3. Let array be ArrayCreate(0).
  4. Let n be 0.
  5. Let anyFound be false.
  6. Let anyRecords be false.
  7. For each record in changeRecords, do:
    1. Let anyFound be true.
    2. If record is undefined, continue.
    3. Let anyRecords be true.
    4. Let status be the result of CreateDataProperty(array, ToString(n), record).
    5. Assert: status is true.
    6. Increment n by 1.
  8. If anyFound is false, return false.
  9. If anyRecords is true, then:
    1. Let arg be array.
  10. Else,
    1. Let arg be null.
  11. Let status be Call(C, undefined, «arg»).
  12. result is intentionally ignored here. An implementation might choose to log abrupt completions here.
  13. Return true.
NoteThe user facing function ''Object.deliverChangeRecords'' returns ''undefined'' to prevent detection if anything was delivered or not.

2.11DeliverAllChangeRecords()

When the abstract operation DeliverAllChangeRecords is called, the following steps are taken:

  1. Let observers be the result of getting [[ObserverCallbacks]]
  2. Let anyWorkDone be false.
  3. For each observer in observers, do:
    1. Let result be DeliverChangeRecords(observer).
    2. If result is true, set anyWorkDone to true.
  4. Return anyWorkDone.
NoteIt is the intention that the embedder will call this internal algorithm when it is time to deliver the change records.

2.12CreateChangeRecord(type, object, name, oldDesc, newDesc)

When the abstract operation CreateChangeRecord is called with string type, Object object, name, Object oldDesc and Object newDesc the following steps are taken:

  1. Let changeRecord be ObjectCreate(%ObjectPrototype%).
  2. Let desc be the PropertyDescriptor{[[Value]]: type, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  3. Let success be the result of calling the [[DefineOwnProperty]] internal method of changeRecord passing "type" and desc as arguments.
  4. Assert: success is true.
  5. Let desc be the PropertyDescriptor{[[Value]]: object, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  6. Let success be the result of calling the [[DefineOwnProperty]] internal method of changeRecord passing "object" and desc as arguments.
  7. Assert: success is true.
  8. If IsPropertyKey(name) is true, then
    1. Let desc be the PropertyDescriptor{[[Value]]: name, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
    2. Let success be the result of calling the [[DefineOwnProperty]] internal method of changeRecord passing "name" and desc as arguments.
    3. Assert: success is true.
  9. If IsDataDescriptor(oldDesc) is true, then
    1. If IsDataDescritor(newDesc) is false or SameValue(oldDesc.[[Value]], newDesc.[[Value]]) is false, then
      1. Let desc be the PropertyDescriptor{[[Value]]: oldDesc, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
      2. Let success be the result of calling the [[DefineOwnProperty]] internal method of changeRecord passing "oldValue" and desc as arguments.
      3. Assert: success is true.
  10. Set the value of the [[Extensible]] internal slot of changeRecord to false.
  11. Return changeRecord.

2.13[[CreateSpliceChangeRecord]]

There is now an abstract operation [[CreateSpliceChangeRecord]]:

?.??.?? [[CreateSpliceChangeRecord]] (object, index, removed, addedCount)

When the abstract operation CreateSpliceChangeRecord is called with the arguments: object, index, removed, and addedCount, the following steps are taken:

  1. Let changeRecord be the result of the abstraction operation ObjectCreate (15.2).
  2. Call the [[DefineOwnProperty]] internal method of changeRecord with arguments ''"type"'', Property Descriptor {[[Value]]: "splice", [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}, and false.
  3. Call the [[DefineOwnProperty]] internal method of changeRecord with arguments ''"object"'', Property Descriptor {[[Value]]: object, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}, and false.
  4. Call the [[DefineOwnProperty]] internal method of changeRecord with arguments ''"index"'', Property Descriptor {[[Value]]: index, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}, and false.
  5. Call the [[DefineOwnProperty]] internal method of changeRecord with arguments ''"removed"'', Property Descriptor {[[Value]]: removed, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}, and false.
  6. Call the [[DefineOwnProperty]] internal method of changeRecord with arguments ''"addedCount"'', Property Descriptor {[[Value]]: addedCount, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}, and false.
  7. Set the value of the [[Extensible]] internal slot of changeRecord to false.
  8. Return changeRecord.

3Public API Specification

3.1Object.observe(O, callback, options = undefined)

When the observe method is called with arguments Object O, function callback and options the following steps are taken:

  1. If Type(O) is not Object, throw a TypeError exception.
  2. If IsCallable(callback) is false, throw a TypeError exception.
  3. If TestIntegrityLevel(callback, "frozen") is true, throw a TypeError exception.
  4. Let accept be GetOption(options, "acceptTypes").
  5. ReturnIfAbrupt(accept).
  6. If accept is undefined, then
    1. Let acceptList be a new List containing "add", "update", "delete", "reconfigure", "setPrototype" and "preventExtensions".
  7. Else
    1. Let acceptList be a new List.
    2. If Type(accept) is not Object, throw a TypeError exception.
    3. Let lenValue be the result of Get(accept, "length")
    4. ReturnIfAbrupt(lenValue).
    5. Let len be ToLength(lenValue).
    6. ReturnIfAbrupt(len).
    7. Let nextIndex be 0.
    8. While nextIndex < len, repeat
      1. Let next be the result of Get(accept, ToString(nextIndex)).
      2. ReturnIfAbrupt(next).
      3. Let nextString be ToString(next).
      4. ReturnIfAbrupt(nextString).
      5. Append nextString to acceptList.
      6. Let nextIndex be nextIndex + 1.
  8. Let skipRecordsValue be GetOption(options, "skipRecords").
  9. ReturnIfAbrupt(skipRecordsValue).
  10. Let skipRecords be ToBoolean(skipRecordsValue).
  11. Let notifier be GetNotifier(O).
  12. Let changeObservers be the value of notifier`s [[ChangeObservers]] internal slot.
  13. If changeObservers already contains a record whose [[Callback]] field is the same as callback, then
    1. Set record.[[Accept]] to acceptList.
    2. Set record.[[Skip]] to skipRecords.
    3. Return O.
  14. Let observerRecord be Record{[[Callback]]: callback, [[Accept]: acceptList, [[Skip]]: skipRecords}.
  15. Append observerRecord to the end of the changeObservers list.
  16. Let observerCallbacks be [[ObserverCallbacks]].
  17. If observerCallbacks already contains callback, return O.
  18. Append callback to the end of the observerCallbacks list.
  19. Return O.

3.2Object.unobserve(O, callback)

When the unobserve method is called with arguments Object O and function callback the following steps are taken:

  1. If Type(O) is not Object, throw a TypeError exception.
  2. If IsCallable(callback) is false, throw a TypeError exception.
  3. Let notifier be GetNotifier(O).
  4. Let changeObservers be the value of notifier's [[ChangeObservers]] internal slot.
  5. If changeObservers does not contain a record whose callback property is the same as callback, return O.
  6. Remove the record whose callback property is the same as callback from the changeObservers list.
  7. Return O.

3.3Array.observe(O, callback, options = undefined)

A new function Array.observe is added, which is equivalent to. TODO(arv) Use a real spec algorithm here. Refactor Object.observe into a new spec algorithm that is shared between these two.


function(O, callback,
    {accept = ["add", "update", "delete", "splice"], skip = false} = {}) {
  return Object.observe(O, callback, {
    acceptTypes: accept,
    skipRecords: skip
  });
}
    

3.4Array.unobserve

A new function Array.unobserve(o, callback) is added, which is equivalent to


function(O, callback) {
  return Object.unobserve(O, callback);
}
    

3.5Object.deliverChangeRecords

When the deliverChangeRecords method is called with argument callback, the following steps are taken:

  1. If IsCallable(callback) is false, throw a TypeError exception.
  2. Repeat
    1. Let result be DeliverChangeRecords(callback).
    2. If result is false return.

3.6Object.getNotifier

When the getNotifier method is called with argument O, the following steps are taken:

  1. If Type(O) is not Object, throw a TypeError exception.
  2. If O is frozen, return null.
  3. Return the result of GetNotifier(O).

4Modifications to existing internal algorithms

Modifications to existing algorithms are indicated like this.

4.19.1.6.3 ValidateAndApplyPropertyDescriptor(O, P, extensible, Desc, current)

When the abstract operation ValidateAndApplyPropertyDescriptor is called with Object O, property key P, Boolean value extensible, and property descriptors Desc, and current the following steps are taken:

This algorithm contains steps that test various fields of the Property Descriptor Desc for specific values. The fields that are tested in this manner need not actually exist in Desc. If a field is absent then its value is considered to be false.

NoteIf undefined is passed as the O argument only validation is performed and no object updates are performed.
  1. Assert: If O is not undefined then P is a valid property key.
  2. Let changeType be a string, initially set to "reconfigure".
  3. If current is undefined, then
    1. If extensible is false, then return false.
    2. Assert: extensible is true.
    3. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then
      1. If O is not undefined, then create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value.
    4. Else Desc must be an accessor Property Descriptor,
      1. If O is not undefined, then create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value.
    5. Let R be CreateChangeRecord("add", O, P, current, Desc).
    6. Call EnqueueChangeRecord(O, R).
    7. Return true.
  4. Return true, if every field in Desc is absent.
  5. Return true, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm.
  6. If the [[Configurable]] field of current is false then
    1. Return false, if the [[Configurable]] field of Desc is true.
    2. Return false, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
  7. If IsGenericDescriptor(Desc) is true, then no further validation is required.
  8. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then
    1. Return false, if the [[Configurable]] field of current is false.
    2. If IsDataDescriptor(current) is true, then
      1. If O is not undefined, then convert the property named P of object O from a data property to an accessor property. Preserve the existing values of the converted property’s [[Configurable]] and [[Enumerable]] attributes and set the rest of the property’s attributes to their default values.
    3. Else,
      1. If O is not undefined, then convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property’s [[Configurable]] and [[Enumerable]] attributes and set the rest of the property’s attributes to their default values.
  9. Else if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then
    1. If the [[Configurable]] field of current is false, then
      1. Return false, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true.
      2. If the [[Writable]] field of current is false, then
        1. Return false, if the [[Value]] field of Desc is present and SameValue(Desc.[[Value]], current.[[Value]]) is false.
    2. else the [[Configurable]] field of current is true, so any change is acceptable.
    3. If [[Configurable]] is not in Desc or SameValue(Desc.[[Configurable]], current.[[Configurable]]) is true and [[Enumerable]] is not in Desc or SameValue(Desc.[[Enumerable]], current.[[Enumerable]]) is true and [[Writable]] is not in Desc or SameValue(Desc.[[Writable]], current.[[Writable]]) is true, then
      1. Let changeType to be the string "update".
  10. Else IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true,
    1. If the [[Configurable]] field of current is false, then
      1. Return false, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false.
      2. Return false, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false.
  11. If O is not undefined, then
    1. For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
  12. Let R be CreateChangeRecord(changeType, O, P, current, Desc).
  13. Call EnqueueChangeRecord(O, R).
  14. Return true.

4.29.1.10 [[Delete]] (P)

When the [[Delete]] internal method of O is called with property key P the following steps are taken:

  1. Assert: IsPropertyKey(P) is true.
  2. Let desc be the result of calling the [GetOwnProperty]] internal method of O with argument P.
  3. ReturnIfAbrupt(desc).
  4. If desc is undefined, then return true.
  5. Let notifier be GetNotifier(O).
  6. If desc.[[Configurable]] is true, then
    1. Remove the own property with name P from O.
    2. Let R be CreateChangeRecord("delete", O, P, desc).
    3. Call EnqueueChangeRecord(O and R).
    4. Return true.
  7. Return false.

4.39.1.4 [[PreventExtensions]] ()

When the [[PreventExtensions]] internal method of O is called the following steps are taken:

  1. Let wasExtensible be the value of O's [[Extensible]] internal slot.
  2. Set the value of the [[Extensible]] internal slot of O to false.
  3. If wasExtensible is false, return true.
  4. Let notifier be GetNotifier(O).
  5. Let R be CreateChangeRecord("preventExtensions", O).
  6. Call EnqueueChangeRecord(O, R).
  7. Return true.

4.49.1.2 [[SetPrototypeOf]] (V)

When the [[SetPrototypeOf]] internal method of O is called with argument V the following steps are taken:

  1. Assert: Either Type(V) is Object or Type(V) is Null.
  2. Let extensible be the value of the [[Extensible]] internal slot of O.
  3. Let current be the value of the [[Prototype]] internal slot of O.
  4. If SameValue(V, current), then return true.
  5. If extensible is false, then return false.
  6. If V is not null, then
    1. Let p be V.
    2. Repeat, while p is not null
      1. If SameValue(p, O) is true, then return false.
      2. Let nextp be the result of calling the [[GetPrototypeOf]] internal method of p with no arguments.
      3. ReturnIfAbrupt(nextp).
      4. Let p be nextp.
  7. Let extensible be the value of the [[Extensible]] internal slot of O.
  8. If extensible is false, then
    1. Let current2 be the value of the [[Prototype]] internal slot of O.
    2. If SameValue(V, current2) is true, then return true.
    3. Return false.
  9. Set the value of the [[Prototype]] internal slot of O to V.
  10. Let notifier be GetNotifier(O).
  11. Let R be ObjectCreate(%ObjectPrototype%).
  12. Let desc be the PropertyDescriptor{[[Value]]: "setPrototype", [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  13. Let success be the result of calling the [[DefineOwnProperty]] internal method of R passing "type" and desc as arguments.
  14. Assert: success is true.
  15. Let desc be the PropertyDescriptor{[[Value]]: O, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  16. Let success be the result of calling the [[DefineOwnProperty]] internal method of R passing "object" and desc as arguments.
  17. Assert: success is true.
  18. Let desc be the PropertyDescriptor{[[Value]]: current, [[Writable]]: false, [[Enumerable]]: true, [[Configurable]]: false}.
  19. Let success be the result of calling the [[DefineOwnProperty]] internal method of R passing "oldValue" and desc as arguments.
  20. Assert: success is true.
  21. Set the [[Extensible]] internal slot of R to false.
  22. Call EnqueueChangeRecord(O, R).
  23. Return true.

4.5Changes to Array methods

4.5.1Array.prototype.pop

22.1.3.16 Array.prototype.pop ()

The last element of the array is removed from the array and returned.

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. ReturnIfAbrupt(O).
  3. Let lenVal be the result of Get(O, "length").
  4. Let len be ToUint32(lenVal).
  5. ReturnIfAbrupt(len).
  6. If len is zero,
    1. Let putStatus be the result of Put(O, "length", 0, true).
    2. ReturnIfAbrupt(putStatus).
    3. Return undefined.
  7. Else, len > 0
    1. Let newLen be len–1.
    2. Let indx be ToString(newLen).
    3. Call the [[BeginChange]] internal passing O and "splice".
    4. Let element be the result of Get(O, indx).
    5. ReturnIfAbrupt(element).
    6. Let deleteStatus be the result of DeletePropertyOrThrow(O, indx).
    7. If deleteStatus.[[type]] is normal, let putStatus be the result of Put(O, "length", newLen, true).
    8. Call the [[EndChange]] internal passing O and "splice".
    9. Let elements be a new List.
    10. Append element to the end of elements.
    11. Let removed be the result of CreateArrayFromList(elements).
    12. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: O, newLen, removed and 0.
    13. Call [[EnqueueChangeRecord]] passing O and R
    14. ReturnIfAbrupt(deleteStatus)
    15. ReturnIfAbrupt(putStatus)
    16. Return element.

4.5.2Array.prototype.push

22.1.3.17 Array.prototype.push ( [ item1 [ , item2 [ , … ] ] ] )

The arguments are appended to the end of the array, in the order in which they appear. The new length of the array is returned as the result of the call.

When the push method is called with zero or more arguments item1, item2, etc., the following steps are taken:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. ReturnIfAbrupt(O).
  3. Let lenVal be the result of Get(O, "length").
  4. Let n be ToLength(lenVal).
  5. ReturnIfAbrupt(n).
  6. Let putStatus be n.
  7. Let items be an internal List whose elements are, in left to right order, the arguments that were passed to this function invocation.
  8. Let numAdded be the length of the items list.
  9. Call the [[BeginChange]] internal method passing O and "splice".
  10. Repeat, while items is not empty and status.[[type]] is normal
    1. Remove the first element from items and let E be the value of the element.
    2. Let putStatus be the result of Put(O, ToString(n), E, true).
    3. If putStatus.[[type]] is normal, increase n by 1.
  11. If putStatus.[[type]] is normal, let putStatus be the result of Put(O, "length", n, true).
  12. Call the [[EndChange]] internal method passing O and "splice".
  13. Let removed be the result of ArrayCreate(0).
  14. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: O, lenVal, removed and numAdded.
  15. Call [[EnqueueChangeRecord]] passing O and R
  16. ReturnIfAbrupt(putStatus)
  17. Return n.

4.5.3Array.prototype.shift

22.1.3.21 Array.prototype.shift ( )

The first element of the array is removed from the array and returned.

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. ReturnIfAbrupt(O).
  3. Let lenVal be the result of Get(O, "length").
  4. Let len be ToLength(lenVal).
  5. ReturnIfAbrupt(len).
  6. If len is zero, then
    1. Let putStatus be the result of Put(O, "length", 0, true).
    2. ReturnIfAbrupt(putStatus).
    3. Return undefined.
  7. Call the [[BeginChange]] internal method passing O and "splice".
  8. Let first be the result of Get(O, "0").
  9. ReturnIfAbrupt(first).
  10. Let status be first
  11. Let k be 1.
  12. Repeat, while status.[[type]] is normal and k < len
    1. Let from be ToString(k).
    2. Let to be ToString(k–1).
    3. Let fromPresent be the result of HasProperty(O, from).
    4. Let status be fromPresent.
    5. If status.[[type]] is normal
      1. If fromPresent is true, then
        1. Let fromVal be the result of Get(O, from).
        2. Let status be fromVal.
        3. If status.[[type]] is normal, let putStatus be the result of Put(O, to, fromVal, true).
      2. Else, fromPresent is false
        1. Let status be the result of DeletePropertyOrThrow(O, to).
      3. Increase k by 1.
  13. If status.[[type]] is normal, let status be the result of DeletePropertyOrThrow(O, ToString(len–1)).
  14. If status.[[type]] is normal, let status be the result of Put(O, "length", len–1, true).
  15. Call the [[EndChange]] internal method passing O and "splice".
  16. Let elements be a new List.
  17. Append first to the end of elements.
  18. Let removed be the result of CreateArrayFromList(elements).
  19. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: O, 0, removed and 0.
  20. Call [[EnqueueChangeRecord]] passing O and R.
  21. ReturnIfAbrupt(status).
  22. Return first.

4.5.4Array.prototype.splice

22.1.3.25 Array.prototype.splice (start, deleteCount [ , item1 [ , item2 [ , … ] ] ] )

When the splice method is called with two or more arguments start, deleteCount and (optionally) item1, item2, etc., the deleteCount elements of the array starting at array index start are replaced by the arguments item1, item2, etc. An Array object containing the deleted elements (if any) is returned. The following steps are taken:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. ReturnIfAbrupt(O).
  3. Let lenVal be the result of Get(O, "length")
  4. Let len be ToLength(lenVal).
  5. ReturnIfAbrupt(len).
  6. Let relativeStart be ToInteger(start).
  7. ReturnIfAbrupt(relativeStart).
  8. If relativeStart is negative, let actualStart be max( (len + relativeStart), 0); else let actualStart be min(relativeStart, len).
  9. If start is not present, then
    1. Let actualDeleteCount be 0.
  10. Else if deleteCount is not present, then
    1. Let actualDeleteCount be len - actualStart
  11. Else,
    1. Let dc be ToInteger(deleteCount).
    2. ReturnIfAbrupt(dc).
    3. Let actualDeleteCount be min(max(dc, 0), len - actualStart).
  12. Let A be undefined.
  13. If O is an exotic Array object, then
    1. Let C be the result of Get(O, "constructor").
    2. ReturnIfAbrupt(C).
    3. If IsConstructor(C) is true, then
      1. Let thisRealm be the running execution context's Realm.
      2. If thisRealm and the value of C's [[Realm]] internal slot are the same value, then
        1. Let A be the result of calling the [[Construct]] internal method of C with argument (actualDeleteCount).
  14. If A is undefined, then
    1. Let A be the result of the abstract operation ArrayCreate with the argument actualDeleteCount.
  15. ReturnIfAbrupt(A).
  16. Let k be 0.
  17. Call the [[BeginChange]] internal method passing O and "splice".
  18. Let deletedItems be a new List.
  19. Let status be relativeStart.
  20. Repeat, while status.[[type]] is normal and k < actualDeleteCount
    1. Let from be ToString(actualStart+k).
    2. Let fromPresent be the result of HasProperty(O, from).
    3. Let status be fromPresent.
    4. If status.[[type]] is normal, then
      1. If fromPresent is true, then
        1. Let fromValue be the result of Get(O, from).
        2. Let status be fromValue.
        3. If status.[[type]] is normal, then
          1. Let status be the result of CreateDataPropertyOrThrow(A, ToString(k), fromValue).
          2. Append fromValue to the end of deletedItems.
      2. Increment k by 1.
  21. If status.[[type]] is not throw, let status be the result of Put(A, "length", actualDeleteCount, true).
  22. If status.[[type]] is not throw, then
    1. Let items be an internal List whose elements are, in left to right order, the portion of the actual argument list starting with item1. The list will be empty if no such items are present.
    2. Let itemCount be the number of elements in items.
  23. If status.[[type]] is not throw and itemCount < actualDeleteCount, then
    1. Let k be actualStart.
    2. Repeat, while status.[[type]] is normal and k < (len – actualDeleteCount)
      1. Let from be ToString(k+actualDeleteCount).
      2. Let to be ToString(k+itemCount).
      3. Let fromPresent be the result of HasProperty(O, from).
      4. Let status be fromPresent.
      5. If status.[[type]] is normal, then
        1. If fromPresent is true, then
          1. Let fromValue be the result of Get(O, from).
          2. Let status be fromValue.
          3. If status.[[type]] is normal, let status be the result of Put(O, to, fromValue, true).
        2. Else, fromPresent is false
          1. Let status be the result of DeletePropertyOrThrow(O, to).
        3. If status.[[type]] is normal, Increase k by 1.
    3. If status.[[type]] is normal, Let k be len.
    4. Repeat, while status.[[type]] is normal and k > (len – actualDeleteCount + itemCount)
      1. Let status be the result of DeletePropertyOrThrow(O, ToString(k–1)).
      2. If status.[[type]] is normal, decrease k by 1.
  24. Else if status.[[type]] is normal and itemCount > actualDeleteCount, then
    1. Let k be (len – actualDeleteCount).
    2. Repeat, while status.[[type]] is normal and k > actualStart
      1. Let from be ToString(k + actualDeleteCount – 1).
      2. Let to be ToString(k + itemCount – 1)
      3. Let fromPresent be the result of HasProperty(O, from).
      4. Let status be fromPresent.
      5. If status.[[type]] is normal, then
        1. If fromPresent is true, then
          1. Let fromValue be the result of Get(O, from).
          2. Let status be fromValue.
          3. If status.[[type]] is normal, let status be the result of Put(O, to, fromValue, true).
        2. Else fromPresent is false,
          1. Let status be the result of DeletePropertyOrThrow(O, to).
        3. If status.[[type]] is normal, decrease k by 1.
  25. If status.[[type]] is normal, then
    1. Let k be actualStart.
    2. Repeat, while status.[[type]] is normal and items is not empty
      1. Remove the first element from items and let E be the value of that element.
      2. Let status be the result of Put(O, ToString(k), E, true).
      3. If status.[[type]] is normal, increase k by 1.
    3. Let status be the result of Put(O, "length", len – actualDeleteCount + itemCount, true).
  26. Call the [[EndChange]] internal method passing O and "splice".
  27. Let removed be CreateArrayFromList(deletedItems).
  28. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: O, actualStart, removed and itemCount.
  29. Call [[EnqueueChangeRecord]] passing O and R
  30. ReturnIfAbrupt(status)
  31. Return A.

4.5.5Array.prototype.unshift

22.1.3.28 Array.prototype.unshift ( [ item1 [ , item2 [ , … ] ] ] )

The arguments are prepended to the start of the array, such that their order within the array is the same as the order in which they appear in the argument list.

When the unshift method is called with zero or more arguments item1, item2, etc., the following steps are taken:

  1. Let O be the result of calling ToObject passing the this value as the argument.
  2. ReturnIfAbrupt(O).
  3. Let lenVal be the result of Get(O, "length")
  4. Let len be ToLength(lenVal).
  5. ReturnIfAbrupt(len).
  6. Let argCount be the number of actual arguments.
  7. Let k be len.
  8. Let status be len.
  9. Call the [[BeginChange]] internal method passing O and "splice".
  10. Repeat, while status.[[type]] is normal and k > 0,
    1. Let from be ToString(k–1).
    2. Let to be ToString(k+argCount –1).
    3. Let fromPresent be the result of HasProperty(O, from).
    4. Let status be fromPresent.
    5. If status.[[type]] is normal, then
      1. If fromPresent is true, then
        1. Let fromValue be the result of Get(O, from).
        2. Let status be fromValue.
        3. If status.[[type]] is normal, let putStatus be the result of Put(O, to, fromValue, true).
        4. Let status be putStatus.
      2. Else, fromPresent is false
        1. Let deleteStatus be the result of DeletePropertyOrThrow(O, to).
        2. Let status be deleteStatus.
      3. If status.[[type]] is not throw, decrease k by 1.
  11. If status.[[type]] is normal, then
    1. Let j be 0.
    2. Let items be a List whose elements are, in left to right order, the arguments that were passed to this function invocation.
  12. Repeat, while status.[[type]] is normal and items is not empty
    1. Remove the first element from items and let E be the value of that element.
    2. Let status be the result of Put(O, ToString(j), E, true).
    3. If status.[[type]] is normal, increase j by 1.
  13. Let status be the result of Put(O, "length", len+argCount, true).
  14. Call the [[EndChange]] internal method passing O and "splice".
  15. Let removed be the result of ArrayCreate(0).
  16. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: O, 0, removed and argCount.
  17. Call [[EnqueueChangeRecord]] passing O and R.
  18. ReturnIfAbrupt(status)
  19. Return len+argCount.

4.6Array Exotic Objects

4.6.1[[DefineOwnProperty]] (P, Desc)

9.4.2.1 [[DefineOwnProperty]] (P, Desc)

When the [[DefineOwnProperty]] internal method of an exotic Array object A is called with property P, and Property Descriptor Desc the following steps are taken:

  1. Assert: IsPropertyKey(P) is true.
  2. If P is "length", then
    1. Return the result of calling ArraySetLength with arguments A, and Desc.
  3. Else if P is an array index, then
    1. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of A passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured.
    2. Let oldLen be oldLenDesc.[[Value]].
    3. Let index be ToUint32(P).
    4. Assert: index will never be an abrupt completion.
    5. If index ≥ oldLen and oldLenDesc.[[Writable]] is false, then return false.
    6. Let succeeded be the result of calling OrdinaryDefineOwnProperty passing A, P, and Desc as arguments.
    7. ReturnIfAbrupt(succeeded).
    8. If succeeded is false, then return false.
    9. If index ≥ oldLen
      1. Set oldLenDesc.[[Value]] to index + 1.
      2. Let succeeded be the result of calling OrdinaryDefineOwnProperty passing A, "length", and oldLenDesc as arguments.
      3. ReturnIfAbrupt(succeeded).
      4. Call the [[BeginChange]] internal method passing A and "splice".
      5. Call the [[EndChange]] internal method passing A and "splice".
      6. Let removed be the result of ArrayCreate(0).
      7. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: A, oldLen, removed and (index + 1 - oldLen).
      8. Call [[EnqueueChangeRecord]] passing A and R
    10. Return true.
  4. Return the result of calling OrdinaryDefineOwnProperty passing A, P, and Desc as arguments.

4.6.2ArraySetLength Abstract Operation

9.4.2.2 ArraySetLength Abstract Operation

When the abstract operation ArraySetLength is called with an exotic Array object A, and Property Descriptor Desc the following steps are taken:

  1. If the [[Value]] field of Desc is absent, then
    1. Return the result of calling OrdinaryDefineOwnProperty passing A, "length", and Desc as arguments.
  2. Let newLenDesc be a copy of Desc.
  3. Let newLen be ToUint32(Desc.[[Value]]).
  4. If newLen is not equal to ToNumber( Desc.[[Value]]), throw a RangeError exception.
  5. Set newLenDesc.[[Value]] to newLen.
  6. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of A passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured.
  7. Let oldLen be oldLenDesc.[[Value]].
  8. If newLen ≥oldLen, then
    1. Return the result of calling OrdinaryDefineOwnProperty passing A, "length", and newLenDesc as arguments.
  9. If oldLenDesc.[[Writable]] is false, then return false.
  10. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true.
  11. Else,
    1. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted.
    2. Let newWritable be false.
    3. Set newLenDesc.[[Writable]] to true.
  12. Let succeeded be the result of calling OrdinaryDefineOwnProperty passing A, "length", and newLenDesc as arguments.
  13. ReturnIfAbrupt(succeeded).
  14. If succeeded is false, return false.
  15. Call the [[BeginChange]] internal method passing A and "splice".
  16. Let removedList be a new List.
  17. Let status be succeeded.
  18. Let numAdded be newLen - oldLen.
  19. If numAdded < 0, then let numAdded be 0.
  20. While status.[[type]] is normal and newLen < oldLen repeat,
    1. Set oldLen to oldLen – 1.
    2. Let oldValue be the result of Get(A, ToString(oldLen)).
    3. Let status be oldValue.
    4. If status.[[type]] is normal, then
      1. Add oldValue to the front of removedList.
      2. Let deleteSucceeded be the result of calling the [[Delete]] internal method of A passing ToString(oldLen).
      3. Let status be deleteSucceeded.
      4. If deleteSucceeded is false, then
        1. Set newLenDesc.[[Value]] to oldLen+1.
        2. If newWritable is false, set newLenDesc.[[Writable]] to false.
        3. Let status be the result of calling OrdinaryDefineOwnProperty passing A, "length", and newLenDesc as arguments.
        4. If status.[[type]] is normal, then
          1. Return false.
  21. If status.[[type]] is normal and newWritable is false, then
    1. Call OrdinaryDefineOwnProperty passing A, "length", and Property Descriptor{[[Writable]]: false} as arguments. This call will always return true.
  22. Call the [[EndChange]] internal passing A and "splice".
  23. Let removed be the result of ArrayCreateFromList(removedList).
  24. Let R be the result of calling [[CreateSpliceChangeRecord]] with arguments: A, newLen, removed and numAdded.
  25. Call [[EnqueueChangeRecord]] passing A and R.
  26. ReturnIfAbrupt(status)
  27. Return true.

AUpdates

11/14/2013:
  - Re-organized structure.
  - Added "Key Algorithms and Semantics" section.
  - Added "Cross-cutting Concerns and Potential Implementation Challenges"
11/13/2013:
  - Fixed a bunch of editorial issues discovered by Joshua Bell.
  - Rebase spec changes against Rev21 ES6 Draft
10/29/2013:
  - notifier.performChange now emits a change record if the return value of the changeFn is an object (feedback from Sept TC-39)
  - "intrinsic" change record types renamed for consistency. "new" -> "add", "updated" -> "update", "deleted" -> "delete", "reconfigured" -> "reconfigure", "prototype" -> "setPrototype" (feedback from Sept TC-39)
  - "preventExtensions" changeRecord type is now emitted when the first time an object's `[[Extensible]]` internal property is set to false.
9/12/2013:
  - Object.observe accept arg can be zero-length (now specifies that accept.length must be > 0).
7/20/2013:
  - Notifier.performChange, Array.observe & some refactoring of algorithms.
1/30/2013:
  - Suppress oldValue when the changeRecord is of type 'reconfigured' and the oldValue and present value are the same. Note that CreateChangeRecord now takes both oldDesc and newDesc as arguments.
12/21/2012:
  - Object.deliverChangeRecords now calls `[[DeliverChangeRecords]]` repeatedly until there no pending records to deliver.
11/19/2012:
  - Object.observer/unobserve now return the object (for consistency with Object.freeze, etc...).
11/13/2012:
  - In Object.unobserve, passing a non-function as the callback now throws (for symmetry with Object.observe).

10/28/2012:
  - In CreateChangeRecord, renamed fourth argument to "oldDesc" (from "desc") for clarity.
  - In CreateChangeRecord, shallow freeze created changeRecord (consistent with Notifier.prototype.notify).

10/23/2012:
  - In notify, moved the check for type not being a string above the check for empty observers so the error will throw regardless of if there are observers.
  - Change the spec language of notify to make it clear to return before creating the changeRecord if there are no observers.

9/11/2012:
  - Added a section that we need to schedule a `{type: "prototype", object: ..., oldValue: ...}` change record when the `[[Prototype]]` internal property is changed.

7/18/2012:
  - Only fire **`"updated"`** changes when value changes (using SameValue) and no other configuration change happens.

7/17/2012: Based on feedback from Mark Miller, Tom van Cutsem and Andreas Rossberg:
  - _anyWorkDone_ should be or'ed with the previous value.
  - Refactored into `[[GetNotifier]]` to ensure it is always initialized.
  - **`"descriptor"`** is now always **`"reconfigured"`**
  - Enforce that _changeRecord_ is frozen in `[[NotifierPrototype]]`.notify.
  - Never pass a descriptor in the change record. Only include oldValue if it was a data property before the change.
  - Remove redundant IsCallable check in unobserve.
  - Ensure that _O_ is an object in Object.getNotifier.
  

7/4/2012: Per security feedback from Mark Miller
  - Object.getNotifier() of frozen object, returns null.
  - Object `[[Notifier]]` is spec'd to be lazily created to avoid infinite regress.
  - All changeRecords are shallowly frozen, and "reconfigured" changeRecords have their oldValue (property descriptor) frozen.
  - Validate **`"object"`** field of _changeRecord_ for _notifyFunction_ so as to ensure that notifier of an object can only broadcast changes of its `[[Target]]`.
  - Validate **`"type"`** and **`"name"`** fields of _changeRecord_ for _notifyFunction_ and perform a shallow freeze to prevent unintentional delivery of shared mutable state.
  - Object.retrieveChangeRecords -> Object.deliverChangeRecords (invoke the function rather than return records).
  - Object callbacks invokations silently ignore all thrown exceptions and return values.
  - Frozen callback function objects cannot be registered as change observers (Object.observe) throws TypeError.

7/2/2012:
  - Remove changeRecord validation. Since there is no way to validate "oldValue", arbitrary data can always be passed.
  - Added Object.getNotifier(obj).notify(changeRecord) separation, so as to allow freezing of an object to prevent side-channel, but retaining the ability to notify if getNotifier() was called prior to freezing.

5/17/2012:
  - Call the callbacks with a ChangeRecord object describing the change.
  - Add sanity checks in `[[ToChangeRecord]]` which is used in `Object.notifyObservers`.
  - Add `Object.retrieveChangeRecords` which allows synchronous access to the pending changes.

3/16/2012:  Two requests from feedback from Rafael Weinstein and Erik Arvidsson:
  - Pass old property values as well as new to `[[FireChangeListeners]]`
  - Schedule change events to be delivered asynchronously "at the end of the turn"