Skip to content

Instantly share code, notes, and snippets.

@jamesyang124
Last active March 9, 2018 08:24
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save jamesyang124/272c6113e6657e5c8f3b to your computer and use it in GitHub Desktop.
Save jamesyang124/272c6113e6657e5c8f3b to your computer and use it in GitHub Desktop.

Internal of prototype and inheritance(chinese)
https://imazole.wordpress.com/2013/12/23/javascript-prototype-chain/ https://www.byvoid.com/blog/javascript-object-prototype


Values, variables, and control flow

  • _ and $ can be used to name a variable or function as first letter. Digits can not be first letter for naming variable or function.

In Ruby, naming variable with $ as first letter will declare a global variable.

  • JavaScript developers prefer fuzzyLittleTurtle than fuzzy_little_turtle, which contrast to Ruby's naming convention.
  • In most case, use strictly comparison === instead of ==.

Equality comparisons and when to use them

  • typeof(NaN) is a number, typeof(undefined) is an undefined, typeof(null) is an object.

Functions

  • A function will use variable in its local scope first. If variable name cannot be found in the local scope, then it will look up in top-level environment gradually until it founds.

    If we have a function variable, it will follow this rule to find variables, which phenomenon is called as closure.

var variable = "top-level";
var cs = "top-level closure";

function parentFunction() {
  var variable = "local";
  var cs = "toper-closure";
  function childFunction() {
    var cs = "closure";
    function cool(){  return variable + cs;  }  
    console.log(variable);
    return cool();
  }
  return childFunction;
}

var child = parentFunction();
console.log(child());
  • {} does not create new local scope, only defining a funtion will do.
  • Funcions without return keyword outputs undefined as result.
  • In Javascript, simple loops may get better performance than calling functions recursively.
  • Example for recursion:
function findSequence(goal) {
  function find(start, history) {
    if (start == goal)
      return history;
    else if (start > goal)
      return null;
    else
      return find(start + 5, "(" + history + " + 5)") 
                  || find(start * 3, "(" + history + " * 3)");
  }
  return find(1, "1");
}
console.log(findSequence(24));
// "(((1 * 3) + 5) * 3)"

|| and && tricks.

  • Function can be a unit like as numbers, strings, etc. Examples of anonymous function :
var func = function(a, b){
  var words = "anonymous fucntion";
  function x(){
    return words + a + b;
  }
  return x();
}
  
console.log(func(" is", " cool."));
// "anonymous fucntion is cool."
  
function remake(a){
  return function(b){
    return b + a;
  };
}
  
var reFunc = remake("string");
console.log(reFunc("Anonymous"));
// "Anonymousstring"

Data structures: Objects and Arrays

  • The values null and undefined do not have any properties.
  • The keyword delete cuts off properties.
  • Trying to read a non-existent property gives the value undefined.
var prop = { str: "Str"};

console.log(prop.str);  // "Str"
delete prop.str;
console.log(prop.str);  // undefined
  • If a property that does not yet exist, then is set with the = operator, it is added to the object.
  • Also, Properties whose names are not valid variable names have to be quoted when creating the object, and approached using brackets.
  • The part between the brackets can be any expression. It is converted to a string to determine the property name it refers to. One can even use variables to name properties:
var bracket = {}
bracket.prop = "prop"
bracket[2+3] = "5"
bracket[bracket.prop] = "Another prop"

console.log(bracket["5"]);
console.log(bracket["prop"]);
  • The operator in can be used to test whether an object has a certain property. It returns boolean.
var nami = {"str": "String"};
console.log("str" in nami);     //  true
// name seems a reserve word in html or browser engine.
// Avoid to use it to name a variable.
// Guess name => refer to Window.name.
  • == operator, when comparing objects, will only return true if both object values given to it are the precise same value. Comparing different objects with identical contents will give false.
  • Each property is associated with a string value that can be used to access it. If it can convert to string, then it can be used to name a property.
var js = {2: "Two", "3": "Three", four: "Four", "another five": "Five"};
console.log(js["2"]);
console.log(js[2+1]);
console.log(js["four"]);
console.log(js["another five"]);

for (var x = 2; x < 4; x++){console.log(typeof js[x]);} // string
for (var x in js){console.log(js[x]);} // All values.

Working with objects.

  • Besides above ways o create an Object, new can used to create an object by constructor function.
  • Use this to set properties in constructor function, properties can bind to an anonymous function which turn into object's method.
var x = new Date(1906, 1, 1);

function Cat(c_name, c_id){
  return {c_name: c_name, c_id: c_id, print: function(c_name){
    return "c_name: " + c_name; 
  }};
}

var x = new Cat("Versilla", 1);
console.log(x);
console.log(x.print(x.c_name));

function Ca(loc, id){
  this.loc = loc;
  this.id = id;
  this.print = function(loc){
    return "Print " + "loc";
  }
}

var z = new Ca("CA", "12345");
console.log(z.loc);
console.log(z.print(z.loc));

new operator

Error Handling

  • throw and try catch. We can re throw if inner exception hanlding cannot address the exception.
  • finally will be execute before leave the try..catch block.
  • Beside throw customed exceptions, we can new Error object for Exceptions.
var x = 60;

try{
  try {
    throw x;
  }catch(e){
    if(e < 50){
      console.log("Caputre x < 50");
    }else{
      throw new Error("Woops!")   
    }
  }
}catch(s){
  console.log(s);
}finally{
  console.log("Last finally");
}

Error in Javascript from MDN source.

Functional Programming

  • Call, apply, and binding.

  • Object and create.

  • Understand prototypes in javascript.

  • call method is to reuse constructor function method to set the property and its value which associate with this. The frist argument is the target, rest of the arguments map to function's argument list. Object may not have call method due to browser capability, but function always has call and apply methods.

  • arguments is an default object inside the function and provide the argument list and values.

  • Function call.

    While the syntax of this function is almost identical to that of apply(), the fundamental difference is that call() accepts an argument list, while apply() accepts a single array of arguments.

  • Inheritance and prototype chain.
    Example 1:

var Shape = function(name){
  if(arguments[0]){
    this.name = name;
  }
  this.id = "undefined";
};
  
var shape = new Shape("Shape");
var obj_2 = Object.create(null);

