僕らは JavaScript を知らない - レキシカルスコープとクロージャとガベージコレクション Lexical Scope, Closure and Garbage Collection

参考:

レキシカルスコープ

スコープ

そもそもスコープとは、変数や関数が参照できる範囲のことを言います。スコープの中で宣言した変数は同じスコープ内でのみ参照でき、スコープの外側から参照されることはありません。

例えば次のコードでは関数 foo 内で宣言された変数 b は関数 foo 内では参照できますが、関数の外側で参照しようとすると ReferenceError となります:

function foo(a) {

    var b = a * 2;

    console.log( b ); // 6
}

foo( 3 );

console.log( b ) // ReferenceError: b is not defined

この関数によるスコープのことを関数スコープと呼びます。

スコープチェーン

次のコードでは変数 a は関数内に宣言されてませんが、 ReferenceError になることはありません。なぜなら、 foo 関数のスコープの外側のスコープに存在する変数 a を使用するためです。現在のスコープに変数が存在しない場合、そのスコープをネストするより外側のスコープまで参照の範囲を広げていくこの仕組みを、スコープチェーンと呼びます。

function foo() {

    var b = a * 2;

    console.log( b );
}

var a = 2;

foo();

レキシカルスコープ

次のコードでは、 bar の実行はグローバルスコープにて行われましたが、返ってくる値はグローバルスコープに存在する x ではなく、 foo による関数スコープ内の x です。 このようにスコープが関数を実行した時ではなく、宣言したときに決定される特徴を持つスコープをレキシカルスコープと呼びます。

var x = 'global';

function foo(){
  var x = 'local';
  return function(){ return x }
};


/*
実体は以下の通りで、グローバルスコープにおいて実行されるが、 返り値は `foo` 関数スコープの `x`
function() { return x }
*/
var bar = foo();

x; // global
bar(); // local

JavaScript はコードを実行する前に、そのコードを意味のある最小単位(トークン, Token) へと分解するレキシング (Lexising) を行います。 コード実行前のレキシング時に決定されるため、レキシカル (Lexical) スコープと呼びます。

上記のコードのように、スコープの階層が異なれば同じ名前を持つ変数を宣言することができます。これをシャドーイング (shadowing) と呼びます。 barfoo スコープ内に変数 x を見つけたため、これを使用し、グローバルスコープに存在する x は無視されることになりました。グローバル変数 x がローカル変数 x の影に隠れた (シャドー, shadow) イメージです。

クロージャ

クロージャとは「関数が内包するスコープを保持する性質」のことを言い、クロージャ関数とはその性質をもつ関数のことを言います。

先程のコードも実はこのクロージャを利用していますが、いまいちわかりにくいので、もう少し分かりやすいコードを見てみましょう:

var counter = function(initialValue) {
    var count = initialValue;
    return function() {
            count++;
            return count;
    }
}


/*
    var count = 7;
    return function() {
            count++;
            return count;
    }
*/
var myCounter7 = counter(7);


/*
    var count = 15;
    return function() {
            count++;
            return count;
    }
*/
var myCounter15 = counter(15);

myCounter7();   // 8
myCounter7();   // 9

myCounter15();  // 16

上記の例では myCounter7 は、作成された時点での内部スコープを保持するクロージャ関数が代入されており、この中では count の値 7 が取り込まれています。myCounter15() では、count の値 15 が保持されています。また、それぞれ関数実行後も count の値が保持され続けていることも確認できます。

ではなぜ、レキシカルスコープを、そしてその内部の変数の値を関数実行後も保持することができるのでしょうか?それを知るためには、メモリの管理について理解する必要があります。

ガベージコレクション

変数や関数を宣言するとデータはメモリ領域を確保しますが、メモリ解放をしないとメモリリークと呼ばれる、ソフトウェアが使用できるメモリ領域が減っていく現象が発生します。

したがってメモリリークを防ぐためには適切なメモリ管理を行う必要があります。その方法は言語により異なりますが、 JavaScript ではガベージコレクションが採用されています。

ガベージコレクションでは、それを備えていない言語とは異なり、割り当てられたメモリが使用されてすでに必要なくなったときに自動的に開放されます。

どのようなときに、ガベージコレクションは割り当てられたメモリを「不要」と判断するのでしょうか。主にはその値が「参照」されているかどうかです。どこからも参照されなくなったら、それはガベージコレクションの対象となります。

具体例を見ましょう:

var obj = { x: 'foo' }
obj = null;

オブジェクト { x: 'foo'} は宣言時、変数 obj から参照されています。したがって明らかにガベージコレクションの対象にはなりませんが、その後、 変数に null を代入されたために、 { x: 'foo'} を参照しているものはなくなりました。どこからも参照されなくなった時点で、このデータは不要と判断され、ガベージコレクションの対象となり、メモリ上から開放されます。

では、ここで先程のクロージャ関数の例を見てみましょう:

var counter = function(initialValue) {
    var count = initialValue;
    return function() {
            count++;
            return count;
    }
}

/*
function(7) {
    var count = 7; 
    return function() {
            count++;
            return count;
    }
}
*/
var myCounter7 = counter(7);

/*
実行している関数は以下の通りだが、`count` はグローバルスコープではなく、
関数の宣言時の外部スコープの変数 `count` を参照している
var count = 7;
return function() {
        count++;
        return count;
}
*/
myCounter7(); // 8

/*
もう一度実行したときも、 `count` の値は `8` のまま参照され続けているので返り値が `9` になる
*/
myCounter7(); // 9

このように、countガベージコレクションの対象とならずにデータを保持することができる理由は、関数が宣言されたときの変数 count を参照するというレキシカルスコープの性質と、参照されている count のデータはメモリ上から開放されず、データが保持され続けるというガベージコレクションの性質、これらふたつの性質を持っているから、ということができます。

僕らは JavaScript を知らない - データ型と参照 Data Type and Reference

参考

データ型

JavaScript は動的型付け言語と呼ばれ、変数の型は存在しません。変数はどのデータ型でも持つことが可能です。

let value = '1'; // String
value = 1; // Number
value = true; // Boolean

データ型には、次の7つの型があります:

  • Boolean
  • Null
  • Undefined
  • Number
  • String
  • Symbol
  • Object

これらのうち、 object 以外はプリミティブ型と呼ばれます。また、オブジェクトはプリミティブ型やオブジェクトの集合です。 typeof 演算子を使うと、データ型の文字列を取得することが出来ます:

typeof undefined     === "undefined"; // true
typeof true          === "boolean";   // true
typeof 1             === "number";    // true
typeof "1"           === "string";    // true
typeof { a: 1 }      === "object";    // true
typeof Symbol()      === "symbol";    // true

ただし、 null だけは "null" ではなく、 "object" を返します。これは JavaScript のバグですが、この挙動に依ったコードがあまりにも多く、これを修正することでより多くのバグを生むため、このままにされているという経緯があります。

Undefined Type

JavaScript において、変数の型は存在せず、データ型のみ存在します。例えば次のようなコードを実行したとします:

var a;

a; // undefined
b; // ReferenceError: b is not defined

これは、変数 a の宣言時の初期値が undefined 型であるためです。

しかし、 typeof 演算子は宣言していない変数 b に対して次のように動作します:

var a;

typeof a; // undefined
typeof b; // undefined

これは安全装置として働きます。例えば、グローバルネームスペースに myFunction が存在するかどうかを確かめ、存在しない場合新たに myFunction を定義するためのコードは次のようになります:

// Uncaught ReferenceError: myFunction is not defined
if (!myFunction) {
    myFunction = function() { console.log('This is my function!') }
}

// typeof 演算子によってエラーは起きない
if (typeof myFunction === undefined) {
    myFunction = function() { console.log('This is my function!') }
}

参照

C++ において次のコードでは、変数 a は変数 myNumber の参照となります。ここでいう参照とは「別名」と考えてよく、 myNumber の別名である a の変更は myNumber にも影響します。メモリ上に変数 myNumber を作成して、そのアドレスを変数 a に渡しているため、変数から変数への参照と呼ばれます。

void myFunction( int& a )
{
    a *= 2;
}
int main()
{
    int myNumber = 2;
    myFunction( myNumber );
}

対して、JavaScript では変数に対する参照はありませんが、値に対する参照を行います。 JavaScript においてオブジェクトは参照型です。2つの異なるオブジェクトは、たとえ同じプロパティを持っていたとしても等値とはみなされません。具体例を確認しましょう:

// メモリ上に `[1, 2, 3]` を作成し、そのアドレスを `a` に代入
// また、新たにメモリ上の別の場所に `[1, 2, 3]` を作成し、そのアドレスを `b` に代入
var a = [ 1, 2, 3 ];
var b = [ 1, 2, 3 ];

// アドレスが異なるので等値ではない
a === b // false

//  `b` の値のアドレスを `a` に代入
a = b

// アドレスが同じなので等値
a === b // true

C++ や他の言語とは異なり、 JavaScript のオブジェクト変数は値のアドレスを保持しているだけで、他の変数のアドレスを保持しません。変数 c d は値を共有しているので、値に対する変更はどちらの変数からでも確認することができます。アドレスの代入、参照の値渡しと呼ばれます。

var c = [ 1, 2, 3 ];

// アドレスの代入
var d = c;

// 参照する値に対する変更
d.push( 4 );

c; // [ 1, 2, 3, 4 ]
d; // [ 1, 2, 3, 4 ]

以下のコードにおいて、変数 b に対する変更は変数 a に影響を与えることはありません。なぜならアドレスが異なるからです。

var a = [ 1, 2, 3 ];
// 参照の代入
var b = a;

// 空の配列リテラルから配列を新たに作成し、そのアドレスを代入
b = [];
a; // [ 1, 2, 3 ]
b; // []

元のオブジェクトを空にしたい場合は、既存の配列に対する変更を行います:

var a = [ 1, 2, 3 ];
var b = a;

b.length = 0;

a; // []
b; // []

オブジェクトの値だけを渡したい場合は Array.prototype.slice メソッドなどを使用します。この場合、b のアドレスと c のアドレスは異なるため、c に対する変更は影響を受けません:

var a = { hoge: 'hoge' }
var b = [ a, 1 ]
var c = b.slice();
c.push( 2 );

a // { hoge: 'hoge' }
b // [ { hoge: 'hoge' }, 1 ]
c // [ { hoge: 'hoge' }, 1, 2 ]

シャローコピー (1段階の深さのコピー) なので、 オブジェクト a の値ではなくアドレスがコピーされます:

var a = { hoge: 'hoge' }
var b = [ a, 1 ]
var c = b.slice();

a.fuga = 'fuga';
c.push( 2 );

a // { hoge: 'hoge', fuga: 'fuga' }
b // [ { hoge: 'hoge', fuga: 'fuga' }, 1 ]
c // [ { hoge: 'hoge', fuga: 'fuga' }, 1, 2 ]

本来プリミティブ型は値を保持しておくことが出来ないのですが、オブジェクトが参照型であることを逆手に取ってオブジェクトでラップしてやれば、プリミティブ型も「参照渡し」することが可能で、関数内の変数 y.b に加えた変更は変数 obj.y にも反映されます:

var a = 100;

(function(x) {
    x = 50;
})(a);

a // 100;

var obj = { b: 100 };

(function(y) {
    y.b = 50;
})(obj);

obj.b // 50;

僕らは 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とかで連絡下さい。スペイン語翻訳のプロジェクトは見た記憶があるので許可はもらえると思います。

Amazon ECS Scheduled Tasks によるコマンドの定期実行

使い方

CMDに実行したいコマンドをカンマ区切りで入力し、Task Definition を作成。その他環境変数とかも f:id:uraway:20171120205258p:plain

作成した Task Definition を Cluster の scheduled task にて設定。Cronかインターバルかを選択できる。 f:id:uraway:20171120205618p:plain

CloudWatch でログも見れるので便利

【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

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

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