{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# JavaScript semantics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Primitives" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Truthiness and falsiness" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Primitive | T/F\n", "---------------------|---------\n", "0 | falsey\n", "`NaN` | falsey\n", "`undefined` | falsey\n", "`null` | falsey\n", "Empty string ('') | falsey\n", "Numbers other than 0 | **truthy**\n", "Non-empty string | **truthy**\n", "`Infinity` | **truthy**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "0 is *falsey*, meaning that it is coerced to false in some contexts:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (0) console.log('0 is truthy'); // no output\n", "Boolean(0); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "1 is *truthy*, meaning that it is coerced to true in some contexts:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1 is truthy\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (1) console.log('1 is truthy'); // => \"1 is truthy\"\n", "console.log(Boolean(1)); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "-1 is truthy:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "-1 is truthy\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (-1) console.log('-1 is truthy'); // => \"-1 is truthy\"\n", "console.log(Boolean(-1)); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "So **0 is falsey, and numbers other than 0 are truthy.**" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`Infinity` is truthy:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Infinity is truthy\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (Infinity) console.log('Infinity is truthy'); // => \"Infinity is truthy\"\n", "console.log(Boolean(Infinity)); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`NaN` is falsey:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (NaN) console.log ('NaN is truthy'); // no output\n", "console.log(Boolean(NaN)); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`undefined` is falsey:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (undefined) console.log('undefined is truthy'); // no output\n", "var foo; if (foo) console.log('undefined is truthy'); // no output\n", "console.log(Boolean(undefined)); // => false\n", "console.log(Boolean(foo)); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`null` is falsey" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (null) console.log('null is truthy'); // no output\n", "console.log(Boolean(null)); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Strings are truthy:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The string \"foo\" is truthy\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (\"foo\") console.log('The string \"foo\" is truthy'); // => 'The string \"foo\" is truthy'\n", "console.log(Boolean('foo')); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "...except for an empty string, which is falsey:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (\"\") console.log('The empty string \"\" is truthy'); // no output\n", "console.log(Boolean('')); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Summary" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1, 'foo', Infinity ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var arr = [0, NaN, undefined, null, '', 1, 'foo', Infinity];\n", "\n", "console.log(arr.filter(Boolean)); // => [1, 'foo', Infinity]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Using the `!` operator:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0, NaN, undefined, null, '' ]\n", "[ 1, 'foo', Infinity ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var arr = [0, NaN, undefined, null, '', 1, 'foo', Infinity];\n", "\n", "// => [ 0, NaN, undefined, null, '' ]\n", "console.log(arr.filter(function(val) {return !val}));\n", "\n", "// => [1, 'foo', Infinity]\n", "console.log(arr.filter(function(val) {return !!val}));" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The `!` operator" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "true\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "console.log(!0); // => true" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "console.log(!1); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Used twice (`!!`):" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "console.log(!!0 === Boolean(0)); // => true" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo\n", "false\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var foo = 'foo';\n", "console.log(foo); // => 'foo'\n", "console.log(!foo); // false\n", "console.log(!!foo); // true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Returning a Boolean value instead of a value stored in a variable:" ] }, { "cell_type": "code", "execution_count": 32, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var foo = 'foo';\n", "function f(foo) { return foo };\n", "console.log(f(foo)); // => foo\n", "\n", "// Is foo truthy?\n", "function ff(foo) { return !!foo };\n", "console.log(ff(foo)); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Type coercion or implied typecasting" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an example of the usefulness of type coercion. There is no need to convert the numeric value of `age` to a string before sending it to `console.log()`, because the JS parser does that for you:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The numeric value of x is 1\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var x = 1;\n", "console.log(\"The numeric value of x is \" + x);\n", "// => \"The numeric value of x is 1\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an example of where type coercion is less useful. If you add a string value representing a number to a numeric value, what you will get back is a concatenated string value:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "11\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var x = 1;\n", "console.log(x + \"1\"); // => \"11\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's another example of where type coercion is less useful. String values are truthy, but they are not **true**: that is, comparison with the boolean `true` is *not* one of the contexts in which string values are coerced to truthiness:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The string \"foo\" is truthy\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "if (\"foo\") console.log('The string \"foo\" is truthy');\n", "// => 'The string \"foo\" is truthy'\n", "\n", "console.log(\"foo\" == true); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The equality operator `==` (and the inequality operator `!=`) performs type coercion. The strict equality operator `===` (and the strict inequality operator `!==`) do not:" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "true\n", "false\n", "true\n", "false\n", "true\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 13, "metadata": {}, "output_type": "execute_result" } ], "source": [ "console.log(null == undefined); // => true\n", "console.log(null === undefined); // => false\n", "\n", "console.log(1 == \"1\"); // => true\n", "console.log(1 === \"1'\"); // => false\n", "\n", "console.log(0 == false); // => true, because 0 is falsey\n", "console.log(0 === false); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's say we want to reduce a sparse array to a dense array by excluding `null` or `undefined` values. The non-strict inequality operator `!=` will produced the desired result, but the strict inequality operator `!==` will not:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0, 1, 2, 3 ]\n", "[ 0, 1, 2, 3, undefined ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var arr = [0, 1, 2, 3, , null, undefined];\n", "\n", "console.log(arr.filter(function (val) {\n", " return val != null; // Non-strict inequality\n", "})); // => [0, 1, 2, 3]\n", "\n", "console.log(arr.filter(function (val) {\n", " return val !== null; // Strict inequality\n", "})); // => [0, 1, 2, 3, undefined]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An alternative is to include only truthy values, with the explicit addition of zero, since zero is falsey:" ] }, { "cell_type": "code", "execution_count": 33, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0, 1, 2, 3 ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 33, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var arr = [0, 1, 2, 3, , , null, undefined];\n", "\n", "console.log(arr.filter(function (val) {\n", " // Return either zero, or any truthy value\n", " return val === 0 || val;\n", "})); // => [0, 1, 2, 3]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Validation" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Very basic validation, rejecting null, undefined, an empty (undefined) array element, and empty string:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n", "false\n", "false\n", "false\n", "true\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function isTruthy(value) {\n", " return (!value) ? false : true; \n", "}\n", "\n", "console.log(isTruthy(null)); // => false\n", "console.log(isTruthy(undefined)); // => false\n", "console.log(isTruthy([1,,][1])); // => false\n", "console.log(isTruthy(\"\")); // => false\n", "\n", "console.log(isTruthy(1)); // => true\n", "console.log(isTruthy(\"foo\")); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Variables" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Strings" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Arrays" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Creating an array using **array literal** notation:" ] }, { "cell_type": "code", "execution_count": 15, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1, 2, 3, 4, 5 ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 15, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myArray = [1, 2, 3, 4, 5];\n", "console.log(myArray); // => [ 1, 2, 3, 4, 5 ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Arrays are a special type of object. They can have named properties, like objects. The built-in named property `length` is an example:" ] }, { "cell_type": "code", "execution_count": 16, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "5\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 16, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var myArray = [1, 2, 3, 4, 5];\n", "console.log(myArray.length); // => 5" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can add a named property to an array (here, a string) and access it using dot notation:" ] }, { "cell_type": "code", "execution_count": 17, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1, 2, 3, 4, 5, name: 'foo' ]\n", "foo\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 17, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myArray.name = 'foo';\n", "\n", "console.log(myArray); // => [ 1, 2, 3, 4, 5, name: 'foo' ]\n", "console.log(myArray.name); // => 'foo'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You can add a function as a named property, making it a **method** of the array. This function will log to the console the property **values** stored in the array:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 1, 2, 3, 4, 5, name: 'foo', list: [Function] ]\n", "[Function]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myArray.list = function() {\n", " for (var i = 0, j = this.length; i < j; i++) {\n", " console.log(this[i]);\n", " }\n", "};\n", "\n", "console.log(myArray); // => [ 1, 2, 3, 4, 5, name: 'foo', list: [Function] ]\n", "console.log(myArray.list); // => [Function]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "One might expect that when called, `myArray.list()` will log to the console not only the property values **1**, **2**, **3**, **4**, and **5**, but also **'foo'** and the **`list()`** function itself. However, the named properties **name** and **list** appear to be just that: properties with names attached to them, but without values. Since they have no values, `myArray.list()` displays nothing for them:" ] }, { "cell_type": "code", "execution_count": 19, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "3\n", "4\n", "5\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 19, "metadata": {}, "output_type": "execute_result" } ], "source": [ "myArray.list();" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here, on the other hand, is a function that takes an array, and using a `for...in` loop, displays the **properties** of the array object:" ] }, { "cell_type": "code", "execution_count": 20, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "undefined" ] }, "execution_count": 20, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var displayArray = function(array) {\n", " for (var prop in array) {\n", " console.log(prop);\n", " }\n", "};" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "`displayArray()` logs to the console the array indices **0**, **1**, **2**, **3**, and **4** for the numeric values stored in the first five indices of the array, followed by the property names **name** and **list**. Evidently, the array indices themselves stand in for or serve as property names, for those first five unnamed values:" ] }, { "cell_type": "code", "execution_count": 21, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "0\n", "1\n", "2\n", "3\n", "4\n", "name\n", "list\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 21, "metadata": {}, "output_type": "execute_result" } ], "source": [ "displayArray(myArray);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The name of a named property can be accessed using both dot and bracket notation:" ] }, { "cell_type": "code", "execution_count": 22, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo\n", "foo\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 22, "metadata": {}, "output_type": "execute_result" } ], "source": [ "console.log(myArray.name); // => 'foo'\n", "console.log(myArray['name']); // => 'foo'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "However, the array indices that seem to stand in for or serve as property names cannot be accessed using dot notation:" ] }, { "cell_type": "code", "execution_count": 23, "metadata": { "collapsed": false }, "outputs": [ { "ename": "SyntaxError", "evalue": "evalmachine.:2\nconsole.log(myArray.0);\n ^^\nUnexpected number", "output_type": "error", "traceback": [ "evalmachine.:2", "console.log(myArray.0);", " ^^", "SyntaxError: Unexpected number", " at Object.exports.runInThisContext (vm.js:73:16)", " at run ([eval]:179:19)", " at onMessage ([eval]:63:41)", " at process.emit (events.js:110:17)", " at handleMessage (child_process.js:324:10)", " at Pipe.channel.onread (child_process.js:352:11)" ] } ], "source": [ "// Does not return '0' but a syntax error\n", "console.log(myArray.0);" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Constructors, instances, and prototypes" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In addition to being an `instanceof` any constructor specified explicitly, an object is also an `instanceof` the built-in `Object` constructor:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "true\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var MyConstructor = function() {};\n", "var myObject = new MyConstructor;\n", "\n", "console.log(myObject instanceof MyConstructor); // => true\n", "console.log(myObject instanceof Object); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If an object is not created with a specified constructor, its constructor is the built-in `Object` constructor, which also has its own prototype (both prototypes are empty here):" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[Function]\n", "{}\n", "[Function: Object]\n", "{}\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var MyConstructor = function() {};\n", "var myObject = new MyConstructor;\n", "\n", "console.log(myObject.constructor); // => [Function]\n", "console.log(myObject.constructor.prototype); // => {}\n", "\n", "var myObject = {}; // Reset using (empty) object literal notation\n", "console.log(myObject.constructor); // => [Function: Object]\n", "console.log(myObject.constructor.prototype); // => {}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Use the built-in `hasOwnProperty()` method to determine if a property is a property of an object or a property of its prototype:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [ { "data": { "text/plain": [ "false" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var MyConstructor = function() {};\n", "var myObject = new MyConstructor;\n", "\n", "MyConstructor.prototype.myProperty = 'foo';\n", "myObject.hasOwnProperty('myProperty'); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Truthiness and falsiness" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Both empty objects and non-empty objects are truthy, but not **true** when compared:" ] }, { "cell_type": "code", "execution_count": 24, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "An empty object is truthy\n", "A non-object is truthy\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 24, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var o1 = {};\n", "var o2 = { whatAmI: \"object\" };\n", "\n", "if (o1) console.log('An empty object is truthy');\n", "if (o2) console.log('A non-object is truthy');\n", "// => 'An empty object is truthy'\n", "// => 'A non-empty object is truthy'\n", "\n", "if (o1 == true) console.log('An empty object is true when compared');\n", "if (o2 == true) console.log('A non-empty object is true when compared');" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "An empty object is not equal to another empty object:" ] }, { "cell_type": "code", "execution_count": 25, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 25, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var o1 = {};\n", "var o2 = {};\n", "\n", "console.log(o1 == o2); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A non-empty object is not equal to another non-empty object even if they have identical properties and values:" ] }, { "cell_type": "code", "execution_count": 26, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "false\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 26, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var o1 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "var o2 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "\n", "console.log(o1 == o2); // => false\n", "console.log(o1 === o2); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This is because objects are stored as **references**, and the references for these two objects are not the same. Only if we assign the reference of one object to another object can both objects point to the same memory location:" ] }, { "cell_type": "code", "execution_count": 27, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "true\n", "true\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 27, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var o1 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "var o2 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "var o1 = o2;\n", "\n", "console.log(o1 == o2); // => true\n", "console.log(o1 === o2); // => true" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we subsequently add another property-value pair to **either** of two such objects, it will be reflected in the other object, because the memory references are identical:" ] }, { "cell_type": "code", "execution_count": 28, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "nobody knows\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 28, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var o1 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "var o2 = { whatAmI: \"object\", whyAmI: \"for demonstration purposes\" };\n", "var o1 = o2;\n", "\n", "o1.whereAmI = \"nobody knows\";\n", "\n", "console.log(o2.whereAmI); // => \"nobody knows\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The only way to compare two **different** objects that have identical properties and values is to compare their property-value pairs one at a time." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Functions" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Functions are also stored as **references**, so that just like objects, they are both truthy and unequal with each other when compared (both empty functions and non-empty functions), regardless of whether strict or non-strict equality operators are used:" ] }, { "cell_type": "code", "execution_count": 29, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo() is truthy\n", "false\n", "false\n", "false\n", "false\n", "foo() is truthy\n", "false\n", "false\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 29, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var foo = function(){},\n", " bar = function(){};\n", "if (foo) console.log(\"foo() is truthy\"); // => \"foo() is truthy\"\n", "console.log(foo == bar); // => false\n", "console.log(foo === bar); // => false\n", "\n", "foo = function(){};\n", "bar = function(){return true};\n", "console.log(foo == bar); // => false\n", "console.log(foo === bar); // => false\n", "\n", "foo = function(){return true};\n", "bar = function(){return true};\n", "if (foo) console.log(\"foo() is truthy\"); // => \"foo() is truthy\"\n", "console.log(foo == bar); // => false\n", "console.log(foo === bar); // => false" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If not given a return value, a function will return `undefined`:" ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "foo\n", "undefined\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function f1() {return 'foo';};\n", "function f2() {};\n", "\n", "console.log(f1()); // => 'foo'\n", "console.log(f2()); // => undefined" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When passed to a function and bound to a function parameter, primitives are passed as *copies of the data* stored in a reference." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A copy of the data of the numeric value **3** is passed to a function:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "4 3\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 6, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var val1 = 3\n", "var f1 = function(x) {return x + 1};\n", "console.log(f1(val1), val1); // => 4 3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A copy of the data of the string value **\"test\"** is passed to a function:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "testfoo test\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var val2 = \"test\";\n", "var f2 = function(x) {return x + 'foo'};\n", "console.log(f2(val2), val2); // => 'testfoo' 'test'" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "When passed to a function and bound to a function parameter, objects are passed as *copies of the reference* itself. Since a copy of the reference still points to the references, this means that properties of an object can be changed." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A copy of the *reference* to an object **{x: 1, y: 2}** is passed to a function:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "{ x: 2, y: 1 } { x: 2, y: 1 }\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var val3 = {x:1, y:2};\n", "var f3 = function(x) {x.x = 2; x.y = 1; return x};\n", "console.log(f3(val3), val3); // => { x: 2, y: 1 } { x: 2, y: 1 }" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "A copy of the *reference* to an array **[1, 2, 3]** is passed to a function:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "[ 0, 2, 3 ] [ 0, 2, 3 ]\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "var val4 = [1, 2, 3];\n", "var f4 = function(x) {x[0] = 0; return x};\n", "console.log(f4(val4), val4); // => [ 0, 2, 3 ] [ 0, 2, 3 ]" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### The `arguments` object" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Every function inherits an array-like `arguments` object. This is a good way to handle a variable or unknown number of arguments:" ] }, { "cell_type": "code", "execution_count": 18, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "1\n", "2\n", "1\n", "2\n", "3\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 18, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function foo() {\n", " for (var i = 0, len = arguments.length; i < len; i++) {\n", " console.log(arguments[i]); // Or do whatever else you like\n", " }\n", "}\n", "\n", "foo(1, 2); // => 1\\n2\n", "foo(1, 2, 3); // => 1\\n2\\n3" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### `this` and lexical scoping" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "In a function defined in the global scope, `this` is defined as the global object (`window` in a browser). Surprisingly, `this` is *also* defined as the global object inside a *nested* function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function outer() {\n", " console.log(\"`this` in function `outer()`: \", this);\n", " function inner1() {\n", " console.log(\"`this` inside nested function `inner()`: \", this);\n", " }\n", " inner();\n", "}\n", "outer(); // => (Node.js global object, logged twice to consile)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Inside a constructor function, `this` refers to the object being constructed. Inside an object created by constructor function, `this` refers to that object. Inside a method of the object, `this` also refers to that object. But inside a *function nested inside the object*, `this` refers to the global object. In order to give a nested function the scope of the object, bind `this` to another variable and use that in the nested function:" ] }, { "cell_type": "code", "execution_count": 14, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "`this` inside object created by constructor `C()`: { prop: 'foo' }\n", "`this` inside object method `myMethod`: { prop: 'foo', myMethod: [Function] }\n", "`this` inside object's nested function `inner()`: { prop: 'foo', myMethod: [Function] }\n" ] }, { "data": { "text/plain": [ "undefined" ] }, "execution_count": 14, "metadata": {}, "output_type": "execute_result" } ], "source": [ "function C() {\n", " this.prop = 'foo';\n", " console.log('`this` inside object created by constructor `C()`:', this);\n", " \n", " this.myMethod = function() {\n", " console.log('`this` inside object method `myMethod`:', this);\n", " }\n", " this.myMethod();\n", "\n", " // Bind `this` to another variable so the nested function gets the object's scope\n", " var self = this;\n", " function inner() {\n", " console.log(\"`this` inside object's nested function `inner()`: \", self);\n", " }\n", " inner();\n", "}\n", "\n", "var obj = new C();\n", "\n", "/* Console output: `this` refers to the object in all three cases.\n", "`this` inside object created by constructor `C()`: { prop: 'foo' }\n", "`this` inside object method `myMethod`: { prop: 'foo', myMethod: [Function] }\n", "`this` inside object's nested function `inner()`: { prop: 'foo', myMethod: [Function] }\n", "*/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Inside an event handler function, `this` refers to the event target element:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function foo() {\n", " console.log('`this` inside an event handler function: ', this);\n", "}\n", "\n", "var div = document.createElement('div');\n", "div.innerHTML = 'click me';\n", "div.onclick = foo;\n", "document.body.appendChild(div);\n", "\n", "/* Console output when element is clicked:\n", "`this` inside an event handler function:
click me
\n", "*/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This might not be desirable if you want to use an object's method as an event handler, since `this` inside such a method will be redefined as the event target:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "function C() {\n", " this.myMethod = function() {\n", " console.log('`this` inside object method `myMethod`:', this);\n", " }\n", "}\n", "\n", "var obj = new C();\n", "\n", "var div = document.createElement('div');\n", "div.innerHTML = 'click me';\n", "div.onclick = obj.myMethod; // inside `myMethod()`, `this` is now the event target\n", "document.body.appendChild(div);\n", "\n", "/* Console output when element is clicked:\n", "`this` inside object method `myMethod`:
click me
\n", "*/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To change the scope of `this` in such a case, call it from within a function:" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": true }, "outputs": [], "source": [ "function C() {\n", " this.myMethod = function() {\n", " console.log('`this` inside object method `myMethod()`:', this);\n", " }\n", "}\n", "\n", "var obj = new C();\n", "\n", "var div = document.createElement('div');\n", "div.innerHTML = 'click me';\n", "\n", "/* Ensure that `this` is defined as the event target inside `scoper()`\n", " and as the object containing `myMethod()` when `myMethod()`\n", " is called */\n", "div.onclick = function scoper() { \n", " console.log('`this` inside enclosing function `scoper()`:', this);\n", " obj.myMethod();\n", "};\n", "\n", "document.body.appendChild(div);\n", "\n", "/* Console output when element is clicked:\n", "`this` inside enclosing function `scoper()`:
click me
\n", "`this` inside object method `myMethod`: { myMethod: [Function] }\n", "*/" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Recursion" ] } ], "metadata": { "kernelspec": { "display_name": "Javascript (Node.js)", "language": "javascript", "name": "javascript" }, "language_info": { "file_extension": "js", "mimetype": "application/javascript", "name": "javascript", "version": "0.12.6" } }, "nbformat": 4, "nbformat_minor": 0 }