var dF3x = () => {}; // This flag is never called. See line 3 of M, below. function M(x) { return function go(func) { if (func === dF3x) return x; else x = func(x); return go; }; } var m = M(x); // x can be any JavaScript value var m_clone = M(m(dF3x));
Calling m(func) for some function "func" changes x in the m-M(x) closure to func(x) (unless it throws an error); but has no effect on x in the m_clone-M(x) closure. Likewise, running m_clone(func) does not affect m. Why? Because x gets reassigned on line 4 of M.
1. When two variables point to the same object in memory, either variable can modify that object, thereby changing the value of both variables. For example,
var a, b; var a = b = [1,2,3]; a.push(4); console.log(a); // [1,2,3,4]; console.log(b); // [1,2,3,4]
2. When two variables point to the same object in memory, and one gets reassigned (as happens on line 4 of M), the variables become independent from one another. For example,
var a, b; a = b = [1,2,3]; a = a.concat(4); console.log(a); // [1,2,3,4] console.log(b); // [1,2,3]
Note: Object.is and === (strict equality) are both used to compare values, but they work differently in two ways: Object.is(NaN, NaN) returns true, whereas NaN === NaN returns false. Object.is(-0, +0) returns true, but -0 === +0 returns false. "===" could have been used in the demonstration below.
log(Object.is(NaN, NaN)); // true log(NaN === NaN); // false log(Object.is(-0, 0)); // true log(-0 === 0) // false
In the example below, m is defined as an array containing three arrays, one of which contains a function. cl is copy of m. m_clone is a clone of m, m_clone = M(m(dF3x)). The value returned by m_clone(dF3x) is, at first, a reference to the array held in the m-M(x) closure. When m(func) reassigns x in the m-M(x) closure, or m_clone(func) reassigns x in its closure for some function "func", the value of x in the m-M(x) closure and the value of x in the m_clone-M(x) become independent of one another. These reassignment happen on the 4th line of M.
As shown in the following examples, changes in m(dF3x) caused by m(v => v.concat(888)) don't change m_clone(dF3x), and changing m_clone(dF3x) by running m_clone(v => v.concat(777)) has no effect on m or cl. Unlike m and m_clone, m and cl point to the same place in memory, as do the values held in the m-M(x) and cl-M(x) closures; i.e., m(dF3x) and cl(dF3x). Therefore, changing x in either closure changes x in the other.
Here's the demonstration code:
// Utility function for logging var log = console.log; // Function used in M (below) to retrieve the // current value of x in the closure var dF3x = () => {} // Function M(x) that creates a closure over any JavaScript value x function M(x) { return function go(func) { if (func === dF3x) { // If func is dF3x, return the current value of x return x; } else { // **Critical Line**: Reassign x to the result of func(x) x = func(x); return go; } } } // Example function to be stored in the array function add(a, b) { return a + b; } // Create closures const m = M([ [6], [7], [add] ]); // Original closure const cl = m; // cl references the same closure as m const m_clone = M(m(dF3x)); // m_clone is a new closure with its own x // **Caption**: Initial State - All closures share the same x log("Initial State:"); log("m(dF3x) is", m(dF3x)); // [ [6], [7], [ [Function: add] ] ] log("cl(dF3x) is", cl(dF3x)); // [ [6], [7], [ [Function: add] ] ] log("m_clone(dF3x) is", m_clone(dF3x)); // [ [6], [7], [ [Function: add] ] ] log("Object.is(m(dF3x), cl(dF3x)):", Object.is(m(dF3x), cl(dF3x))); // true log("Object.is(m(dF3x), m_clone(dF3x)):", Object.is(m(dF3x), m_clone(dF3x))); // true // **Caption**: Mutating x via m_clone before any reassignment m_clone(dF3x).push(1111); log(" After m_clone(dF3x).push(1111):"); log("m(dF3x) is", m(dF3x)); // Mutated array includes 1111 log("cl(dF3x) is", cl(dF3x)); // Also includes 1111 log("m_clone(dF3x) is", m_clone(dF3x)); // Includes 1111 log("Object.is(m(dF3x), m_clone(dF3x)):", Object.is(m(dF3x), m_clone(dF3x))); // true // **Caption**: Reassigning x in m - This breaks the shared reference m(v => v.concat(888)); // Reassign x in m to a new array log("After m(v => v.concat(888)):"); log("m(dF3x) is", m(dF3x)); // New array with 888 log("cl(dF3x) is", cl(dF3x)); // Also updated (same closure as m) log("m_clone(dF3x) is", m_clone(dF3x)); // Remains unchanged with 1111 log("Object.is(m(dF3x), m_clone(dF3x)):", Object.is(m(dF3x), m_clone(dF3x))); // false // **Caption**: Reassigning x in m_clone - Independent state m_clone(v => v.concat(777)); // Reassign x in m_clone to a new array log("After m_clone(v => v.concat(777)):"); log("m(dF3x) is", m(dF3x)); // Unchanged from previous step log("cl(dF3x) is", cl(dF3x)); // Unchanged (same as m) log("m_clone(dF3x) is", m_clone(dF3x)); // New array with 1111 and 777 log("Object.is(m(dF3x), m_clone(dF3x)):", Object.is(m(dF3x), m_clone(dF3x))); // false // **Caption**: Mutating x in m_clone after reassignment m_clone(dF3x).push(2222); log("After m_clone(dF3x).push(2222):"); log("m(dF3x) is", m(dF3x)); // Unchanged log("cl(dF3x) is", cl(dF3x)); // Unchanged log("m_clone(dF3x) is", m_clone(dF3x)); // Now includes 1111, 777, 2222 // **Caption**: Mutating x in m m(dF3x).push(3333); log("After m(dF3x).push(3333):"); log("m(dF3x) is", m(dF3x)); // Includes 888, 3333 log("cl(dF3x) is", cl(dF3x)); // Also includes 888, 3333 log("m_clone(dF3x) is", m_clone(dF3x)); // Unchanged // Mutating x via m_clone after any reassignment m_clone(dF3x).push(444); log(" After m_clone(dF3x).push(444):"); log("m(dF3x) is", m(dF3x)); // Does not include 444 log("cl(dF3x) is", cl(dF3x)); // Also does not include 444 log("m_clone(dF3x) is", m_clone(dF3x)); // Only the clone has been updated log("Object.is(m(dF3x), m_clone(dF3x)):", Object.is(m(dF3x), m_clone(dF3x))); // Now it's false
This is the text returned by the demonstration code using Node.js:
Initial State: m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ] ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ] ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ] ] Object.is(m(dF3x), cl(dF3x)): true Object.is(m(dF3x), m_clone(dF3x)): true After m_clone(dF3x).push(1111): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111 ] Object.is(m(dF3x), m_clone(dF3x)): true After m(v => v.concat(888)): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111 ] Object.is(m(dF3x), m_clone(dF3x)): false After m_clone(v => v.concat(777)): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 777 ] Object.is(m(dF3x), m_clone(dF3x)): false After m_clone(dF3x).push(2222): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 777, 2222 ] After m(dF3x).push(3333): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888, 3333 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888, 3333 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 777, 2222 ] After m_clone(dF3x).push(444): m(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888, 3333 ] cl(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 888, 3333 ] m_clone(dF3x) is [ [ 6 ], [ 7 ], [ [Function: add] ], 1111, 777, 2222, 444 ] Object.is(m(dF3x), m_clone(dF3x)): false