僕らは JavaScript を知らない - 巻き上げ Hoisting

参考:

JavaScript において、変数を宣言するには varletconst を利用します。これらのうち、 var let では、初期値を指定せずに宣言した場合すべて undefined になります。

var a;
let b;

console.log(a); // undefined
console.log(b); // undefined

変数や関数の宣言が、自身のスコープ内のコードの一番上に来ているかのような動作を巻き上げ (Hoisting) と呼びます。例えば、次のコードを実行してみましょう。

a = 1;
var a;

console.log(a);

上記のコードを上から順に解釈していけば、 undefined と出力されそうですが、実際には 1 が出力されます。関数宣言でも同様です:

foo();

function foo() {
    console.log(1);
}

foo 関数の宣言前に foo を実行しているようにみえますが、出力は 1 になります。コードの実行順に即したコードはそれぞれ次のようになります:

var a;
a = 1;

console.log(a);
function foo() {
    console.log(1);
}

foo();

このように、変数や関数の宣言をスコープ内の一番上 (今回はグローバルスコープ) にもってくることを巻き上げ (Hoisting) と呼びます。実際の JavaScript の動作としては、コード実行前のコンパイル時に宣言式を処理しているというだけで、宣言式を物理的に動かしているということはありません。

注意点として関数式は巻き上げされないため、次のコードではエラーが発生します:

foo(); // Uncaught TypeError: foo is not a function

var foo = function() {
    console.log(1);
}

変数 foo の宣言は巻き上げられて処理されます。しかし、関数の代入はコードの実行時に処理されるため、 foo()undefined を実行し、 TypeError が発生します。コードの実行順に即して書き直すと次のようになるでしょう:

var foo;

foo();

foo = function() {
    console.log(1);
}

foo の値が undefined になることがよく理解できるのではないでしょうか。

また、 let / const では巻き上げは起こりません。

console.log(a); // ReferenceError: a is not defined

let a = 1;

僕らは JavaScript を知らない - シンボル Symbol

初めてプログラミングを触って、それからずっと2年くらい JavaScript 使ってますが、なかなか初心者から抜け出せないなあという思いがあり、恥を忍んで JavaScript の勉強記事を書くことにしました。たぶん何回か続きます。

参考:

シンボル Symbol

ES6 (実装はもう3年以上前?) では "Symbol" と呼ばれるプリミティブ(基本)データ型が追加されました。下記のコードでは、実際にシンボルを生成しています:

var sym = Symbol( "some optional description" );

typeof sym;       // "symbol"
sym.toString();     // "Symbol(some optional description)"

var obj = { };
obj[sym] = "foobar";
Object.getOwnPropertySymbols( obj );
// [ Symbol(some optional description) ]

注意すべき特徵は、

  • new 演算子で生成するわけではない
  • 引数は任意。一度生成されたシンボルは自身のみと等しい
  • シンボルの値そのものを取得することは出来ない

ということです。ほとんどの JavaScript デベロッパーには使う機会のない代物かもしれません。具体的な使用例を以下に見ていきましょう。

列挙定数

シンボルには「一度生成されたシンボルは自身のみと等しい」という特徴があるため、次のように定数として表すことが出来ます:

const EVN_LOGIN = Symbol( "event.login" );
const EVN_LOGOUT = Symbol( "event.logout" );

Redux の ActionTypes もシンボルで定義したいところですが、残念ながら、シンボルはJSONへのパースができないため Redux DevTool との相性が悪いということもあってか、推奨されていませんでした[^1]。

オブジェクトプロパティとしてのシンボル

シンボルをオブジェクトの特別なキープロパティとして使うことも出来ます。

const MEMBER = Symbol( "a" );

function save(value) {
    return save[MEMBER] = value;
}

動作自体は以下と変わりありません:

function save(value) {
    return save.__member = value;
}

シンボルをオブジェクトのキーにした場合、シンボルは Object.getOwnPropertyNames メソッドからは秘匿されます:

