目次
- 自己紹介
- はじめに(想定読者/持ち帰ってほしいこと/お断り)
- 背景
- 書籍紹介/内容
- おわりに
自己紹介
- 2021/8~ PHPを使った開発に着手
- 2021/11~ Vue.jsを使ったタスクにも着手
- 最近ではちょっとづつレビュワーとしても動くようになりました。
想定読者
- あくまでメインはサーバーサイドorインフラでフロントはちょっとしか書かないよ、って方
- JSを基礎からきちんと学べていない方
持ちかえってほしいこと
- JSってこんなにクセあるんだ~と再確認してもらう
- 自分の認識(主にサーバーサイド言語)とのギャップを実感してもらう
お断り
かなり、JSの土台の話になるので、、、
- フロントエンドメインの使い手の方
- 既にキチンと基礎からJS学ばれている方
- 逆にJS、JSライブラリ、JSフレームワークを実務で全く使っていない方
にとってはムダな時間になると思いますので、休憩、作業頂いて全く構いません
背景
Vue.jsでとあるタスクに取り掛かっているときに以下のコードをかいた
処理内容は「storeメソッド内で、dataプロパティに設定した配列型のプロパティarray
が空の時、何もしない」というもの
...
<script>
data() {
return {
array: []
}
},
methods: {
store() {
if (! this.array) {
// this.arrayに値が存在しない時の処理
return;
}
// this.arrayに値が存在した時の処理
// こっちに入る。。。
}
}
...
</script>
しかし、、、実際にブラウザで実行してみるとarray
が空の時、想定とは逆の挙動(if文に入らない)
僕の脳内
「PHPでは空配列だったらif文の処理に入ってくれるのに、なぜだ!!」
$array = [];
if (! $array) {
echo 'empty!';
} else {
echo 'has data!';
}
// 出力:empty!
改めてJSのクセを実感し、基本からJSの言語仕様を知りたい、と決意
※あくまでPHPとの比較なので、他言語を含めて「クセ」と書いているわけではありませんのでご了承ください。
参考書籍
タイトル
「開眼!JavaScript」(オーライリー社)
Amazon URL
公式サイト
内容
- オブジェクト、this、配列…などJSの構成要素ごとに章が区切られ、各要素を使う際の決まりや注意点が記載
- シンプルなサンプルコードも多く掲載されている。サンプルコードはJSfiddleで確認することができる
- ページ数は160ページなので、サクッと読める
- JSfiddle見ながらでも10時間あれば読み切れそう
⇒今回は全16章の内、約50%を占める1~4章を共有
⇒なかでも個人的インパクトが大きかったものを5つピックアップ
内容
1, リテラルを使って値を生成
var myString = new String('male'); // 文字列オブジェクト(実際のコーディングでは非推奨)
var myStringLiteral = 'male'; // リテラルによる生成(プリミティブ型。≠オブジェクト)
var myArray = new Array('foo', 'bar'); // 配列オブジェクト(実際のコーディングでは非推奨)
var myArrayLiteral = ['foo', 'bar']; // リテラルによる生成
console.log(myString.constructor, myStringLiteral.constructor);
console.log(myArray.constructor, myArrayLiteral.constructor);
// 出力
function String() { [native code] }, function String() { [native code] } // 同じもの
function Array() { [native code] }, function Array() { [native code] } // 同じもの
- リテラルはnewを使う形と一緒!
- リテラルは無意識に使うことだが裏側ではオブジェクトを生成している、ことが注意!
- 基本はオブジェクトを生成している
- 但しString, Number, Booleanはリテラルによる宣言をするとプリミティブ型として値が生成される
- ただ、オブジェクトのような「ふるまい」ができる??
2, プリミティブ型の値はオブジェクトのように扱うとオブジェクトのようにふるまう
// プリミティブ値を宣言
var primitiveString1 = "foo";
var primitiveString2 = String('foo'); // new演算子を使っていないのでプリミティブ値となる
var primitiveString3 = String('foo'); // new演算子を使っているのでオブジェクトとなる
var primitiveNumber1 = 10;
var primitiveNumber2 = Number('10'); // new演算子を使っていないのでプリミティブ値となる
console.log(primitiveString1.toString(), primitiveString2.toString());
// 出力:"foo" "foo"
console.log(primitiveString3.toString()); // これが使えるのはわかる
// 出力:"foo"
console.log(primitiveNumber1.toString(), primitiveNumber2.toString());
// 出力:"10" "10"
// プリミティブ値を宣言
var myNull = null;
var myUndefined = undefined;
console.log(myNull.toString());
console.log(myUndefined.toString());
// 出力 : エラー
// nullとundefinedは対応するコンストラクタを持っていないためオブジェクトに変換されない=toStringというメソッドはどこにも存在しない
- toString : オブジェクトの生成時に自動で生成されるインスタンスメソッド
- プリミティブ型では本来使えないはずだが、実際はエラーにならずオブジェクトのように使用できる
- これが「オブジェクトのようにふるまう」ということ
3, プロパティへの参照はどのように解決されるか
var myArray = [];
console.log(myArray.foo);
// 出力:undefined
- JavaScriptは以下の順序でundefinedを返している
- myArrayにfooプロパティを探しに行く⇒存在しないので上位のオブジェクトを探しに行く
- 次にArray.prototypeにfooプロパティを探しに行く⇒ここにも存在しないためさらに上位
- 次にObject.prototypeにfooプロパティを探しに行く⇒ここにも存在しないため、undefinedを返す
- これがプロトタイプチェーン(継承元⇒継承先)
- 最終点はObject.prototypeでここになければ、undefinedを返す
// myArrayは配列オブジェクト
var myArray = ['foo', 'bar'];
console.log(myArray.join());
// 出力 : "foo, bar"
console.log(myArray.hasOwnProperty('join'));
// 出力:false
- メソッドも一緒のような動き
- join() : 配列を文字列に置換するメソッド(ざっくり説明なので詳細は公式へ)
- myArrayにjoinがあるか否かの判定はfalse
- しかし、myArrayでjoinが使える
- join()は本来Array.prototype.joinで定義されているが、myArrayの宣言時にArrayコンストラクタ関数が裏で定義され、同時にArray.prototypeにjoin()が追加された
- 尚、hasOwnProperty自体もObject.prototype.hasOwnPropertyに定義されているので、同じ類
- こちらを見ながらだとイメージしやすい
4, オブジェクトは同値判定に参照を使用
var objectFoo = { same: 'same' };
var objectBar = { same: 'same' };
// 厳密等価演算子でも等価演算子でも結果は一緒
console.log(objectFoo === objectBar);
// 出力:false
console.log(objectFoo == objectBar);
// 出力:false
// オブジェクトが同値とみなされる場合
var objectA = {foo: 'bar'};
var objectB = objectA;
console.log(objectA === objectB);
// 出力:true
// これらのオブジェクトは同じオブジェクトを参照しているため同値
objectA.foo = 'hogehoge';
console.log(objectA);
// 出力 : { foo: "hogehoge" }
console.log(objectB);
// 出力 : { foo: "hogehoge" }
- 値が同じか否かは関係ない
- 同じ「アドレス」を参照しているか否かが関係する
- 「アドレスを参照する」のイメージが湧かない場合はこちらを見ながらだとイメージつきやすい
5, 関数に引数を渡す
var addFunction = function(number1, number2) {
var sum = number1 + number2;
return sum;
}
console.log(addFunction(3, 3)); // 出力:6
console.log(addFunction(3)); // 出力:NaN ⇒ これはまだわかる。戻り値で気づくから
console.log(addFunction(3, 3, 3)); // 出力:6 ⇒ これは勘弁してほしい
- JavaScriptの嫌いな挙動No1
- 実引数が仮引数より少ない場合、実引数で指定していない仮引数にはundefinedが代入
- 実引数が仮引数より多い場合、関数内で仮引数の数よりあふれる実引数へのアクセスはargumentsを使う
- ES6は残余引数
...args
が推奨
- ES6は残余引数
- ただ一言「実引数と仮引数の数が違うならエラーを吐いてほしい…」
- メリットで用いられる「引数の数が決まっていない場合」ってのがイメージが湧かない…(配列にすればいいのでは?)
ちなみに
※コメントで頂いたご指摘により、内容修正しました(2022/02/14)。
@raccy 様、ありがとうございました。
では、そもそもなぜ当初のコードが(自分の)想定外の挙動を起こしたのかというと、「それがJavaScriptの言語仕様だから」という点に尽きます。
...
<script>
data() {
return {
array: []
}
},
methods: {
store() {
if (! this.array) {
// this.arrayに値が存在しない時の処理
return;
}
// this.arrayに値が存在した時の処理
// こっちに入る。。。
}
}
...
</script>
arrayには空の配列ではあるが、内部的にArrayオブジェクト(及びObjectオブジェクト)から継承しているので、Arrayオブジェクト自体は存在すると判断され、! this.array
自体がfalseを返したんだな~と納得しました。
コメントで頂いた
大事なのは、何が真値なのか偽値なのかを言語毎に理解することです。
と
真偽についての正確な情報はその言語の公式なドキュメントを参考にしてください。言語仕様は常に変化しているため、バージョンによっては変更される可能性もあることも注意してください。
が言語仕様を理解する大事なことだと思います。
自分のように少ない経験から導いた仮説だけで結論を出すのは非常に危険だ、と実感しました。
最後に
- 個人的には時間的制約がなく、JSだけに限れば、新しいフレームワーク触るよりもJavaScriptを深く知る方が、後々活きてくると思って着手しました
- 新しいライブラリ、フレームワークのキャッチアップが早く、深くなることを期待
- 本書籍が終わったらJavaScript: The Good Partsに着手し、より実用的なJSを知ろうと思います。
- あと、認識間違ってそうなところあれば、気軽に指摘ください!
ご清聴ありがとうございました。