LoginSignup
45
38

More than 5 years have passed since last update.

復讐のJavaScript / 基礎とTips

Posted at

オブジェクトの比較

valueOf()メソッドを定義することで、比較演算子を用いてオブジェクトを比較した際の元になる値を指定できる。

即時関数の書き方色々

function式として認識させられる構文であれば、別にカッコを使わなくても即時関数が書けることになります。ここで使えるもの、それは単項演算子です。
via 即時関数(function(){ ... })()の別の書き方いろいろ - 泥のように

var a = "global";

(function(){
  var a = "another paren";
  alert(a); //"another paren"
}());

+function(){
  var a = "plus";
  alert(a); //plus
}();

-function(){
  var a = "minus";
  alert(a); //minus
}();

!function(){
  var a = "ex";
  alert(a); //ex
}();

void function(){
  var a = "void";
  alert(a); //void
}();

typeof function(){
  var a = "typeof";
  alert(a); //typeof
}();

(function(){}())と(function(){})()について かの有名なダグラス・クロックフォードさんは、 (function() { // }()); ってつかえよーうって推奨してます。
via 知ってて当然?初級者のためのJavaScriptで使う即時関数(function(){...})()の全て - 三等兵

長い変数名を短縮して使うスマートなやり方

即時関数を使う。

(function(v){
    Object.extend(v, {
        href: v._getAttr,
        src: v._getAttr,
        ...
    });
})(Element.attributeTranslations.read.values);

関数プロパティを使う

counter.num = 1; // 関数のプロパティを初期化。関数宣言は巻き上げられるのでこれは問題ない
function counter() {
    return counter.num ++; // 自身のプロパティにアクセス
}

console.log(counter()); // =>1
console.log(counter()); // =>2

argumentsは配列ではなくArgumentsオブジェクト

example.js
(function(x,y) {
    console.log(typeof arguments); // =>object
    console.log(arguments.join); // =>false

    x = 100; // arguments[0]が変更される
    console.log(arguments); // [100, 2, 3, 4, 5, 10, 20] 
    arguments['x'] = 200; // 新たにxプロパティを定義
    console.log(arguments); // [100, 2, 3, 4, 5, 10, 20, x: 200] 
})(1,2,3,4,5,10,20); 

関数定義式でも関数名を指定できる

example.js
/* 0.3以下の乱数を返す冗長な関数 */
var f = function func() { // 関数定義式で関数名を定義
    var r = Math.random();
    if (0.3 > r) {
       return r;
    } else {
        return func(); // この関数内でだけ、この名前で呼び出せる
    }
};

console.log(f()); // =>0.2691462936345488(例)
console.log(func()); // ReferenceError: func is not defined

Array.join()はECMAScript標準ではない

  • 配列操作系のStaticメソッドにはECMAScript標準でないものがある
  • 強い処理系依存なので注意
example.js
var a = {'0': 'a', '1': 'b', '2': 'c', 'length': 3}; // 配列のようなオブジェクト
var s = Array.join(a, '+'); // ChromeではTypeError
console.log(s); // =>a+b+c(Firefox)

/* これならok */
var s = Array.prototype.join.call(a, '+');
console.log(s); // =>a+b+c(Chrome/Firefox)

/* Array.isArray()はECMAScript5標準なのでok */
console.log(!!Array.isArray); // =>chrome/Firefox:true

配列に含まれるある値のインデックスを列挙する

example.js
/* 配列aに含まれる全てのxのインデックスを返す関数 */
function findAll (a, x) {
    var results = [],
        len = a.length,
        pos = 0;
    while(pos < len) {
        pos = a.indexOf(x, pos);
        if (pos === -1) {
            break;
        }
        results.push(pos);
        pos += 1;
    }
    return results;
}

var a = [1,2,1,3,4,1];
console.log(findAll(a, 1)); // =>[0, 2, 5] 

break可能な.forEach()

example.js
function foreach(a, f, t) {
    try {
        a.forEach(f, t);
    }
    catch(e) {
        if (e === foreach.break) {
            return;
        } else {
            throw e;
        }
    }
}
foreach.break = new Error('StopIteration');

var a = [1,2,3,4,5];
var results = [];
foreach(a, function(x) {
    results.push(x);
    if (x == 3) {
        throw foreach.break; // break
    }
}); 
console.log(results); // =>[1, 2, 3]

/* .some()を使う方法 */
results = [];
a.some(function(x) {
    results.push(x);
    if (x == 3) {
        return true;
    }
}); 
console.log(results); // =>[1, 2, 3] 

備考:
JavaScriptで配列をループで処理するベストな書き方は? - QA@IT
JavaScriptのforEach的なものでbreakしたい話 - 車輪を再発明 / koba04の日記

配列のlength上書き

配列のlengthを上書きするとその長さ以上のインデックスを持つ要素は削除される。

example.js
var a = [1,2,3,4,5];
a.length = 3;
console.log(a); // =>[1, 2, 3] 

このような操作が好ましくない場合、definePropertyでlengthを書き込み不可にすることができる。

example.js
var a = [1,2,3,4,5]; // これは配列
Object.defineProperty(a, 'length', { writable: false});
a.length = 3; // strictモードならTypeError
console.log(a); // =>[1, 2, 3, 4, 5] 

配列オブジェクトのプロパティ

  • 配列はオブジェクトなので任意のプロパティを設定できる。
  • ただしこれは配列の要素ではないことに注意。
  • 配列のインデックスになるのは0から2の32乗-1の整数のみ。
  • 配列のインデックスはプロパティ名としてふるまう。
example.js
var a = [1,2,3]; // これは配列
console.log(a); // =>[1, 2, 3] 

var o = {0: 1, 1: 2, 2: 3}; // これはオブジェクト
console.log(o); // =>Object {0: 1, 1: 2, 2: 3} 

a.test = 10; // 配列オブジェクトのプロパティを追加※要素ではない
o.test = 10; // オブジェクトのプロパティを追加
console.log(a); // =>[1, 2, 3, test: 10] (Objectにはならない)
console.log(o); // => Object {0: 1, 1: 2, 2: 3, test: 10} 

for/in文で配列を扱う

example.js
var a = [1,2,3];
a[10] = 10; // 疎な配列
a.test = 'test'; // 配列オブジェクトのプロパティを追加

var results = [];
for (var i in a) {
    // 非負数の整数でなければスキップする
    if (String(Math.floor(Math.abs(Number(i)))) !== i) {
        continue;
    }
    results.push(i);
}
console.log(results); // =>["0", "1", "2", "10"] 

ゲッター/セッターメソッドを使う

example.js
var me = {
    firstName: 'Hanako',
    lastName: 'Yamada',

    get fullName() {
        return this.firstName + ' ' + this.lastName;
    }
};

console.log(me.fullName); //=> Hanako Yamada

任意のゲッター/セッターを指定する事も出来る

Object.defineProperty(me, 'age', {get: getAge, set: setAge});

function getAge () {
    return this._age;
}

function setAge(age) {
    this._age = age - 5;
}

me.age = 10;
console.log(me.age); // =>5

me.ageの操作に対して暗黙にgetAge()/setAge()が呼び出される。

プロパティ属性を取得/設定する

example.js
var o = {
    x: 1,
    y: 2,
    get sum() {
        return x + y;
    }
};

var des;

/* データプロパティのプロパティ属性を取得 */
des = Object.getOwnPropertyDescriptor(o, "x");
console.log(des); // =>Object {value: 1, writable: true, enumerable: true, configurable: true} 

/* アクセサメソッドのプロパティ属性を取得 */
des = Object.getOwnPropertyDescriptor(o, "sum");
console.log(des); // =>Object {get: function, set: undefined, enumerable: true, configurable: true}

/* プロパティ属性を設定 */
Object.defineProperty(o, "x", { writable: false } ); // xの書き込みを不可に
o.x = 10; // 書き込み不可なのでstrict modeならTypeErrorに
des = Object.getOwnPropertyDescriptor(o, "x");
console.log(des); // =>Object {value: 1, writable: false, enumerable: true, configurable: true} 

/* ただしまだ再定義可なので、この方法なら書き込みできる */
Object.defineProperty(o, "x", { value: 10 } );
des = Object.getOwnPropertyDescriptor(o, "x");
console.log(des); // =>Object {value: 10, writable: false, enumerable: true, configurable: true} 

/* 再定義不可にしてみる */
Object.defineProperty(o, "x", { configurable: false } );
des = Object.getOwnPropertyDescriptor(o, "x");
console.log(des); // =>Object {value: 10, writable: false, enumerable: true, configurable: false} 

/* 再定義も禁止されているのでエラーとなる */
Object.defineProperty(o, "x", { value: 20 } );
des = Object.getOwnPropertyDescriptor(o, "x"); // TypeError

オブジェクトをロックする

オブジェクトを拡張不可にし、プロパティ属性の再定義を禁止します。

example.js
var o = {x: 1};
Object.seal(o); // oは拡張不可/再定義不可
o.y = 2;
console.log(o.y); //=>undefined(strictモードならTypeError)

Object.freeze(o); // oはさらに読み込み専用
o.x = 10;
console.log(o.x); //=>1

オブジェクトのプロトタイプを調べる

example.js
var o = {x: 1};
var j = Object.create(o);

console.log(Object.getPrototypeOf(j)); // =>Object {x: 1} 

/* 正しくない(古い)方法 */
console.log(j.constructor.prototype); // =>Object {}

/* 別の方法 */
var b = o.isPrototypeOf(j); // oがjのプロトタイプであるか調べる
console.log(b); // =>true

オブジェクトにあるプロパティが存在するか調べる

example.js
var o = {x: 1, y: 2};
var j = Object.create(o);
Object.defineProperty(j, "tmp", { enumerable: false } ); //j.tmpは列挙禁止

/* 独自プロパティtmpがある場合はtrue*/
console.log(j.hasOwnProperty("tmp")); // =>true

/* 列挙可能な独自プロパティtmpがある場合はtrue*/
console.log(j.propertyIsEnumerable("tmp")); // =>false

/* 継承プロパティはfalse */
console.log(j.hasOwnProperty("x")); // =>false

/* 継承プロパティも含める場合はこう */
console.log("x" in j); // =>true

オブジェクトをJSONに変換する

example.js
var o = {"x": 1, "y": 2, "tmp": null};
var json = JSON.stringify(o);
console.log(json); // =>'{"x": 1, "y": 2}'

// JSON.stringify()はオブジェクトにtoJSONメソッドが定義されているとそちらを使う
o.toJSON = function() {
    var res = {};
    for (i in this) {
        if (i !== 'tmp') {
            res[i] = this[i];
        }
    }
    return res;
};
json = JSON.stringify(o);
console.log(json); // =>'{"x":1,"y":2}' 

ラベル付きのbreak/continue

break文とcontinue文はラベルを付けた文の規定のか所へジャンプすることができる。

break文とcontinue文でのラベル指定 - 繰り返し処理 - JavaScript入門

真偽値へ変換する

ある型の値を真偽値に変更するには通常Boolean()型キャストを用いるが、以下の方法もある。

example.js
var s = 'bool';
console.log(!!s); // =>true;

このように論理否定演算子!を2つ前置すると、対応する倫理値に変換する。
(1つの場合は当然ながら反転。この場合はfalse)

&&(論理積演算子)を知る

  • &&は真偽値を返すわけではない
  • 左から順に評価していき、trueもしくはfalseに評価される値を返す
  • falseに評価される値に出会ったときそれを返し、残りは評価すらしない。
  • 以下の例でp.xはもし評価されればTypeErrorとなるが、左側がfalseである限りp.xが評価されることはないのでTypeErrorとはならない。
example.js
var o = { x: 1};
var p = null;
console.log(o && o.x); // =>1
console.log(p && p.x); // =>null

以上の法則から以下のようなコードを書くことができる。

example.js
// 以下はどちらも同じ動作となる。
if (a == b) stop();
(a == b) && stop();

グローバル変数はグローバルオブジェクトのプロパティ

example.js
var g = 'global';
console.log(this.g); // =>global

関数スコープとホイスティング

関数内で定義された変数は関数スコープを持ち、どこで定義しようと、関数の先頭で定義したのと同じ意味を持つ。

(1)変数がホイスティング(巻き上げ)される例

1.js
var scope = 'global';
function f() {
    console.log(scope); // =>'undefined'
    var scope = 'local';
    console.log(scope); // =>'local'
}

(2)実際にはこう解釈されて実行されている

2.js
var scope = 'global';
function f() {
    var scope;
    console.log(scope); // =>'undefined'
    var scope = 'local';
    console.log(scope); // =>'local'
}

一般的には変数のスコープは短いほど良く、使用する直前に宣言するのが良いとされているが、JavaScriptの場合はこのホイスティングの特性により、混乱を避けるため関数の先頭で宣言する方が良いとされる。

型変換を極める

オブジェクト、配列は全てtrue

example.js
var B = new Boolean(false); // BooleanObject
console.log(!!B); // => true

各種オペランドによる型変換の例

example.js
new now = new Date(); // DateObjectを生成
typeof (now + 1) // =>"String"(+は日付を文字列へ変換)
typeof (now - 1) // =>"Number"(-はオブジェクトを数値型へ変換)
now == now.toString() // => true(明示的な型変換と暗黙の型変換)
now > (now - 1) // => true(>はDateを数値に変換)

ラッパーオブジェクトの挙動を理解する

example.js
var s = 'abc';
s.replace('b', 'd'); // StringObjectに自動変換後、自動的に破棄
console.log(s); // =>abc
var v = s.replace('b', 'd');
console.log(v); // =>adc

/* 明示的にStringオブジェクトを作ったとしても同じ */

var S = String('abc'); // => StringObject
S.replace('b', 'd'); // 結果は保持されない
console.log(S); // =>abc

/* 文字列型とStringObjectの比較 */

if (s == S) {} // =>true
if (s === S) {} // =>false

配列/オブジェクトの代入は参照である

example.jp
var a = [1, 2, 3];
var b = [1, 2, 3];
if (a === b) // => false

var c = a;
if (a === c) // => true

値がNaNであるか調べる

example.js
var num = NaN;
if (num == NaN) // false
if (num !== num) // true
if (isNaN(num)) // true

debuggerでブレークポイントを設定できる

debugger ステートメントは、プロシージャ内の任意の場所に置いて、実行を中断できます。 debugger ステートメントは、プログラム コードのブレークポイントと同じ動作をします。
via debugger ステートメント (JavaScript)

"debugger"はECMAScriptの予約語扱いなので、JSHintでエラーを出さないためにはdebugオプションをtrueにします。

.jshintrc
"debug": true,

"use strict"は関数単位での適用とする

strictモードの適用は、「スクリプト全体」ではなく、「関数単位での適用」とする(←※これ重要!)
via “use strict”(厳格モード)を使うべきか?|もっこりJavaScript|ANALOGIC(アナロジック)

インクリメント、デクリメントは推奨されない

plusplus ++、--の使用を禁止する。 これらの演算子がコードクオリティを下げるという主張もあるため (Python文化圏とか) 。 (The Good Partsでも言及されていた気がしたが忘れた。本を貸したままなので内容確認できず。知っている方がいらしたらコメント頂けると幸いです)
via JavaScript - JSHint Options 日本語訳 - Qiita

JSHintではplusplusオプションを指定すると++、--の使用を禁止。

なお、Python にインクリメント・デクリメント演算子が実装されない理由の 1 つに、上のような一見意味不明のコードを避けるということがあるようです (http://www.gossamer-threads.com/lists/python/dev/455027)。
via インクリメント・デクリメント演算子 | Linux 修験道

主な出典

O'Reilly Japan - JavaScript 第6版

45
38
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
45
38