I. Usage of in
- for...in
Enumerates all enumerable properties of an object
-
Detecting DOM/BOM properties
if ("onclick" in elem) { // Element supports onclick } if ("onerror" in window) { // window supports onerror } -
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
-
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 } -
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:
-
No need to care about parameter order when calling, interface becomes more usable
-
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:
-
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
- Common length property usage
Save counter way: arr[arr.length] = value;
Or simpler: arr.push(value);
- 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
-
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 -
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
-
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>$&</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 <code>/(\a+)(\b+)/</code>, was given, <code>p1</code> is the match for <code>\a+</code>, and <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"
No comments yet. Be the first to share your thoughts.