JavaScript学習--Item 17サイクルとprototype最後のいくつかの小さなtips

34425 ワード

1.優先的にObjectタイプではなく配列を使用して順序のある集合を表す
ECMAScript規格では、JavaScriptのObjectタイプにおける属性の格納順序は規定されていません.
でもforを使ってるinループがObject中の属性を遍歴する場合,確かにある順序に依存する必要がある.ECMAScriptはこの順序を明確に規範化していないだけに、JavaScript実行エンジンごとに独自の特徴に基づいて実現できるので、異なる実行環境ではforを保証することはできない.inサイクルの挙動は一致した.
たとえば、次のコードは、reportメソッドを呼び出すときの結果が不確定です.
function report(highScores) {  
    var result = "";  
    var i = 1;  
    for (var name in highScores) { // unpredictable order 
        result += i + ". " + name + ": " +  
        highScores[name] + "
"
; i++; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // ?

実行結果がデータの順序に確立されていることを保証する必要がある場合は、Objectタイプを直接使用するのではなく、配列タイプを優先的に使用してデータを表します.また、forの使用もできるだけ避ける.Inループで、明示的なforループを使用します.
function report(highScores) {  
    var result = "";  
    for (var i = 0, n = highScores.length; i < n; i++) {  
        var score = highScores[i];  
        result += (i + 1) + ". " +  
        score.name + ": " + score.points + "
"
; } return result; } report([{ name: "Hank", points: 1110100 }, { name: "Steve", points: 1064500 }, { name: "Billy", points: 1050200 }]); // "1. Hank: 1110100 2. Steve: 1064500 3. Billy: 1050200
"

順序に特に依存するもう1つの挙動は浮動小数点数の計算である.
var ratings = {  
    "Good Will Hunting": 0.8,  
    "Mystic River": 0.7,  
    "21": 0.6,  
    "Doubt": 0.9  
};  

Item 2では,(0.1+0.2)+0.3の結果と,0.1+(0.2+0.3)の結果がそれぞれ0.600000000001と0.6であることを,浮動小数点数の加算動作が交換則を満たすことさえできなかったと述べた.
したがって、浮動小数点数の算術操作では、任意の順序を使用することはできません.
var total = 0, count = 0;  
for (var key in ratings) { // unpredictable order 
    total += ratings[key];  
    count++;  
}  
total /= count;  
total; // ? 

for..inの遍歴順序が異なる場合、最後に得られたtotal結果も異なり、以下は2つの計算順序とそれに対応する結果である.
(0.8 + 0.7 + 0.6 +0.9) / 4 // 0.75
(0.6 + 0.8 + 0.7 +0.9) / 4 // 0.7499999999999999

もちろん、浮動小数点数の計算という問題については、上記の浮動小数点数をまず10倍に拡大して整数データにし、計算が終わったら10倍に縮小するなど、整数数を使用して表す解決策があります.
(8+ 7 + 6 + 9) / 4 / 10    // 0.75
(6+ 8 + 7 + 9) / 4 / 10    // 0.75

2、絶対にObjectに行かないでください.prototypeに列挙可能な属性を追加
もしあなたのコードがforに依存しているなら..inオブジェクトタイプの属性を巡回する場合は、Object.prototypeに列挙可能な属性を追加します.
しかし、JavaScriptの実行環境を強化するには、Objectが必要になることが多い.prototypeオブジェクトに新しいプロパティまたはメソッドを追加します.たとえば、オブジェクト内のすべてのプロパティ名を取得する方法を追加できます.
Object.prototype.allKeys = function() {  
    var result = [];  
    for (var key in this) {  
        result.push(key);  
    }  
    return result;  
};  

しかし、結果は次のようになりました.
({ a: 1, b: 2, c: 3}).allKeys(); // ["allKeys", "a", "b","c"]

1つの実行可能な解決策はObjectではなく関数を使用することである.prototypeで新しいメソッドを定義します.
function allKeys(obj) {  
    var result = [];  
    for (var key in obj) {  
        result.push(key);  
    }  
    return result;  
}  

しかし、もしあなたがObjectに行く必要があるなら.prototypeに新しい属性を追加し、forにこの属性を追加しないでください.inサイクル中に遍歴すると、ES 5環境が提供するObjectを利用することができる.defineProjectメソッド:
Object.defineProperty(Object.prototype, "allKeys", {  
    value: function() {  
        var result = [];  
        for (var key in this) {  
            result.push(key);  
        }  
        return result;  
    },  
    writable: true,  
    enumerable: false,  
    configurable: true  
});  

以上のコードの重要な部分はenumerableプロパティをfalseに設定することです.これで、for..inループではこの属性を巡回できません.
3、配列遍歴の場合、forではなくforループを優先的に使用します.inサイクル
前のItemでこの問題について話しましたが、次のコードについて、最後の平均数がどれくらいなのかわかりますか?
var scores = [98, 74, 85, 77, 93, 100, 89];  
var total = 0;  
for (var score in scores) {  
    total += score;  
}  
var mean = total / scores.length;  
mean; // ? 

計算すると、最後の結果は88になるはずです.
でも忘れないでfor.inサイクルでは,valueではなくkeyが遍歴され,配列についても同様である.だから上記for..inサイクルにおけるscoreは、所望の98,74などの一連の値ではなく、0,1などの一連のインデックスである.
最後の結果は(0+1+...+6)/7=21だと思うかもしれません
しかし、この答えも間違っています.もう一つのポイントは、for..inループのkeyのタイプは常に文字列タイプであるため、ここの+オペレータは実際に文字列の結合操作を実行します.
最後に得られたtotalは、実際には文字列00123456である.この文字列を数値タイプに変換した値は123456で、要素の個数7で除算すると、最後の結果が得られます:1763.571428571428
したがって,配列遍歴には標準的なforループを用いるのが望ましい.
4、ループではなくループメソッドを優先的に使用
ループを使用する場合、DRY(Don’t Repeat Yourself)の原則に違反しやすい.これは、通常、コピー・ペーストの方法を選択して、セグメントのループ文を手書きで書くことを避けるためです.しかし、このようにしてコードに大量の重複コードが現れ、開発者も意味なく「重複造輪」している.さらに重要なのは、コピー・ペースト時に、開始インデックス値、終了判断条件など、ループ内の詳細を無視しやすいことです.
たとえば、nが集合オブジェクトの長さであると仮定すると、以下のforループにこの問題があります.
for (var i = 0; i <= n; i++) { ... }
//       ,   i < n
for (var i = 1; i < n; i++) { ... }
//       ,   i = 0
for (var i = n; i >= 0; i--) { ... }
//       ,   i = n - 1
for (var i = n - 1; i > 0; i--) { ... }
//       ,   i >= 0

ループのいくつかの詳細処理でエラーが発生しやすいことがわかります.JavaScriptが提供する閉パッケージ(Item 11を参照)を使用すると、ループの詳細を再利用のためにカプセル化できます.実際、ES 5は、この問題を処理するためのいくつかの方法を提供している.その中のArray.prototype.forEachは最も簡単なものです.これを利用して、ループをこのように書くことができます.
//   for  
for (var i = 0, n = players.length; i < n; i++) {
    players[i].score++;
}

//   forEach
players.forEach(function(p) {
    p.score++;
});

集合オブジェクトの遍歴に加えて、元の集合の各要素を操作して新しい集合を得るのが一般的なモードです.forEachメソッドを使用して、次のように実現することもできます.
//   for  
var trimmed = [];
for (var i = 0, n = input.length; i < n; i++) {
    trimmed.push(input[i].trim());
}

//   forEach
var trimmed = [];
input.forEach(function(s) {
    trimmed.push(s.trim());
});

しかし、ある集合を別の集合に変換するパターンが一般的であるため、ES 5はArray.prototype.mapメソッドは、コードをより簡単で優雅にするために使用されます.
var trimmed = input.map(function(s) {
    return s.trim();
});

また,ある条件に従って集合をフィルタリングし,元の集合のサブセットを得る一般的なモードもある.ES 5ではArrayが提供されている.prototype.フィルタはこのモードを実現します.このメソッドは、trueまたはfalseを返す関数であるPredicateをパラメータとして受け入れます.trueを返すと、要素が新しいセットに保持されることを意味します.falseを返すと、要素が新しいセットに表示されないことを意味します.例えば、以下のコードを使用して、商品の価格をフィルタリングし、[min,max]区間の商品のみを保持します.
listings.filter(function(listing) {
    return listing.price >= min && listing.price <= max;
});

もちろん、上記の方法はES 5をサポートする環境で利用可能である.他の環境では、2つの選択肢があります.1.underscoreやlodashなどのサードパーティ製ライブラリを使用すると、オブジェクトとコレクションを操作するための一般的な方法がかなり提供されます.2.必要に応じて自分で定義する.
たとえば、ある条件に基づいて集合の前の要素を取得する方法を定義します.
function takeWhile(a, pred) {
    var result = [];
    for (var i = 0, n = a.length; i < n; i++) {
        if (!pred(a[i], i)) {
            break;
        }
        result[i] = a[i];
    }
    return result;
}

var prefix = takeWhile([1, 2, 4, 8, 16, 32], function(n) {
    return n < 10;
}); // [1, 2, 4, 8]

この方法をよりよく再利用するためにArray.に定義することができる.prototypeオブジェクトでは、特定の影響はItem 42を参照することができる.
Array.prototype.takeWhile = function(pred) {
    var result = [];
    for (var i = 0, n = this.length; i < n; i++) {
        if (!pred(this[i], i)) {
            break;
        }
        result[i] = this[i];
    }
    return result;  
};

var prefix = [1, 2, 4, 8, 16, 32].takeWhile(function(n) {
    return n < 10;
}); // [1, 2, 4, 8]

ループ関数を使用するよりもループを使用する方が良いのは、breakとcontinueを使用する必要がある場合だけです.たとえば,forEachを用いて上記のtakeWhileメソッドを実装する場合に問題があり,predicateを満たさない場合にはどのように実装すればよいのか.
function takeWhile(a, pred) {
    var result = [];
    a.forEach(function(x, i) {
        if (!pred(x)) {
            // ?
        }
        result[i] = x;
    });
    return result;
}

内部の異常を使用して判断することができますが、同じように不器用で低効率です.
function takeWhile(a, pred) {
    var result = [];
    var earlyExit = {}; // unique value signaling loop break
    try {
        a.forEach(function(x, i) {
            if (!pred(x)) {
                throw earlyExit;
            }
            result[i] = x;
        });
    } catch (e) {
        if (e !== earlyExit) { // only catch earlyExit
            throw e;
        }
    }
    return result;
}

しかしforEachを使用すると、コードは以前よりも冗長になります.これは明らかに問題がある.この問題に対して、ES 5は、以下に示すように、早期終了のループを処理するためのsomeおよびevery法を提供する.
[1, 10, 100].some(function(x) { return x > 5; }); // true
[1, 10, 100].some(function(x) { return x < 0; }); // false

[1, 2, 3, 4, 5].every(function(x) { return x > 0; }); // true
[1, 2, 3, 4, 5].every(function(x) { return x < 3; }); // false

この2つの方法はいずれも短絡方法(Short-circuiting):いずれかの要素がsome方法のpredicateでtrueを返す限り、someは戻ります.everyメソッドのpredicateでfalseを返す要素はいずれかのみであり、everyメソッドもfalseを返します.
したがってtakeWhileは次のように実現できます.
function takeWhile(a, pred) {
    var result = [];
    a.every(function(x, i) {
        if (!pred(x)) {
            return false; // break
        }
        result[i] = x;
        return true; // continue
    });
    return result;
}

実際、これが関数式プログラミングの考え方です.関数プログラミングでは、明示的なforループやwhileループを見ることはめったにありません.サイクルの細部はよくカプセル化されています.
5、まとめ
  • forを使用中..inループの場合、遍歴の順序に依存しないでください.
  • Objectタイプを使用してデータを保存する場合は、そのデータが無秩序であることを保証する必要があります.
  • 順序付きの集合を表す必要がある場合は、Objectタイプではなく配列タイプを使用します.
  • Objectを避ける.prototypeに属性を追加します.
  • Objectに確実に必要であれば.prototypeにメソッド属性を追加するには、独立した関数の代わりに使用することを考慮します.
  • Objectを使用する.definePropertyはforに追加されないことができます..inループした属性.
  • 配列を巡回するときは、forを使用するのではなく、標準的なforループを使用します.inサイクル.
  • 必要に応じて配列の長さを予め保存しておき、性能を向上させる.
  • 遍歴方法Arrayを用いる.prototype.forEachとArray.prototype.ループの代わりにmapを使用して、コードをより明確に読み取ることができます.
  • 繰り返し出現するループについては、それらを抽象化することが考えられる.サードパーティによって提供された方法または自己実現.
  • 明示的なループは、場合によっては有用であり、それに応じてsomeまたはeveryメソッドを使用することもできる.