かもメモ

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

Ajaxを使ってWordPressからコンテンツを取得したい。

WordPress製のサイトに独自のコンテンツなどを取得できるAjaxを作成する方法のメモ

  1. 呼び出すアクション名を決める
  2. Ajax送信先をjsから使えるように出力する
  3. Ajaxで呼び出される関数を作成する (PHP)
  4. フロントからAjaxでの呼出しを作成する (javascript)

1. 呼び出すアクション名を決める

アクション名はPHPの関数名やnoce、jsのAjax内などで何度も使うので、どんな仕様にするか決めて仕様に合ったアクション名を先に決めておくのが良いと思います。

今回は my-ajax-action というアクション名にします。

2. Ajax送信先のパスなどの出力

WordPressAjaxのリクエス送信先 /wp-admin/admin-ajax.php のパスやnoneなどの情報をjavascriptグローバル変数として出力する

<?php // function.php
function my_enqueue_scripts() {
  $handle = 'my-script';
  // Ajaxの呼出しが書かれているスクリプトのパス
  $jsFile = 'path/to/myscript.js';
  
  wp_register_script($handle, $jsFile, ['jquery']);
  
  // 1.で決めたアクション名
  $acrion = 'my-ajax-action';
  // 配列をJSのオブジェクトに変換したscriptを出力する
  wp_localize_script($handle, 'MY_AJAX', [
    'api'    => admin_url( 'admin-ajax.php' ),
    'action' => $acrion,
    'nonce'  => wp_create_nonce( $acrion ),
  ]);
  
  wp_enqueue_script($handle);
}
add_action( 'wp_enqueue_scripts', 'my_enqueue_scripts' );

$handleで指定された myscript.js の出力タグの前に次のようなスクリプトが出力されます。

<script type='text/javascript'>
/* <![CDATA[ */
var MY_AJAX = {
  "api": "http:\/\/example.com\/wordpress\/wp-admin\/admin-ajax.php",
  "action": "my-ajax-action",
  "nonce": "生成されたNONCE値"
}
/* ]]> */
</script>

※ 実際には1行で出力されます。グローバル変数になっちゃうので、ちょっとイケてない感あります…

複数のアクションを作成する場合は、アクションとそれに対応したnonceが取得しやすいように配列をネストするなどして工夫してください。

3. 呼び出される関数の作成 (PHP)

3.1 Ajaxから呼び出される関数を定義する

add_action アクションフックで、Ajaxから呼び出される関数を登録する。

<?php // function.php
function my_ajax_event() {
}
add_action( 'wp_ajax_my-ajax-action', 'my_ajax_event' );
add_action( 'wp_ajax_nopriv_my-ajax-action', 'my_ajax_event' );
  • wp_ajax_{action名} … ログインユーザーの時、呼び出される
  • wp_ajax_nopriv_{action名} … 非ログインユーザーの時、呼び出される

フロントから呼び出される場合は、両方のアクションフックを設定する必要がある。
管理画面で使うだけなら、常にログインユーザーなので wp_ajax_{action名} の方だけでOK。

3.2 Ajaxで呼び出される関数の処理を作成する

<?php // function.php
function my_ajax_event() {
  // wp_create_nonce の引数に渡したものと同じ アクション名
  $action = 'my-ajax-action';
  
  // nonceのチェック
  if( check_ajax_referer($action, 'nonce', false) ) {
    $data = [];

    // Ajax(post)のdataから渡される値を使用する場合は $_POST から取得する (適時エスケープ)
    $id = intval( $_POST['post_id'] );
    $name = esc_html( $_POST['name'] );
    
    /* wordpressから色々取得したり、Ajaxで返すデータ作成する */
    
    // 配列をjson形式にエンコード
    $data = json_encode($data);
  } else {
    // エラー
    status_header( '403' );
    $data = 'Forbidden';
  }

  // Ajaxに返す
  header( 'Content-Type: application/json; charset=UTF-8' );
  echo $data;

  die();
}
add_action( 'wp_ajax_my-ajax-action', 'my_ajax_event' );
add_action( 'wp_ajax_nopriv_my-ajax-action', 'my_ajax_event' );

nonceのチェック

check_ajax_referer( $action, $query_arg, $die )
  • $action (string)
    nonceのアクション名
    nonce作成時の wp_create_nonce() に渡したアクション名と同じ文字列を指定する
  • $query_arg (string)
    $_REQUESTで渡される配列の nonce が格納されているキーを指定
    上の例の場合は data: { nonce: 値 } としてAjaxから送ればOK
  • $die (boolean) default: true
    trueの時 nonce が無効なら die() する
    falseの時 nonce のチェック結果を true / false で返す