// 1. In here, we use constructor function Shape, to set obj_2's  property.
Shape.call(obj_2, "Object");
console.log(obj_2);

// 2. add more properties. prototype is similar to Super class, will be overriden by its constructor.
Shape.prototype = { name: "Super Class", "value": "Values" };

var new_shape = new Shape("Shape");
var old_shape = new Shape();
console.log(new_shape.name);   // name: "Shape"
console.log(old_shape.name);  // name: "Super Class"

// 3. replace call() to apply() method.
function Shape2(name){
  this.name = name;
  Shape.apply(this, ["Still Shape"]);
}

var shape2 = new Shape2("Shape2");
console.log(shape2.name);     // name: "Still Shape"

Example 2:
Use another object animals as target, set i to animal's index.

var animals = [
{species: 'Lion', name: 'King'},
{species: 'Whale', name: 'Fail'}
];

for (var i = 0; i < animals.length; i++) {
  (function (i) { 
    this.print = function () { 
      console.log('#' + i  + ' ' + this.species 
                + ': ' + this.name); 
    }
    console.log("cool");
    console.log(this);
  }).call(animals[i], i);
}

console.log(animals);

Closure

  • http://eloquentjavascript.net/03_functions.html
  • Function is a first class variable, so the closure property should be applied for the local variable is availabe to the function object in the topper functional scope. Different function object recreate the local variable again.
function wrap(n){
  var localVar = n;
  return function(){ return localVar; };
}
var wrap1 = (wrap(2))(); 
console.log(wrap1); // 2
var wrap2 = (wrap(3))(); // 3
console.log(wrap2); // 3

function multiplier(factor){
  return function(number){
    return factor * number;
  }
}

var mul = (multiplier(5))(3);
console.log(mul); // 15

List all possible combinations

function findSolution(start, target, history) {
  if (start == target){
    //console.log(history);
    return history;
  }
  else if (start > target){
    return null;
  } 
  else{
    var res1 = findSolution(start + 2, target, "(" + history + " + 2)");
    var res2 = findSolution(start * 4, target, "(" + history + " * 4)");
    return res1 && res2 ? res1 + "\n" + res2 : (res1 ? res1 : (res2 ? res2 : "") + "\n");
  }
}
console.log(findSolution(2, 8, "2"));
// (((2 + 2) + 2) + 2)
// (2 * 4)

Access property

  • use dot or bracket to access property. With bracket, it evaluate it as expression, with dot, it should be exactly the directly property name.
var j = { p: 1, pp: 2 }; 
j["p".concat("p")]; // 2
j.pp; // 2
  • Both string and arrays has length property.
  • The binary in operator, when applied to a string and an object, returns a Boolean value that indicates whether that object has that property.
var j = { p: 1, "p  p": 2 };
console.log("p" in j);

Global object

  • In browsers, the global scope object is stored in the window variable. Each global variable is present as a property of this object.

apply

  • to dynamically get parameters, instead of just one parameter, use app1y.
function func(callback){
  return function(){
    callback.apply(null, arguments);
  }
}
  
(func(function(arg1, arg2){ console.log(arg2);}))(1, 2, 3);
// 2

Declare object's property as a function

function propFunc(){
  console.log(arguments);
}
  
// first approach: create object instance
var Obj =  {
  list_all: propFunc
};
  
var obj = Object.create(Obj);
  
// second approach: create object constructor
function Obj2(){
  // this represent the new Object instance.
  this.list_all = propFunc;
}
var obj2 = new Obj2();
  
obj.list_all(1, 2, 3, 4, 5);
// [1, 2, 3, 4, 5]

JSON

  • JSON data format Similar to JS object, but property name has to be double quoted. No comments, no variable and function calls. Only simple data expression allowed. JSON.stringfy() and JSON.parse() do the convertions.
var j = JSON.stringify({"app": 2, "app p": 1});
console.log(j); // {"app":2, "app p": 1}
JSON.parse(j)["app p"]; // 2

filter

  • It accept an callback which takes array elements as arguments, return filtered result.
function mockFilter(callback){
  var res = [];
  this.forEach(function(elem){
    if(callback(elem)){
      res.push(elem);
    }
  });
  return res;
}
  
Array.prototype.mf = mockFilter;
  
[6, 2, 3, 4, 5, 1, 7, 10, -11].filter(function(elem){ return elem > 2 });
// [6, 3, 4, 5, 7, 10]
[6, 2, 3, 4, 5, 1, 7, 10, -11].mf(function(elem){ return elem > 2 });
// [6, 3, 4, 5, 7, 10]

function isBigEnough(element) {
  return element >= 10;
}
var filtered = [12, 5, 8, 130, 44].filter(isBigEnough);
// filtered is [12, 130, 44]

bind

this.x = 9; 
var module = {
  x: 81,
  getX: function() { return this.x; }
};

module.getX(); // 81

var getX = module.getX;
getX(); // 9, because in this case, "this" refers to the global object

// Create a new function with 'this' bound to module
var boundGetX = getX.bind(module);
boundGetX(); // 81

Root of object inheritance

// Define the Person constructor
var Person = function(firstName) {
  this.firstName = firstName;
};

// Add a couple of methods to Person.prototype
Person.prototype.walk = function(){
  console.log("I am walking!");
};

Person.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName);
};

// Define the Student constructor
function Student(firstName, subject) {
  // Call the parent constructor, making sure (using Function#call)
  // that "this" is set correctly during the call
  Person.call(this, firstName);

  // Initialize our Student-specific properties
  this.subject = subject;
};

// Create a Student.prototype object that inherits from Person.prototype.
// Note: A common error here is to use "new Person()" to create the
// Student.prototype. That's incorrect for several reasons, not least 
// that we don't have anything to give Person for the "firstName" 
// argument. The correct place to call Person is above, where we call 
// it from Student.
Student.prototype = Object.create(Person.prototype); // See note below

// Set the "constructor" property to refer to Student
Student.prototype.constructor = Student;

// Replace the "sayHello" method
Student.prototype.sayHello = function(){
  console.log("Hello, I'm " + this.firstName + ". I'm studying "
              + this.subject + ".");
};

// Add a "sayGoodBye" method
Student.prototype.sayGoodBye = function(){
  console.log("Goodbye!");
};

