B-Teck!

お仕事からゲームまで幅広く

【JavaScript】最大値と最小値を指定して範囲内の値を持った配列を作成する(ES2015対応版)

この記事は下記の記事をES2015対応機能でリライトしたものです。 beatdjam.hatenablog.com

/** 
* range
* 範囲内の整数値を持った配列を作成する
* @param {number} max 範囲の最大値
* @param {number} min 範囲の最小値(デフォルト値:0)
* @param {function} filer フィルタ関数(デフォルト値:全てtrue)
* @return {array} 作成した配列
*/
function range(max,min = 0,filter=(e)=>{return true}){
    return Array(max-min+1).fill()
                           .map((v,i)=>{return min+i})
                           .filter(filter);
}
console.log(range(5));
// 第一引数までの配列を作成する
// Array [ 0, 1, 2, 3, 4, 5 ]
console.log(range(5,1));
// 第二引数から第一引数までの配列を作成する
// Array [ 1, 2, 3, 4, 5 ]
console.log(range(5,1,e=>{return e%2 === 0}));
// 第二引数から第一引数までのうち、第三引数の条件に合致する配列を作成する
// Array [ 2, 4 ]
console.log(range(-5,-11));
// 負の値も生成可能
// Array [ -11, -10, -9, -8, -7, -6, -5 ]

前回は(最小値、最大値、フィルタ関数)という引数の並びだったけど、
最大値までの連番を作る方が用途として多かったので、
(最大値、最小値、フィルタ関数)という並びに変更した。

処理の概要は、

  • Array(max-min+1)で必要な長さの配列を生成
  • 配列の要素をfill()undefinedにする ※空要素の場合map()で処理がうまく走らないので必要
  • 最小値+indexの値を各要素に格納
  • フィルタ関数が指定されていなければ全てtrue、
    指定されていればフィルタ関数でfilter()する

【JavaScript】thisの種類

JavaScriptthisにはいくつかの種類があり、状況によって動作が変わる。
現在はおおまかに分けて5つ?

コンストラクタ呼出し

function Func1(arg1, arg2){
  this.arg1 = arg1;
  this.arg2  = arg2;
}

let obj1 = new Func1('test', 123456);
// obj1.arg1 => 'test'
// obj1.arg2 => 123456

Functionオブジェクトのコンストラクタをnewで呼び出してインスタンスを生成する場合。
この文脈でのthisインスタンス自身を指す。

メソッド呼出し

let obj = {
  val : 'test',
  func1 : function(){
    console.log(this.val);
    // =>'test'
  }  
}
obj.func1();

オブジェクトの中のメソッドからthisを参照した場合。
メソッドの存在するオブジェクトをthisとする。

関数呼び出し

function func1(arg1, arg2){
  this.arg1 = arg1;
  this.arg2  = arg2;
}

func1('test', 123456);
console.log(window.arg1);
//windowオブジェクトにプロパティがセットされる。
//windowはグローバルオブジェクトのため、プロパティもグローバルになってしまう。

Functionオブジェクトをnewをつけずにそのまま呼び出した場合。
普通に呼び出される関数の中でthisを使用すると、Function内にスコープが限定されず、
windowオブジェクトを参照してしまう。

Strictモードを使用した場合はエラーが発生して利用できなくなるため安全。

'use strict';
function func1(arg1, arg2){
  this.arg1 = arg1;
  this.arg2  = arg2;
}

func1('test', 123456);
console.log(window.arg1);
//Exception: TypeError: this is undefined

関数呼び出しの注意点

let obj = {
  val : 'obj prop',
  func1 : function(){
    // メソッド呼び出し
    // obj.val
    console.log(this.val);
    
    function nestedFunc(){
      // 関数呼び出し
      // window.val
      console.log(this.val);      
    }
    nestedFunc();
  }  
}

window.val = 'window.prop';
obj.func1();

メソッド呼出しの中で関数呼出しを行った場合でも、単体で関数呼出しを行った場合と同様にthiswindowを指してしまう。
ECMASript6(ECMAScript2015)の場合は、後述するアロー関数を用いることで、thisを一意にすることができる。
それ以外の環境の場合、下記のような対処法がある。

  • self
let obj = {
  val : 'obj prop',
  func1 : function(){
    let self = this;
    console.log(self.val);
    // =>'obj prop'
    
    function nestedFunc(){
      console.log(self.val);
    // =>'obj prop'
    }
    nestedFunc();
  }  
}

window.val = 'window.prop';
obj.func1();

参照するべきthisを変数に保持してしまう方法。
慣例的にselfthatthisなどの変数名が利用される。
単純だがわかりやすい。

  • bind()
let obj = {
  val : 'obj prop',
  func1 : function(){
    console.log(this.val);
    
    function nestedFunc(){
      console.log(this.val);      
    };
    // thisをbindしたfunctionを定義
    let bindFunc = nestedFunc.bind(this);
    bindFunc();
    //またはそのまま実行
    nestedFunc.bind(this)();
  }  
}
window.val = 'window.prop';
obj.func1();

参照されるべきthisの値をbind()で束縛した新しいFunctionオブジェクトを作成する。 作成した関数を実行することで別のものを参照しないようにする方法。
部分適用はしやすいが、少し可読性が良くない。

個人的には、どちらかと言えばselfの方が嬉しい気持ちになる。

applyまたはcallで呼び出し時

let obj = {
  val : 'obj prop',
}

function func1(){
  console.log(this.val);
}  

func1();
// => undefined
func1.call(obj);
// => 'obj prop'
func1.apply(obj);
// => 'obj prop'

Function.prototype.apply()Function.prototype.call()は、
引数で与えられたオブジェクトをthisとしてセットして呼び出すことができる。

applycallの違い

let obj = {
  val : 'obj prop',
}

function func1(arg1, arg2){
  console.log(this.val + ' ' + arg1 + ' ' + arg2);
}

func1.call(obj, 'test', 'args');
// => 'obj prop test args'
func1.apply(obj, ['test', 'args']);
// => 'obj prop test args'

両方共第一引数はthisにセットする値だが、下記の点が異なる。
* callは第二引数以降に与えた引数が、呼び出すオブジェクトに引数として渡される。
* applyは第二引数に渡した配列の中身が引数として渡される。

アロー関数で呼び出し時

let obj = {
  val : 'obj prop',
  func1 : function(){
    console.log(this.val);
    // アロー関数は、外側のthisを自身のthisとして扱うため、
    // func1のthis.val = obj.valを参照する。
    let nestedFunc = () => {
      console.log(this.val);      
    }
    nestedFunc();
  }  
}

window.val = 'window.prop';
obj.func1();

ECMASript6(ECMAScript2015)以降では、アロー関数という仕組みが提供されている。 アロー関数は、自分が宣言されているスコープのthisを引き継いで関数内でthisとして扱うため、
直感に反しないthisの扱い方をすることができる。

アロー関数の注意点

window.val = 'window val';
let func = () => {
  console.log(this.val);      
}

let obj2 = {val : 2};
func();
// => windos val
func.call(obj2);
// => windos val
func.apply(obj2);
// => windos val

前述したように、アロー関数のthisは外側のスコープのthisを引き継いでセットされる。
アロー関数の場合、callapplyで呼び出してもこの前提は変わらず、thisを置き換えることが出来ない。

【JavaScript】今までブログに載せたJavaScriptのスニペットをまとめた

今までぽろぽろ書いてたJavaScriptとかを、ちょっと書き直したりしてまとめた。
書いたあとで知ったこととか踏まえるともっときれいに書けたり短くかけたりして面白かった。

MyToolBox/js at master · beatdjam/MyToolBox · GitHub

自分が過去に書いたコードはやっぱり過去のコードなので、
時折見返して手を入れたり、思い出して今ならどうするかとか考えると学びがある。