Ajax(post)のdataで渡される値は$_POSTで取得することができます。
nonceのチェック関数は内部では$_REQUESTから取得している様なので、$_REQUESTの方がget/postどちらでも取得できるで良いのかもしれません。

Ajaxで呼び出される関数の最後に die() がないと返されるデータの最後に0が追加されてしまうので、忘れないようにdieしておく必要があります。

4. Javascriptの作成 (Ajax呼出し)

後はフロントのjavascriptからAjaxで呼び出すだけです。
URLやアクション名は 2. で作成したグローバル変数から使用します。

// global MY_AJAX
"use strict";
$(function() {
  $.ajax({
    url: MY_AJAX.api,
    type: 'post',
    data: {
      // 呼び出すアクション名
      action: MY_AJAX.action,
      // アクションに対応するnonce
      nonce: MY_AJAX.nonce,
      // ▼ その他 渡したいデータがあれば適時 ▼
      post_id: 12345,
      name: 'name'
    }
  })
  .done(function( res ) {
    console.log( res );
  })
  .fail(function( jqXHR, textStatus, errorThrown ) {
    console.log( jqXHR, textStatus, errorThrown, arguments);
  });
});

 
これで、Ajaxを使ってWordPressから独自のコンテンツとか値を取得することができるようになりました。
Ajaxを使うために出力するjavascriptの変数がglobal変数になってしまうのが、やっぱイケてない感じなので、どうにか出来ないものかと思っています。。。


[参考]

世界一わかりやすいWordPress 導入とサイト制作の教科書 (世界一わかりやすい教科書)

世界一わかりやすいWordPress 導入とサイト制作の教科書 (世界一わかりやすい教科書)

JS 配列の重複を取り除きたい

配列の重複した値を削除したい時のメモ。

filterでループさせindexOfで値の重複をチェックするパターン

array.indefOf(value)がvalue自身のインデックスと異なる場合は既に配列内に同じ値があるという判定

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false];

function removeDuplicateValue(arr) {
  return arr.filter((val, i, self) => {
    return self.indexOf(val) === i;
  });
}

console.log( removeDuplicateValue1(arr) );
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined ]

NaNが消えてしまう…

var a = NaN
console.log( a === a ); // false

NaNは自身を含めどの値であってもイコールにならないっぽいので、array.indexOf(NaN) は常に-1となり上記の方法では消えてしまう様です。
NaNを残すようにするには最初に出てきたNaNの時だけfilterのコールバック関数内でtrueを返すようにすればOKでした。(しかしNaN残して使いたい場合ってあるのかな…

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false, NaN];

function removeDuplicateValue(arr) {
  let hasNaN = false;
  return arr.filter((val, i, self) => {
    if(isNaN(val) && !hasNaN ) {
      hasNaN = true;
      return true;
    }
    return self.indexOf(val) === i;
  });
}

console.log( removeDuplicateValue(arr) );
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

Set (ES6) を使うパターン

Set
Set オブジェクトにより、primitive valuesでもオブジェクト参照でも、どんな型でも一意の値を格納します。
Set オブジェクトは値のコレクションです。挿入順に要素を反復することができます。Set内の値は 1度だけ発生します。その値はSetコレクション内で一意です。
[出典] Set - JavaScript | MDN

var arr = [0, 1, 2, 3, 4.1, true, false, 1, "2", "", 4.2, null, undefined, NaN, false, NaN];

var a1 = Array.from( new Set(arr) );
console.log(a1);
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

var a2 = [...new Set(arr)];
console.log(a2);
// => [ 0, 1, 2, 3, 4.1, true, false, '2', '', 4.2, null, undefined, NaN ]

Array.from()
Array.from() メソッドは、配列型 (array-like) オブジェクトや反復可能 (iterable) オブジェクトから新しい Array インスタンスを生成します。
[出典] Array.from() - JavaScript | MDN

new Set()で1いの値の反復可能オブジェクトにしてArray.from()に渡して配列化。

スプレッド演算子 ...
スプレッド演算子 は、複数の引数 (関数呼び出しのため) または複数の要素 (配列リテラルのため)、あるいは複数の値 (分割代入のため) が置かれるところで式が展開されます。
[出典] スプレッド演算子 - JavaScript | MDN

配列や反復可能オブジェクトを値で分割展開できる演算子のようです。(スプレッド演算子って名前初めて知りました)
配列の初期化時に反復可能オブジェクトを展開して値として渡して配列化しているようです。
EX

var arr1 = [1,2,3];
var arr2 = [4,5,6];
arr1.push(...arr2); 
arr1; //=> [1, 2, 3, 4, 5, 6];