// Example usage:
var student1 = new Student("Janet", "Applied Physics");
student1.sayHello();   // "Hello, I'm Janet. I'm studying Applied Physics."
student1.walk();       // "I am walking!"
student1.sayGoodBye(); // "Goodbye!"

// Check that instanceof works correctly
console.log(student1 instanceof Person);  // true 
console.log(student1 instanceof Student); // true

for/in and enumerable

  • The standard properties in Object.prototype are all nonenumerable, which is why they do not show up in such a for/in loop. for/in only traverse enumerable properties.
Object.prototype.nonsense = "hi";
for (var name in map)
  console.log(name);
// → pizza
// → touched tree
// → nonsense
console.log("nonsense" in map);
// → true
console.log("toString" in map);
// → true

// Delete the problematic property again
delete Object.prototype.nonsense;
  • Sometimes you may not need your object has properties from the Object.prototype, for doing so, as follow example:
var map = Object.create(null);
map["pizza"] = 0.069;
console.log("toString" in map);
// → false
console.log("pizza" in map);
// → true`

get and set

var pile = {
  elements: ["eggshell", "orange peel", "worm"],
  get height() {
    return this.elements.length;
  },
  set height(value) {
    console.log("Ignoring attempt to set height to", value);
  }
};

// Or

Object.defineProperty(TextCell.prototype, "heightProp", {
  get: function() { return this.text.length; }
});

var cell = new TextCell("no\nway");
console.log(cell.heightProp);
// → 2
cell.heightProp = 100;
console.log(cell.heightProp);
// → 2

Object.create

function createObject(proto) {
    function ctor() { } // null constructor
    ctor.prototype = proto;
    return new ctor();
}

// Usage:
Student.prototype = createObject(Person.prototype);

strict mode

  • By putting the string "use strict" at the top of a file or a function body to enable strict mode.
function canYouSpotTheProblem() {
  "use strict";
  for (counter = 0; counter < 10; counter++)
    console.log("Happy happy");
}

canYouSpotTheProblem();
// → ReferenceError: counter is not defined
  • In strict mode is that the this binding holds the value undefined in functions that are not called as methods. When making such a call outside of strict mode, this refers to the global scope object.
function Person(name) { this.name = name; }
var ferdinand = Person("Ferdinand"); // oops
console.log(name);
// → Ferdinand

"use strict";
function Person(name) { this.name = name; }
// Oops, forgot 'new'
var ferdinand = Person("Ferdinand");
// → TypeError: Cannot set property 'name' of undefined

Exception handling: try and catch, throw and finally

  • finally is similar with Ruby's ensure.
function promptDirection(question) {
  var result = prompt(question, "");
  if (result.toLowerCase() == "left") return "L";
  if (result.toLowerCase() == "right") return "R";
  throw new Error("Invalid direction: " + result);
}

function look() {
  if (promptDirection("Which way?") == "L")
    return "a house";
  else
    return "two angry bears";
}

try {
  console.log("You see", look());
} catch (error) {
  console.log("Something went wrong: " + error);
}

try {
  console.log("You see", look());
} finally {
  console.log("no matter what happen execute this in after");
}

Assertions

function AssertionFailed(message) {
  this.message = message;
}
AssertionFailed.prototype = Object.create(Error.prototype);

function assert(test, message) {
  if (!test)
    throw new AssertionFailed(message);
}

function lastElement(array) {
  assert(array.length > 0, "empty array in lastElement");
  return array[array.length - 1];
}

Regular Expression

  • When the regular expression contains subexpressions grouped with parentheses, the text that matched those groups will also show up in the array. The whole match is always the first element. The next element is the part matched by the first group (the one whose opening parenthesis comes first in the expression), then the second group, and so on.

  • When a group does not end up being matched at all (for example when followed by a question mark), its position in the output array will hold undefined. Similarly, when a group is matched multiple times, only the last match ends up in the array.

console.log(/bad(ly)?/.exec("bad"));
// → ["bad", undefined]
console.log(/(\d)+/.exec("123"));
// → ["123", "3"]
  • /x^/ does not match any string (there cannot be an x before the start of the string).
/x^/.test("");
// false
  • Avoid loop in backtracking.
/([01]+)+b/
/([01]+)+b/.test("x")
// start inner loop matcher /[01]/ then cannot find it
// backtrack to outer group loop then inner loop again.
// which leads infinite loop.
  • It is also possible to pass a function, rather than a string, as the second argument to replace. For each replacement, the function will be called with the matched groups (as well as the whole match) as arguments, and its return value will be inserted into the new string
var stock = "1 lemon, 2 cabbages, and 101 eggs";
function minusOne(match, amount, unit) {
  amount = Number(amount) - 1;
  if (amount == 1) // only one left, remove the 's'
    unit = unit.slice(0, unit.length - 1);
  else if (amount == 0)
    amount = "no";
  return amount + " " + unit;
}
console.log(stock.replace(/(\d+) (\w+)/g, minusOne));
// → no lemon, 1 cabbage, and 100 eggs
  • [^] (any character that is not in the empty set of characters) as a way to match any character. We cannot just use a dot here because block comments can continue on a new line, and dots do not match the newline character.
  • Remove comments.
