Yabu.log

ITなどの雑記

JavaScriptパターン第4章ノート

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

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

Wikipedia

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パターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

コンストラクタよりリテラルの方が良い・・・が一貫したこの章のテーマです。

オブジェクトリテラル記法

キーと値の組みをカンマで区切り、波括弧で囲う記法でオブジェクトを定義できる。

数値、文字、関数、正規表現、配列、別のオブジェクト を含めることができる。

コンストラクタを使う方法は冗長なのでオブジェクトの宣言はなるべくオブジェクトリテラルで行うべき

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から住所を取得

geoapi.heartrails.com

最寄駅を取得可能なハートレイルズ様の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パターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

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に似たようなアドオンを入れた覚えがある。

*1:JSONがオブジェクトリテラルだったという驚くべき話は次回へ続く

JavaScriptパターン第1章ノート

JavaScriptパターン ―優れたアプリケーションのための作法

JavaScriptパターン ―優れたアプリケーションのための作法

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

github.com

ググりまくった結果、travis.ymlにnodeのバージョンが設定されていなかったため、travis上ではかなり古いバージョンが動いていた?

ローカル

$ node -v
v8.8.1

travis

$ node --version
v0.10.48

ES6が対応していないバージョンのnodeが動いていたのか?とりあえずtravisで動かすnodeなどのバージョンはtravis.ymlで設定できるらしいから、バージョン指定を追加したところテストが成功した。

今まで動いていたものが急に壊れた理由はES6の文法を無理やり突っ込んだからだろう。phantomJsではlet文は使えなかったが、constは使えたので入れ込んだままにしていた。それが原因だと思う。

初心者が適当に作ってググりながらなんとか運用している感が強いが、英語の情報を我慢して読めば、トラブルシューティングになかなか役立つ情報に出会える(今回の事象は英語にアレルギーを持ったまま日本語オンリーでググってたら解決できなかった)

私のようなSIの底辺コピペプログラマーでも英語が我慢できるかできないかで結構差がつくと思う。

やっぱり英語の情報を根気よく読むことが大事だ。

*1:最近気になってるbabelのリポジトリじゃないですか

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のバージョンで管理できる。

前例を調査中・・・

*1:関数の中でスコープが共通でない別関数のvar宣言された変数をグローバルスコープとして参照するような変なことをしていない限り、この置換は問題ないはず。

*2:https://github.com/aurelia/cli/issues/259

multi-word-replacerのgithub-pagesにbootstrapを被せた

https://yuyabu.github.io/multiple-word-replacer/

bootstrapの公式ドキュメントを読み込んでレスポンシブデザインに対応した。 暇な人はブラウザのウインドウのサイズを縮めてみたり、携帯からアクセスしてみて欲しい。

まぁ、このツールを携帯から参照することなんてないと思うけど・・・

ちなみにcssは一行も書いていない。 過去にbootstrap含む複数のフレームワークcssと勝手に追加した意味不明な自作cssが干渉しあって大変な目にあった反省から できるだけbootstrap標準のものを利用する方針で適応した。

フロントエンドで大規模なものを作るときに、構成管理とかがきちんとできていないと炎上するんだろうなぁ。 凄まじいことになりそうだ。cssのクラスやidなどの管理方針などをきちんと考えられない・画面の設計ノウハウのないプログラマーcssなんて一行も書かないに限るね。

過去に俺がググってコピペで適当に組み合わせたcssを直してる人いたらごめんなさい。

フロントエンドエンジニアさん南無〜