var set = new Set();
set.add(1);
set.add(2);
set.add(3);
console.log(set); // => Set { 1, 2, 3 }
console.log( [...set] ); // => [ 1, 2, 3 ]

Set()を使えば簡単に重複を削除できるのですが、Array.from...含めてまだサポートしていないブラウザも有るようなのでWEBで本格的に使えるようになるのは使えるようになるのはもう少し先なのかな。と思いました。


[参考]

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

プログラマ脳を鍛える数学パズル シンプルで高速なコードが書けるようになる70問

JS 2つの配列の差分を取得したい

  1. 値の差分をチェック
  2. 値とインデックスでチェック

1. 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]

2. 
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]

3. 
[]
["いちご", "あおい", "らん"]

4. 
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]

5. 
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]

1. 2つの配列の値の差分を新しい配列にして返す

いずれかの配列にしか値が存在しない場合を返せばいいので、indexOfで配列内に値が存在するかをチェックして判定。

function getArrayDiff(arr1, arr2) {
  let arr = arr1.concat(arr2);
  return arr.filter((v, i)=> {
    return !(arr1.indexOf(v) !== -1 && arr2.indexOf(v) !== -1);
  });
}

👇 出力結果

1.  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]
// =>  [ 1, 3, 5, 6, 11 ]
2.
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]
// => [ 0, 2, 3, 4, 6, '', NaN, '3', null, undefined ]
3. 
[]
["いちご", "あおい", "らん"]
// => [ 'いちご', 'あおい', 'らん' ]
4.
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]
// => [ 'サーバル', 3, 'かばん', 7, 'ボス' ]
5.
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ 'あぶくま', 'きそ', 'しまかぜ', 'ひびき', 'わかば', 'ながなみ' ]

Array.prototype.includes() - JavaScript | MDN
配列に値があるかBoolean値で返すarray.includes()という関数もあるっぽいけど、ECMAScript 2017 Draft (ECMA-262)って書いてあるからES7なのかな?
 

2. 2つの配列の値の差分をインデックス含めてチェックして返す

2-1. 新旧の比較のように、配列Aと配列Bを比べ配列Aから変化しているものを返す

function getArrayIndexValueDiff(oldArr, newArr) {
  return newArr.filter((v, i)=> oldArr[i] !== v);
}

👇 出力結果

1.  
old: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
new: [8, 2, 8, 4, 7, 9, 11, 8, 10]
// =>  [ 8, 8, 7, 9, 11, 10 ]
2.
old: [0, 1, 2, 3, 4, 5, 6]
new: ['', 1, NaN, "3", null, 5, undefined]
// => [ '', NaN, '3', null, undefined ]
3. 
old: []
new: ["いちご", "あおい", "らん"]
// => [ 'いちご', 'あおい', 'らん' ]
4.
old: [1, "サーバル", 3, "かばん"]
new: [1, 7, "ボス"]
// => [ 7, 'ボス' ]
5.
old: ["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
new: ["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ 'ながなみ' ]

2-2. お互いの配列で異なっているものを返す

function getArrayIndexValueDiffBoth(arr1, arr2) {
  let _a1 = arr1,
      _a2 = arr2,
      _a2Len,
      res = [[], []],
      reverse = false;

  // ループのベースは要素が多い方の配列にする
  if(_a1.length < _a2.length) {
    _a1 = arr2;
    _a2 = arr1;
    reverse = true;
  }
  _a2Len = _a2.length;

  res[0] = _a1.filter((v, i)=> {
    if(_a2[i] !== v) {
      // 元の配列に存在していない値(undefined)は除外
      if( i < _a2Len) {
        res[1].push( _a2[i] );
      }
      return true;
    }
  });

  if(reverse) {
    res.reverse();
  }
  return res;
}

もう少しエレガントな書き方がありそうな気がする…
👇 出力結果

1. 
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
[8, 2, 8, 4, 7, 9, 11, 8, 10]
// => [ [ 1, 3, 5, 6, 7, 9, 10 ], [ 8, 8, 7, 9, 11, 10 ] ]
2. 
[0, 1, 2, 3, 4, 5, 6]
['', 1, NaN, "3", null, 5, undefined]
// =>[ [ 0, 2, 3, 4, 6 ], [ '', NaN, '3', null, undefined ] ]
3. 
[]
["いちご", "あおい", "らん"]
// => [ [], [ 'いちご', 'あおい', 'らん' ] ]
4. 
[1, "サーバル", 3, "かばん"]
[1, 7, "ボス"]
// => [ [ 'サーバル', 3, 'かばん' ], [ 7, 'ボス' ] ]
5. 
["あぶくま", "きそ", "しまかぜ", "ひびき", "わかば", "ながなみ"]
["あぶくま", "きそ", "しまかぜ", "ひびき", "ながなみ"]
// => [ [ 'わかば', 'ながなみ' ], [ 'ながなみ' ] ]

 
アロー関数で書いている部分は別にアロー関数でなくても大丈夫なはずです。
差分ってえっちなコトバな印象になってしまったのはきっとインターネッツのせい。


プログラマのための論理パズル 難題を突破する論理思考トレーニング

プログラマのための論理パズル 難題を突破する論理思考トレーニング

JS call apply vs bind アロー関数

javascriptの関数とthisとの関係のメモの続きです。
関数のthisを決めることができるcall()apply()で実行される関数がFunction.prototype.bindやアロー関数でthisが決められているものだった場合どうなるのか気になったので調べてみました。

call と apply

call()apply()this を指定して関数を実行するメソッド。
違いは関数に渡す引数の指定方法だけ。

call() は 関数に渡す引数を1つ1つ列挙していく

myFnc.call(this, param1, param2, param3);

apply() は 関数に渡す引数を第二引数に配列で渡す。実行される関数には引数は展開されて渡される。(argumentsでも参照できる)

myFnc.apply(this, [param1, param2, param3]);

bind (ES5)

Function.prototype.bind()新たな関数(a bound function)を生成して返すメソッド。

var fnc = function() { console.log(this); };
var bindFnc = fnc.bind(null);
console.log(bindFnc); // => [Function: bound fnc]

bind() 関数は、新たな関数(束縛された関数 = a bound function)を生成して返します。この新たな関数の本体(ECMAScript 5 の観点では内部の call プロパティ)は、call() 先の関数(束縛された関数のターゲット関数)【訳注: fun.bind(thisArg) の fun 】 と同じです。ターゲット関数の this の値が bind() に与えた第 1 引数になり(束縛され)、それを上書きすることはできません。
[出典] Function.prototype.bind() - JavaScript | MDN

bindで生成された関数は実行時に内部的にcallが呼ばれるという事だと考えられるので、この関数をcall()apply()で呼出しても、実行される関数内でthisbind()で指定したものに置き換えられるので、bind()で指定したものが実行される関数内のthisになる。

'use strict';
var global = Function("return this")(); // global オブジェクトを取得
global.name = 'global';
var obj1 = { name: "obj1" };
var obj2 = { name: "obj2" };

var callback = function() {
  console.log('> callback this is ', this.name);
};

var applyFnc = function(f) {
  if(typeof(f) === 'function') {
    let _this = this || global;
    console.log('this is', _this.name);
    f.apply(_this);
  }
};

applyFnc( callback );
// => this is global
// => > callback this is  global

// Function.prototype.bind()
applyFnc( callback.bind(obj1) );
// => this is global
// => > callback this is  obj1

applyFnc.call( obj2, callback.bind(obj1) );
// => this is obj2
// => > callback this is  obj1

アロー関数 ()=> (ES6)

アロー関数式 は、function 式 と比べてより短い構文を持ち、this の値を語彙的に束縛します (ただし、自身の this や arguments, super, new.target は束縛しません)。アロー関数は、常に 匿名関数 です。
call や apply からの呼び出し
this はすでに語彙的に束縛されているため、call() や apply() メソッドを介してアロー関数が呼ばれた場合、引数で渡されるだけなので this は影響を受けません
[出典] アロー関数 - JavaScript | MDN

アロー関数は記述が出てきた場所のスコープにあるthisを関数のthisに決定するというイメージです。
引用元にも書かれているようにcall()apply()thisの値を渡されようと、アロー関数作成時に決定したthisが実行される関数のthisになります。

'use strict';
var global = Function("return this")();
global.name = 'global';
var obj1 = { name: "obj1" };
var obj2 = { name: "obj2" };

var applyFnc = function(f) {
  if(typeof(f) === 'function') {
    let _this = this || global;
    console.log('this is', _this.name);
    f.apply(_this);
  }
};

obj1.fnc1 = function() {
  applyFnc(() => {
    console.log('> callback this is ', this.name);
  });
};
obj1.fnc1();
// => this is global
// => > callback this is  obj1


obj1.fnc2 = function() {
  applyFnc.call(obj2, () => {
    console.log('> callback this is ', this.name);
  });
};
obj1.fnc2();
// => this is obj2
// => > callback this is  obj1

 

概ね予測通りの結果でした。
.bind()が新しい関数を生成するというのは知らなかったので、この機会に知ることができて良かったです。同時にデフォルト引数を設定できることも初めて知り寧ろこちらの方に興味惹かれるものがありましたw
こんな事をしているからなかなか本が読み進められないわけです…


[参考]

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

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

Javascript 関数とthisについてのメモ

以前書いた記事でオブジェクトの関数内でsetTimeout()に無記名関数を渡した際のthisの値について理解が不明瞭だったのでjavascriptの関数とthisについて調べたメモです。

関数について

関数宣言 (function declaration)

function 関数名(){}
関数の巻き上げが発生する

!function() {
  var res = add(3, 4); // 7
  function add(n1, n2) {
    return n1 + n2;
  }
}();

関数宣言はスコープの先頭まで巻き上げられるので、上記の記述は下記の解釈となる

!function() {
  function add(n1, n2) {
    return n1 + n2;
  }
  var res = add(3, 4); // 7
}();

関数式 (funtion expression)

var 関数名 = function(){}
※ 関数式の場合関数名は必須ではない。(無記名関数)
関数の巻き上げは発生しない

!function() {
  var res = add(3, 4); // TypeError: add is not a function
  var add = function(n1, n2) {
    return n1 + n2;
  }
}();

thisについて

Javascriptの関数は その関数を呼び出しているオブジェクトを表すthisオブジェクト を持っている。
[出展]: オブジェクト指向JavaScriptの原則

Strictモードでの違い

thisの値にnullundefinedが設定されている時

  • 標準モード this = グローバルオブジェクト
  • Strictモード this = nullundefinedの値のまま

標準モード

function showThis() {
  console.log(this);
}
showThis(); // => global Object (Window)

Strictモード

'use strict';
function showThis() {
  console.log(this);
}
showThis(); // => undefined

グローバススコープではthisはグローバルオブジェクトと指す。= WEBブラウザではWindow
グローバル変数はグローバルオブジェクのプロパティとして扱われる。

"use strict";
console.log(this); // Window

// window.a = 'Hibiki'; と同じ意味
var a = 'Hibiki';

function say() {
  var a = 'Abukuma';
  console.log(a); // "Abukuma"
  console.log(window.a); // "Hibiki"
}

say();

オブジェクトのメソッドとして定義してある関数内のthisは通常そのオブジェクト自身

var obj = {
  name: 'サーバル',
  say: function() {
    // `this` is obj 
    console.log(this, this.name + 'ちゃんすごーい!');
  }
};

obj.say(); // Object, "サーバルちゃんすごーい!"

thisの値は関数が呼び出される際に設定される

function sayThisName() {
  console.log(this.name);
}

var obj1 = {
  name: 'かばん',
  sayName: sayThisName
};

var obj2 = {
  name: 'サーバル',
  sayName: sayThisName
};

var name = 'ミライ';

obj1.sayName(); // かばん
obj2.sayName(); // サーバル

// 標準モード: `this` is Window => "ミライ"
// Strict Mode: Error `this` is undefined
sayThisName();

setTimeoutのcallback関数のthis

setTimeout()windowオブジェクトが持っているメソッドなので省略せずに書くとwindow.setTimeout()

var obj = {
  name: 'MyObject',
  fnc: function() {
    var self = this; // MyObject
    window.setTimeout(function() {
      console.log(self); // => MyObject
      console.log(this);
    }, 1000);
  }
};

setTimeoutが書かれているのと同じスコープにある変数が実行されるcallback関数の中でも使えるので、 setTimeoutの第一引数に無記名関数を渡すのは👇と同じなのではないかと考えています。(厳密にはどうか解りませんが…)

var obj = {
  name: 'MyObject',
  fnc: function() {
    var self = this; // MyObject
    var callback = function() {
      console.log(self); // => MyObject
      console.log(this);
    };
    window.setTimeout(callback, 1000);
  }
};

なので、setTimeoutに渡されるコールバック関数は通常の関数と同じだと考えていました。

オブジェクト内の関数内に関数を作成して呼び出した場合のthis

var obj = {
  name: 'MyObjct',
  fnc: function() {
    console.log(this); // MyObject
    // 関数宣言
    function objFnc () {
      console.log(this);
      // 標準モード =>  Global Object[window]
      // Strict Mode => undefined
    }
    objFnc();
    // 関数式
    var myFnc = function() {
      console.log(this);
      // 標準モード =>  Global Object[window]
      // Strict Mode => undefined
    };
    myFnc();

    // 即時関数
    (function() {
      console.log(this);
      // 標準モード =>  Global Object[window]
      // Strict Mode => undefined
    })();
  }
};
obj.fnc();

オブジェクト内の関数内に関数を作成して実行しても、その関数を呼び出しているオブジェクトは無いので関数内のthisはStrictモードならundefined、標準モードならグローバルオブジェクト(WEBブラウザならwindow)になるので、Strictモードならcallback関数のthisundefinedになるのではないかと思っていたのですが、実際は👇

var obj = {
  name: 'MyObject',
  fnc: function() {
    var self = this; // MyObject
    var callback = function() {
      console.log(self); // => MyObject
      console.log(this);
      // 標準モード =>  Global Object[window]
      // Strict Mode => Global Object[window]
    };
    window.setTimeout(callback, 1000);
  }
};

コールバック関数内のthisは標準モードでもStrictモードでも windowとなっていました。
これはsetTimeout関数のcallbackはwindowがcallback関数を呼び出す仕様(applyやcallで実行されるなどthiswindowになる仕様)になっているという事なのだと思います。

setTimeout() によって実行されるコードは、setTimeout() が呼び出された関数とは別の実行コンテキスト内で実行されます。結果的に、呼び出された関数の this キーワードは window (または global) オブジェクトに設定され、setTimeout が呼び出された関数の this 値と同じにはなりません。
window.setTimeout - Web API インターフェイス | MDN

ドキュメント見に行けば済んでいた話でした。
Strictモードと標準モードで違いがあることを下手に知っていたために混乱してしまっていましたorz  

追記
window.setTimeoutで実行されるthisがwindowになるのはsetTimeoutを呼出しているオブジェクトが指定されるからではないか?と思い、callapplysetTimeoutを呼び出すとどうなるのか試して見たところ…

var fnc = function() { console.log(this); };
window.setTimeout.call(null, fnc, 100); // Uncaught TypeError: Illegal invocation
window.setTimeout.apply(null, [fnc, 100]); // Uncaught TypeError: Illegal invocation
var _setTimeout = window.setTimeout;
_setTimeout.call(null, fnc, 100); // Uncaught TypeError: Illegal invocation
_setTimeout.apply(null, [fnc, 100]); // Uncaught TypeError: Illegal invocation

どうやらこの動作は許可されない様です…


[参考]

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

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

JS オブジェクトのキーがあるかチェックしたい。

オブジェクト({})のキーが存在しているか調べる時、if文を使うと値によっては上手くいかない。

var obj = {
  A: 0,
  B: 1,
  C: 2,
  D: ""
};
var foo;
obj.E = foo;

["A", "B", "C", "D", "E", "F"].forEach(function(key) {
  if( obj[key] ) {
    console.log( obj[key] );
  }
});

👇 出力

B 1
C 2

key in object を使えばOK

["A", "B", "C", "D", "E", "F"].forEach(function(key) {
  if( key in obj ) {
    console.log( key, obj[key] );
  }
});

👇 出力

A 0
B 1
C 2
D ""
E undefined

配列の値の有無をチェックしたい場合

配列(Array)はindexOfをを使うと値があればそのインデックスを返してくれるので、結果が -1 でなければ値があると判定することができる。

var arr = [2, true, false, '', -1, null, undefined];

arr.indexOf(2); // 0
arr.indexOf(false); // 2

// 空文字もOK
arr.indexOf(''); // 3 

// null や undefined も大丈夫
arr.indexOf(null); // 5
arr.indexOf(undefined); // 6

// 暗黙の型変換はしないっぽい
arr.indexOf("2"); // -1

JS 0や文字列を含んだ数字のチェックをしたいメモ

数値を扱う時や数値変換を行う時に0が鬼門になることが多いのでメモ

var checklist = [
  0,
  "0",
  "",
  undefined,
  "undefined",
  false,
  "false",
  true,
  "true",
  null,
  "null",
  NaN,
  "NaN",
  Infinity,
  "Infinity",
  -Infinity,
  "-Infinity"
];

-0で数値変換した場合

checklist.forEach(function(v) {
  console.log(v + " (" + typeof(v) + ") =>", v-0);
});

出力👇

0 (number) => 0
"0" (string) => 0
"" (string) => 0
undefined (undefined) => NaN
"undefined" (string)  => NaN
false (boolean)  => 0
"false" (string) => NaN
true (boolean)  => 1
"true" (string) => NaN
null (object)   => 0
"null" (string) => NaN
NaN (number)   => NaN
"NaN" (string) => NaN
Infinity (number)   => Infinity
"Infinity" (string) => Infinity
-Infinity (number)   => -Infinity
"-Infinity" (string) => -Infinity

isNaN() と Number.isNaN()

isNaN()Number.isNaN() とでは判定結果が異なる。
isNaN() は暗黙の型変換でNaNになるものも含まれるっぽい。

isNaN()

checklist.forEach(function(v) {
  if( isNaN(v) ) {
    console.log('isNaN >', v, "("+typeof(v)+")");
  }
});

出力👇

isNaN > undefined (undefined)
isNaN > "undefined" (string)
isNaN > "false" (string)
isNaN > "true" (string)
isNaN > "null" (string)
isNaN > NaN (number)
isNaN > "NaN" (string)

Number.isNaN() (ES6)

checklist.forEach(function(v) {
  if( Number.isNaN(v) ) {
    console.log('Number.isNaN >', v, "("+typeof(v)+")");
  }
});

出力👇

Number.isNaN >  NaN (number)

isFinite() と Number.isFinite()

isNaNと同様で判定結果が異なる。
isNaNと同じくglobalにある isFinite() は暗黙の型変換で判定してるっぽい。

isFinite()

checklist.forEach(function(v) {
  if( isFinite(v) ) {
    console.log('isFinite >', v, "("+typeof(v)+")");
  }
});

出力👇

isFinite > 0 (number)
isFinite > "0" (string)
isFinite > "" (string)
isFinite > false (boolean)
isFinite > true (boolean)
isFinite > null (object)

""(空文字列) true, false, null はすり抜けてしまう。

Number.isFinite() (ES6)

checklist.forEach(function(v) {
  if( Number.isFinite(v) ) {
    console.log('Number.isFinite >', v, "("+typeof(v)+")");
  }
});

出力👇

Number.isFinite > 0 (number)

"0", "123"のような文字列化しているものは弾かれてしまう。

Number.isInteger() (ES6)

型チェック込で整数かどうかを判別するので、少数や文字列になっているものも弾かれる。

Number.isFinite("0");   // false
Number.isFinite(12.8); // false

数値と文字列化している数字のみチェックしたい

var checklist = [
  0,
  "0",
  "",
  undefined,
  "undefined",
  false,
  "false",
  true,
  "true",
  null,
  "null",
  NaN,
  "NaN",
  Infinity,
  "Infinity",
  -Infinity,
  "-Infinity",
  "123",
  -5,
  12.8,
  "A"
];

var a = checklist.filter(function(val, i) {
  // 文字列になった数字も取り出したいので、isFinite() を使う
  // true, false がすり抜けてしまうので、`boolean` 型も弾く
  if( !isFinite(val) || typeof(val) === "boolean" ) {
    return false;
  }
  if( val-0 === 0 ) {
    // 暗黙の型変換で 0 になるものは 数字で構成されているかチェックする
    if(/[0-9]/g.test(val)) return true;
  } else {
    return true;
  }
});

console.log(a); // [ 0, '0', '123', -5, 12.8 ]

暗黙の型変換で0になるものだけ正規表現でチェックをしているので、そこまで実行速度に影響は無いとは思います。

条件式版も作ってみましたが、条件がこれでOKかちょっと不安があります…

var a = checklist.filter(function(val, i) {
  // 文字列になった数字も取り出したいので、isFinite() を使う
  // true, false がすり抜けてしまうので、`boolean` 型も弾く
  if( !isFinite(val) || typeof(val) === "boolean" ) {
    return false;
  }
  // 条件は本当にコレで大丈夫???
  if( typeof(val) === 'number'
   || (typeof(val) === 'string' && val !== "")
   || val - 0 !== 0
  ) {
    return true;
  }
});

console.log(a); // [ 0, '0', '123', -5, 12.8 ]

感想

なんとか数字と文字列化した数字を判定することができました。
ゆるふわ型javascriptの暗黙の型変換ほんと鬼門です。
そう考えるとRubyfalsenilのときだけfalseになる。って凄く便利なんですね!


[参考]

JS 配列の要素が全て条件を満たすか調べたい

EX: 配列に含まれる数が全て0 <= N <= 255 かどうか調べたい
次のデータから条件にマッチする配列だけを取り出す。

// 調べる配列の入ったデータ
var data = [
  [ 217, 0, 0, 0 ],
  [ 169, 254, 0, 1 ],
  [ 0, 0, 0, 256 ],
  [ 1, 0, -1, 255 ],
  [ '', 0, 0, 0 ] 
];

forEachだとループの途中でbreakできないっぽい。
なので条件に合わないものがあっても最後までチェックするので無駄があり。

for break

条件に合わない値が出てきたらbreakでループを抜ける方法

var a = [];
d.forEach(function(arr, i) {
  let isOK = true;
  for(let j=0,l=arr.length; j<l; j+=1) {
    let n = arr[j];
    if( typeof(n) !== 'number' || (n < 0 || n > 255) ) {
      isOK = false;
      break;
    }
  }
  if(isOK) {
    a[a.length] = arr;
  }
});
console.log(a); // =>  [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

Array.prototype.every (ES5)

allPassed = array.every(callback[, thisObject]);
与えられた関数によって実行されるテストに配列のすべての要素が合格するかどうかをテストします。
出典: [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/every:title]

callbackが1つでもfalseを返した時点でループから抜ける

d.forEach(function(arr, i) {
  let callback = function(i) {
    return (typeof(i) === 'number' && (i >= 0 && i <= 255) );
    // 条件に合わないの否定でもOK
    // return !(typeof(i) !== 'number' || (i < 0 || i > 255) );
  };
  if( arr.every(callback) ) {
    a[a.length] = arr;
  }
});
console.log(a); // => [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

Array.prototype.some (ES5)

array.some(callback[, thisObject]);
与えられた関数によって実行されるテストに合格する要素が配列の中にあるかどうかをテストします。
出典: [https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/some:title]

callbackが1つでもtrueを返した時点でループから抜ける

d.forEach(function(arr, i) {
  let callback = function(i) {
    // NGな条件式を返せばOK
    return (typeof(i) !== 'number' || (i < 0 || i > 255) );
  };
  // NGに全くマッチしなければ(falseなら)、全ての値が条件を満たしている
  if( !arr.some(callback) ) {
    a[a.length] = arr;
  }
});
console.log(a); // => [ [ 217, 0, 0, 0 ], [ 169, 254, 0, 1 ] ]

👆日本語で説明を書くと余計混乱しそう…

感想とまとめ

関数 ループから抜ける条件
for breakキーワードが出てきた段階
every callback関数がfalseを返した段階
some callback関数がtrueを返した段階

every, someを使えばシンプルに書けて良さそう。(条件式返すのがちょっと気持ち悪い気もするけど多分慣れの問題。
タイトルの意味合い的にはeveryだけど、条件式の書きやすさではsomeを使ったほうが良い場合もありそう。
少しづづES5から覚えていきたい…


[参考]

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

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

Javascript 自然数の約数の総和を求めたい

プログラム書く仕事が無いので、お脳が腐らないように息抜きにプログラムのパズルをしています。
自然数の約数の総和を求める方法を考えてたのでメモ。

数学的なやつ

自然数NP1a1P2a2素因数分解できる時
Nの約数の総和は

(1+P1+P1**2+...+P1**a1)(1+P2+P2**2+...+P2**a2)...

↓ 例

12 = 2**2 × 3
(1+2+2**2)(1+3) = 7*4 = 28

数学的な公式だと、素因数分解をするとエレガントに約数の総和が求められるのですが、素因数分解を行うプログラムが大変そうだったので別の方法を考えることにしました。

約数をループで探していく

  • N%C === 0 の時 Cは整数Nの約数
  • 1の次は2なので、N自身を除いた最大の約数は N/2 以下の数になる

と、上記の条件から作った関数がこちら

// n: int, n > 0
function divisorSum(n) {
  let c, r = n;
  if(n <= 0) return;
  c = Math.floor(n/2)+1;
  while(c--) {
    if(n%c === 0) r += c;
  }
  return r;
}

divisorSum(12) // => 28

 
N/2以下でループさせれば計算量が多少は少なくて済む気がします。(間違ってたらご指摘ください。数学苦手なので自信はありません。)
他にエレガントな方法や素因数分解するプログラムのヒントがあれば知りたいので教えてください!!
(素因数分解は脳内でどうやって素因数分解しているかを順序立ててトレースすれば良さそうな気もするけど、ループで約数探すより処理が多そうな気が…


[参考]

プログラマのための論理パズル 難題を突破する論理思考トレーニング

プログラマのための論理パズル 難題を突破する論理思考トレーニング

GoogleMaps マウスホイールでのズームしないようにしたい。

シングルページのサイトなどで100%幅でGoogleMapsを表示させるような時、マウスホイールで地図がズームしてしまうと不都合があるので、地図のマウスホイールでのズーム機能をOFFにするメモ。

scrollwheel オプションを使用する。

マウスホイールでのズーム機能はscrollwheelオプションを付けてmapオブジェクトを作成することで変更が可能です。

  • true: マウスホイールでのズーム有効 (デフォルト値)
  • false: マウスホイールでのズーム無効

サンプルコード

function initialize() {
  var mapOptions = {
    center: new google.maps.LatLng(60.1500745,24.978537),
    zoom: 13,
    // disables scrollwheel zooming on the map
    scrollwheel: false
  };
  // Create GoogleMap
  var map = new google.maps.Map(document.getElementById('map'), mapOptions);
}
google.maps.event.addDomListener(window, 'load', initialize);

See the Pen google map customize by KIKIKI (@chaika-design) on CodePen.

さすがGoogleオプションを追加するだけ。
お店や会社の場所を表示してるとかで予期せぬスクロールでどこ?ってならないように地図上でマウスホイールでスクロールが発生しそうなデザインの時はこのオプションを使用するのがユーザビリティ的にも良いかなぁと思います。


[参考]