function stripComments(code) {
  return code.replace(/\/\/.*|\/\*[^]*\*\//g, "");
}

// Split each line.
string.split(/\r?\n/).forEach(function(line) {});

Date

  • JavaScript uses a convention where month numbers start at zero (so December is 11), yet day numbers start at one. This is confusing and silly. Be careful.
console.log(new Date(2009, 11, 9));
// → Wed Dec 09 2009 00:00:00 GMT+0100 (CET)
console.log(new Date(2009, 11, 9, 12, 59, 59, 999));
// → Wed Dec 09 2009 12:59:59 GMT+0100 (CET)

Module

// Example can be run directly in your JavaScript console

// Create a function that takes two arguments and returns the sum of those arguments
var adder = new Function('a', 'b', 'return a + b');

// Call the function
adder(2, 6);
// > 8
function require(name) {
  var code = new Function("exports", readFile(name));
  var exports = {};
  code(exports);
  return exports;
}

console.log(require("weekDay").name(1));
// → Monday
  • To solve duplicate require in every time. We need a cache so dont have to load the files in same require. Also, exports object allowed to export the values out.
function require(name) {
  if (name in require.cache)
    return require.cache[name];

  var code = new Function("exports, module", readFile(name));
  var exports = {}, module = {exports: exports};
  code(exports, module);

  require.cache[name] = module.exports;
  return module.exports;
}
require.cache = Object.create(null);
  • Browser won't do anything while read and execute js file. This means that if every require call went and fetched something from some faraway web server, the page would freeze for a painfully long time while loading its scripts.

  • A solution is to wrap the code that makes up your module in a function so that the module loader can first load its dependencies in the background and then call the function, initializing the module, when the dependencies have been loaded.

var defineCache = Object.create(null);
var currentMod = null;

function getModule(name) {
  if (name in defineCache)
    return defineCache[name];

  var module = {exports: null,
                loaded: false,
                onLoad: []};
  defineCache[name] = module;
  // customed asynchronous read file function
  backgroundReadFile(name, function(code) {
    currentMod = module;
    new Function("", code)();
  });
  return module;
}

/*
The define function itself uses getModule to fetch or create the module objects 
for the current module’s dependencies.Its task is to schedule the moduleFunction
(the function that contains the module’s actual code) to be run whenever those 
dependencies are loaded. 
*/

function define(depNames, moduleFunction) {
  var myMod = currentMod;
  var deps = depNames.map(getModule);

  deps.forEach(function(mod) {
    if (!mod.loaded)
      mod.onLoad.push(whenDepsLoaded);
  });

  function whenDepsLoaded() {
    if (!deps.every(function(m) { return m.loaded; }))
      return;

    var args = deps.map(function(m) { return m.exports; });
    var exports = moduleFunction.apply(null, args);
    if (myMod) {
      myMod.exports = exports;
      myMod.loaded = true;
      myMod.onLoad.every(function(f) { f(); });
    }
  }
  whenDepsLoaded();
}

// Exlain depNames.map(getModule); without arguemnts.
function returnInt(element) {
  return parseInt(element, 10);
}

['1', '2', '3'].map(returnInt); // [1, 2, 3]
// Actual result is an array of numbers (as expected)

DOM

  • document.documentElement as DOM root.
  • document.getElementById, document.getElementByTag, document.getElementByClassName, and document.createTextNode.
  • insertBefore, appendChild, replaceChild, and removeChild.
<script>
  function count(selector) {
    return document.querySelectorAll(selector).length;
  }
  console.log(count("p"));           // All <p> elements
  // → 4
  console.log(count(".animal"));     // Class animal
  // → 2
  console.log(count("p .animal"));   // Animal inside of <p>
  // → 2
  console.log(count("p > .animal")); // Direct child of <p>
  // → 1
</script>

CSS specificy

Event Handler

  • Instead of polling to monitor the state of document, register event as callback when the specific action is performed.
// in global object => Window object
<p>Click this document to activate the handler.</p>
<script>
  addEventListener("click", function() {
    console.log("You clicked!");
  });
</script>

<button>Click me</button>
<p>No handler here.</p>
<script>
  var button = document.querySelector("button");
  button.addEventListener("click", function() {
    console.log("Button clicked.");
  });
  // button.removeEventListener("click", once);
</script>
  • Event handlers registered on nodes with children will also receive some events that happen in the children. If a button inside a paragraph is clicked, event handlers on the paragraph will also receive the click event. (Propogate)

  • But if both the paragraph and the button have a handler, the more specific handler—the one on the button—gets to go first. The event is said to propagate outward, from the node where it happened to that node’s parent node and on to the root of the document. Finally, after all handlers registered on a specific node have had their turn, handlers registered on the whole window get a chance to respond to the event.

  • At any point, an event handler can call the stopPropagation method on the event object to prevent handlers “further up” from receiving the event. For example, you have a button inside another clickable element and you don’t want clicks on the button to activate the outer element’s click behavior.

  • It is also possible to use the target property to cast a wide net for a specific type of event.

<button>A</button>
<button>B</button>
<button>C</button>
<script>
  document.body.addEventListener("click", function(event) {
    if (event.target.nodeName == "BUTTON")
      console.log("Clicked", event.target.textContent);
  });
</script>

Prevent default event handler

  • Many events have a default action associated with them. If you click a link, you will be taken to the link’s target. If you press the down arrow, the browser will scroll the page down. If you right-click, you’ll get a context menu. And so on.

  • For most types of events, the JavaScript event handlers are called before the default behavior is performed. If the handler doesn’t want the normal behavior to happen, typically because it has already taken care of handling the event, it can call the preventDefault method on the event object.

<a href="https://developer.mozilla.org/">MDN</a>
<script>
  var link = document.querySelector("a");
  link.addEventListener("click", function(event) {
    console.log("Nope.");
    event.preventDefault();
  });
</script>

KeyDown not just fire once.

  • Despite its name, "keydown" is fired not only when the key is physically pushed down. When a key is pressed and held, the event is fired again every time the key repeats.
  • The previous example looked at the keyCode property of the event object. This is how you can identify which key is being pressed or released.
  • The charCodeAt method on strings gives us a way to find this code.
console.log("Violet".charCodeAt(0));
// → 86
console.log("1".charCodeAt(0));
// → 49

<p>Press Ctrl-Space to continue.</p>
<script>
  addEventListener("keydown", function(event) {
    if (event.keyCode == 32 && event.ctrlKey)
      console.log("Continuing!");
  });
</script>

Get typed text by keypress event

  • If you are interested in the actual text being typed? Getting that text from key codes is awkward. Instead, there exists another event, "keypress", which is fired right after "keydown" (and repeated along with "keydown" when the key is held) but only for keys that produce character input. The charCode property in the event object contains a code that can be interpreted as a Unicode character code.
<p>Focus this page and type something.</p>
<script>
  addEventListener("keypress", function(event) {
    console.log(String.fromCharCode(event.charCode));
  });
</script>

Mouse events

  • mousedown and mouseup similar to keydown and keyup events. mousedown will only count once every click, will not repeatedly fired the event in keeping pressed.
  • dblclick is (double-click) event.
  • After the "mouseup" event, a "click" event is fired on the most specific node that contained both the press and the release of the button. For example, if I press down the mouse button on one paragraph and then move the pointer to another paragraph and release the button, the "click" event will happen on the element that contains both those paragraphs.
  • Its pageX and pageY properties, which contain the event’s coordinates (in pixels) relative to the top-left corner of the document.
<style>
  body {
    height: 200px;
    background: beige;
  }
  .dot {
    height: 8px; width: 8px;
    border-radius: 4px; /* rounds corners */
    background: blue;
    position: absolute;
  }
</style>
<script>
  addEventListener("click", function(event) {
    var dot = document.createElement("div");
    dot.className = "dot";
    dot.style.left = (event.pageX - 4) + "px";
    dot.style.top = (event.pageY - 4) + "px";
    document.body.appendChild(dot);
  });
</script>

Mouse Motion

<p>Drag the bar to change its width:</p>
<div style="background: orange; width: 60px; height: 20px">
</div>
<script>
  var lastX; // Tracks the last observed mouse X position
  var rect = document.querySelector("div");
  rect.addEventListener("mousedown", function(event) {
    if (event.which == 1) {
      lastX = event.pageX;
      addEventListener("mousemove", moved);
      event.preventDefault(); // Prevent selection
    }
  });

  function buttonPressed(event) {
    if (event.buttons == null)
      return event.which != 0;
    else
      return event.buttons != 0;
  }
  function moved(event) {
    if (!buttonPressed(event)) {
      removeEventListener("mousemove", moved);
    } else {
      var dist = event.pageX - lastX;
      var newWidth = Math.max(10, rect.offsetWidth + dist);
      rect.style.width = newWidth + "px";
      lastX = event.pageX;
    }
  }
</script>
  • Not all browsers give "mousemove" events a meaningful which property. There is a standard property called buttons, which provides similar information, but that is also not supported on all browsers. Fortunately, all major browsers support either buttons or which, so the buttonPressed function in the example first tries buttons, and falls back to which when that isn’t available.

  • Whenever the mouse pointer enters or leaves a node, a mouseover or mouseout event is fired.

  • When the mouse moves from a node onto one of its children, mouseout is fired on the parent node, though the mouse did not actually leave the node’s extent. To make things worse, these events propagate just like other events, and thus you will also receive mouseout events when the mouse leaves one of the child nodes of the node on which the handler is registered.

  • To work around this problem, we can use the relatedTarget property of the event objects created for these events. It tells us, in the case of mouseover, what element the pointer was over before and, in the case of mouseout, what element it is going to.

  • In case of onmouseover event, the relatedTarget property retrieves the element that the mouse pointer left, and in case of onmouseout event, the relatedTarget property retrieves the element that the mouse pointer entered.

<p>Hover over this <strong>paragraph</strong>.</p>
<script>
  var para = document.querySelector("p");
  function isInside(node, target) {
    for (; node != null; node = node.parentNode)
      if (node == target) return true;
  }
  para.addEventListener("mouseover", function(event) {
    // if not left h3 self then set red.
    if (!isInside(event.relatedTarget, para))
      para.style.color = "red";
  });
  para.addEventListener("mouseout", function(event) {
    // if not enter h3 self, set back.
    if (!isInside(event.relatedTarget, para))
      para.style.color = "";
  });
</script>
  • This can be more easily achieved CSS pseudoselector :hover.
<style>
  p:hover { color: red }
</style>
<p>Hover over this <strong>paragraph</strong>.</p>

Scroll

// scroll down progress bar 
<style>
  .progress {
    border: 1px solid blue;
    width: 100px;
    position: fixed;
    top: 10px; right: 10px;
  }
  .progress > div {
    height: 12px;
    background: blue;
    width: 0%;
  }
  body {
    height: 2000px;
  }
</style>
<div class="progress"><div></div></div>
<p>Scroll me...</p>
<script>
  var bar = document.querySelector(".progress div");
  addEventListener("scroll", function() {
    var max = document.body.scrollHeight - innerHeight;
    var percent = (pageYOffset / max) * 100;
    bar.style.width = percent + "%";
  });
</script>
  • Calling preventDefault on a scroll event does not prevent the scrolling from happening. In fact, the event handler is called only after the scrolling takes place. Event with event.cancelable property is able to call it.

  • https://developer.mozilla.org/en-US/docs/Web/API/event.preventDefault

  • The scroll event cannot be canceled. You can cancel 2 things however: mouse scroll and buttons associated with scrolling.

  • The global innerHeight variable gives us the height of the window, which we have to subtract from the total scrollable height—you can’t keep scrolling when you hit the bottom of the document. (There’s also an innerWidth to go along with innerHeight.) By dividing pageYOffset, the current scroll position, by the maximum scroll position and multiplying by 100, we get the percentage for the progress bar.

Blur and Focus

  • When an element gains focus, the browser fires a focus event on it. When it loses focus, a blur event fires.
  • These two events do not propagate. A handler on a parent element is not notified when a child element gains or loses focus.

Load

  • When a page finishes loading, the load event is fired on the window and the document body objects. This is often used to schedule initialization actions that require the whole document to have been built. Remember that the content of <script> tags is run immediately when the tag is encountered. This is often too soon, such as when the script needs to do something with parts of the document that appear after the <script> tag.
  • Elements such as images and script tags that load an external file also have a load event that indicates the files they reference were loaded. Like the focus-related events, loading events do not propagate.
  • When a page is closed or navigated away from (for example by following a link), a beforeunload event is fired. The main use of this event is to prevent the user from accidentally losing work by closing a document. Preventing the page from unloading is not done with the preventDefault method. Instead, it is done by returning a string from the handler. The string will be used in a dialog that asks the user if they want to stay on the page or leave it. This mechanism ensures that a user is able to leave the page, even if it is running a malicious script that would prefer to keep them there forever in order to force them to look at dodgy weight loss ads.

Script Execution Timeline

  • It is important to understand that even though events can fire at any time, no two scripts in a single document ever run at the same moment. If a script is already running, event handlers and pieces of code scheduled in other ways have to wait for their turn. This is the reason why a document will freeze when a script runs for a long time. The browser cannot react to clicks and other events inside the document because it can’t run event handlers until the current script finishes running.
  • For cases where you really do want to do some time-consuming thing in the background without freezing the page, browsers provide something called web workers. A worker is an isolated JavaScript environment that runs alongside the main program for a document and can communicate with it only by sending and receiving messages.
addEventListener("message", function(event) {
  postMessage(event.data * event.data);
});

var squareWorker = new Worker("code/squareworker.js");
squareWorker.addEventListener("message", function(event) {
  console.log("The worker responded:", event.data);
});
squareWorker.postMessage(10);
squareWorker.postMessage(24);
  • The script that created the worker sends and receives messages through the Worker object, whereas the worker talks to the script that created it by sending and listening directly on its global scope—which is a new global scope, not shared with the original script.

Debouncing

  • Some types of events have the potential to fire rapidly, many times in a row (the "mousemove" and "scroll" events, for example). When handling such events, you must be careful not to do anything too time-consuming or your handler will take up so much time that interaction with the document starts to feel slow and choppy.

  • If you do need to do something nontrivial in such a handler, you can use setTimeout to make sure you are not doing it too often. This is usually called debouncing the event.

<textarea>Type something here...</textarea>
<script>
  var textarea = document.querySelector("textarea");
  var timeout;
  textarea.addEventListener("keydown", function() {
    clearTimeout(timeout);
    timeout = setTimeout(function() {
      console.log("You stopped typing.");
    }, 500);
  });
</script>
  • Instead of immediately performing an action in the event handler, we set a timeout instead. We also clear the previous timeout (if any) so that when events occur close together (closer than our timeout delay), the timeout from the previous event will be canceled.
  • Giving an undefined value to clearTimeout or calling it on a timeout that has already fired has no effect.
function displayCoords(event) {
    document.body.textContent =
      "Mouse at " + event.pageX + ", " + event.pageY;
  }

  var scheduled = false, lastEvent;
  addEventListener("mousemove", function(event) {
    // last event keep update it for coming mousemove event.
    lastEvent = event;
    if (!scheduled) {
      scheduled = true;
      setTimeout(function() {
        scheduled = false;
        displayCoords(lastEvent);
      }, 250);
    }
  });
  • update coordination every 250 ms.

Canvas

  • Using Scalable Vector Graphics (SVG) or Canvas. Canvas has to redraw to relocate to new position. SVG does not need to. SVG can embedded or in img tag. A canvas is a single DOM element that encapsulates a picture. Two interfaces one is 2D graphics, and another is WebGL for 3D graphics.
<p>Normal HTML here.</p>
<svg xmlns="http://www.w3.org/2000/svg">
  <circle r="50" cx="50" cy="50" fill="red"/>
  <rect x="120" y="5" width="90" height="90"
        stroke="blue" fill="none"/>
</svg>

<p>Before canvas.</p>
<canvas width="120" height="60"></canvas>
<p>After canvas.</p>
<script>
  var canvas = document.querySelector("canvas");
  var context = canvas.getContext("2d");
  context.fillStyle = "red";
  context.fillRect(10, 10, 100, 50);
</script>
  • In the canvas interface, a shape can be filled, meaning its area is given a certain color or pattern, or it can be stroked, which means a line is drawn along its edge. The same terminology is used by SVG.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  cx.strokeStyle = "blue";
  cx.fillStyle = "red";
  // x, y related top-left coordination and width then height.
  cx.fillRect(5, 5, 50, 50);
  cx.lineWidth = 5;
  cx.strokeRect(135, 5, 50, 50);
</script>

Canvas Paths

  • Paths are not values that can be stored and passed around. Instead, if you want to do something with a path, you make a sequence of method calls to describe its shape.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  cx.beginPath();
  // 0,0       50,0
  // 
  // 0,10      50,10
  cx.moveTo(50, 10);
  cx.lineTo(10, 70);
  cx.lineTo(90, 70);
  cx.fill();
</script>
  • cx.quadraticCurveTo(60, 10, 90, 90); control point is x60 and y90.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  cx.beginPath();
  cx.moveTo(10, 90);
  // control=(60,10) goal=(90,90)
  cx.quadraticCurveTo(60, 10, 90, 90);
  cx.lineTo(60, 10);
  cx.closePath();
  cx.stroke();
</script>
  • The bezierCurve method draws a similar kind of curve. Instead of a single control point, this one has two—one for each of the line's endpoints.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  cx.beginPath();
  cx.moveTo(10, 90);
  // control1=(10,10) control2=(90,10) goal=(50,90)
  cx.bezierCurveTo(10, 10, 90, 10, 50, 90);
  cx.lineTo(90, 10);
  cx.lineTo(10, 10);
  cx.closePath();
  cx.stroke();
</script>
  • arcTo() take no less than 5 arguments.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  cx.beginPath();
  cx.moveTo(10, 10);
  // control=(90,10) goal=(90,90) radius=20
  cx.arcTo(90, 10, 90, 90, 20);
  cx.moveTo(10, 10);
  // control=(90,10) goal=(90,90) radius=80
  cx.arcTo(90, 10, 90, 90, 80);
  cx.stroke();
</script>
  • fillText.
  var cx = document.querySelector("canvas").getContext("2d");
  cx.font = "28px Georgia";
  cx.fillStyle = "fuchsia";
  cx.fillText("I can draw text, too!", 10, 50);
  • drawImage allows to draw image from img or another canvas.
<canvas></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  var img = document.createElement("img");
  img.src = "http://eloquentjavascript.net/img/player_big.png";
  var spriteW = 34, spriteH = 70;
  img.addEventListener("load", function() {
    var cycle = 0;
    setInterval(function() {
      cx.clearRect(0, 0, spriteW, spriteH);
      cx.drawImage(img,
                   // source rectangle
                   cycle * spriteW, 0, spriteW, spriteH,
                   // destination rectangle
                   0,               0, spriteW, spriteH);
      cycle = (cycle + 1) % 8;
    }, 120);
  });
</script>
  • translate to move coordination system. rotate rotate it. The system will apply by its execution order.

  • The save and restore methods on the 2D canvas context perform this kind of transformation management. They conceptually keep a stack of transformation states. When you call save, the current state is pushed onto the stack, and when you call restore, the state on top of the stack is taken off and used as the context’s current transformation.

<canvas width="600" height="300"></canvas>
<script>
  var cx = document.querySelector("canvas").getContext("2d");
  function branch(length, angle, scale) {
    cx.fillRect(0, 0, 1, length);
    if (length < 8) return;
    cx.save();
    cx.translate(0, length);
    cx.rotate(-angle);
    branch(length * scale, angle, scale);
    cx.rotate(2 * angle);
    branch(length * scale, angle, scale);
    cx.restore();
  }
  cx.translate(300, 0);
  branch(60, 0.5, 0.8);
</script>
  • If the calls to save and restore were not there, the second recursive call to branch would end up with the position and rotation created by the first call. It wouldn’t be connected to the current branch but rather to the innermost, rightmost branch drawn by the first call. The resulting shape might also be interesting, but it is definitely not a tree.
  • http://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/
  • SVG can be used to produce crisp graphics that look good at any zoom level. It is more difficult to use than plain HTML but also much more powerful.

HTTP

  • Form with GET method will add a query string to its URL, code start with %are URL encoding. The ampersand & use to seperate the parameters.
  • XMLHttpRequest does not tied to XML only. It can be text, json, etc.
var req = new XMLHttpRequest();
// relative URL
req.open("GET", "example/data.txt", false);
req.send(null);
console.log(req.responseText);
// → This is the content of data.txt

// if it is a xml file.
<fruits>
  <fruit name="banana" color="yellow"/>
  <fruit name="lemon" color="yellow"/>
  <fruit name="cherry" color="red"/>
</fruits>

var req = new XMLHttpRequest();
req.open("GET", "example/fruit.xml", false);
req.send(null);
console.log(req.responseXML.querySelectorAll("fruit").length);


// respond to JSON
var req = new XMLHttpRequest();
req.open("GET", "example/fruit.json", false);
req.send(null);
console.log(JSON.parse(req.responseText));
// → {banana: "yellow", lemon: "yellow", cherry: "red"}
  • Browsers protect users by disallowing JavaScripts to make HTTP requests to other domains. But servers can include a header like this in their response to explicitly indicate to browsers that it is okay for the request to come from other domains Access-Control-Allow-Origin: *.

XMLHttpRequest

function getURL(url, callback) {
  var req = new XMLHttpRequest();
  req.open("GET", url, true);
  req.addEventListener("load", function() {
    if (req.status < 400)
      callback(req.responseText);
    else
      callback(null, new Error("Request failed: " +
                               req.statusText));
  });
  req.addEventListener("error", function() {
    callback(null, new Error("Network error"));
  });
  req.send(null);
}

getURL("data/nonsense.txt", function(content, error) {
  if (error != null)
    console.log("Failed to fetch nonsense.txt: " + error);
  else
    console.log("nonsense.txt: " + content);
});

Simplify XMLHttpRequest to Promises

function get(url) {
  return new Promise(function(succeed, fail) {
    var req = new XMLHttpRequest();
    req.open("GET", url, true);
    req.addEventListener("load", function() {
      if (req.status < 400)
        succeed(req.responseText);
      else
        fail(new Error("Request failed: " + req.statusText));
    });
    req.addEventListener("error", function() {
      fail(new Error("Network error"));
    });
    req.send(null);
  });
}

  function showMessage(msg) {
    var elt = document.createElement("div");
    elt.textContent = msg;
    return document.body.appendChild(elt);
  }

  var loading = showMessage("Loading...");
  getJSON("example/bert.json").then(function(bert) {
    return getJSON(bert.spouse);
  }).then(function(spouse) {
    return getJSON(spouse.mother);
  }).then(function(mother) {
    showMessage("The name is " + mother.name);
  }).catch(function(error) {
    showMessage(String(error));
  }).then(function() {
    document.body.removeChild(loading);
  });
  • When building a system that requires communication between a JavaScript program running in the browser (client-side) and a program on a server (server-side), there are several different ways to model this communication.
  • One is remote procedure call call a function instead RESTful style.

Form

  • Form fields do not necessarily have to appear in a <form> tag. You can put them anywhere in a page. Such fields cannot be submitted (only a form as a whole can). Below list common form fields.
<p><input type="text" value="abc"> (text)</p>
<p><input type="password" value="abc"> (password)</p>
<p><input type="checkbox" checked> (checkbox)</p>
<p><input type="radio" value="A" name="choice">
   <input type="radio" value="B" name="choice" checked>
   <input type="radio" value="C" name="choice"> (radio)</p>
<p><input type="file" checked> (file)</p>
  • Form fields can get focus and blur. The value in document.activeElement corresponds to the currently focused element.
<input type="text">
<script>
  document.querySelector("input").focus();
  console.log(document.activeElement.tagName);
  // → INPUT
  document.querySelector("input").blur();
  console.log(document.activeElement.tagName);
  // → BODY
</script>
  • For some pages, the user is expected to want to start interacting with a form field immediately. JavaScript can be used to focus this field when the document is loaded, but HTML also provides the autofocus attribute, which produces the same effect but lets the browser know what we are trying to achieve. But only one element will get focus at ayn time.
<input type="text" autofocus>
  • Browsers traditionally also allow the user to move the focus through the document by pressing the Tab key. We can influence the order in which elements receive focus with the tabindex attribute. By default, most types of HTML elements can not be focused. But you can add a tabindex attribute to any element, which will make it focusable.
<input type="text" tabindex=1> <a href=".">(help)</a>
<button onclick="console.log('ok')" tabindex=2>OK</button>
  • Disabled fields cannot be focused or changed, and unlike active fields, they usually look gray and faded.
<button>I'm all right</button>
<button disabled>I'm out</button>

Elements array in Form

  • The <form> element, in turn, has a property called elements that contains an array-like collection of the fields inside it.
<form action="example/submit.html">
  Name: <input type="text" name="name"><br>
  Password: <input type="password" name="password"><br>
  <button type="submit">Log in</button>
</form>
<script>
  var form = document.querySelector("form");
  console.log(form.elements[1].type);
  // → password
  console.log(form.elements.password.type);
  // → password
  console.log(form.elements.name.form == form);
  // → true
</script>
  • Submitting a form normally means that the browser navigates to the page indicated by the form’s action attribute, using either a GET or a POST request. But before that happens, a "submit" event is fired. This event can be handled by JavaScript, and the handler can prevent the default behavior by calling preventDefault on the event object.
<form action="example/submit.html">
  Value: <input type="text" name="value">
  <button type="submit">Save</button>
</form>
<script>
  var form = document.querySelector("form");
  form.addEventListener("submit", function(event) {
    console.log("Saving value", form.elements.value.value);
    event.preventDefault();
  });
</script>
  • Intercepting "submit" events in JavaScript has various uses. We can write code to verify that the values the user entered make sense and immediately show an error message instead of submitting the form when they don’t. Or we can disable the regular way of submitting the form entirely, as in the previous example, and have our program handle the input, possibly using XMLHttpRequest to send it over to a server without reloading the page.

value in TextArea

  • The <input> DOM elements with password or text have a value property that holds their current content as a string value. Setting this property to another string changes the field’s content.
<textarea></textarea>
<script>
  var textarea = document.querySelector("textarea");
  textarea.addEventListener("keydown", function(event) {
    // The key code for F2 happens to be 113
    if (event.keyCode == 113) {
      replaceSelection(textarea, "Khasekhemwy");
      event.preventDefault();
    }
  });
  function replaceSelection(field, word) {
    var from = field.selectionStart, to = field.selectionEnd;
    field.value = field.value.slice(0, from) + word +
                  field.value.slice(to);
    // Put the cursor after the word
    field.selectionStart = field.selectionEnd =
      from + word.length;
  };
</script>
  • checkbox has a checked property.
<input type="checkbox" id="purple">
<label for="purple">Make this page purple</label>
<script>
  var checkbox = document.querySelector("#purple");
  checkbox.addEventListener("change", function() {
    document.body.style.background =
      checkbox.checked ? "mediumpurple" : "";
  });
</script>
  • radio buttons has value property.
<input type="radio" name="color" value="mediumpurple"> Purple
<input type="radio" name="color" value="lightgreen"> Green
<input type="radio" name="color" value="lightblue"> Bluet
<script>
  var buttons = document.getElementsByName("color");
  function setColor(event) {
    document.body.style.background = event.target.value;
  }
  for (var i = 0; i < buttons.length; i++)
    buttons[i].addEventListener("change", setColor);
</script>
  • select tag with the nested option tag. It has value property for it. The following example extracts the selected values from a multiple select field and uses them to compose a binary number from individual bits. Hold Ctrl (or Command on a Mac) to select multiple options.
<select multiple>
  <option value="1">0001</option>
  <option value="2">0010</option>
  <option value="4">0100</option>
  <option value="8">1000</option>
</select> = <span id="output">0</span>
<script>
  var select = document.querySelector("select");
  var output = document.querySelector("#output");
  select.addEventListener("change", function() {
    var number = 0;
    for (var i = 0; i < select.options.length; i++) {
      var option = select.options[i];
      if (option.selected)
        number += Number(option.value);
    }
    output.textContent = number;
  });
</script>
  • file has a files array.
<input type="file">
<script>
  var input = document.querySelector("input");
  input.addEventListener("change", function() {
    if (input.files.length > 0) {
      var file = input.files[0];
      console.log("You chose", file.name);
      if (file.type)
        console.log("It has type", file.type);
    }
  });
</script>
  • Objects in the files property have properties such as name (the file name), size (the file’s size in bytes), and type (the media type of the file, such as text/plain or image/jpeg).
  • The files is initially empty. The reason there isn’t simply a file property is that file fields also support a multiple attribute, which makes it possible to select multiple files at the same time.
<input type="file" multiple>
<script>
  var input = document.querySelector("input");
  input.addEventListener("change", function() {
    Array.prototype.forEach.call(input.files, function(file) {
      var reader = new FileReader();
      reader.addEventListener("load", function() {
        console.log("File", file.name, "starts with",
                    reader.result.slice(0, 20));
      });
      reader.readAsText(file);
    });
  });
</script>
  • Reading a file is done by creating a FileReader object, registering a "load" event handler for it, and calling its readAsText method, giving it the file we want to read. Once loading finishes, the reader’s result property contains the file’s content.
  • FileReaders also fire an "error" event when reading the file fails for any reason. The error object itself will end up in the reader’s error property. If you don’t want to remember the details of yet another inconsistent asynchronous interface, you could wrap it in a Promise like this:
function readFile(file) {
  return new Promise(function(succeed, fail) {
    var reader = new FileReader();
    reader.addEventListener("load", function() {
      succeed(reader.result);
    });
    reader.addEventListener("error", function() {
      fail(reader.error);
    });
    reader.readAsText(file);
  });
}

Storing Data in Client Side

  • When such an application needs to remember something between sessions, you cannot use JavaScript variable since those are thrown away every time a page is closed. You could set up a server, connect it to the Internet, and have your application store something there. But this adds a lot of extra work and complexity. Sometimes it is enough to just keep the data in the browser.

  • Sites from different domains get different storage compartments. That means data stored in localStorage by a given website can, in principle, only be read (and overwritten) by scripts on that same site.

  • Browsers also enforce a limit on the size of the data a site can store in localStorage, typically on the order of a few megabytes. That restriction, along with the fact that filling up people’s hard drives with junk is not really profitable, prevents this feature from eating up too much space.

Notes: <select id="list"></select>
<button onclick="addNote()">new</button><br>
<textarea id="currentnote" style="width: 100%; height: 10em">
</textarea>

<script>
  var list = document.querySelector("#list");
  function addToList(name) {
    var option = document.createElement("option");
    option.textContent = name;
    list.appendChild(option);
  }

  // Initialize the list from localStorage
  var notes = JSON.parse(localStorage.getItem("notes")) ||
              {"shopping list": ""};
  for (var name in notes)
    if (notes.hasOwnProperty(name))
      addToList(name);

  function saveToStorage() {
    localStorage.setItem("notes", JSON.stringify(notes));
  }

  var current = document.querySelector("#currentnote");
  current.value = notes[list.value];

  list.addEventListener("change", function() {
    current.value = notes[list.value];
  });
  current.addEventListener("change", function() {
    notes[list.value] = current.value;
    saveToStorage();
  });

  function addNote() {
    // pop prompt msg window
    var name = prompt("Note name", "");
    if (!name) return;
    if (!notes.hasOwnProperty(name)) {
      notes[name] = "";
      addToList(name);
      saveToStorage();
    }
    list.value = name;
    current.value = notes[name];
  }
</script>

Project: Paint program

Node.js and Server

  • Notes will coming in future.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment