Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- JavaScript Guide
- By: Andrew Gerst
- JavaScript Pitfalls
- http://jsfiddle.net/gerst20051/BfNa8/
- Table of Contents
- - What not to do?
- - for in problem
- - Number methods
- - String trim
- - typeof
- - Array.isArray
- - True or False?
- - Operators
- -- &&
- -- ||
- -- !
- -- Bitwise
- - Labels (Break statement)
- - Switch statement
- - With statement
- - apply form
- - this
- - Closure
- - Closure Conditional
- - later method
- - curry method
- - try-catch
- - Sealer/Unsealer
- - Pseudoclassical Inheritance
- - Prototypal Inheritance
- - New Constructor
- - Functional Inheritance
- - Functional Inheritance (Privacy)
- - Shared Secrets
- - Super Methods
- - Event handlers in a loop
- - The Y Combinator
- - Prototype
- What not to do?
- - Don't make functions in a loop
- -- It can be wasteful because a new function object is created on every iteration.
- -- It can be confusing because the new function closes over the loop's variables, not over their current values.
- -- Anything that doesn't change in every iteration of the loop should be moved outside of the loop.
- The for in problem
- - Functions inherited from a prototype are included in the for in enumeration
- for (name in object) {
- if (Object.prototype.hasOwnProperty.call(object, name) {
- ...
- }
- }
- Number methods
- if (!Number.prototype.trunc) {
- Number.prototype.trunc = function trunc(number) {
- return Math[number >= 0 ? 'floor' : 'ceil'](number);
- }
- }
- String trim
- - String trim method was added in ES5.
- if (typeof String.prototype.trim !== 'function') {
- String.prototype.trim = function () {
- return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/,"$1");
- };
- }
- typeof
- - Returns a string identifying the type of a value.
- - Returns the wrong value for null and not very helpful with arrays.
- type -> typeof
- ----------------------------------
- object -> 'object'
- function -> 'function'
- array -> 'object' // ERROR
- number -> 'number'
- string -> 'string'
- boolean -> 'boolean'
- null -> 'object' // ERROR
- undefined -> 'undefined'
- Object.toType = (function toType(global){
- return function(obj){
- if (obj === global) return "global";
- return ({}).toString.call(obj).match(/\s([a-z|A-Z]+)/)[1].toLowerCase();
- }
- })(this);
- Array.isArray
- alert(Array.isArray([])); // true
- if (typeof Array.isArray !== 'function') {
- Array.isArray = function (value) {
- return Array.prototype.toString.apply(value) === '[object Array]';
- }
- }
- True or False?
- Falsy values
- - false
- - null
- - undefined
- - "" (empty string)
- - 0
- - NaN
- All other values (including all objects) are truthy.
- "0" and "false"
- &&
- - The guard operator, aka logical and
- - If first operand is truthy then return second operand else return first operand.
- - It can be used to avoid null references
- if (a) {
- return a.member;
- } else {
- return a;
- }
- can be written as
- return a && a.member;
- ||
- - The default operator, aka logical or
- - If first operand is truthy then result is first operand else result is second operand.
- - It can be used to fill in default value.
- var last = input || nr_items;
- // If input is truthy, then last is input, otherwise set last to nr_items.
- !
- - Prefix logical not operator
- - If the operand is truthy, the result is false. Otherwise, the result is true.
- - !! produces booleans.
- Bitwise
- & | ^ >> >>> <<
- - The bitwise operators convert the 64-bit floating point operand to a 32-bit signed integer, and turn the result back into 64-bit floating point.
- - Typically used for machine values. JavaScript doesn't have machine values.
- - Don't do multiplication by using shift. Not a win and not faster in JavaScript like it is in C.
- Labels (Break statement)
- - Statements can have labels.
- - Break statements can refer to those labels.
- - Label can be any JavaScript identifier that is not a reserved word.
- loop: for (;;) {
- ...
- if (...) {
- break loop;
- }
- ...
- }
- Switch statement
- - The switch value does not need to be a number. It can be a string.
- - The case values can be expressions.
- - Danger: Cases fall through to the next case unless a disruptive statement like break ends the case.
- switch (expression) {
- case ';':
- case ',':
- case '.':
- punctuation();
- break;
- default:
- noneOfTheAbove();
- }
- With statement
- - Inteded as a convenient short-hand
- - Ambiguous
- - Error-prone
- - Don't use it
- with (o) {
- foo = koda;
- }
- Can do any of these assignments based on composition of o.
- o.foo = koda;
- o.foo = o.koda;
- foo = koda;
- foo = o.koda;
- What it actually does...
- if ('foo' in o) {
- o.foo = 'koda' in o ? o.koda : koda;
- } else {
- foo = 'koda' in o ? o.koda : koda;
- }
- Using with as a proxy...
- var docWrapped = {
- foo: 5
- };
- with ({document: docWrapped}) {
- window.document.write(document.foo); // 5
- }
- Apply form
- functionObject.apply(thisObject, arguments);
- functionObject.call(thisObject, argument);
- - A function's apply or call method allows for calling the function, explicitly specifying thisObject.
- - It can also take an array of parameters or a sequence of parameters.
- Function.prototype.call = function (thisObject) {
- return this.apply(thisObject, Array.prototype.slice.apply(arguments, [1]));
- };
- this
- - this is a bonus parameter. It's value depends on the calling form.
- - this gives methods access to their objects.
- - this is bound at invocation time.
- Invocation form | this
- ---------------------------------------------------------------
- function | the global object (ES3) - undefined (ES5 Strict)
- method | the object
- constructor | the new object
- apply | argument
- Closure
- - A function that "closes over" some local variables is called a closure.
- - Doesn't reallocate memory and array every time function is called.
- - Still has the advantage of referencing its local variables.
- - The function that returns names[n] is assigned to digit_name.
- - This is one of the most important features of JavaScript and makes it one of the most brilliant programming languages in the world.
- var digit_name = (function(){
- var names = ['zero','one','two','three','four','five','six','seven','eight','nine'];
- return function(n){
- return names[n];
- };
- }());
- alert(digit_name(3)); // three
- Closure Conditional
- - Can't tell you which case is optimal in which scenario but it is another option.
- var digit_name = (function(){
- var names;
- return function(n){
- if (!names) {
- names = ['zero','one','two','three','four','five','six','seven','eight','nine'];
- }
- return names[n];
- };
- }());
- later method
- if (typeof Object.prototype.later !== 'function') {
- Object.prototype.later = function (msec, method) {
- var that = this, args = Array.prototype.slice.apply(arguments, [2]);
- if (typeof method === 'string') {
- method = that[method];
- }
- setTimeout(function(){
- method.apply(that, args);
- }, msec);
- return that; // Cascade
- };
- }
- curry method
- - arguments inherits array methods (ES5).
- - Since arguments isn't an array calling the slice method returns an array.
- function curry(func) {
- var args = arguments.slice(1);
- return function(){
- return func.apply(null, args.concat(arguments.slice()));
- };
- }
- var inc = curry(function add(a,b) {
- return a + b;
- }, 1);
- console.log(inc(6)); // 7
- try-catch
- - If you want to ignore an exception don't name the variable e.
- try {
- func(args);
- } catch (ignore) {}
- Sealer/Unsealer
- - Useful for if you have a package and you don't want the mailman to see the package.
- function make_sealer() {
- var boxes = [], values = [];
- return {
- sealer: function (value) {
- var i = boxes.length, box = {};
- boxes[i] = box;
- values[i] = value;
- return box;
- },
- unsealer: function (box) {
- return values[boxes.indexOf(box)];
- }
- };
- }
- Pseudoclassical Inheritance
- function Gizmo(id) {
- this.id = id;
- }
- Gizmo.prototype.toString = function(){
- return "gizmo " + this.id;
- };
- function Hoozit(id) {
- this.id = id;
- }
- Hoozit.prototype = new Gizmo(); // Inherit from Gizmo
- Hoozit.prototype.test = function(id){
- return this.id === id;
- };
- Prototypal Inheritance
- var gizmo = new_constructor(Object, function(id){
- this.id = id;
- }, {
- toString: function(){
- return "gizmo " + this.id;
- }
- });
- var hoozit = new_constructor(gizmo, function(id){
- this.id = id;
- }, {
- test: function(id){
- return this.id === id;
- }
- });
- New Constructor
- function new_constructor(extend, initializer, methods) {
- var func, prototype = Object.create(extend && extend.prototype);
- if (methods) {
- methods.keys().forEach(function(key){
- prototype[key] = methods[keys];
- });
- }
- func = function(){
- var that = Object.create(prototype);
- if (typeof initializer === 'function') {
- initializer.apply(that, arguments);
- }
- return that;
- };
- func.prototype = prototype;
- prototype.constructor = func;
- return func;
- }
- Functional Inheritance
- function gizmo(id){
- return {
- id: id,
- toString: function(){
- return "gizmo " + this.id;
- }
- };
- }
- function hoozit(id){
- var that = gizmo(id);
- that.test = function(testid){
- return testid === this.id;
- };
- return that;
- }
- Functional Inheritance (Privacy)
- - Referring directly to id parameter
- function gizmo(id){
- return {
- toString: function(){
- return "gizmo " + id;
- }
- };
- }
- function hoozit(id){
- var that = gizmo(id);
- that.test = function(testid){
- return testid === id;
- };
- return that;
- }
- Shared Secrets
- - Generally unnecessary but nice knowing it's possible
- function gizmo(id, secret){
- secret = secret || {};
- secret.id = id;
- return {
- toString: function(){
- return "gizmo " + secret.id;
- }
- };
- }
- function hoozit(id){
- var secret = {};
- var that = gizmo(id, secret);
- that.test = function(testid){
- return testid === secret.id;
- };
- return that;
- }
- Super Methods
- - If you find yourself needing super methods you should take a step back there might be a simpler way to think about it.
- function hoozit(id){
- var secret = {};
- var that = gizmo(id, secret);
- var super_toString = that.toString;
- that.test = function(testid){
- return testid === secret.id;
- };
- that.toString = function(){
- return super_toString.apply(that);
- };
- return that;
- }
- Event handlers in a loop
- // WRONG
- - They all alert the same value because div_id keeps changing.
- - They all alert the id of the div that ended the loop.
- for (i ...) {
- div_id = divs[i].id;
- divs[i].onclick = function(){
- alert(div_id);
- };
- }
- // CORRECT
- - Avoids a function within a loop
- function make_handler(div_id){
- return function(){
- alert(div_id);
- }
- }
- for (i ...) {
- div_id = divs[i].id;
- divs[i].onclick = make_handler(div_id);
- }
- Tennent's Principle of Correspondence
- - Would JavaScript still be a useful language without variables? YES
- - Shows that anything you can write with variables you can write with function enclosures instead to do the same thing.
- function factorial(n){ // result: variable
- var result = 1;
- while (n > 1) {
- result *= n;
- n -= 1;
- }
- return result;
- }
- function factorial(n){ // result: parameter
- return (function(result){
- while (n > 1) {
- result *= n;
- n -= 1;
- }
- return result;
- }(1));
- }
- The Y Combinator
- - Suppose we had a language without variables, assignment, and named functions could we still do recursion? YES
- - Incredibly nested functions within functions.
- function y(le){
- return (function(f){
- return f(f);
- }(function(f){
- return le(function(x){
- return f(f)(x);
- });
- }));
- }
- var factorial = y(function(fac){
- return function(n){
- return n <= 2 ? n : n * fac(n - 1);
- };
- });
- var number120 = factorial(f);
- Prototype
- A prototype is an object from which other objects inherit properties. Any object can be a prototype and every object has a prototype by default. Since prototypes are themselves objects, every prototype has a prototype too. There is only one exception, the default object prototype at the top of every prototype chain.
- An object in JavaScript is any unordered collection of key-value pairs. If it’s not a primitive (undefined, null, boolean, number or string) it’s an object.
- The true prototype of an object is held by the internal [[Prototype]] property. ECMA 5 introduces the standard accessor Object.getPrototypeOf(object) which to-date is implemented in Firefox, Safari, Chrome and IE9. In addition all browsers except IE support the non-standard accessor __proto__. Failing that we can ask the object’s constructor for its prototype property.
- When a primitive is asked for it’s prototype it will be coerced to an object.
- // (works in IE<=8 too, due to double-negative)
- false.__proto__ === Boolean(false).__proto__; // true
- It rarely makes sense to set a prototype for one instance and only one instance, since it would be equally efficient just to add properties directly to the instance itself.
- // fails in IE<=8
- var a = {};
- a.__proto__ = Array.prototype;
- a.length; // 0
- JavaScript makes no distinction between constructors and other functions, so every function gets a prototype property. Conversely, anything that is not a function does not have such a property.
- // Function will never be a constructor but it has a prototype property anyway
- Math.max.prototype; // [object Object]
- // Function intended to be a constructor has a prototype too
- var A = function(name){
- this.name = name;
- };
- A.prototype; // [object Object]
- // Math is not a function so no prototype property
- Math.prototype; // null
- A function’s prototype property is the object that will be assigned as the prototype to all instances created when this function is used as a constructor. It’s important to understand that a function’s prototype property has nothing to do with it’s actual prototype.
- // (example fails in IE)
- var A = function(name){
- this.name = name;
- };
- A.prototype == A.__proto__; // false
- A.__proto__ == Function.prototype; // true - A's prototype is set to its constructor's prototype property
- Constructor. this is returned as new object and its internal [[prototype]] property will be set to the constructor's default prototype property.
- var Circle = function(radius){
- this.radius = radius;
- // next line is implicit, added for illustration only
- // this.__proto__ = Circle.prototype;
- };
- Augment Circle's default prototype property thereby augmenting the prototype of each generated instance.
- Circle.prototype.area = function(){
- return Math.PI*this.radius*this.radius;
- }
- // Create two instances of a circle and make each leverage the common prototype.
- var a = new Circle(3), b = new Circle(4);
- a.area().toFixed(2); // 28.27
- b.area().toFixed(2); // 50.27
- If I modify the existing prototype’s property then this is true, because a.__proto__ is a reference to the object defined by A.prototype at the time it was created.
- var A = function(name) {
- this.name = name;
- };
- var a = new A('alpha');
- a.name; // alpha
- A.prototype.x = 23;
- a.x; // 23
- But if I replace the prototype property with a new object, a.__proto__ still references the original object.
- var A = function(name) {
- this.name = name;
- };
- var a = new A('alpha');
- a.name; // alpha
- A.prototype = {x:23};
- a.x; // null
- The default prototype looks like an object with one property, the constructor.
- var A = function(){};
- A.prototype.constructor == A; // true
- var a = new A();
- a.constructor == A; // true (a's constructor property inherited from it's prototype)
- A.prototype.constructor == a.constructor; // true
- The expression a instanceof A will answer true if a’s prototype falls within the same prototype chain as A’s prototype property. This means we can trick instanceof into failing.
- var A = function(){};
- var a = new A();
- a.__proto__ == A.prototype; // true - so instanceof A will return true
- a instanceof A; // true
- // Mess around with a's prototype
- a.__proto__ = Function.prototype;
- // a's prototype no longer in same prototype chain as A's prototype property
- a instanceof A; // false
- Every constructor has a prototype property which it uses to assign prototypes to all instances it generates. This also applies to native constructors too such as Function and String. By extending (not replacing!) this property we get to update the prototype of every instance of the given type.
- String.prototype.times = function(count){
- return count < 1 ? '' : new Array(count + 1).join(this);
- };
- "hello!".times(3); // "hello!hello!hello!";
- "please...".times(6); // "please...please...please...please...please...please..."
- Since every object and every prototype (bar one) has a prototype, we can think of a succession of objects linked together to form a prototype chain. The end of the chain is always the default object’s prototype.
- a.__proto__ = b;
- b.__proto__ = c;
- c.__proto__ = {}; // default object
- {}.__proto__.__proto__; // null
- The prototypical inheritance mechanism is internal and non-explicit. When object a is asked to evaluate property foo, JavaScript walks the prototype chain (starting with object a itself), checking each link in the chain for the presence of property foo. If and when foo is found it is returned, otherwise undefined is returned.
- Prototypical inheritance is not a player when property values are set. a.foo = 'bar' will always be assigned directly to the foo property of a. To assign a property to a prototype you need to address the prototype directly. You can add a property to a previously defined object type by using the prototype property. This defines a property that is shared by all objects of the specified type, rather than by just one instance of the object.
- prototype is a property of a Function object. It is the prototype of objects constructed by that function.
- __proto__ is internal property of an object, pointing to its prototype. By the standard, this property should not be accessible for the programmer (although it in fact is in Firefox).
- So, suppose the following code creating a "point" object.
- function Point(x, y) {
- this.x = x;
- this.y = y;
- }
- var myPoint = new Point();
- Now, Point is a function (a constructor), so it is an object of the type Function. myPoint is an object constructed by the constructor Point(). Therefore, myPoint.__proto__ and Point.prototype are the same object.
- __proto__ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build __proto__ when you create an object with new:
- ( new Foo ).__proto__ === Foo.prototype
- ( new Foo ).prototype === undefined
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement