28. You Don’t Know JS: Up & Going Chapter 2: Into JavaScript

Tatsuya Asami
38 min readJul 26, 2018

--

【所要時間】

9時間37分 (2018年7月25,26日)

【概要】

JavaScriptについて学ぶ

【要約・学んだこと】

Values & Types

JSはtyped valuesを持ち、 typed variablesを持たない。

var a;
typeof a; // "undefined"

a = "hello world";
typeof a; // "string"

a = 42;
typeof a; // "number"

a = true;
typeof a; // "boolean"

a = null;
typeof a; // "object" -- weird, bug

a = undefined;
typeof a; // "undefined"

a = { b: "c" };
typeof a; // "object"

a valueは異なるタイプのvalueを持つ。 type of a は a のタイプを尋ねているのではなく、現在の a のタイプを尋ねている。

type of null は “null” を返すと予想されるときに、誤って “object” を返す。
これはJSにずっとあるバグで、修正される見込みもない。

a = null;
typeof a;
“object”

a がundefined を返すことを期待するときは、var a; のようにvalueをセットしていないときと同様の挙動をする。variable はvalueを返さないfunctionや、void operatorを使い、様々な方法でこの ”undefined” になることができる。

var a;
undefined

Objects

object typeはそれぞれが独自のタイプを持つpropertyとしてセットした複合的なvalueを指す。これはおそらくJSで最も有用なvalueの1つ。

var obj = {
a: "hello world",
b: 42,
c: true
};

obj.a; // "hello world"
obj.b; // 42
obj.c; // true

obj["a"]; // "hello world"
obj["b"]; // 42
obj["c"]; // true
var obj = {
a: “hello world”,
b: 22,
c: false,
};
obj.a;
“hello world”
obj.b;
22
obj.c;
false
obj["a"];
"hello world"

Propertyは dot notation (obj.a) か、 bracket notation(obj[”a”]) のどちらかでアクセエスされることができる。dot notationの方が短くて読みやすいので好ましい。

bracket notationは、obj[“hello world!”]といった特別な文字を含むproperty名を持つときに有用。こういったpropertyは、しばしば key として参照される。[ ] notationは variableか string literal(“..” or ‘..’)を必要とする。

bracket notationは property や key にアクセスしたいが、名前が他のvariableに含まれているときにも有用である。

var obj = {
a: "hello world",
b: 42
};

var b = "a";

obj[b]; // "hello world"
obj["b"]; // 42
var obj = {
a: “hello”,
b: 10,
};
var b = “a”;

obj[b];
“hello”
obj[“b”];
10

さらに詳しくはこのシリーズの this & Object Prototypes title を参照。

他によく使われるtypeとして、array と function がある。だが、これらは適切なbuilt-in typeというより、object typeの特別なsub typeとして考えるべき。

Arrays

arrayは、特定の名前があるpropertyやkeyではなく、数値でインデックスされた位置にあるvalueを持つobject

var arr = [
"hello world",
42,
true
];

arr[0]; // "hello world"
arr[1]; // 42
arr[2]; // true
arr.length; // 3

typeof arr; // "object"

JSと同様、arrayのインデックスの最初のelementは0から始まる。

arrayは特殊objectのため、自動更新されるlength propertyを含むpropertyを持つことができる。

var arr = [
“Hi”,
24,
true,
];
arr[0];
“Hi”
arr[1];
24
arr[2];
true
arr[“length”]
3

理論上arrayを名前付きpropertyを持つnormal objectとして使うことも、objectを使うこともできるが、arrayに似た数値propertyのみ与えることができる。だが、不適切な使用法として考えられている。

数値的に配列されたvalueを使用し、名前付きpropertyにオブジェクトを使用することが、最も適切である。

Functions

function foo() {
return 42;
}

foo.bar = "hello world";

typeof foo;
"function"
typeof foo();
"number"
typeof foo.bar;
"string"

functionはobjectのサブタイプだ。typeof は”function”を返し、function はメインタイプを実行し、propertyを持つことができるが、通常限られた場合のみ、foo.barなどのfunction object propertyのみに使用する。

詳しくはこのシリーズのthe first two chapters of the Types & Grammar参照。

Built-In Type Methods

これらのbuilt-in typeとsubtypeは非常に強力で有用なpropertyとmethodとして公開されている。

var a = "hello world";
var b = 3.14159;

a.length; // 11
a.toUpperCase(); // "HELLO WORLD"
b.toFixed(4); // "3.1416"

a.toUpperCase()の呼び方は、valueに存在するmethodだけよりも複雑だ。

a.toUpperCase() などのpropertyやmethodを参照して“hello world”ようなprimitive valueをobjectとして使うとき、JSは自動的にvalueをそのobject wrapperに対応するように、ボックス化される。

var a = “hello”
var b = 2.456543;
a.toUpperCase();
“HELLO”
b.toFixed(3);
“2.457”
b.length;
undefined
a.length;
5

簡単に言えば、通常nativeと呼ばれるString objectのwrapper formがあり、primitive string typeと対になる。

string valueはstring objectiveで包まれ、number valueはnumber object, booleanはboolean objectで包まれる。大抵の場合、object wrapper formは気にする必要はなく、ただ使えば良い。

JS nativeについてさらに詳しくはこのシリーズのChapter 3 of the Types & Grammar title参照。objectのプロトタイプについてはChapter 5 of the this & Object Prototypes title参照。

Comparing Values

JSには2タイプのcomparing valueがある。equality と inequalityだ。comparisonの結果は、比較されるtypeに関わらず、厳密にboolean valueとなる。

Coercion

coercionは explicitとimplicit2つのフォームから成り立つ。Explicit coercionは、単に他のtypeへの変換が、コードから明らかに見えるもので、implicit coercionは、type conversionが他の操作の副作用として、わかりにくく起こる。

coercionは悪いものでもなければ、驚くことでもない。type coercionで構築できる大抵の場合は、わかりやすく、そしてコードの可読性を向上させる。

さらに詳しくはChapter 4 of the Types & Grammar title参照。

Here’s an example of explicit coercion:

var a = "42";var b = Number( a );a;				// "42"
b; // 42 -- the number!

And here’s an example of implicit coercion:

var a = "42";var b = a * 1;	// "42" implicitly coerced to 42 herea;				// "42"
b; // 42 -- the number!

Truthy & Falsy

booleanではないvalueがbooleanに変換されると、それぞれtrue か falseになるのか。

The specific list of “falsy” values in JavaScript is as follows:

  • "" (empty string)
  • 0, -0, NaN (invalid number)
  • null, undefined
  • false

この “falsy” list以外は“truthy.” になる。

  • "hello"
  • 42
  • true
  • [ ], [ 1, "2", 3 ] (arrays)
  • { }, { a: 42 } (objects)
  • function foo() { .. } (functions)

実際にbooleanをcoercionされるなら、booleanでないvalueだけがこのtruthy falsy coercionに従うことを覚えておくことが重要。

Equality

equalityのoperatorは4つある。==, ===, !=, and !==
! フォームは、対応するものの”同じではない”という意味で、non-equalityはinequalityとは別物。
==: valueが同じかをチェックする。
===: valueとtypeが同じかをチェックする。
しかし、この表現は正確ではない。
==: 強制的に許可されるvalueが同じかをチェックする。
=== : 強制を使わずににvalueとが同じかをチェックする。

implicit coercionは == loose-equality によって許可されるが、 === strict-equalityには許可されない。

var a = "42";
var b = 42;

a == b; // true
a === b; // false

a == b comparisonでは、JSはtypeがマッチしないことに気がつく。typeが一致するまで一つまたは両方のvalueを異なるtypeにcoerceする順序付きの一連のステップを経て、単純なvalue equalityをチェックすることができる。
coercion経由でa == btrue を与えられるようにする方法は2つある。42 == 42もしくは"42" == "42"だ。
答えは"42"42になり、 42 == 42 を比べる方だ。

この場合はどちらでも結果は変わらないが、もっと複雑な場合はどうだろう。

a === bfalse を作る。なぜなら、coercionは許可されていないからで、simple value complarisonは明らかに失敗だ。多くのディベロッパは=== がより予測しやすいと感じているので、いつも=== をつかい、== は避ける。だが、== はよく学べばいいツールである。

詳しくは section 11.9.3 of the ES5 specification (http://www.ecma-international.org/ecma-262/5.1/) 参照。

  • もしcomparisonでどちらかのvalueが true か false valueなら、== を避け、=== を使う。
  • もしcomparisonのどちらかのvalueが特定のvalue(0,””,[])なら、== をさけ=== を使う。
  • その他の場合は == を使うのが安全だ。それだけでなく、読みやすさを向上させるコードがかける。

こういったルールで使い分ける人もいる。

これらのルールは、equalityのために比べられるvariableを通して、コードやvalueの種類について真剣に考えることを要求する。

!= non-equality form は== とペアになり、!===== とペアになる。

これらのルールは、non-equality comparisonのために、対象を保持する。

function や array を含む objectなどの2つのnon-primitive valueをcomparingするときは、特に注意が必要。
なぜならそれらのvalueはreferenceによって保持されるので、===== のcomparisonはreferenceがマッチするかどうかだけを単にチェックし、基本的なvalueは何もない。

例えば、arrayはデフォルトで全てのvalueの間に , を打つことが強制され、stringsに変換される。同じコンテンツに2つのarrayがあるときは、== が同じになると考えるが、実際は違う。

var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";
a == c; // true
b == c; // true
a == b; // false

== equality comparison rulesについてさらに詳しくは、the ES5 specification (section 11.9.3) and also consult Chapter 4 of the Types & Grammar title of this seriesと、 Chapter 2 for more information about values versus references を参照。

Inequality

<, >, <=, and >= operatorはinequalityとして使われ、rational comparisonとして参照される。通常 numberのような比較可能なvalueに使われる。
しかし、JS string valueはアルファベットルールを使って比較されることもできる。("bar" < "foo").

coercionは、== comparisonをinequality operatorに適用できる。(完全に一緒ではない。)特にstrict inequality operatorは存在せず、=== strict equalityの動作と同じようなcoercionは許可しない。

var a = 41;
var b = "42";
var c = "43";
a < b; // true
b < c; // true

b<c ではcomparisonのどちらのvalueもstringで、comparisonはlexicographically(辞書的に)にされている。しかし、a<bのように片側か両側がstringでないと、どちらのvalueもnumberにcoerceされ、numeric comparisonが起こる。

最大の問題は、異なるvalue typeである可能性のcomparisonは、1つのvalueが正しいnumberにならないときだ。

var a = 42;
var b = "foo";
a < b; // false
a > b; // false
a == b; // false

b valueはNaN <> comparisonで、invalid number value NaN にcoercing され、NaN は他のvalueよりも大きくも小さくもないのでfalse

== comparisonでは前述の通り、42 == NaN"42" == "foo" として解釈されるので、false

inequality comparisonルールについて詳しくは section 11.8.5 of the ES5 specification and also consult Chapter 4 of the Types & Grammar title

Variables

JSでvariable name(function nameを含む)は、妥当なidentifiers(識別子)でなければならない。完全に理解するのは非常に複雑だが、基本的なASCIIアルファベットキャラクターならばシンプルだ。

identifier は a-z, A-Z, $, or _ からスタートしなければならない。また、 0-9 も持つことができる。

property name にもvariable identifierと同じ原則が適用される。variableとして使えない言葉もあるが、propertyでは使えるものもある。これらをreserved words という。それには (for, in, if, etc.)また、null, true, and false を含む。

reserved wordについてさらに詳しくは Appendix A of the Types & Grammar title 参照。

Function Scopes

variableを宣言するキーワードに var を使う。これは現在のfunction scopeか、functionの最も外側にあるならglobal scopeに属する。

Hoisting

scope内に var があるときはいつでも、宣言は全てのscopeに属し、どこにでもアクセスできる。これは 比喩的にhoisting (巻き上げ)と呼ばれ、 概念的には、var 宣言がscopeのトップに移動される。技術的には、このプロセスはどうやってコードがコンパイルされるかによって、より正確に説明できる。

var a = 2;foo();					// works because `foo()`
// declaration is "hoisted"
function foo() {
a = 3;
console.log( a ); // 3 var a; // declaration is "hoisted"
// to the top of `foo()`
}
console.log( a ); // 2

variable hoistingを var 宣言が現れるより先に使うのは一般的でなく、また好ましくない。foo() が公式に宣言する前に現れるので、hoisted functionを宣言するのが一般的だ。

Nested Scopes

variableを宣言するのはscopeのどこでも可能。

function foo() {
var a = 1;
function bar() {
var b = 2;
function baz() {
var c = 3;
console.log( a, b, c ); // 1 2 3
}
baz();
console.log( a, b ); // 1 2
}
bar();
console.log( a ); // 1
}
foo();

c はbar() の中には利用できない。なぜなら baz() の中で宣言されたからで、同様に b はfoo()では利用できない。

function bar() {
var b = 2;
function baz() {
var c = 3;
console.log( a, b, c );
}
baz();
console.log( a, b, c );
}

bar();
console.log( a, b );
}
foo();
VM729:17 Uncaught SyntaxError: Unexpected token }

もし利用できない場所のscopeにあるvariableのvalueにアクセスすると、ReferenceError が投げられる。宣言されていないvariableをセットしようとすると、top-level global scopeにvariableが作られるか、エラーになる。(strict modeかどうかによる。)

function foo() {
a = 1; // `a` not formally declared
}
foo();
a; // 1 -- oops, auto global variable :(

上記は悪い例。

function levelにvariablesを作る宣言に加えて、ES6は let keywordを使い、individual block({ .. })に属するvariablesを宣言することができる。さらにscopingルールはfunctionとほぼ同じように機能する。

function foo() {
var a = 1;
if (a >= 1) {
let b = 2;
while (b < 5) {
let c = b * 2;
b++;
console.log( a + c );
}
}
}
foo();
// 5 7 9

var の代わりに let を使うことで、 b は if statement にのみ属し、 foo() functionのscope全体には属さない。c も同様に while loop にのみ属する。
block scopingはvariable scopeの管理にとても使い勝手が良い。

function foo() {
var a = 3;
if (a >= 1) {
let b = 2;
while (b < 5) {
let c = b * 2;
b++;
console.log( a + c);
}
}
}
foo();
VM773:11 7
VM773:11 9
VM773:11 11

scopeについて詳しくはこのシリーズのthe Scope & ClosuresSee the ES6 & Beyondの let block scoping. を参照。

Conditionals

if..else..if について

if (a == 2) {
// do something
}
else if (a == 10) {
// do another thing
}
else if (a == 42) {
// do yet another thing
}
else {
// fallback to here
}

これは機能するが、それぞれのに対して a を指定する必要があるので、少し面倒。
switch statementを使う方法もある。

a = 43;
switch (a) {
case 2:
console.log( "2" )
break;
case 10:
console.log( "10" )
break;
case 42:
console.log( "42" )
break;
default:
console.log( "others" )
}
VM922:13 others
a = 10;
switch (a) {
case 2:
console.log( "2" )
break;
case 10:
console.log( "10" )
break;
case 42:
console.log( "42" )
break;
default:
console.log( "others" )
}
VM910:7 10

もし1つの case を実行するstatementが必要なら、break がいる。
case に break がない場合で、case がマッチまたは実行された場合、case にマッチしなくても、次の case statementで実行が続けられる。これを fall throughと呼ぶ。

switch (a) {
case 2:
case 10:
// some cool stuff
break;
case 42:
// other stuff
break;
default:
// fallback
}

a が 2 か 10のとき、 some cool stuff コードのstatementを実行する。
JSの条件付きの別のフォームは、conditional operator、ternary operator(三元)と呼ぶ。単独の if..else statement より、さらに正確だ。

var a = 42;var b = (a > 41) ? "hello" : "world";// similar to:// if (a > 41) {
// b = "hello";
// }
// else {
// b = "world";
// }

テスト式(a > 41)がtrueと評価され、最初のclause(“hello”)が結果となり、そうでなければ2つめのclause(“world”)が結果となる。いかなる場合でも結果は b に代入される。

conditional operatorは代入に使われなければいけないわけではないが、最も一般的な使い方だ。

testing conditions and other patterns for switch and ? : については、see the Types & Grammartitle 参照。

Strict Mode

ES5から加えられたstrict modeは、特定の挙動のルールを厳しくするものだ。一般的により安全に、適切にガイドラインを保つために使われ、コードをエンジンによってより最適化する。全てのプログラムで使うべき。

pragmaを置く場所次第で、個別のfunctionかファイル全体を strict化することができる。

function foo() {
"use strict";
// this code is strict mode function bar() {
// this code is strict mode
}
}
// this code is not strict mode

Compare that to:

"use strict";function foo() {
// this code is strict mode
function bar() {
// this code is strict mode
}
}
// this code is strict mode

strictモードの主な違いは、var を省略し、implicit auto-global variable宣言を許可しないことだ。

function foo() {
"use strict"; // turn on strict mode
a = 1; // `var` missing, ReferenceError
}
foo();

strict modeを導入し、エラーやバグがあると、strictモードを避けたくなるが、それはよくない。strict modeが問題を起こすときは、プログラムに修正点があるというサインだ。

コードを安全にし、最適化するだけでなく、その言語の今後の方向性も表現している。今のうちにstrict modeに慣れた方がいい。

strict modeについて詳しくは、the Chapter 5 of the Types & Grammar 参照。

Functions As Values

function foo() {
// ..
}

syntaxからは明らかでないように思えるかもしれないが、 foo は基本的にただのscopeの外側のvariableで、宣言されているfunctionの参照が与えられている。function自体はvalueで、42や[1,2,3]のようなものである。

value(argument)をfunctionにパスするだけでなく、function自体をvariablesに代入されたvalueにするか、他のfunctionにパスしたり、返されたりする。

このようなfunction valueはexpression(式)と考えられ、他のvalueかexpressionと同じである。

var foo = function() {
// ..
};
var x = function bar(){
// ..
};

最初のfunction expressionは、name がないため anonymousと呼ばれるfoo variableに割り当てる。

2つ目のfunction expressionは、 bar という名前で、x variableに割り当てられている。名前のあるfunction expressionがより好ましいが、anonymous function expressionもまだ一般的である。

詳しくは Scope & Closures 参照。

Immediately Invoked Function Expressions (IIFEs)

前項の他にもfunction expression(関数式)を実行する方法がある。

(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"

外側の( .. )は(function IIFE(){ .. }) function expressionを囲っており、normal function宣言として扱わるのを防ぐために必要とされる。
function最後の() は、直前に参照されているfunction expressionを実際に実行するものである。

function foo() { .. }

// `foo` function reference expression,
// then `()` executes it
foo();

// `IIFE` function expression,
// then `()` executes it
(function IIFE(){ .. })();

上記の2つのfunction referenceはどちらも直後に()で実行される。

なぜならIIFEはただのfunctionで、functionはvariable scopeを作るり、このfunctionでIIFEを使うと、IIFEの外側のコードに影響を与えないvariableを宣言することがよくある。

var a = 42;(function IIFE(){
var a = 10;
console.log( a ); /
})();
console.log( a );
VM990:5 10
VM990:8 42

IIFEはreturn value(戻り値)ももつ。

var x = (function IIFE(){
return 42;
})();

x;
42

この42 value はIIFE functionが実行されたreturnをとり、xに代入される。

Closure

closureはJSで最も重要な事の1つだが、あまり理解されていない。ここでは概要だけをみる。

function makeAdder(x) {
// parameter `x` is an inner variable

// inner function `add()` uses `x`, so
// it has a "closure" over it
function add(y) {
return y + x;
};

return add;
}

外側のmakeAdder(..)への各呼び出しで返されるadd(..) function内部への参照は、makeAdder(..)に渡されたx valueが、何であろうと、覚えていることができる。

// `plusOne` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusOne = makeAdder( 1 );

// `plusTen` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusTen = makeAdder( 10 );

plusOne( 3 ); // 4 <-- 1 + 3
plusOne( 41 ); // 42 <-- 1 + 41

plusTen( 13 ); // 23 <-- 10 + 13

plusOne(3)は、3(y)を1(覚えられている1)に加え、4という結果になる。
plusTen(13)は、13(y)を10(覚えられている10)に加え、23という結果になる。

Modules

JSでclosureが最も使われるのは、module patternだ。moduleは外界から隠されたprivateな実装詳細(variable, function)と外側からアクセス可能なpublic APIを定義できる。

function User(){
var username, password;

function doLogin(user,pw) {
username = user;
password = pw;

// do the rest of the login work
}

var publicAPI = {
login: doLogin
};

return publicAPI;
}

// create a `User` module instance
var fred = User();

fred.login( "fred", "12Battery34!" );

User() functionは username と password variable をもつ外部scopeにとして、また内部の doLogin() function として機能する。これらは外部からアクセスできない User moduleのプライベートな内部詳細である。

ここでは意図的に普通のことに思えるかもしれないが new User() を呼べない。User()はただのfunctionで、例示されるclassではない、なので通常呼ばれる。new を使うのは、不適切で、リソースの無駄である。

User()を実行すると、User moduleのinstance(実証)が作られる。全く新しい scopeが作成され、それゆえこれらの内部variable/functionそれぞれの全く新しいコピーが作成される。このinstanceを fred に代入する。もし User() を実行すると、fredから完全に切り離された、新たなinstanceを得る。

doLogin() functionの内部は、username と password を closureする。つまりUser() function の実行が終わったあとも、アクセスを維持することができる。

publicAPIは1つの property/methodを持つobjectで、loginはdoLogin()functionの内部を参照する。

外側のUser() functionはすでに実行を終えているのがポイント。通常 usernameやpasswordのような variableの内部は消える。しかし、ここでは違う。なぜならlogin() functionの中の closure が、それらを維持するからだ。

そのため fred.login(..)を呼ぶことができる。(doLogin(..)の内部を呼ぶことと同じ)そして、variable内部の username や password にまだアクセスすることができる。

cosureについて詳しくは the Scope & Closures title of this series for a much more in-depth exploration 参照。

this Identifier

ここでは概念を完結に説明。 this は”object-oriented patterns” に関係するとように見える一方で、 JSでは this はことなるメカニズムである。

functionが this referenceを内部に持つと、 this referenceはいつも object を指摘する。しかし、どのobjectを指摘するかは、functionがどう呼び出されたかで決まる。

重要でかつ誤解しやすいが、thisがfunction自体を参照しない。

function foo() {
console.log( this.bar );
}

var bar = "global";

var obj1 = {
bar: "obj1",
foo: foo
};

var obj2 = {
bar: "obj2"
};

// --------

foo(); // "global"
obj1.foo(); // "obj1"
foo.call( obj2 ); // "obj2"
new foo(); // undefined
  1. foo() は non-strict modeでは、this を global objectにセットし終える。 strict mode では、 this はundefined で、bar propertyにアクセスし 、エラーとなる。よって、 global は this. bar に発見された valueである。
  2. obj1.foo() は this を obj1 object にセットする。
  3. foo.call(obj2) は this を obj2 object にセットする。
  4. new foo() は this を brand new empty object にセットする。

this を理解するには、question のfunctionがどう呼ばれるか実験するのがいい。

this についてさらに詳しくは、Chapters 1 and 2 of the this & Object Prototypes title を参照。

Prototypes

JSのprototypeメカニズムはかなり複雑だ。ここでは軽く説明する。

objectのpropertyをreferenceするとき、もしpropertyが存在しなければ、JSは自動的にobjectの内部prototype referenceを、そのpropertyを探すための他のobjectに使う。

あるobjectが作成されるときにfallback(後退)し、内部 prototype reference linkage(結合)が発生する。

var foo = {
a: 42
};

// create `bar` and link it to `foo`
var bar = Object.create( foo );

bar.b = "hello world";

bar.b; // "hello world"
bar.a; // 42 <-- delegated to `foo`

a property は実際には bar object に存在しないが、 bar が foo とprototype-link しているので、JSは自動的に foo object の a を探すようにfall backする。

このlinkageはおかしいように思える。最もよく使われるのが、 classメカニズムと inheritance を模倣しようとするときだ。

しかし、 behavior delegation がもっと自然だ。リンクされたobjectを意図的に設計し、必要な動作がある動作の一部に対して別のobjectにdelegate(委任) できるようにする。

さらにprototypeとbehavior delegationについて詳しくは Chapters 4–6 of the this & Object Prototypes を参照。

Old & New

新しい技術は古いブラウザで使えないことがある。最新の技術は安定して使えるブラウザがないことさえある。
JSの新しい機能を古いブラウザで使うためには、polyfilling と transpilingがある。

Polyfilling

“polyfill” はRemy Sharp によって開発され、(https://remysharp.com/2010/10/08/what-is-a-polyfill) 新しい特徴の定義を参照し、同じ動作をするコードを作るり、古いJS環境で使うことができる。

if (!Number.isNaN) {
Number.isNaN = function isNaN(x) {
return x !== x;
};
}

if statementはすでに存在する定義がES6ブラウザでpolyfill definitionするのを防ぐ。もしまだ存在しなければ、 Number.isNaN(..)を定義する。

ここではNaN valueを使ったが、これは全ての言語でそれ自身と equal でないvalueだ。 NaN valueが x!== x が true となる唯一のvalueである。

ただし、全ての新しい特徴がpolyfillableできる訳ではない。使用する際は最新の注意が必要。

もしくは信頼できるpolyfills セットを使うべきだ。
ES5-Shim (https://github.com/es-shims/es5-shim)
ES6-Shim (https://github.com/es-shims/es6-shim).

Transpiling

新しいコードを古いコードに変換するのには、transpilling が polyfill よりもいい。transpillingは transforming+compiling という意味。
基本的に新しいsyntax formでコードは書かれるが、ブラウザに展開するのは古いsyntax formである。ビルドプロセスでtranspilerを入れることは、コードlinterやminifierに近い。

なぜ古いコードをそのまま書かないのか?それにはいくつかの理由がある。

  • 新しいsyntaxが言語に加えられるのは、コードをより読みやすく、維持しやすくするためだ。自分のためだけではなく他のメンバーのためにも、新しくすっきりしたコードを書く方が好ましい。
  • 古いブラウザでtranspileし、新しいブラウザでは新しいsyntaxを使えば、最適化されたブラウザのパフォーマンスが使用できる。これによって、ブラウザメーカーがより実装や最適化のテストをし、現実的なコードを使うことができる。
  • 新しいsyntaxを早く使うことが、実用でのテストとなり、JS作者たちにフィードバックを提供できる。これにより早い段階で修正ができる。

ES6で加えられた“default parameter values.” を例に見てみよう。

function foo(a = 2) {
console.log( a );
}
foo(); // 2
foo( 42 ); // 42

簡潔である。これが古いコードだと

function foo() {
var a = arguments[0] !== (void 0) ? arguments[0] : 2;
console.log( a );
}

このように書く必要がある。

さらに、たとえ古いブラウザでも、いいsyntaxを使えることで、意図している動作をわかりやすく説明することができる。

最後にtranspilersの重要ポイントは、JS開発のエコシステムとプロセスとして標準と考えられていることだ。JSは以前より急速に発展し、次々と新しいsyntaxや特徴が加えられている。

transpilerをデフォルトで使えば、使いたいと思ったときにいつでも新しいsyntaxに切り替えることができる。

こちらも参照。

Non-JavaScript

これまではJS言語自体を学んできた。JSは多くの場合ブラウザのような環境で働く。JSはJSによって直接操作される訳ではない。

最もよくあるnon-JavaScriptは、DOM APIだ。

var el = document.getElementById( "foo" );

このコードがブラウザで動作しているとき、document variableはglobal variableとして存在しているが、それはJSエンジンに供給されておらず、JSの仕様でコントロールされてる訳でもない。普通のJS objectのように見えるformをとっているが、そうではない。それはspecial objectで、よく”host object”と呼ばれる。

document の getElementById(..)は標準JS functionのように見えるが、ブラウザのDOM に供給された、built-in methodに現れたインターフェイスに過ぎない。最新のブラウザではこのレイヤーがJSにある場合もあるが、古いものではDOMと、その挙動はCやC++のように実装されている。

次の例でinput/outputを見てみよう。

alert(..)もはJSエンジン自体ではなく、ブラウザによってJSプログラムに提供されている。

console.log(..)も同様。

このシリーズではnon-Javascript JavaScript メカニズムは実質カバーされていないが、JSプログラムを書く際に注意しよう。

【わからなかったこと】

パート毎にはなし。

【感想】

1つずつ学んできたことが線になってない感はある。
今までは項目を
段落毎など細かく読む→まとめる→実行する

という順番だったが、

今回は
その項目に書かれていることを一通り全部読む→書く を先に行い、実行するに変えた。

こっちの方が書かれていることの書かれていることの解読に時間をとられるケースが少なかった。

--

--

Tatsuya Asami
Tatsuya Asami

Written by Tatsuya Asami

Front end engineer. React, TypeScript, Three.js

Responses (1)