var o = {
    foo: 42,
    [ Symbol( "bar" ) ]: "hello world",
    baz: true
};

Object.getOwnPropertyNames( o );   // [ "foo","baz" ]

しかし、次のメソッドを使用することでオブジェクトのシンボルプロパティを取得することができます:

Object.getOwnPropertySymbols( o );    // [ Symbol(bar) ]

Well Known Symbol

シンボル関数オブジェクトにあらかじめ定義されたシンボルのことを Well Known Symbol と呼びます。

たとえば、 iterate という Well Known Symbol は Symbol.iterate として参照可能です。これらは、 JavaScript の組み込み関数の挙動をオーバーライドするために使用することが出来ます。

また、これらを @@ プレフィックスを付けて呼ぶ(@@iterate)ことがあります。

var arr = [4,5,6,7,8,9];

for (var v of arr) {
    console.log( v );
}
// 4 5 6 7 8 9

// for ... of ループの挙動を自作のイテレータでオーバーライド
arr[Symbol.iterator] = function*() {
    var idx = 1;
    do {
        yield this[idx];
    } while ((idx += 2) < this.length);
};

for (var v of arr) {
    console.log( v );
}
// 5 7 9

Symbol.for / Symbol.keyFor

シンボルを使用する可能性のあるコードがアクセスできるように、シンボルは外部のスコープに定義しなければなりませんが、 Symbol.for メソッドを使えば、グローバルな空間における一意の文字列で管理することが可能です。

Symbol.for メソッドは、文字列を引数にとり、その文字列ですでにシンボルが定義されていればそのシンボルの値を、定義されていなければ新たに作成し、シンボルの値を返します。

また、 Symbol.keyFor メソッドでは、登録されたシンボルに渡された文字列を得ることが可能です。

var s = Symbol.for( "something cool" );

var desc = Symbol.keyFor( s );
console.log( desc );            // "something cool"

// 再びシンボルを取得
var s2 = Symbol.for( desc );

s2 === s;                       // true

Symbol については以上になります。 余裕ができたら Well Known Symbol についても詳しく書くことにしましょう。

You Don't Know JS の日本語訳プロジェクトってないのかな?あったら教えて下さい。 もしなくて、日本語訳に興味がある方がいたら一緒にやりましょう。Twitterとかで連絡下さい。スペイン語翻訳のプロジェクトは見た記憶があるので許可はもらえると思います。

【SQL】今週の日曜日・今週の土曜日を求める

メモ:

CURRENT_DATE - interval (DAYOFWEEK(CURRENT_DATE) - 1) day AS THIS_SUNDAY

日曜日始まりとする。 今日の曜日インデックス(DAYOFWEEK(CURRENT_DATE))から日曜日の曜日インデックスを引くと、今日が日曜日から何日目かが分かる。

CURRENT_DATE + interval (7 - DAYOFWEEK(CURRENT_DATE)) day AS THIS_SATURDAY

土曜日の曜日インデックスから今日の曜日インデックスを引くと、土曜日は今日から何日目かが分かる。

もしかしたらそれ用の関数とか用意されてるのだろうか

Alpineでパッケージをインストールしようとしたらエラーがでたので解消する

メモ

ERROR: unsatisfiable constraints:
  py-pip (missing):
    required by: world[py-pip]
ERROR: Service 'web' failed to build: The command '/bin/sh -c apk add py-pip' returned a non-zero code: 1

インストール可能なパッケージ一覧(インデックス)の更新オプションを付けると解消される (--update)

apk add --update py-pip

Macでファイル内の文字列を再帰的に置換するコマンド

カレントディレクトリ以下すべてのファイル内のHOGEという文字列をFUGAに置換する

find ./ -type f | xargs sed -i '' 's/HOGE/FUGA/g'
  • xargs … 標準入力から受け取ったデータを、任意のコマンドに引数として与えるコマンド

  • sed … StreamEditor. 入力ストリームに対する文字列の置換を行う。Linux のものとは微妙に動作が異なるらしい

参考: Macでsedコマンドが思うように動かなくてハマった