かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JS 配列の初期化についてのメモ

長さNの配列をSで埋めて作成したい。

var a = (new Array(3)).fill('A'); // ["A", "A", "A"]

length N の配列の作成は new Array(N)
arr.fill()メソッドは第一引数で指定された値で配列の値を設定する。

arr.fill(value[, start = 0[, end = this.length]])
value 配列に設定する値。
start オプション。開始位置。
end オプション。終了位置。
出典: Array.prototype.fill() - JavaScript | MDN

配列にStringで入っている値を数値化したい。

文字列から配列を作成したような時、配列の値がStringな状態だと後の計算に支障が出やすいので数値変換したい様な時。

var str = "1 3 5";
var a = str.split(" ").map(function(i){ return i-0; }); // [1, 3, 5]

mapを使って各要素を変換する。


[参考]

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

JS 呼び出し元の this を担保するメモ。

例 オブジェクト内の関数でsetTimeoutを使ってその中で、オブジェクト自身をthisとして使いたいような場合

var obj = {
  name: 'obj',
  func: function() {
    console.log(this); // => Object
    setTimeout(function() {
      console.log(this, this.name); // => Window, "result"
    }, 100);
  }
};
obj.func();

そのまま書くと、setTimeoutで呼び出された関数のthisWindowになる
Functionが参照型だから、thisがwindowになってる? undifinedではなくwindowになる理由の理解がまだ不明瞭。

伝統的な方法

var obj = {
  name: 'obj',
  func: function() {
    var self = this;
    setTimeout(function() {
      console.log(self, self.name); // => Object, "obj"
    }, 100);
  }
};
obj.func();

thisをスコープ内にself_thisといった別の変数に割り当てて関数内で変数が定義されていなければ上のスコープから変数を探すjavascriptの仕様を利用する方法。
(self = thisobjへの参照を変数に格納してるって理解で合ってる?)

jQuery $.proxy() を使う方法

var obj = {
  name: 'obj',
  func: function() {
    setTimeout($.proxy(function() {
      console.log(self, self.name); // => Object, "obj"
    }, this), 100);
  }
};
obj.func();

jQuery.proxy( function, context )
context
Type: PlainObject
The object to which the context (this) of the function should be set.
jQuery.proxy() | jQuery API Documentation

$.proxy()は第二引数に第一引数のFunction内で this として扱うものを指定できる。

ES5 Function.prototype.bind を使う方法

'use strict';
var obj = {
  name: 'obj',
  func: function() {
    setTimeout(function() {
      console.log(this, this.name); // => Object, "obj"
    }.bind(this), 100);
  }
};
obj.func();

Function.prototype.bind()
bind() メソッドは、呼び出された時に新しい関数を生成します。最初の引数 thisArg は新しい関数の this キーワードにセットされます。2 個目以降の引数は、新しい関数より前に、ターゲット関数の引数として与えられます。

fun.bind(thisArg[, arg1[, arg2[, ...]]])
thisArg
束縛された関数が呼ばれる時、this 値としてターゲット関数に渡される値を指定します。束縛された関数が new 演算子によって構築された場合、この引数は無視されます。
Function.prototype.bind() - JavaScript | MDN

function(){}.bind(<関数内でthisとして使いたいもの>) の様な感じでFunctionのthisを拘束する。
下記のような使い方もできる。

'use strict';
var obj = {
  name: 'obj',
  say: function() { console.log( this, this.name ); }
};

setTimeout( obj.say.bind(obj), 100 ); // => Object, `obj`

[参考] es5のサポート状況

ES6 アロー関数() => を使う

まだIE11とかまだサポートされていないブラウザもありますが、ES6のアロー関数を使えば関数内のthisは呼出し元のthisが保証されるという事のようなので、次のように書く事ができます。

'use strict';
var obj = {
  name: 'obj',
  func: function() {
    setTimeout( () => {
      console.log(this, this,name); // => Object, "obj"
    }, 100);
  }
};
obj.func();

[参考] es6のサポート状況

アロー関数 - JavaScript | MDN
this を束縛しない
//(中略)
アロー関数がスコープ内の this の値を捕捉するため、
//(略)

これを読む限りアロー関数関数が定義されているスコープを関数内のthisとして使用する という事、つまり「thisが何を指すかは関数定義時に決まる」のではないかと思います。(間違ってたら指摘ください
 

ES5 から ES6 へ書き換える際の アロー関数 の落とし穴

アロー関数は関数を定義した際にそのスコープをthisとして束縛するようなので、いままで function() で書いていたものをそのまま () => に書き換えると思わぬ落とし穴にハマってしまうことがあります。

例えば下記のようなコード

// ES5
'use strict';
var obj = {
  name: 'obj',
  callback: function() {
    console.log(this, this.name);
  },
  func: function() {
    setTimeout( this.callback.bind(this), 100 );
  },
};
obj.func(); // => Object "obj"

これを単純にアロー関数に書き換えると... 👇

// ES6
'use strict';
var obj = {
  name: 'obj',
  callback: () => {
    console.log(this, this.name);
  },
  func: () => {
    setTimeout( this.callback, 100 );
  },
};
obj.func(); // => consoleには何も表示されない

これは下記と書き方と同等かと思います。({}はスコープを作成しない...はず)

var obj = {}; // new Object(); でも同じ?
obj.name = 'obj';
obj.callback = () => console.log(this, this.name);
obj.func = () => setTimeout( this.callback, 100 );

なので、アロー関数で定義されたobj.callbackobj.func 内の thisobjではなく、Window(グローバルオブジェクト)に拘束されてしまっているので上手く動作しなかったのだと考えられます。

この場合は、
アロー関数を使わずに、ES5のFunction.prototype.bind()を使って obj.callback 関数を呼び出す

'use strict';
var obj = {
  name: 'obj',
  callback: function() {
    console.log(this, this.name);
  },
  func: function() {
    setTimeout( this.callback.bind(this), 100 );
  },
};
obj.func(); // => Object "obj"

又は
thisobjに拘束してあるアロー関数内から obj.callback 関数を呼び出す

var obj = {
  name: 'obj',
  callback: function() {
    console.log(this, this.name);
  },
  func: function() {
    setTimeout( () => this.callback(), 100 );
  }
};
obj.func(); // => Object "obj"

の様な書き方にすれば上手く動作します。

クラスのようなprototypeにメソッドを付けている様な場合も同様のようです。

だめな例
'use strict';
var MyObject = function() {
  this.name = 'obj';
  return this;
};
MyObject.prototype.callback = () => {
  console.log(this, this.name); // this が Windowになってしまう
}
MyObject.prototype.func = () => {
  setTimeout( this.callback, 100); // this が Windowになってしまう
};
var obj = new MyObject();
obj.func(); // =>

callbackfunc 内の this が Window(グローバルオブジェクト)になるのでうまく動作しない。

動作する例

'use strict';
var MyObject = function() {
  this.name = 'obj';
  return this;
};
MyObject.prototype.callback = function() {
  console.log(this, this.name);
}
MyObject.prototype.func = function() {
  setTimeout( this.callback.bind(this), 100);
  // 又は
  // setTimeout( ()=>this.callback(), 100);
};
var obj = new MyObject();
obj.func(); // => MyObject, "obj"

関数の定義をコンストラクタ内などに記述すればアロー関数で定義することもできました。

'use strict';
var MyObject = function() {
  this.name = 'obj';
  this.callback = () => {
    console.log(this, this.name);
  };
  this.func = () => {
    setTimeout(this.callback, 100);
  }
  return this;
};
var obj = new MyObject();
obj.func(); // => MyObject, "obj"

※ 但し継承させたオブジェクトなどで試していないので、この書き方はもしかすると継承させたオブジェクトからだとアロー関数内のthisが継承させたオブジェクトにならないなど不具合が出る可能性がありそうな気がしています。
 

まとめ

ES5もES6もちゃんと学習をしていなくて、ES6にしたら関数はすべてアロー関数() =>にするのかな?のような曖昧な理解だったので、ES6はfunction()()=>を挙動の違いを理解して適材適所に共存させて使うんだ!と今回調べてみて少し理解が深まったように思います。

溜まっていたを技術書少しづづ読んでいます。
今読んでいる「オブジェクト指向JavaScriptの原則」という本は薄めの本ですが、あやふやなまま使っていたECMAScript5で導入されたことをベースに書かれているようなのでES6の前にしっかり読んで理解しておきたいと思ってます。


[参考]

オブジェクト指向JavaScriptの原則

オブジェクト指向JavaScriptの原則

jQuery .triggerと.onとで実行される関数へのデータの渡し方の違いメモ。

オブジェクトのイベントを呼出して、オブジェクト自身をイベント内で使いたい時とかのメモ

.trigger

.trigger( eventType [, extraParameters ] )

extraParameters
Type: Array or PlainObject
Additional parameters to pass along to the event handler.
.trigger() | jQuery API Documentation

呼び出されるイベントの第二引数以降でデータを受取ることができる。

ex

配列で渡す場合

var $d = $(document);

$d.on('trigger_event', function(evt, param1, param2) {
  console.log( param1, param2 ); // => 'foo', 'bar'
});

// 発火
$d.trigger('trigger_event', ['foo', 'bar']);

オブジェクトで渡す場合

var $d = $(document);

$d.on('trigger_event', function(evt, data) {
  console.log( data.flg ); // => true
});

// 発火
$d.trigger('trigger_event', {flg: true});

.on

.on( events [, selector ] [, data ], handler )

data
Type: Anything
Data to be passed to the handler in event.data when an event is triggered.
.on() | jQuery API Documentation

呼び出されるイベントでは event.data で受取ることができる。

ex

var myObj = {
  dispatchEventListener: function(evt) {
     console.log( evt.data.self); // => myObj
  }
};

var $d = $(document);
$d.on('dispatch_event', {self: myObj}, myObj.dispatchEventListener);

// 発火
$d.trigger('dispatch_event');

.trigger と .on 両方からデータを渡す

データの渡し方が異なるので両方からデータを渡せば、オブジェクトの外からオブジェクトの中の関数に値を渡せたりします。

var MyObj = function() {
  var self = this;
  $(document).on('dispatch_event', {self: self}, self.dispatchEventListener);
  return this;
}

MyObj.prototype.dispatchEventListener = function(evt, data) {
  console.log( evt.data.self); // => [MyObj]
  console.log( data.flg ); // => true
};

var myObj = new MyObj();

// 発火
$(document).trigger( 'dispatch_event', {flg: true} );

わーい!

初めてプログラミングを書きはじめたのがAS(ActionScript)だったので、そこでなんとなく覚えた EventDispatcher 的な書き方に影響されている気がします。EventDispatcher と EventListener の定義の違いの理解があやふやですが…

久しぶりにコーディングのお仕事でjQuery触ったら、やっぱりすごく忘れてて予想外に時間がかかってしまった。。。orz
どうせまた忘れるだろうからメモをしました。やはり日々の触れてるって継続が大切でなのすね…


[参考]

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発

初めてのJavaScript 第3版 ―ES2015以降の最新ウェブ開発