メインコンテンツへ移動

『JavaScript 言語精粹』学習ノート

無料2015-04-30#JS#JavaScript语言精粹

『JavaScript 言語精粹』の中の精粹

一.in の使い方

  1. for...in

オブジェクトのすべての列挙可能プロパティを列挙

  1. DOM/BOM プロパティの検出

    if ("onclick" in elem) {
        // 要素は onclick をサポート
    }
    if ("onerror" in window) {
        // window は onerror をサポート
    }
    
  2. JS オブジェクトのプロトタイププロパティの検出(hasOwnProperty() 関数と組み合わせ)

    if ("attr" in obj && !obj.hasOwnProperty("attr")) {
        // obj はプロトタイププロパティ attr を持つ
    }
    

注意:プロトタイププロパティは同名のインスタンスプロパティによってシャドウされるため、このような検出はできない:

    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
    

二.||演算子と&&演算子

  • || はデフォルト値の埋め込みに使用可能。例えば:

     var obj = {};
     obj.name = "";  // 注意 JS には偽値の山:+0、-0、0、NaN、null、undefined、""、''、false
     var name = obj.name || "ayqy";
     alert(name);
     
    

P.S. 個人的にはこのものはあまり良くないと思う。元のプロパティ値がどのような形式の偽値でもないことが確定できない限り、デフォルト値が元の値を上書きする

  • && は undefined 値からプロパティを読み取って TypeError を引き起こすのを回避可能。例えば:

     var obj = {};
     //var attr = obj.obj.attr;   // エラー、TypeError、undefined 値からプロパティを読み取れない
     var attr = obj.obj && obj.obj.attr; // obj.obj が存在し偽値でない場合のみその attr を取得
     
    

P.S. 個人的には&&とあまり変わらず、直接 if で検出する方が良い。より明確なロジックフローを示せるだけでなく、より多くの検出条件を追加しやすい

P.S. これら 2 つの使い方は本の第 1 部分で紹介されており、言語特性の展示に偏っている可能性があり、推奨用法ではない

個人的にはこのように使わないことを提案。もちろん、このようなコードを見たらその作用と脆弱性を理解できる必要がある

三.グローバル変数汚染の削減

  1. 「名前空間」を使用。つまり空オブジェクト。実質は作成するグローバル変数を 1 つに減らす。例えば YUI の Y オブジェクト、JQuery の JQuery と$オブジェクト。。例えば:

    var app = {};   // 名前空間:app
    app.Consts = {
        // 子名前空間:定数
        
        URL : {
            // 子子名前空間:URL
        }
    }
    app.Modules = {
        // 子名前空間:モジュール
    }
    app.Data = {
        // 子名前空間:データ
    }
    
  2. IIFE(匿名関数即時実行)を使用。実質はグローバル変数を全く作成せず、0 汚染

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

しかし欠点は明らか。オブジェクトキャッシュと共有を実現できず、クロージャを出ればなくなる

したがって一般的な做法は名前空間と IIFE を組み合わせる。全体を名前空間で組織し、モジュール内部の適切な場所で IIFE を使用

四.4 種類の呼び出しモード

  • メソッド呼び出しモード:obj.fun(); または obj"fun";

  • 関数呼び出しモード:fun(); この時 this は global オブジェクトを指す

  • コンストラクタ呼び出しモード:new Fun(); 新しいオブジェクトの prototype は Fun の prototype を指し、this はこの新しいオブジェクトを指す

  • apply 呼び出しモード:fun.apply(this, arrArgs);

五.arguments オブジェクトについて

arguments オブジェクトは配列オブジェクトではなく、すべての配列メソッドをサポートするわけでもない。ただ少し特殊な普通オブジェクト。特殊なのはその length プロパティ(動的更新)

以下のようにテスト可能:

    function fun(){
        var x = Object.prototype.toString.call(arguments);
        alert(x);
        var arr = [];
        alert(Object.prototype.toString.call(arr));
    };
    
    fun();
    // IE8:[object Object] と [object Array]、Chrome:[object Arguments] と [object Array]
    // 違っても問題ない、反正どちらも Array ではない
    

小さなテクニック:slice メソッドで arguments オブジェクトを配列に変換可能。例えば:

    function fun(){
        //arguments.sort();   // エラー、sort() 関数をサポートしない
        var arr = Array.prototype.slice.call(arguments);    // 変換
        arr.sort(); // エラーにならない、変換成功を示す
        alert(arr);
    };
    
    fun(3, 2);  // 2, 3
    

注意:slice だけがこの奇効を持つ。concat にはない。無参の slice と無参の concat は配列に対する効果は同じだが(どちらもコピー)

六.カスケード(チェーン呼び出し)の実装方式

戻り値のない関数に this を返させる。したがってチェーン呼��出しをサポート。例えば JQuery 中の:

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

七.コンストラクタ呼び出し方式が不適切だとスコープ汚染を引き起こす

new 演算子を使わず、直接コンストラクタを呼び出すと、例えば Fun(); この時 this は global プロパティ、つまりブラウザ環境下の window オブジェクトを指し、グローバルスコープを汚染する可能性がある

八.仮引数リストを単一オブジェクトに簡素化

例えば:

function fun(height, width, margin, padding){   // パラメータが長すぎる、順序が覚えられない
    // do something
}

/*
 * @param {number} arg.height 高度
 * @param {number} arg.width 宽度 
 * @param {number} arg.margin 留白
 * @param {number} arg.padding 补白
 */
function fun(arg){  // パラメータ順序を覚える必要がない
    // do something
}

利点は以下の通り:

  1. 呼び出し時にパラメータ順序を気にする必要がなくなり、インターフェースがより使いやすくなる

  2. 直接 JSON オブジェクトを渡せる

欠点:具体的なパラメータが必要かを説明する注釈をたくさん書く必要がある。arg だけでは全くわからない

九.偽造防止オブジェクト(永続的オブジェクト)

偽造防止オブジェクトのプロパティは置換または削除可能だが、該オブジェクトの完全性は損なわれない

永続的オブジェクトとも呼ばれる。1 つの永続的オブジェクトは 1 つの単純機能関数の集合

P.S. 偽造防止オブジェクトの概念は本の [関数化] 部分に出現。現在はまだ関数化が表現したい意味を完全に理解できない。自分を太らせてから再看

