Skip to main content

Study Notes on "JavaScript: The Good Parts"

Free2015-04-30#JS#JavaScript语言精粹

The good parts from "JavaScript: The Good Parts"

I. Usage of in

  1. for...in

Enumerates all enumerable properties of an object

  1. Detecting DOM/BOM properties

    if ("onclick" in elem) {
        // Element supports onclick
    }
    if ("onerror" in window) {
        // window supports onerror
    }
    
  2. Detecting prototype properties of JS objects (combined with hasOwnProperty() function)

    if ("attr" in obj && !obj.hasOwnProperty("attr")) {
        // obj has prototype property attr
    }
    

Note: Prototype properties will be shadowed by instance properties with the same name, so cannot detect such cases:

    function Super(){
        this.attr = 1;
    }
    function Sub(){
        this.attr = 2;
    }
    Sub.prototype = new Super();
    var obj = new Sub();
    alert(("attr" in obj && !obj.hasOwnProperty("attr")));  /// false
    

II. || Operator and && Operator

  • || can be used to fill default values, for example:

     var obj = {};
     obj.name = "";  // Note JS has a bunch of falsy values: +0, -0, 0, NaN, null, undefined, "", '', false
     var name = obj.name || "ayqy";
     alert(name);
     
    

P.S. Personally I think this is not very useful, unless you can be sure the original property value cannot be any form of falsy value, otherwise the default value will overwrite the original value

  • && can be used to avoid TypeError from reading properties of undefined values, for example:

     var obj = {};
     //var attr = obj.obj.attr;   // Error, TypeError, cannot read property from undefined
     var attr = obj.obj && obj.obj.attr; // Only takes attr if obj.obj exists and is not falsy
     
    

P.S. Personally I think it's similar to &&, might as well use if detection directly, not only can show clearer logic flow, but also easier to add more detection conditions

P.S. These two usages are introduced in the first part of the book, may be more inclined to demonstrate language features, not recommended usage

Personally suggest not to use this way, of course, need to understand the function and loopholes when seeing such code

III. Reducing Global Variable Pollution

  1. Use "namespace", i.e., empty object, essentially reduces created global variables to 1, such as YUI's Y object, JQuery's JQuery and $ objects. For example:

    var app = {};   // Namespace: app
    app.Consts = {
        // Sub-namespace: constants
        
        URL : {
            // Sub-sub-namespace: URL
        }
    }
    app.Modules = {
        // Sub-namespace: modules
    }
    app.Data = {
        // Sub-namespace: data
    }
    
  2. Use IIFE (Immediately Invoked Function Expression), essentially creates no global variables at all, 0 pollution

    (function(){
        // Module1
    })();
    (function(){
        // Module2
    })();
    

But the disadvantage is obvious, cannot achieve object caching and sharing, gone once out of closure

So the general practice is to combine namespace and IIFE, organize overall with namespace, use IIFE in appropriate places inside modules

IV. 4 Invocation Patterns

  • Method invocation pattern: obj.fun(); or obj"fun";

  • Function invocation pattern: fun(); at this time this points to global object

  • Constructor invocation pattern: new Fun(); new object's prototype points to Fun's prototype, this points to this new object

  • Apply invocation pattern: fun.apply(this, arrArgs);

V. About arguments Object

arguments object is not an array object, nor supports all array methods, just a somewhat special ordinary object, special in its length property (dynamically updated)

Can do the following test:

    function fun(){
        var x = Object.prototype.toString.call(arguments);
        alert(x);
        var arr = [];
        alert(Object.prototype.toString.call(arr));
    };
    
    fun();
    // IE8: [object Object] and [object Array], Chrome: [object Arguments] and [object Array]
    // Different doesn't matter, anyway neither is Array
    

A small trick: can use slice method to convert arguments object to array, for example:

    function fun(){
        //arguments.sort();   // Error, doesn't support sort() function
        var arr = Array.prototype.slice.call(arguments);    // Convert
        arr.sort(); // No error, indicates conversion successful
        alert(arr);
    };
    
    fun(3, 2);  // 2, 3
    

Note: Only slice has this magical effect, concat doesn't, although parameterless slice and parameterless concat have same effect for arrays (both copy a copy)

VI. Implementation of Cascading (Chained Calls)

Let functions without return values return this, so supports chained calls, such as in JQuery:

$("#adBlock").removeClass("active").hide();

VII. Improper Constructor Invocation Can Cause Scope Pollution

If not using new operator, directly calling constructor, such as Fun(); at this time this points to global property, i.e., window object in browser environment, may pollute global scope

VIII. Simplifying Parameter List to Single Object

For example:

function fun(height, width, margin, padding){   // Parameters too long, can't remember order
    // do something
}

/*
 * @param {number} arg.height Height
 * @param {number} arg.width Width 
 * @param {number} arg.margin Margin
 * @param {number} arg.padding Padding
 */
function fun(arg){  // No need to remember parameter order
    // do something
}

Benefits are as follows:

  1. No need to care about parameter order when calling, interface becomes more usable

  2. Can directly pass a JSON object in

Disadvantage: Need to write a bunch of comments explaining which parameters are needed, because can't tell from a single arg

IX. Pseudopackages (Persistent Objects)

Properties of pseudopackages can be replaced or deleted, but the object's integrity is not compromised

Also called persistent objects, a persistent object is a collection of simple functional functions

P.S. Concept of pseudopackages appears in the [Functional] part of the book, currently cannot fully understand what functional wants to express, wait until fattened up before looking again

For functional part please see [Ayqy: "JavaScript: The Good Parts" Functional](http://ayqy.net/blog/《JavaScript 语言精粹》之函数化/), content has been completed

X. Arrays

Essentially key-value pairs, i.e., objects, so no advantage in access speed

Difference is Array.prototype provides many array operation functions, plus special length property

Note:

  1. Array's actual occupied memory space

    var arr = [1];
    arr[99] = 100;
    

At this time value pointed by arr doesn't occupy space for 100 elements, because above result is equivalent to:

    var arr = {
        "0" : 1,
        "99" : 100
    };

2. length property is writable

Can use arr.length = n; to delete all elements with index >= n

  1. Common length property usage

Save counter way: arr[arr.length] = value;

Or simpler: arr.push(value);

  1. Array type detection

Because typeof arr returns "object", and instanceof operator fails across frames, so detecting array type is not easy

Book gives a super troublesome way:

    function isArray(value){
        return value &&
            typeof value === "object" &&
            typeof value.length === "number" &&
            typeof value.splice === "function" &&
            !(value.propertyIsEnumerable("length"));
    }
    

Actually there's a simple method that didn't exist when author wrote the book:

Can use Object.prototype.toString.call(value) === '[object Array/Function...]' for type checking, can also distinguish native objects and custom objects, for example:

    [object JSON]   // Native JSON object
    [object Object] // Custom JSON object
    

For more information about type detection please see [Ayqy: JS Study Notes 11_Advanced Techniques](http://ayqy.net/blog/JS 学习笔记 11_高级技巧/)

XI. Regular Expressions

JS regular expression functionality is not complete, but basically sufficient, for example only supports 3 modes: g/i/m ~ global mode/ignore case mode/multi-line mode

And supported special metacharacters (\d, \s, etc.) are relatively few, but fortunately supports non-capturing groups and regex lookahead, still quite good

So need to do more testing when using regular expressions in JS, for more information about regular expressions please see Ayqy: Regular Expression Study Notes

There's also an infrequently used point: RegExp object properties

  • global: Whether g mode is on

  • ignoreCase: Returns whether i mode is on

  • lastIndex: Returns starting position for next exec match

  • multiline: Returns whether multi-line mode is on

  • source: Returns regular expression string

Need special attention: RegExp objects created with literal way may reference same instance, for example:

var regex1 = /abc/g;
var regex2 = /abc/g;
var str = "aaa\r\nabc\r\naaa";

alert([regex1.lastIndex, regex2.lastIndex]);    // 0, 0
regex1.test(str);
alert([regex1.lastIndex, regex2.lastIndex]);    // 8, 0
alert(regex1 === regex2);   // false

Old version browsers will output 8, 8 and true for last two lines,据说 regex literal sharing instances is ES3 specification, local test found IE8 doesn't have problem, but theoretically IE6 has this problem (when IE6 came out, ES5 hadn't come out yet)

XII. Native Object Operation Functions

P.S. Here only introduce points needing special attention, for complete various operation functions please see [Ayqy: JS Study Notes 1_Basics and Common Sense](http://ayqy.net/blog/JS 学习笔记 1_基础与常识/)

1. Array Operation Functions

  1. arr1.push(arr2); will insert arr2 as single element, for example:

    var arr1 = [1];
    var arr2 = [2, 3];
    arr1.push(arr2);
    alert(arr1[2]); // undefined
    alert(arr1[1][1]);  // 3
    
  2. arr.shift() is much slower than arr.pop(), because deleting first element needs to update all element indices, while deleting last element doesn't need to

  3. arr.unshift(), insert element at array head, IE6 returns undefined, originally should return new array length

2. Object Operation Functions

obj.hasOwnProperty("hasOwnProperty") returns false

3. Regular Expression Object Operation Functions

regex.exec(str) function is most powerful, of course, execution speed is also slowest

regex.test(str) is simplest, fastest, note: don't turn on g mode, because purely waste (test only returns match/no match, one scan is enough)

4. String Operation Functions

  • str.replace(regex, fun); is a very useful function, can use regex to match target parts, can also use fun to further process matched parts

Special attention: If regex doesn't have g mode on, then only replaces first matched part, not whole string replacement

Meanings of fun's various parameters are as follows:

<table class="fullwidth-table"><tbody><tr><td class="header">Possible name</td><td class="header">Supplied value</td></tr><tr><td><code>match</code></td><td>The matched substring. (Corresponds to <code>$&amp;</code> above.)</td></tr><tr><td><code>p1, p2, ...</code></td><td>The <em>n</em>th parenthesized submatch string, provided the first argument to <code>replace()</code> was a <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp" title="The RegExp constructor creates a regular expression object for matching text with a pattern."><code>RegExp</code></a> object. (Corresponds to <code>$1</code>, <code>$2</code>, etc. above.) For example, if&nbsp;<code>/(\a+)(\b+)/</code>, was given, <code>p1</code> is the match for <code>\a+</code>, and&nbsp;<code>p2</code> for <code>\b+</code>.</td></tr><tr><td><code>offset</code></td><td>The offset of the matched substring within the total string being examined. (For example, if the total string was <code>'abcd'</code>, and the matched substring was <code>'bc'</code>, then this argument will be 1.)</td></tr><tr><td><code>string</code></td><td>The total string being examined.</td></tr></tbody></table>
  • str.replace(regex, replacement); replacement part's $ has special meanings:

    • $$: Represents $, if $ in replacement part needs to be escaped with $

    • $&: Represents entire matched text

    • $n: For example $1, $2, $3... represents captured text

    • $`: Represents text before matched part

    • $': Represents text after matched part

For more detailed examples please see W3School: JavaScript replace() Method

  • str.split(regex); has a special case, need special attention:

     var str = "a & b & c";    // & is delimiter
     var arr = str.split(/\s*(&)\s*/);   // With capture
     var arr2 = str.split(/\s*&\s*/);    // Without capture
     var arr3 = str.split(/\s*(?:&)\s*/);    // With non-capture
     alert(arr); // [a, &, b, &, c]
     alert(arr2);    // [a, b, c]
     alert(arr3);    // [a, b, c]
     
    

    If accidentally use regex with capturing groups, result may differ from expected

XIII. Bad Parts

P.S. Here don't intend to copy book content verbatim, only give worst parts author agrees with

  • Automatic semicolon insertion. Indeed not good, such as typical ASI error:

     function fun(){
         return
         {
             attr : 1;
         }
     }
     
     alert(fun().attr);  // TypeError: Cannot read property 'attr' of undefined
     
    
  • typeof. Not useful, this is design mistake, historical reason

  • parseInt() and octal. A very hidden error:

     var year = "2015";
     var month = "08";
     var day = "09";
     
     alert(parseInt(year));  // IE8: 2015 Chrome: 2015
     alert(parseInt(month)); // IE8: 0    Chrome: 8
     alert(parseInt(day));   // IE8: 0    Chrome: 9
     
    

Because numeric values starting with 0 are considered octal, parseInt() omitting second parameter will parse 08/09 as octal, so result is 0

Too easy to cause parsing errors when processing time dates, so best not to omit parseInt()'s second parameter

P.S. Chrome is normal now, maybe some ES version made changes to parseInt(), Chrome made implementation. So bad parts don't exist forever, of course, whether it's bad part also depends on how programmer uses it..

  • Unicode. JS support for Unicode is not good, although also historical reason, but not supporting is not supporting

P.S. When JS came out Unicode didn't expect to have 1 million characters, JS characters are 16-bit, can correspond to first 65536 Unicode characters, and Unicode for expansion, represents each remaining character with a pair of characters, but JS treats such characters as two characters, so..

XIV. Chicken Ribs (Useless)

P.S. Same as above, only list what author agrees with

  • continue has low performance. Can eliminate with if

  • Bitwise operation has low performance. (Other books don't mention this point) Because need to fuss: double -> int -> do bitwise operation -> double

  • Whether new should be used or not. If used may pollute scope

  • void operator. Unary operation, returns undefined, so can be used to implement IIFE, for example:

     void function(){
         // do something
     }();
     
    

Author suggests not to use void, because not many people know what it does

XV. JSON

Integer's first digit cannot be 0, Douglas (the guy who invented JSON) himself said, numeric values can be integer, real number or scientific notation, but first digit cannot be 0, to avoid octal ambiguity

Test code is as follows:

var json = '{"number" : 9}';
var obj = JSON.parse(json);
alert(obj.number);  // 9

var json = '{"number" : 09}';   // Note leading 0
var obj = JSON.parse(json);
alert(obj.number);  // Error
// Chrome: SyntaxError: Unexpected number
// IE8: Syntax error
// FF: SyntaxError: JSON.parse: expected ',' or '}' after property value in object

Reference Materials:

  • "JavaScript: The Good Parts"

Comments

No comments yet. Be the first to share your thoughts.

Leave a comment