Mercurial > js4py
changeset 150:5118d0e38ba4 core-javascript-latex
$ hg merge core-javascript # into core-javascript-latex
| author | jf3243 |
|---|---|
| date | Tue, 01 Nov 2011 08:58:38 +0000 |
| parents | 5666f217df39 (current diff) 6c98b9680b2a (diff) |
| children | |
| files | core-javascript/index.rst core-javascript/js/exercise-1.js core-javascript/js/exercise-2.js core-javascript/js/solution-1-a.js core-javascript/js/solution-1-b.js core-javascript/js/solution-1-c.js core-javascript/js/solution-1-d.js core-javascript/js/solution-2-a.js core-javascript/js/solution-2-b.js core-javascript/js/solution-2-c.js core-javascript/js/solution-2-d.js |
| diffstat | 38 files changed, 762 insertions(+), 532 deletions(-) [+] |
line wrap: on
line diff
--- a/core-javascript/arguments.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/arguments.rst Tue Nov 01 08:58:38 2011 +0000 @@ -1,6 +1,17 @@ Arguments ========= +This can be omitted at a first reading. The main point here is that +in JavaScript + +#. There is no checking for number and type of arguments. + +#. The arguments actually supplied are stored in a special variable, + called *arguments*. + +#. The value of *arguments* is almost, but not exactly, an array. + + Test code --------- @@ -8,13 +19,3 @@ :language: javascript :start-after: start return_arguments :end-before: end return_arguments - - -Discussion ----------- - -Explanation ------------ - -Exercises ----------
--- a/core-javascript/bind-is-transient.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/bind-is-transient.rst Tue Nov 01 08:58:38 2011 +0000 @@ -16,8 +16,54 @@ Discussion ---------- +We create an object that has a *return_this* method. The outcome of +calling *return_this* depends on how it is called. + +There's a logic in the test code that produces contradictory, or at +least surprising, outcomes. + +From + +.. code-block: javascript + + obj.return_this === return_this; + method_deferred === obj.return_this; + method_deferred === return_this; + +we would expect + +.. code-block: javascript + + obj.return_this() === return_this(); + method_deferred() === obj.return_this(); + method_deferred() === return_this(); + +But this means that in *obj.return_this()* the *obj* is not relevant. +But JavaScript (or its designers) wanted objects to have methods, so +they introduced a special rule. + + Explanation ----------- -Exercises ---------- +JavaScript does not distinguish classes and instances. Python, and +perhaps other dynamic languages, does. This allows Python to supply a +bound method, which retains a reference to the object, when a function +belonging to the class is called on an instance. + +Here's a Python (version 3) command line dialogue that illustrates +this. + +>>> def method(): pass # The function. +... +>>> class A: method = method # The class. +>>> A.method # Function from class. +<function method at 0x15be518> +>>> A.method is method # Same as the original function. +True +>>> a = A() # Instance. +>>> a.method # Bound method, not original function. +<bound method A.method of <__main__.A object at 0x16b9d10>> + +The odd behaviour shown in the JavaScript test code is a consequence +of JavaScript not distinguishing between classes and instances.
--- a/core-javascript/core-javascript-work/counters.css Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/counters.css Tue Nov 01 08:58:38 2011 +0000 @@ -1,17 +1,17 @@ -body { - background: #DDD; - font-family: sans-serif; -} - -#example { - padding: 20px; -} - -#example span { - padding: 10px; - margin: 10px; - border: 10px solid blue; - background: #DDF; - foreground: blue; - font-weight: bold; -} +body { + background: #DDD; + font-family: sans-serif; +} + +#example { + padding: 20px; +} + +#example span { + padding: 10px; + margin: 10px; + border: 10px solid blue; + background: #DDF; + foreground: blue; + font-weight: bold; +}
--- a/core-javascript/core-javascript-work/counters.html Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/counters.html Tue Nov 01 08:58:38 2011 +0000 @@ -1,18 +1,18 @@ -<html> -<head> -<script src="library.js"></script> -<script src="counters.js"></script> -<link rel="stylesheet" type="text/css" href="counters.css" /> -<title>JS for Python: example: counters</title> -</head> -<body> -<h1>Counters</h1> - -<p>Click on a counter to increment its value.</p> - -<div id="example"> -<p>This will disappear if JavaScript is working properly.</p> -</div> - -<p>Return to documentation of <a href="../counters-example.html">Counters example</a>.</p> -</body> +<html> +<head> +<script src="library.js"></script> +<script src="counters.js"></script> +<link rel="stylesheet" type="text/css" href="counters.css" /> +<title>JS for Python: example: counters</title> +</head> +<body> +<h1>Counters</h1> + +<p>Click on a counter to increment its value.</p> + +<div id="example"> +<p>This will disappear if JavaScript is working properly.</p> +</div> + +<p>Return to documentation of <a href="../counters-example.html">Counters example</a>.</p> +</body>
--- a/core-javascript/core-javascript-work/counters.js Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/counters.js Tue Nov 01 08:58:38 2011 +0000 @@ -1,79 +1,79 @@ -(function() -{ - // Define a Counter class. - var counter = {}; // Prototype object for Counter. - - counter.__init__ = function(name){ - - this.name = name; - this.count = 0; - }; - - counter.onclick = function(event){ - - this.count ++; - }; - - counter.html = function(){ - - return this.name + ' ' + this.count; - }; - - Counter = SimpleClass(counter); - - - // Make explicit use of global variables. - var global = (function(){return this;})(); - - - // Interaction. - var onclick_factory = function(models){ - - var onclick = function(event){ - - event = event || global.event; // For IE event handling. - var target = event.target || event.srcElement; - var id = target.id; - if (id) { - var id_num = +id.slice(1); - var model = models[id_num]; - model.onclick(); - var html = model.html(); - if (html){ - global.document.getElementById(id).innerHTML = html; - } - } - }; - return onclick; - }; - - - // Set up the web page. - global.onload = function(){ - - var models = [ - Counter('apple'), - Counter('banana'), - Counter('cherry'), - Counter('date') - ]; - - var element = document.getElementById('example'); - - element.innerHTML = ( - '<span id="a0">apple 0</span>' - + '<span id="a1">banana 0</span>' - + '<span id="a2">cherry 0</span>' - + '<span id="a3">date 0</span>' - ); - - element.onclick = onclick_factory(models); - element = undefined; // Avoid IE memory leak. - }; - -})(); - - - - - +(function() +{ + // Define a Counter class. + var counter = {}; // Prototype object for Counter. + + counter.__init__ = function(name){ + + this.name = name; + this.count = 0; + }; + + counter.onclick = function(event){ + + this.count ++; + }; + + counter.html = function(){ + + return this.name + ' ' + this.count; + }; + + Counter = SimpleClass(counter); + + + // Make explicit use of global variables. + var global = (function(){return this;})(); + + + // Interaction. + var onclick_factory = function(models){ + + var onclick = function(event){ + + event = event || global.event; // For IE event handling. + var target = event.target || event.srcElement; + var id = target.id; + if (id) { + var id_num = +id.slice(1); + var model = models[id_num]; + model.onclick(); + var html = model.html(); + if (html){ + global.document.getElementById(id).innerHTML = html; + } + } + }; + return onclick; + }; + + + // Set up the web page. + global.onload = function(){ + + var models = [ + Counter('apple'), + Counter('banana'), + Counter('cherry'), + Counter('date') + ]; + + var element = document.getElementById('example'); + + element.innerHTML = ( + '<span id="a0">apple 0</span>' + + '<span id="a1">banana 0</span>' + + '<span id="a2">cherry 0</span>' + + '<span id="a3">date 0</span>' + ); + + element.onclick = onclick_factory(models); + element = undefined; // Avoid IE memory leak. + }; + +})(); + + + + +
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/demo_testtools.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,14 @@ +TEST('all-pass', function() +{ + assert( 2 + 2 === 4 ); + assert( 'the ' + 'cat' === 'the cat'); +}); + + +TEST('2-4-fail', function() +{ + assert( true ); + assert( false ); + assert( 1 ); + assert( 0 ); +});
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/exercise-1.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,14 @@ +var AAA = (function (BBB) +{ + BBB.fff = function(ccc) + { + return { + ggg: function(ddd) + { + return BBB.hhh(ddd, ccc.iii()); + } + }; + }; + return BBB; + +}(AAA || {}));
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/exercise-2.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,11 @@ +var copy_attributes = function(tgt, src){ + + tgt.aaa = src.get_aaa(); + tgt.bbb = src.get_bbb(); + tgt.ccc = src.get_ccc(); + tgt.ddd = src.get_ddd(); + tgt.eee = src.get_eee(); + tgt.fff = src.get_fff(); + tgt.ggg = src.get_ggg(); + tgt.hhh = src.get_hhh(); +};
--- a/core-javascript/core-javascript-work/library.js Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/library.js Tue Nov 01 08:58:38 2011 +0000 @@ -1,25 +1,25 @@ -// start create -var create = function(parent){ - - var tmp = function(){}; - tmp.prototype = parent; - var child = new tmp(); - - return child; -}; -// end create - - -// start SimpleClass -var SimpleClass = function(prototype){ - - var cls = function(){ - - var instance = create(prototype); - instance.__init__.apply(instance, arguments); - return instance; - }; - - return cls; -}; -// end SimpleClass +// start create +var create = function(parent){ + + var tmp = function(){}; + tmp.prototype = parent; + var child = new tmp(); + + return child; +}; +// end create + + +// start SimpleClass +var SimpleClass = function(prototype){ + + var cls = function(){ + + var instance = create(prototype); + instance.__init__.apply(instance, arguments); + return instance; + }; + + return cls; +}; +// end SimpleClass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-1-a.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,15 @@ +var AAA = function () +{ + var BBB = AAA || {}; + BBB.fff = function(ccc) + { + return { + ggg: function(ddd) + { + return BBB.hhh(ddd, ccc.iii()); + } + }; + }; + return BBB; + +}();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-1-b.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,5 @@ +var AAA = function () +{ + var BBB = AAA || {}; + return BBB; +}();
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-1-c.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,1 @@ +var AAA = AAA || {};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-1-d.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,11 @@ +var AAA = AAA || {}; + +AAA.fff = function(ccc) +{ + return { + ggg: function(ddd) + { + return AAA.hhh(ddd, ccc.iii()); + } + }; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-2-a.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,10 @@ +var copy_attributes = function(tgt, src){ + + var keys = ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg', 'hhh']; + var i; + var key; + + for(i=0; i< keys.length; i++){ + tgt[key] = src['get_' + key](); + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-2-b.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,11 @@ +var copy_attributes_factory = function(keys){ + + return function(src, tgt){ + var i; + var key; + + for(i=0; i< keys.length; i++){ + tgt[key] = src['get_' + key](); + }; + }; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-2-c.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,15 @@ +var Fields = function(keys){ + this.keys = keys; +}; + +Fields.prototype = { + copy_attributes : function(src, tgt){ + + var keys = this.keys; + var i; + + for(i=0; i< keys.length; i++){ + tgt[key] = src['get_' + key](); + }; + } +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/solution-2-d.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,23 @@ +// Rhino $ js -f library.js -f solution-2-d.js - + +var fields = {}; + +fields.__init__ = function(keys){ + this.keys = keys; +}; + +fields.copy_attributes = function(src, tgt){ + + var keys = this.keys; + var key; + var i; + + for(i=0; i< keys.length; i++){ + key = keys[i]; + tgt[key] = src['get_' + key](); + }; +}; + +var Fields = SimpleClass(fields); + +
--- a/core-javascript/core-javascript-work/test_library.js Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/test_library.js Tue Nov 01 08:58:38 2011 +0000 @@ -1,40 +1,40 @@ -var telltail = 'global'; - -var TEST = function(title, code){ - print( 'Testing: ' + title ); - code(); -}; - -var assert = function(arg){ - if(!arg){ - ddt(); - } -}; - - -// start SimpleClass -TEST('SimpleClass', function() -{ - var fruit = {}; - - fruit.__init__ = function(name, colour){ - - this.name = name; - this.colour = colour; - }; - - fruit.greet = function(){ - - return "Hello, I'm a " + this.color + " " + this.name + "."; - }; - - var Fruit = SimpleClass(fruit); - - var bn = Fruit('banana', 'yellow'); - var rc = Fruit('cherry', 'red'); - var gs = Fruit('Granny Smith', 'green'); - - assert( bn.greet() === "Hello, I'm a undefined banana." ); - -}); -// end SimpleClass +var telltail = 'global'; + +var TEST = function(title, code){ + print( 'Testing: ' + title ); + code(); +}; + +var assert = function(arg){ + if(!arg){ + ddt(); + } +}; + + +// start SimpleClass +TEST('SimpleClass', function() +{ + var fruit = {}; + + fruit.__init__ = function(name, colour){ + + this.name = name; + this.colour = colour; + }; + + fruit.greet = function(){ + + return "Hello, I'm a " + this.color + " " + this.name + "."; + }; + + var Fruit = SimpleClass(fruit); + + var bn = Fruit('banana', 'yellow'); + var rc = Fruit('cherry', 'red'); + var gs = Fruit('Granny Smith', 'green'); + + assert( bn.greet() === "Hello, I'm a undefined banana." ); + +}); +// end SimpleClass
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/core-javascript-work/testtools.js Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,30 @@ +var telltail = 'global'; +var _assertion_count = 0; + +// Define a global function 'log'. +var log; +if (this.hasOwnProperty('jsArguments')){ + + log = function(s){ + print(s + '\n'); + }; +} else { + log = print; +} + +// +var TEST = function(title, code){ + + log( 'Testing: ' + title ); + _assertion_count = 0; + code(); +}; + +var assert = function(arg){ + _assertion_count += 1; + if (!arg){ + var msg = '! assert ' + _assertion_count + ' has failed'; + log(msg); + } +}; +
--- a/core-javascript/core-javascript-work/tutorial.js Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/core-javascript-work/tutorial.js Tue Nov 01 08:58:38 2011 +0000 @@ -1,201 +1,203 @@ -var telltail = 'global'; - -var TEST = function(title, code){ - print( 'Testing: ' + title ); - code(); -}; - -var assert = function(arg){ - if(!arg){ - ddt(); - } -}; - -'0' == 0 || ddt; -0 == '' || ddt; -'0' != '' || ddt; -NaN != NaN || ddt; - - -// start closures -TEST('closures', function() -{ - // A factory for creating get-set pairs. - var get_set_factory = function(){ - - var value; - var get = function(){ - return value; - }; - var set = function(new_value){ - value = new_value; - }; - - return { - get: get, - set: set - }; - }; - - // Create and unpack two get-set pairs. - var tmp; - tmp= get_set_factory(); - var get_a = tmp.get; - var set_a = tmp.set; - - tmp= get_set_factory(); - var get_b = tmp.get; - var set_b = tmp.set; - - // Test that both pairs work. - set_a(12); - assert( get_a() === 12 ); - - set_b(13); - assert( get_b() === 13 ); - - // Test that each pair has its own value. - assert( get_a() === 12 ); - -}); -// end closures - - -// start return_this -TEST('return_this', function() -{ - var return_this = function(){ - return this; - }; - - var default_this = return_this(); - assert( default_this.telltail === 'global' ); - - var obj = {}; - obj.return_this = return_this; - assert( obj.return_this === return_this ); - - assert( obj.return_this() === obj ); - assert( obj['return_this']() === obj ); - - assert( return_this.call(obj) === obj ); - assert( return_this.apply(obj) === obj ); - - assert( return_this.call(undefined).telltail === 'global' ); - assert( return_this.apply(undefined).telltail === 'global' ); - -}); -// end return_this - - -// start transient-bind -TEST('transient-bind', function() -{ - // Set up the test object. - var return_this = function(){ - return this; - }; - var obj = { - return_this : return_this - }; - - // Deferring method call changes the outcome. - var method_deferred = obj.return_this; - assert( method_deferred === obj.return_this ); - assert( method_deferred() !== obj.return_this() ); - - // The two outcomes, precisely stated. - assert( obj.return_this() === obj ); - assert( method_deferred().telltail === 'global' ); - -}); -// end transient-bind - - -// start millers-device -TEST('millers-device', function() -{ - var array = []; - var obj = {}; - var fn = function(){}; - - assert( typeof array === 'object' ); - assert( '' + obj === '[object Object]' ); - - assert( obj.toString() === '[object Object]' ); - - var object_toString = obj.toString; - - assert( object_toString.call(array) === '[object Array]' ); - assert( object_toString.call(fn) === '[object Function]' ); - -}); -// end millers-device - - -// start return_arguments -TEST('return_arguments', function() -{ - var return_arguments = function(){ - return arguments; - }; - - var my_arguments = return_arguments(0, 1); - - assert( my_arguments.length === 2 ); - - assert( my_arguments[0] === 0 ); - assert( my_arguments[1] === 1 ); - - assert( '' + my_arguments === '[object Object]' ); - assert( '' + [0, 1] === '0,1' ); - - var array_slice = [].slice; - var my_arguments_fixed = array_slice.call( my_arguments ); - - assert( '' + my_arguments_fixed === '0,1'); - - var obj = {}; - assert( obj.toString.call( my_arguments_fixed ) === '[object Array]' ); - -}); -// end return_arguments - - -// start new-operator -TEST('new-operator', function() -{ - var this_in_Fn; - - var Fn = function(){ - this_in_Fn = this; - }; - - this_in_Fn = null; - var non_instance = Fn(); - assert( non_instance === undefined ); - assert( this_in_Fn.telltail === 'global'); - - this_in_Fn = null; - var instance = new Fn(); - assert( instance === this_in_Fn ); - assert( instance.telltail === undefined ); - - Fn.prototype.telltail = 'instance-of-Fn'; - assert( instance.telltail === 'instance-of-Fn' ); - - Fn.prototype = { - telltail : 'prototype-is-changed' - }; - var second_instance = new Fn(); - assert( second_instance.telltail === 'prototype-is-changed' ); - - Fn.prototype = second_instance; - second_instance.i = 42; - - var third_instance = new Fn(); - assert( third_instance.telltail === 'prototype-is-changed' ); - assert( third_instance.i === 42 ); - -}); -// end new-operator +// [Rhino] $ js -f testtools.js -f tutorial.js +// [JSDB] $ jsdb -load testtools.js -load tutorial.js + +// start warm-up +TEST('warm-up', function() +{ + assert( '0' == 0 ); + assert ( 0 == '' ); + assert( '0' != '' ); + + var a = NaN; + assert( a != a ); + +}); +// end warm-up + + +// start closures +TEST('closures', function() +{ + // A factory for creating get-set pairs. + var get_set_factory = function(){ + + var value; + var get = function(){ + return value; + }; + var set = function(new_value){ + value = new_value; + }; + + return { + get: get, + set: set + }; + }; + + // Create and unpack two get-set pairs. + var tmp; + tmp= get_set_factory(); + var get_a = tmp.get; + var set_a = tmp.set; + + tmp= get_set_factory(); + var get_b = tmp.get; + var set_b = tmp.set; + + // Test that both pairs work. + set_a(12); + assert( get_a() === 12 ); + + set_b(13); + assert( get_b() === 13 ); + + // Test that each pair has its own value. + assert( get_a() === 12 ); + +}); +// end closures + + +// start return_this +TEST('return_this', function() +{ + var return_this = function(){ + return this; + }; + + var default_this = return_this(); + assert( default_this.telltail === 'global' ); + + var obj = {}; + obj.return_this = return_this; + assert( obj.return_this === return_this ); + + assert( obj.return_this() === obj ); + assert( obj['return_this']() === obj ); + + assert( return_this.call(obj) === obj ); + assert( return_this.apply(obj) === obj ); + + assert( return_this.call(undefined).telltail === 'global' ); + assert( return_this.apply(undefined).telltail === 'global' ); + +}); +// end return_this + + +// start transient-bind +TEST('transient-bind', function() +{ + // Set up the test object. + var return_this = function(){ + return this; + }; + var obj = { + return_this : return_this + }; + assert( obj.return_this === return_this ); + + // Deferring method call changes the outcome. + var method_deferred = obj.return_this; + assert( method_deferred === obj.return_this ); + assert( method_deferred() !== obj.return_this() ); // AAA + + // The two outcomes, precisely stated. + assert( obj.return_this() === obj ); + assert( method_deferred().telltail === 'global' ); + + // Another view on the matter. + assert( method_deferred === return_this ); + assert( method_deferred() === return_this() ); // BBB + +}); +// end transient-bind + + +// start millers-device +TEST('millers-device', function() +{ + var array = []; + var obj = {}; + var fn = function(){}; + + assert( typeof array === 'object' ); + assert( '' + obj === '[object Object]' ); + + assert( obj.toString() === '[object Object]' ); + + var object_toString = obj.toString; + + assert( object_toString.call(array) === '[object Array]' ); + assert( object_toString.call(fn) === '[object Function]' ); + +}); +// end millers-device + + +// start return_arguments +TEST('return_arguments', function() +{ + var return_arguments = function(){ + return arguments; + }; + + var my_arguments = return_arguments(0, 1); + + assert( my_arguments.length === 2 ); + + assert( my_arguments[0] === 0 ); + assert( my_arguments[1] === 1 ); + + assert( '' + my_arguments === '[object Object]' ); + assert( '' + [0, 1] === '0,1' ); + + var array_slice = [].slice; + var my_arguments_fixed = array_slice.call( my_arguments ); + + assert( '' + my_arguments_fixed === '0,1'); + + var obj = {}; + assert( obj.toString.call( my_arguments_fixed ) === '[object Array]' ); + +}); +// end return_arguments + + +// start new-operator +TEST('new-operator', function() +{ + // A 'traditional class'. It records 'this' as a side effect. + var this_in_Fn; + var Fn = function(){ + this_in_Fn = this; + }; + + // Call the 'class' without the 'new'. + this_in_Fn = null; // Clear any previous value. + var non_instance = Fn(); + assert( non_instance === undefined ); + assert( this_in_Fn.telltail === 'global'); + + // Call the 'class' with the 'new'. + this_in_Fn = null; // Clear any previous value. + var instance = new Fn(); + assert( instance === this_in_Fn ); + assert( instance.telltail === undefined ); + + // Demonstrate that instance is a descendant of Fn.prototype. + Fn.prototype.telltail = 'instance-of-Fn'; + assert( instance.telltail === 'instance-of-Fn' ); + + // We can give Fn a new prototype. + instance.new_telltail = 'child-of-instance'; + Fn.prototype = instance; + + // And now an instance of Fn has two telltails. + var second_instance = new Fn(); + assert( second_instance.telltail === 'instance-of-Fn' ); + assert( second_instance.new_telltail === 'child-of-instance' ); + +}); +// end new-operator
--- a/core-javascript/exercise-1.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/exercise-1.rst Tue Nov 01 08:58:38 2011 +0000 @@ -13,7 +13,7 @@ #. Suggest a better way of going about things. -.. literalinclude:: /js/exercise-1.js +.. literalinclude:: /core-javascript-work/exercise-1.js :language: javascript
--- a/core-javascript/exercise-2.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/exercise-2.rst Tue Nov 01 08:58:38 2011 +0000 @@ -5,7 +5,7 @@ everything to hide the origin of the code. But it's not something I've made up. -.. literalinclude:: /js/exercise-2.js +.. literalinclude:: /core-javascript-work/exercise-2.js :language: javascript The exercise is
--- a/core-javascript/index.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/index.rst Tue Nov 01 08:58:38 2011 +0000 @@ -6,6 +6,7 @@ getting-started counters-example + testtools immutables equality
--- a/core-javascript/js/exercise-1.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -var AAA = (function (BBB) -{ - BBB.fff = function(ccc) - { - return { - ggg: function(ddd) - { - return BBB.hhh(ddd, ccc.iii()); - } - }; - }; - return BBB; - -}(AAA || {}));
--- a/core-javascript/js/exercise-2.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -var copy_attributes = function(tgt, src){ - - tgt.aaa = src.get_aaa(); - tgt.bbb = src.get_bbb(); - tgt.ccc = src.get_ccc(); - tgt.ddd = src.get_ddd(); - tgt.eee = src.get_eee(); - tgt.fff = src.get_fff(); - tgt.ggg = src.get_ggg(); - tgt.hhh = src.get_hhh(); -};
--- a/core-javascript/js/solution-1-a.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -var AAA = function () -{ - var BBB = AAA || {}; - BBB.fff = function(ccc) - { - return { - ggg: function(ddd) - { - return BBB.hhh(ddd, ccc.iii()); - } - }; - }; - return BBB; - -}();
--- a/core-javascript/js/solution-1-b.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,5 +0,0 @@ -var AAA = function () -{ - var BBB = AAA || {}; - return BBB; -}();
--- a/core-javascript/js/solution-1-c.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,1 +0,0 @@ -var AAA = AAA || {};
--- a/core-javascript/js/solution-1-d.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -var AAA = AAA || {}; - -AAA.fff = function(ccc) -{ - return { - ggg: function(ddd) - { - return AAA.hhh(ddd, ccc.iii()); - } - }; -};
--- a/core-javascript/js/solution-2-a.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,10 +0,0 @@ -var copy_attributes = function(tgt, src){ - - var keys = ['aaa', 'bbb', 'ccc', 'ddd', 'eee', 'fff', 'ggg', 'hhh']; - var i; - var key; - - for(i=0; i< keys.length; i++){ - tgt[key] = src['get_' + key](); - } -};
--- a/core-javascript/js/solution-2-b.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,11 +0,0 @@ -var copy_attributes_factory = function(keys){ - - return function(src, tgt){ - var i; - var key; - - for(i=0; i< keys.length; i++){ - tgt[key] = src['get_' + key](); - }; - }; -};
--- a/core-javascript/js/solution-2-c.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,15 +0,0 @@ -var Fields = function(keys){ - this.keys = keys; -}; - -Fields.prototype = { - copy_attributes : function(src, tgt){ - - var keys = this.keys; - var i; - - for(i=0; i< keys.length; i++){ - tgt[key] = src['get_' + key](); - }; - } -};
--- a/core-javascript/js/solution-2-d.js Mon Oct 31 14:22:00 2011 +0000 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -var fields = {}; - -fields.__init__ = function(keys){ - this.keys = keys; -}; - -fields.copy_attributes = function(src, tgt){ - - var keys = this.keys; - var i; - - for(i=0; i< keys.length; i++){ - tgt[key] = src['get_' + key](); - }; -}; - -var Fields = Class(fields); - -
--- a/core-javascript/millers-device.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/millers-device.rst Tue Nov 01 08:58:38 2011 +0000 @@ -1,18 +1,22 @@ Mark Miller's device ==================== +This can be omitted at a first reading. Mark Miller is one of the +leading JavaScript experts at Google. He's been credited an ingenious +application of call that gives a simple and reliable test for whether +or not an object is an array. + +This may not sound very much, but it is something that had been +puzzling the other experts for some years. For example, Crockford in +his *Good Parts* (published 2008, page 61) gives a much more complex +and less reliable solution to this problem. + +The moral of this story is that even the experts in JavaScript can +have difficulty finding the best way to solve a simple problem. + Test code --------- .. literalinclude:: /core-javascript-work/tutorial.js :language: javascript :start-after: start millers-device :end-before: end millers-device - -Discussion ----------- - -Explanation ------------ - -Exercises ----------
--- a/core-javascript/solution-1.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/solution-1.rst Tue Nov 01 08:58:38 2011 +0000 @@ -3,7 +3,7 @@ Here's the code we're studying. -.. literalinclude:: /js/exercise-1.js +.. literalinclude:: /core-javascript-work/exercise-1.js :language: javascript @@ -22,7 +22,7 @@ body. We can always do this, without changing the meaning, provided the function is called only once. -.. literalinclude:: /js/solution-1-a.js +.. literalinclude:: /core-javascript-work/solution-1-a.js :language: javascript There, that's better. We no longer have to read to the end of the @@ -38,7 +38,7 @@ BBB. In other words, it mutates the object pointed to by BBB, but the variable BBB still points to the same object as it did before. -.. literalinclude:: /js/solution-1-b.js +.. literalinclude:: /core-javascript-work/solution-1-b.js :language: javascript @@ -59,7 +59,7 @@ Therefore, our truncated version of the original exercise is equivalent to -.. literalinclude:: /js/solution-1-c.js +.. literalinclude:: /core-javascript-work/solution-1-c.js :language: javascript This idiom is very common in JavaScript, and is (as here) a @@ -94,7 +94,7 @@ The following is almost equivalent to our original JavaScript code. Note that BBB.hhh has been changed to AAA.hhh. -.. literalinclude:: /js/solution-1-d.js +.. literalinclude:: /core-javascript-work/solution-1-d.js :language: javascript
--- a/core-javascript/solution-2.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/solution-2.rst Tue Nov 01 08:58:38 2011 +0000 @@ -4,7 +4,7 @@ Here's the code we are studying. The first task is make a loop that does the assignments. -.. literalinclude:: /js/exercise-2.js +.. literalinclude:: /core-javascript-work/exercise-2.js :language: javascript @@ -18,7 +18,7 @@ Here's the solution. There's a bit of overhead in setting up the loop, but the body of the loop is straightforward. -.. literalinclude:: /js/solution-2-a.js +.. literalinclude:: /core-javascript-work/solution-2-a.js :language: javascript @@ -31,7 +31,7 @@ This is a common refactoring pattern, and so is worth learning well. First we give the code, and then the explanation. -.. literalinclude:: /js/solution-2-b.js +.. literalinclude:: /core-javascript-work/solution-2-b.js :language: javascript @@ -56,7 +56,7 @@ the class supplying the *new* operator to create the new instance (and chaos results if the user forgets). -.. literalinclude:: /js/solution-2-c.js +.. literalinclude:: /core-javascript-work/solution-2-c.js :language: javascript A minor point is the trailing sequence of right curly braces. Two are @@ -64,21 +64,28 @@ happens if we put a semicolon there? -A Class class -------------- +A SimpleClass solution +---------------------- Here's another solution to the third task, which does not require the user to supply a *new* operator when creating instances. Instead it uses a factory function, which we call *Class* , that creates an instance constructor function from a prototype object. -.. literalinclude:: /js/solution-2-d.js +.. literalinclude:: /core-javascript-work/solution-2-d.js :language: javascript -A bound method class +Exercise: write test -------------------- +Here's an exercise. Write a JavaScript file that tests this solution. + + +Exercise: bound method class +---------------------------- + + Recall that this-based instance methods are somewhat fragile. In other words, code like this will fail (and make unwanted changes to the global object): @@ -101,3 +108,5 @@ var myclass = {} // The prototype object. // Add methods to myclass. var MyClass = BoundMethodClass(myclass); + +The exercise is to create (and test) a BoundMethodClass factory.
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/core-javascript/testtools.rst Tue Nov 01 08:58:38 2011 +0000 @@ -0,0 +1,76 @@ +Test tools +========== + +To make it easier to test code, and to ensure that example code is +correct, there is a small testtools.js file in the work folder. + + +How to write tests +------------------ + +To prepare a test create a file like this. It's also in the work +folder. (For real examples the file will give *assert* a more +interesting argument, whose truth or falsity is perhaps not obvious.) + +.. literalinclude:: /core-javascript-work/demo_testtools.js + :language: javascript + + +How to run tests +---------------- + +Here's how to run the tests at the command line, finishing with a +command prompt that allows you inspect the state and also run the test +again. + + +Rhino +^^^^^ + +For Rhino use *load* to run the test again. + +.. code-block:: sh + + core-javascript-work$ js -f testtools.js -f demo_testtools.js -f - + Testing: all-pass + Testing: 2-4-fail + ! assert 2 has failed + ! assert 4 has failed + Rhino 1.7 release 2 2010 01 20 + js> load('demo_testtools.js') // You type this. + Testing: all-pass + Testing: 2-4-fail + ! assert 2 has failed + ! assert 4 has failed + js> + + +JSDB +^^^^ + +For JSDB use *run* to run the test again. + +.. code-block:: sh + + C:core-javascript-work> jsdb -load testtools.js -load demo_testtools.js + Testing: all-pass + Testing: 2-4-fail + ! assert 2 has failed + ! assert 4 has failed + js> load('demo_testtools.js') // You type this. + Testing: all-pass + Testing: 2-4-fail + ! assert 2 has failed + ! assert 4 has failed + js> + + +testools.js +----------- + +(Optional on a first reading.) Here's the file testools.js. It's +short and simple (and has a not-nice dependency on global variables). +It also has a branch based on whether it's being run on JSDB or Rhino. + +.. literalinclude:: /core-javascript-work/testtools.js + :language: javascript
--- a/core-javascript/what-is-new.rst Mon Oct 31 14:22:00 2011 +0000 +++ b/core-javascript/what-is-new.rst Tue Nov 01 08:58:38 2011 +0000 @@ -1,6 +1,16 @@ What is new? ============ +This can be omitted at a first reading, and omitted altogether if you +never have to deal with code that uses the *new* operator. If you do, +then you **must** read about the missing *new* problem. + +JavaScript has an object tree. This is a fundamental language +feature. The *new* operator is a way of adding objects to the tree. +The *create* function, which is built-in to the latest version of +JavaScript, can be defined in terms of *new*, and vice versa. + + Test code --------- @@ -9,11 +19,44 @@ :start-after: start new-operator :end-before: end new-operator + Discussion ---------- -Explanation ------------ +By changing the prototype object of *Fn* we start building a tree of +objects. + +There is a built-in relation between an object and its parent. There +is no relation between the object and its constructor, here called +*Fn*, except that at the time of construction the parent of the object +is Fn.prototype. + +The exact rules for the behaviour of *new* are complicated and not +given here. + + +No missing *new* warning +------------------------ -Exercises ---------- +We saw that when *Fn* is called without the *new* then during its +execution the value of *this* is the global object. If the +constructor *Fn* mutates *this* (and almost every constructor does), +then it is the global object that is mutated. + +This is very bad: + +#. You may clobber someone elses data. + +#. Someone else may clobber your data. + +#. If you create two instances, you will clobber your own data. + +#. Testing for this error involves extra work. + +#. Bugs that arise from this error can appear random and can be hard + to find. + +.. note:: + + Crockford writes **A much better alternative is to not use new at + all."**
