JavaScriptパターン第4章ノート
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (76件) を見る
4章のテーマは関数です。
関数の記述方法
関数式
Var add = function add(a,b){ return a + b; };
Addを削除して無名関数にすることも可能。
関数宣言
function foo(){ //処理内容 }
関数式と関数宣言の違いについて
- 式は使用される前に記述されている必要がある、
- 宣言は使用箇所の後に書いても問題ない(呼び出すことができる)
- 宣言はグローバル空間か別の関数の中にしか存在できない。
- 式で宣言したものは関数の引数で指定したり、戻り値として返すこともできる。
//- 式は使用される前に記述されている必要がある、 let num = add(1,2) Add = function(a,b){return a+ b}; //宣言は使用箇所の後に書いても問題ない(呼び出すことができる) num = add2(1,2) function add2(a,b){return a+b}
巻き上げ時の動作の違い
関数式は変数に代入しているので巻き上げが起こった場合エラーになってしまう 関数宣言の場合は新たに宣言した方で呼び出される
function test1(){ console.log("test1"); } function test2(){ console.log("test2"); } function hoisting(){ console.log(typeof test1);//function console.log(typeof test2);//undefined test1();//hoisting test1 test2();//Uncaught TypeError: test2 is not a function function test1(){ console.log("hoisting test1"); } var test2 = function(){ console.log("hoisting test2"); } }
ちなみに関数式の末尾にはセミコロンを必ずつけるべき(つけなくても補間されるけどね)
APIパターン
コールバック
関数を引数として別の関数に渡すことをコールバックという 渡すときには丸括弧()をつけない。(つけると呼び出した時点でも実行されちゃう…)
手頃なサンプルとしてArray.prototype.map()がある。 https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map
var test = [1,2,3].map(function(a){ return a *a; }); //ES2015 allow function ver var test = [1,2,3].map((a) =>{ return a *a; });
余談ですが、1年ほど前にpythonを勉強している時にmapとreduceというのは使いまくった覚えがあります。
またまた余談ですが、ネットを眺めると、関数は常に()付きで扱う、的な勘違いをして無駄に実行してしまうサンプルなどが散見される。qiitaなら優しい人がその丸カッコは不要ですよ〜って指摘してくれるけど
カリー化
Wikipediaの例はわかりやすかったが、本書の例は結構難しい。
本書の例
function schofinkelize(fn){ var slice = Array.prototype.slice, stored_args = slice.call(arguments,1); return function(){ var new_args = slice.call(arguments), args = stored_args.concat(new_args); return fn.apply(null,args); } } //nomal function function add(x,y){ return x + y; } // currying var newadd = schofinkelize(add,5); newadd(4);//9
function div(x, y) { return x / y; } //div(1, 3) == 1 / 3, これはカリー化ではない。 function cdiv(x) { return function(y) { return div(x, y); } } console.log(cdiv(10)(3)); //=> 10/3 = 3.333... var inv = cdiv(1); console.log(inv(2)); //=> 1/2 = 0.5
引数の一部を部分的に適応した関数を新たに作る、程度の認識。 callbackを使うとこんなこともできるよ、という例で出したため今回はあまり掘り下げず、 Javascriptで関数型プログラミングを学ぶ素敵な本を見つけたのでそこでみっちりやる予定。
JavaScriptパターン第3章ノート
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (76件) を見る
コンストラクタよりリテラルの方が良い・・・が一貫したこの章のテーマです。
オブジェクトリテラル記法
キーと値の組みをカンマで区切り、波括弧で囲う記法でオブジェクトを定義できる。
数値、文字、関数、正規表現、配列、別のオブジェクト を含めることができる。
コンストラクタを使う方法は冗長なのでオブジェクトの宣言はなるべくオブジェクトリテラルで行うべき
var obj1 = { sample_string:"normal val",// string literal sample_number:5, //number literal sample_function:function(){ console.log("this is test function"); }, //function literal sample_regexp:/normal/gi, //regure expression literal sample_array:[1,1,9], // array literal obj2:{ sample_string:"this is nested object" } } //実験 console.log(obj1.sample_number + 15);//20 console.log(obj1.sample_regexp.test(obj1.sample_string));//true console.log(obj1.sample_array.find((num)=> {return num > 8}));//9 console.log(obj1.obj2.sample_string);//this is nested object var val1= obj1.sample_function; val1();//this is test function console.log(obj1);//{sample_string: "normal val", sample_number: 5, sample_regexp: /normal/gi, sample_array: Array(3), sample_function: ƒ, …}
カスタムコンストラクタ関数
Javascriptにはクラスがないので、クラスをインスタンスするためのコンストラクタ、というより、Objectを作成する関数という扱いなのか?そこがちょっとよくわからなかった。
var Person = function (name) { this.name = name; this.say = function () { return "I am " + this.name; }; }; val John = new Person("john"); John.say();// I am John
コンストラクタにnewをつけ忘れると
コンストラクタは只の関数なので、newをつけずとも呼べてしまうが、その場合コンストラクタ内部でのthisはグローバルオブジェクト(window)を指してしまう
//※上記のPersonカスタムコンストラクタ関数を流用しています var ken = Person("ken"); ken.say();//Uncaught TypeError: Cannot read property 'say' of undefined console.log(ken)//undefined window.say();//I am ken say();//I am ken
初期化中のオブジェクトにプロパティを追加していると思いきや、グローバル変数を作ってるという事態になりかねない。対策としては
- strictモード
- コンストラクタ内で別のオブジェクトリテラルを返す
- コンストラクタ内でthisを使わない
- thisの型を検査し、適切に自オブジェクトをnew付きで呼び出す(自己呼び出しコンストラクタ)
のどれかが対策としては推奨される
配列リテラル記法
配列も組み込みコンストラクタではなくリテラルを使うべき。理由は数値型を単発で与えた時の挙動が特殊なため
var arr1 = new Array(3); //空の、長さ3の配列を作成 var arr2 = [3];//要素が 3 一つの配列を作成 arr1//[empty x 3] arr2//[3] var arr3 = new Array(3.14);//長さに小数点は採用できない・・・(エラー) //Uncaught RangeError: Invalid array length
JSON
JSONでは関数や正規表現リテラルは使えないが、配列と文字だけを扱うならオブジェクトリテラルとして解釈可能。
JSONってweb apiとかで帰ってくる値でよく見る気がするが、そのままオブジェクトリテラルとして解釈できるのに驚いた。
例:WEB APIから住所を取得
最寄駅を取得可能なハートレイルズ様のAPIを利用しました。郵便番号はGoogle日本法人のオフィス(〒106-6126)にしました。
http://geoapi.heartrails.com/api/json?method=getStations&postal=1066126
{"response":{"station":[{"name":"\u516d\u672c\u6728","kana":"\u308d\u3063\u307d\u3093\u304e","line":"\u6771\u4eac\u30e1\u30c8\u30ed\u65e5\u6bd4\u8c37\u7dda","y":35.662799,"x":139.731151,"postal":"1060032","prev":"\u795e\u8c37\u753a","next":"\u5e83\u5c3e","prefecture":"\u6771\u4eac\u90fd","distance":328.0940048843225}]}}
こちらの取得結果をevalでjavascriptで評価して見ます*1
var json_for_eval = '{"response":{"station":[{"name":"\u516d\u672c\u6728","kana":"\u308d\u3063\u307d\u3093\u304e","line":"\u6771\u4eac\u30e1\u30c8\u30ed\u65e5\u6bd4\u8c37\u7dda","y":35.662799,"x":139.731151,"postal":"1060032","prev":"\u795e\u8c37\u753a","next":"\u5e83\u5c3e","prefecture":"\u6771\u4eac\u90fd","distance":328.0940048843225}]}}' eval('var nearestStation = ' + json_for_eval); nearestStation.response.station[0].name;//六本木 nearestStation.response.station[0].line;//東京メトロ日比谷線
ただしJSONではオブジェクトリテラルでは表現可能な関数、正規表現のリテラルは記載できません。
あとevalを使ってJSONをパースするのはアンチパターンなのでJSON.parseを利用するのがベターでしょう。
正規表現リテラル
リテラルを使う場合はコンストラクタで必要なエスケープが一部不要。簡潔性のため積極的にリテラルを使うべき。
ただし正規表現リテラルのオブジェクトは構文解析時にしか作られないため、実行時に組み立てるようなものはリテラルではなくコンストラクタを使うべき。
*1:evalは引数の文字列ををjavascriptとして解釈して実行しようとする関数です
JavaScriptパターン第2章ノート
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (76件) を見る
4章まで読めたが、アウトプットが捗らない。5章は写経しないと俺の脳みそにはきつい。
補足:即時関数について
本記事では即時関数を利用しています。ピンとこない方は別記事に書いたものを読んでから本記事を読んでみてください。
Javascriptの変数スコープ
関数ごとにスコープを持つか、グローバル変数化のどちらかであるが、プログラミングの鉄則としてグローバル変数は極力使わないほうがいいという前提に立てば、意図せずグローバル変数を作ってしまう事象は極力避けるべきである。
暗黙のグローバルを避けよ
Varを使わない変数宣言は避けるべき。グローバルスコープを意図せず汚染する恐れがあるため
(function(){ test = "hello" //暗黙のグローバル変数 console.log(test) }()); console.log(test); //hello
コード二行目のtestの宣言のvarを略さず影ば以下のようなエラーが出る。
ReferenceError: Can't find variable: test
代入の連鎖は禁止。片方がグローバル変数になる
(function(){ var b=a="test" console.log("a is " + a+ " in function");//test console.log("b is " + b+ " in function");//test }()); console.log("a is " + a+ " outside function");//test console.log("b is " + b+ " outside function");//エラー(スコープ外)
暗黙的、明示的なグローバル変数の違い
暗黙の変数はdelete演算子で削除できるが 明示的に宣言したグローバル変数は削除でない
暗黙のグローバル変数は変数ではなく グローバルオブジェクトのプロパティとなるため。 変数は削除できないがプロパティは削除できる
var globalVal ="global"; (function(){ implicitVal = "implict global"; }()); //どちらもglobalスコープ console.log(globalVal); console.log(implicitVal); delete globalVal; delete implicitVal; console.log(globalVal);//global console.log(implicitVal);//ReferenceError: Can't find variable: implicitVal
変数の巻き上げ
実に恐ろしい恐ろしい。 一般的に変数は利用箇所の近くで宣言するのが定石だが ES6以前の環境では明示的に関数の先頭に変数を宣言するのが望ましい。let,constの場合はreference errorになるが、巻き上げは起こっているらしい。
var val1 = "test" console.log(val1); //test (function(){ //↓変数の巻き上げ!!!!!↓ console.log(val1);//undifind var val1 = "testtest"; console.log(val1); }()); console.log(val1)
単独varパターン
ちょっと気持ち悪いが巻き上げによるバグを喰らわないためには致し方ない
(function test(){ var a=1,b=2,c=3; console.log(a+b+c); }());
eval()はワル
eval関数は引数の文字列をjavascriptのコードとして解析して実行しようとする。 eval("alert('eval is evil!!!!')"); 本書ではJSONのパースにevalを使ってはいけない、という例が出てきた。*1
XSSなどのセキュリティーリスクがあるので注意。
コードの中で eval() が使われているのを見つけたら、「eval() はワル」という呪文を唱えましょう。
余談ですが、この部分はeval() is Evilの有名なダジャレだけど、訳したり説明したりする気が一切ないのがちょっと切ない。
波括弧の位置
細かいイディオムの話は個人の嗜好でしかないと思うし、統一さえ取れて入ればなんでもいいと思うけど、波括弧の前に改行を入れるのはやめた方がいいと思った
function func() { return {name: "Batman" }; } var test = fund(); console.log(typeof test);//undefined
上記の例だと二行目returnのあとにundifindが補完されreturn undifind
となり、続くオブジェクトリテラルは無視される。
一般的な命名ルール
コンストラクタは大文字始まりのキャメルケース 変数、関数は小文字始まりのキャメルケース
ただし変数にアンダースコアを含めたり、アンダースコアから始めることで変数の意味を補完するノウハウが紹介されていた
ドキュメンテーションについて
JSDoc Toolkitというjavadocのようなものがあり、そちらを利用できる。
多分Eclipseのようにメソッドの上で/**
と入力し改行すると、ドキュメントのテンプレートが入るようなプラグインが各エディタにあるはず。昔salesforceのapex開発をしていた時にsublime textに似たようなアドオンを入れた覚えがある。
JavaScriptパターン第1章ノート
JavaScriptパターン ―優れたアプリケーションのための作法
- 作者: Stoyan Stefanov,豊福剛
- 出版社/メーカー: オライリージャパン
- 発売日: 2011/02/16
- メディア: 大型本
- 購入: 22人 クリック: 907回
- この商品を含むブログ (76件) を見る
Javascriptはオブジェクト思考の言語である
ネィティブオブジェクト 言語側が用意 Arrayなど
Hostオブジェクト:ホストや環境が用意 Windodwなど
が、クラスという概念はない
Prototype
継承などの再利用の鍵となる
Prptopypeはオブジェクトであり、あらゆる関数はprototypeプロパティを持つ
ES5
ES3が99、ES4がポシャったため出版時の最新バージョンとなる
本書執筆時点で搭載ブラウザがないとかいてあるが、こんにちではつぎのバージョンであるES6がまともなブラウザには全て搭載れているという点にフロントエンドの変化の速さを感じる
Strict modeはes5から導入らしい。勝手に6からだと思っていた
JSLintの紹介
JavaScript はインタプリタ言語のため静的な構文解析が存在しない JSLintという静的解析ツールを使い、実行前に構文エラーなどはチェックすべき
自動化したテストがたくさんエラーを吐いている
さて、最近導入してみたKarma + jasmineなテスト環境だが ローカルでは成功するが、travis上のものがエラーを吐いている。
Travis CI - Test and Deploy Your Code with Confidence
const Hoek = require('hoek'); ^^^^^ SyntaxError: Use of const in strict mode.
strict modeやらconstやらまたES6か、と検討をつけつつエラーメッセージをググってみる。nodeのバージョン指定から7を消したらうまくいったとの事例を発見。*1
ググりまくった結果、travis.ymlにnodeのバージョンが設定されていなかったため、travis上ではかなり古いバージョンが動いていた?
ローカル
$ node -v v8.8.1
$ node --version v0.10.48
ES6が対応していないバージョンのnodeが動いていたのか?とりあえずtravisで動かすnodeなどのバージョンはtravis.ymlで設定できるらしいから、バージョン指定を追加したところテストが成功した。
今まで動いていたものが急に壊れた理由はES6の文法を無理やり突っ込んだからだろう。phantomJsではlet文は使えなかったが、constは使えたので入れ込んだままにしていた。それが原因だと思う。
初心者が適当に作ってググりながらなんとか運用している感が強いが、英語の情報を我慢して読めば、トラブルシューティングになかなか役立つ情報に出会える(今回の事象は英語にアレルギーを持ったまま日本語オンリーでググってたら解決できなかった)
私のようなSIの底辺コピペプログラマーでも英語が我慢できるかできないかで結構差がつくと思う。
やっぱり英語の情報を根気よく読むことが大事だ。
PhantomJSではlet文が使えない
用語
- ES6:大きな変更が入った最近のjavascriptの仕様。モダンなブラウザは大体対応している
- Jasmine : javascriptのテスト環境
- Karma:テストランナー。Jasmineを動かす
- PhantomJS:CUI用のブラウザー。テストランナーが利用している
テストを書いたのでリフアクタリング!の勢いでとりあえず変数宣言のvarを全てletに置き換えたが*1テストでエラーになってしまった。
PhantomJS 2.1.1 (Mac OS X 0.0.0) ERROR SyntaxError: Expected an identifier but found 'arr2' instead
Karma runs your tests in real browser (in your case it is PhantomJs) so you should check browser support 'let' and 'const' or not. OR you should use preprocessors which compile your es6 code to es5.
ES6 variables (const and let) · Issue #2442 · karma-runner/karma · GitHub
調べてみたところ jkarma(jasmineのテストランナー)が利用するPhantomJSというテスト用ブラウザがES6をサポートしていないため 使えないようだ。
ES6にはバージョン2.5から対応するらしい(最新は2.1)
ES2015 (also colloquially known as ES6) will be possible with the new effort towards version 2.5 (see #14458 for details).
https://github.com/ariya/phantomjs/issues/14506
調査中に同じトラブルがissueに上がっているossのプロジェクト*2が何個かあったが普通にletをvarに置き換えて対応していた。ES6向けの環境に向けて作っているなら、本来ならletのままでいいはずだが、テストのためにコードを変更しているということだろうか。
レガシーコード改善ガイドじゃないけど、テストのためにスコープいじるのはやだぁ。 対策としてはプリプロセッサとしてBabelというES6→commonJSの変換ができるトランスコンパイラを指定することでテスト時には commonJSにコンパイル後のソースで実行しながら、プロジェクトのソースはES6のバージョンで管理できる。
前例を調査中・・・
multi-word-replacerのgithub-pagesにbootstrapを被せた
https://yuyabu.github.io/multiple-word-replacer/
bootstrapの公式ドキュメントを読み込んでレスポンシブデザインに対応した。 暇な人はブラウザのウインドウのサイズを縮めてみたり、携帯からアクセスしてみて欲しい。
まぁ、このツールを携帯から参照することなんてないと思うけど・・・
ちなみにcssは一行も書いていない。 過去にbootstrap含む複数のフレームワークのcssと勝手に追加した意味不明な自作cssが干渉しあって大変な目にあった反省から できるだけbootstrap標準のものを利用する方針で適応した。
フロントエンドで大規模なものを作るときに、構成管理とかがきちんとできていないと炎上するんだろうなぁ。 凄まじいことになりそうだ。cssのクラスやidなどの管理方針などをきちんと考えられない・画面の設計ノウハウのないプログラマーはcssなんて一行も書かないに限るね。
過去に俺がググってコピペで適当に組み合わせたcssを直してる人いたらごめんなさい。
フロントエンドエンジニアさん南無〜