関数化部分は [黯羽轻扬:『JavaScript 言語精粹』之関数化](http://ayqy.net/blog/《JavaScript 言語精粹》之関数化/) を参照。内容はすでに補充完了

十.配列

本質はキーバリューペア。つまりオブジェクト。したがってアクセス速度上の優位性はない

違いは Array.prototype が多くの配列操作関数を提供すること。さらに特殊な length プロパティがある

注意:

  1. 配列が実際に占有するメモリ空間

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

この時 arr が指す値は 100 個の要素の空間を占有していない。上記の結果は以下に相当:

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

2. length プロパティは書き込み可能

arr.length = n; でインデックス値 >= n のすべての要素を削除可能

  1. 一般的な length プロパティの使い方

カウンタ方式を省略:arr[arr.length] = value;

またはより簡単:arr.push(value);

  1. 配列タイプ検出

typeof arr は "object" を返し、instanceof 演算子は跨 frame 時に無効になるため、配列タイプの検出はあまり容易ではない

本には超面倒な方式が記載:

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

実は著者が本を書いた時にはまだ存在しなかった簡単な方法がある:

Object.prototype.toString.call(value) === '[object Array/Function...]' でタイプチェック可能。ネイティブオブジェクトとカスタムオブジェクトを区別するのにも使用可能。例えば:

    [object JSON]   // ネイティブ JSON オブジェクト
    [object Object] // カスタム JSON オブジェクト
    

タイプ検出に関する詳細情報は [黯羽轻扬:JS 学習ノート 11_高級テクニック](http://ayqy.net/blog/JS 学習ノート 11_高級テクニック/) を参照

十一.正規表現

JS の正規表現機能は完全ではないが、基本的に十分。例えば 3 種類のモードのみサポート:g/i/m 〜 グローバルモード/大文字小文字無視モード/複数行モード

サポートする特殊メタ文字(\d、\s など)も比較的少ないが、非キャプチャ型グループと正規表現ルックアヘッドをサポートするのは非常に良い

したがって JS で正規表現を使用するにはより多くのテストが必要。正規表現に関する詳細情報は 黯羽轻扬:正規表現学習ノート を参照

もう 1 つあまり使用しない点:RegExp オブジェクトのプロパティ

  • global:g モードをオンにしたか

  • ignoreCase:i モードをオンにしたかを返す

  • lastIndex:次回 exec マッチの開始位置を返す

  • multiline:複数行モードをオンにしたかを返す

  • source:正規表現文字列を返す

特に注意が必要:リテラル方式で作成した RegExp オブジェクト場合によって同じインスタンスを参照。例えば:

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

旧バージョンブラウザでは最後 2 行は 8, 8 と true を出力。正規表現リテラルのインスタンス共有は ES3 の規定とのこと。本机テストでは IE8 は問題存在しないが、理論的には IE6 はこの問題が存在(IE6 が出た時、ES5 はまだ出ていない)

十二. ネイティブオブジェクト操作関数

P.S. ここでは特に注意が必要な点のみ紹介。完全な各種操作関数は [黯羽轻扬:JS 学習ノート 1_基礎と常識](http://ayqy.net/blog/JS 学習ノート 1_基礎と常識/) を参照

###1.配列操作関数

  1. arr1.push(arr2); は arr2 を単一要素として挿入。例えば:

    var arr1 = [1];
    var arr2 = [2, 3];
    arr1.push(arr2);
    alert(arr1[2]); // undefined
    alert(arr1[1][1]);  // 3
    
  2. arr.shift() は arr.pop() より非常に遅い。先頭要素を削除するにはすべての要素のインデックスを更新する必要があるが、末尾要素を削除するには不要

  3. arr.unshift()。配列先頭に要素を挿入。IE6 は undefined を返す。本来は新しい配列の長さを返すべき

###2.オブジェクト操作関数

obj.hasOwnProperty("hasOwnProperty") は false を返す

###3.正規表現オブジェクト操作関数

regex.exec(str) 関数の機能が最も強大。もちろん、実行速度も最も遅い

regex.test(str) が最も簡単で最も速い。注意:g モードをオンにしない。純粋な無駄(test はマッチ/マッチしないのみを返す。1 回のスキャンで十分)

###4.文字列操作関数

  • str.replace(regex, fun); は非常に使いやすい関数。regex でターゲット部分をマッチでき、fun でマッチ部分をさらに処理可能

特に注意:regex が g モードをオンにしていない場合、最初のマッチ部分のみ置換。全文字列置換ではない

fun の各パラメータの意味は以下の通り:

<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 部分中の$は特殊な意味を持つ:

    • $$:$を表す。置換部分中に$がある場合$でエスケープする必要

    • $&:マッチしたテキスト全体を表す

    • $n:例えば$1, $2, $3... はキャプチャしたテキストを表す

    • $`:マッチ部分より前のテキストを表す

    • $':マッチ部分より後のテキストを表す

より詳細な例は W3School:JavaScript replace() メソッド を参照

  • str.split(regex); には特例が存在。特に注意が必要:

     var str = "a & b & c";    // & は区切り文字
     var arr = str.split(/\s*(&)\s*/);   // キャプチャ含む
     var arr2 = str.split(/\s*&\s*/);    // キャプチャ含まず
     var arr3 = str.split(/\s*(?:&)\s*/);    // 非キャプチャ含む
     alert(arr); // [a, &, b, &, c]
     alert(arr2);    // [a, b, c]
     alert(arr3);    // [a, b, c]
     
    

    うっかりキャプチャ型グループを含む正規表現を使用すると、結果が予想と異なる可能性

十三.糟粕

P.S. ここでは本の内容をそのまま複製するつもりはなく、筆者が同意する最も最悪な部分のみを挙げる

  • 自動セミコロン挿入。確かに良くない。典型的な ASI エラー:

     function fun(){
         return
         {
             attr : 1;
         }
     }
     
     alert(fun().attr);  // TypeError: Cannot read property 'attr' of undefined
     
    
  • typeof。使いにくい。これは設計上のミス。歴史的原因

  • paseInt() と 8 進数。非常に隠れたエラー

     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
     
    

0 で始まる数値は 8 進数と見なされ、2 番目のパラメータを省略した parseInt() は 08/09 を 8 進数として解析するため、結果は 0

時間日付を処理するのは解析エラーを引き起こしやすいため、最好parseInt() の 2 番目のパラメータを省略しない

P.S. Chrome 中は正常。おそらくある ES バージョンで parseInt() に変更があり、Chrome が実装した。したがって糟粕は永遠に存在するわけではない。もちろん、糟粕かも programmer がどう使うかによる。。

  • Unicode。JS の Unicode サポートは良くない。歴史的原因だが、サポートしないのはサポートしない

P.S. JS が出た時 Unicode 自身も 100 万個の文字になるとは思っていなかった。JS の文字は 16 ビットで、最初の 65536 個の Unicode 文字に対応可能。Unicode は拡張のため、残りの各文字を 1 対の文字で表す。しかし JS はこのような文字を 2 つの文字と見なす。したがって。。

十四.鶏肋

P.S. 同上。筆者が同意するもののみを列挙

  • continue パフォーマンスが低い。if で消除可能

  • ビット演算パフォーマンスが低い。(他の本はこの点に言及していない)double -> int -> ビット演算 -> double と折腾する必要がある

  • new は結局使うべきか使わないべきか。使うとスコープを汚染する可能性

  • void 演算子。単項演算。undefined を返す。したがって IIFE の実装に使用可能。例えば:

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

著者は void を使わないことを提案。何をするものか知っている人がほとんどいないため

十五.JSON

整数の先頭は 0 にできない。ダグラス(JSON を発明したおじいさん)本人が言った。数値は整数、実数または科学計数可能。しかし先頭は 0 にできない。8 進数の曖昧さを避けるため

テストコードは以下の通り:

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

var json = '{"number" : 09}';   // 先頭 0 に注意
var obj = JSON.parse(json);
alert(obj.number);  // エラー
// Chrome:SyntaxError: Unexpected number
// IE8:構文エラー
// FF:SyntaxError: JSON.parse: expected ',' or '}' after property value in object

参考資料:

  • 『JavaScript 言語精粹』

コメント

コメントはまだありません

コメントを書く