Instructor: James Riely
class
declarations are not primitive
name
, age
, addr
)
var empty = {}; /* Object literal with no contents */
var person = { /* Object literal with three properties */
name: "Alice",
age: 50,
addr: "243 S Wabash Ave"
};
> [ person.name, person.name.length ]
[ 'Alice', 5 ]
> person.age = person.age + 1
51
> person.occupation
undefined /* huh???? */
> person.occupation.length
Uncaught TypeError: Cannot read property 'length' of undefined
function f (x) {
console.log ("1: " + x);
console.log ("2: " + x.data);
console.log ("3: " + x.data.length);
}
> f ({ data: [11,21,31] });
1: [object Object]
2: 11,21,31
3: 3
> f ("pizza");
1: pizza
2: undefined
Uncaught TypeError: Cannot read property 'length' of undefined
function f (x) {
console.log ("1: " + (x / 2));
console.log ("2: " + (x[0]));
}
> f ([11,21,31]);
1: NaN
2: 11
> f (8);
1: 4
2: undefined
> f (undefined);
1: NaN
Uncaught TypeError: Cannot read property '0' of undefined
var person = { name: "Alice", age: 50, addr: "243 S Wabash Ave" };
person.occupation = "Developer";
person["happy place"] = "Home";
> for (p in person) { console.log (p + ": " + person[p]); }
name: Alice
age: 50
addr: 243 S Wabash Ave
occupation: Developer
happy place: Home
> JSON.stringify(person)
'{"name":"Alice","age":50,"addr":"243 S Wabash Ave","occupation":"Developer","happy place":"Home"}'
var counter1 = { n: 0, get: function () { return this.n++; } };
var counter2 = { n: 0, get () { return this.n++; } };
> [counter1.get(), counter2.get()]
[0, 0]
> [counter1.get(), counter2.get()]
[1, 1]
> [counter1.get, counter2.get]
[ [Function: get], [Function: get] ]
> [counter1.get.toString(), counter2.get.toString()]
[ 'function () { return this.n++; }', 'get () { return this.n++; }' ]
var counter1 = { n: 0, get: function () { return this.n++; } };
var counter3 = { n: 0, get: function () { return n++; } };
> counter1.get()
0
> counter3.get()
Uncaught ReferenceError: n is not defined
at Object.get (repl:1:43)
var counter1 = { n: 0, get: function () { return this.n++; } };
> counter1.get()
0
> counter1.n = -2000
> counter1.get()
-2000
function createCounter4 () {
var n = 0;
return {
get: function () { return n++; }
};
};
var [c4a,c4b] = [createCounter4(), createCounter4()]
> c4a.get()
0
> [c4a,c4b].map (x => x.get())
[ 1, 0 ]
> [c4a,c4b].map (x => x.get())
[ 2, 1 ]
createCounter
returns an object literal with closure for get
function createCounter5 () {
var n = 0;
return {
get: function () { return this.n++; }
huh: function () { console.log ("this.n=" + this.n); }
};
};
var c5 = createCounter5 ();
> c5.get ();
NaN
> c5.huh ();
this.n=undefined
function createCounter6 () {
var n = 0;
return function () { return n++; };
}
var [c6a,c6b] = [createCounter6(), createCounter6()]
> c6a()
0
> [c6a,c6b].map (x => x())
[ 1, 0 ]
> [c6a,c6b].map (x => x())
[ 2, 1 ]
this
based on calling context. See
here
o.m()
is method context, this===o
f()
is function context, this===global/window
new C()
is constructor context, this
is new object
var o = { getThis () { return this; } };
o.getThis() === o; // true
var fThis = o.getThis; // save method as function
fThis() === o // false --- this=o not closed above
fThis() === global // true (in Node.js)
fThis() === window // true (in Browswer)
var oJS = { n: -1, add (i) { this.n += i; return this.n; }};
var fAdd = oJS.add
fAdd(1) // NaN --- closure does not bind this=oJS
object oScala { var n = -1; def add(i:Int) = { this.n += i; this.n } }
var fAdd = oScala.add
fAdd(1) // 0 --- closure binds this=oScala
this
is not bound in the closure
this
is closed when converting method to function
var o = {
v : 5,
add : function (xs) {
return xs.map(function(x){ return this.v + x; });
}
}
o.add([10,20,30]) // [ NaN, NaN, NaN ]
Old Hack:
var o = {
v : 5,
add : function (xs) {
var me = this;
return xs.map(function(x){ return me.v + x; });
}
}
o.add([10,20,30]) // [ 15, 25, 35 ]
ES5. Sad.
var o = {
v : 5,
add : function (xs) {
return xs.map(function(x){ return this.v + x; }.bind(this));
}
}
o.add([10,20,30]) // [ 15, 25, 35 ]
ES6. The right way:
var o = {
v : 5,
add : function (xs) {
return xs.map(x => this.v + x);
}
}
o.add([10,20,30]) // [ 15, 25, 35 ]
Fat arrow treats this
like a regular variable
this
bound in closure rather than when called
var y = {getThese: function(xs){ return xs.map(x => this)}};
var z = {getThese: function(xs){ return xs.map(function(x){ return this; })}};
y.getThese([10,20,30])[0] === y // true
z.getThese([10,20,30])[0] === global // true (in Node.js)
z.getThese([10,20,30])[0] === window // true (in Browswer)
function createCounter () {
return {
n: -1,
next: function () { return ++this.n; },
reset: function () { this.n = -1; }
};
}
var c1 = createCounter (); // Function context
var c2 = createCounter ();
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 0, 1, 2, undefined, 0, 1 ]
> [c2.next(), c2.next(), c2.next(), c2.reset(), c2.next(), c2.next()]
[ 0, 1, 2, undefined, 0, 1 ]
new
this
function Counter () {
this.n = -1;
this.next = function () { return ++this.n; },
this.reset = function () { this.n = -1; }
}
var c1 = new Counter (); // Constructor context
var c2 = new Counter ();
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 0, 1, 2, undefined, 0, 1 ]
> [c2.next(), c2.next(), c2.next(), c2.reset(), c2.next(), c2.next()]
[ 0, 1, 2, undefined, 0, 1 ]
prototype
object of function
function Counter () { this.n = -1; }
Counter.prototype.next = function () { return ++this.n; }
Counter.prototype.reset = function () { this.n = -1; }
var c1 = new Counter ();
var c2 = new Counter ();
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 0, 1, 2, undefined, 0, 1 ]
> [c2.next(), c2.next(), c2.next(), c2.reset(), c2.next(), c2.next()]
[ 0, 1, 2, undefined, 0, 1 ]
c1
and c2
share
Counter.prototype
Counter.prototype
resembles vtable for a class
function Counter () { this.n = -1; }
Counter.prototype.next = function () { return ++this.n; }
Counter.prototype.reset = function () { this.n = -1; }
var c1 = new Counter ();
var c2 = new Counter ();
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 0, 1, 2, undefined, 0, 1 ]
> [c2.next(), c2.next(), c2.next(), c2.reset(), c2.next(), c2.next()]
[ 0, 1, 2, undefined, 0, 1 ]
function Counter () { this.n = -1; }
Counter.prototype.next = function () { return this.n += 1; }
Counter.prototype.reset = function () { this.n = -1; }
var c1 = new Counter ();
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 0, 1, 2, undefined, 0, 1 ]
> Counter.prototype.next = function () { return this.n += 2; }
> Counter.prototype.reset = function () { this.n = -2; }
> [c1.next(), c1.next(), c1.next(), c1.reset(), c1.next(), c1.next()]
[ 3, 5, 7, undefined, 0, 2 ]
__proto__ !=== prototype
__proto__
prototype
__proto__
is set to the function's prototype
function Foo(y) { this.y = y; }
Foo.prototype.x = 10;
Foo.prototype.calculate = function (z) {
return this.x + this.y + z;
};
var b = new Foo(20);
var c = new Foo(30);
__proto__
at runtime!
function UpCounter () { this.n = -1; }
UpCounter.prototype.next = function () { return ++this.n; }
function DownCounter () { this.n = 1; }
DownCounter.prototype.next = function () { return --this.n; }
var c = new UpCounter ();
c.next (); // Returns 0
c.next (); // Returns 1
c.__proto__ = DownCounter.prototype
c.next (); // Returns 0
c.next (); // Returns -1
function ResetCounter () { this.reset(); }
ResetCounter.prototype.reset = function () { this.n = -1; }
var c = new ResetCounter ();
c.next (); // TypeError: c.next is not a function
function Counter () { this.n = -1; }
Counter.prototype.next = function () { return ++this.n; }
ResetCounter.prototype.__proto__ = Counter.prototype
c.next (); // Returns 0
c.next (); // Returns 1