カテゴリー : JavaScript

[Mocha] before, beforeEach, after, afterEach の実行順番

「あれ? Mocha で before, beforeEach, after, afterEach の実行順番ってどういう順番だっけ?」となることが多かったので記事にしました。

mocha.js

実行順番は before, beforeEach, afterEach, after

結論から書くと、

  1. before
  2. beforeEach
  3. test
  4. afterEach
  5. after

の順番で実行されます。

Mocha のサンプルコード

以下、before, beforeEach, afterEach, after の実行順序を確認するための mocha のサンプルコードです。

describe('before test', function() {
  before(function() {
    console.log('before');
  });
 
  beforeEach(function() {
    console.log('beforeEach');
  });
 
  after(function() {
    console.log('after');
  });
 
  afterEach(function() {
    console.log('afterEach');
  });
 
  it('test', function() {
    console.log('test');
  });
});

何の処理がどこで呼び出されるのか、よく理解してテストコードを書いていきたいですね。

[JavaScript] 小数点以下の桁数を取得する方法

JavaScript で小数点以下の桁数を取得する方法をご紹介します。

小数点以下の桁数を取得する getDecimalPlace メソッド

/**
 * 小数点以下の桁数を取得する
 * @param {Number} number
 * @return {Number} decimalPlace
 **/
var getDecimalPlace = function(number) {
  if (typeof number !== 'number') {
    return null;
  }
 
  var decimalPlace = 0;
  var numbers = number.toString().split('.');
  if (numbers[1]) {
    decimalPlace = numbers[1].length;
  }
 
  return decimalPlace;
};

使い方

第一引数に数字を指定すると、戻り値は小数点以下の桁数が返ってきます。

var number = 1234.56789;
 
getDecimalPlace(number); // 5
getDecimalPlace('not number'); // null

[JavaScript] 指定した id, class 名を持つ全ての要素を削除する

JavaScript で、指定した id もしくは class 名を持つ全ての要素を削除するメソッドをご紹介します。

jQuery は使わずに pure JavaScript で実装しています。

指定した id を持つ要素を削除する

var removeIdElement = function(id){
  var e = document.getElementById(id);
  if (e) {
    e.parentNode.removeChild(e);
  }
};

指定した class 名を持つ全ての要素を削除する

var removeClassElement = function(className){
  var elements = document.getElementsByClassName(className);
  for (var i = 0; i < elements.length; i++) {
    var e = elements[i];
    if (e) {
      e.parentNode.removeChild(e);
    }
  }
};

自分自身の要素を削除するには、parentNode で親ノードに辿って removeChild で子ノードを削除しているところがポイントです。

参考情報

[JavaScript] 改行コード \r\n \r \n を半角スペースに置換する正規表現とスニペット

JavaScript で改行文字を半角スペースに置換する正規表現とスニペットです。

var text = 'spam\r\nspam\rspam\nspam';
console.log(text);
// spam
// spam
// spam
 
var replacedText = text.replace(/\r\n|\r|\n/g, ' ');
console.log(replacedText);
// spam spam spam spam

\r\n \r \n どのパターンでも半角スペースに置き換えてくれます。

参考情報

Promise 参考記事まとめ [JavaScript/ECMASCript6]

JavaScript 標準仕様の ECMASCript6 (ES2015) の Promise について理解が定着していないので、よく読み返す記事を自分用にまとめました。

Node.js における Promise についてはこちらの記事もどうぞ。

ひとまず、以上です。

自分用のよく使うパターンみたいなのも、まとめていきたいなと思います。

異なるバージョンの Moment.js と Moment Timezone で変数を使い回したら見事にバグった

最新バージョンの moment-timezone@0.5.5 で生成した moment object を、古いバージョンの moment@2.4.0 を使ってる method に引数で渡して、日付処理をおこなったら華麗にバグったというお話です。

前提

どちらも Thu Dec 01 2016 09:00:00 GMT+0900 (JST) の値が保存されていることを期待してます。

調査結果

古いバージョンの moment@2.4.0 で作成した変数を console.log した結果

期待通り _d: Thu Dec 01 2016 09:00:00 GMT+0900 (JST) が入っていることが確認できました。

  { [Number: 1480579200000]
  _i: [ 2016, 11, 1 ],
  _f: undefined,
  _l: undefined,
  _strict: undefined,
  _isUTC: true,
  _pf: 
   { empty: false,
     unusedTokens: [],
     unusedInput: [],
     overflow: -1,
     charsLeftOver: 0,
     nullInput: false,
     invalidMonth: null,
     invalidFormat: false,
     userInvalidated: false,
     iso: false },
  _a: [ 2016, 11, 1, 0, 0, 0, 0 ],
  _d: Thu Dec 01 2016 09:00:00 GMT+0900 (JST),
  _z: 
   { name: 'america_los_angeles',
     displayName: 'America/Los_Angeles',
     zones: [ [Object], [Object], [Object], [Object] ] },
  _offset: 480 }

最新バージョンの moment-timezone@0.5.5 で作成した変数を console.log した結果

_d: Fri Nov 25 2016 00:00:00 GMT+0900 (JST) には、期待している 2016/12/01 の値が入ってませんでした。

代わりに _i 以下に存在する _d: Thu Dec 01 2016 09:00:00 GMT+0900 (JST) の方に期待している 2016/12/01 の値が入ってました。

{ [Number: 1479999600000]
  _isAMomentObject: true,
  _i: 
   { [Number: 1480579200000]
     _i: [ 2016, 11, 1 ],
     _f: undefined,
     _l: undefined,
     _strict: undefined,
     _isUTC: true,
     _pf: 
      { empty: false,
        unusedTokens: [],
        unusedInput: [],
        overflow: -1,
        charsLeftOver: 0,
        nullInput: false,
        invalidMonth: null,
        invalidFormat: false,
        userInvalidated: false,
        iso: false },
     _a: [ 2016, 11, 1, 0, 0, 0, 0 ],
     _d: Thu Dec 01 2016 09:00:00 GMT+0900 (JST),
     _z: 
      { name: 'america_los_angeles',
        displayName: 'America/Los_Angeles',
        zones: [Object] },
     _offset: 480 },
  _isUTC: false,
  _pf: 
   { empty: false,
     unusedTokens: [],
     unusedInput: [],
     overflow: -1,
     charsLeftOver: 0,
     nullInput: false,
     invalidMonth: null,
     invalidFormat: false,
     userInvalidated: false,
     iso: false,
     parsedDateParts: [],
     meridiem: null },
  _locale: 
   Locale {
     _calendar: 
      { sameDay: '[Today at] LT',
        nextDay: '[Tomorrow at] LT',
        nextWeek: 'dddd [at] LT',
        lastDay: '[Yesterday at] LT',
        lastWeek: '[Last] dddd [at] LT',
        sameElse: 'L' },
     _longDateFormat: 
      { LTS: 'h:mm:ss A',
        LT: 'h:mm A',
        L: 'MM/DD/YYYY',
        LL: 'MMMM D, YYYY',
        LLL: 'MMMM D, YYYY h:mm A',
        LLLL: 'dddd, MMMM D, YYYY h:mm A' },
     _invalidDate: 'Invalid date',
     ordinal: [Function],
     _ordinalParse: /\d{1,2}(th|st|nd|rd)/,
     _relativeTime: 
      { future: 'in %s',
        past: '%s ago',
        s: 'a few seconds',
        m: 'a minute',
        mm: '%d minutes',
        h: 'an hour',
        hh: '%d hours',
        d: 'a day',
        dd: '%d days',
        M: 'a month',
        MM: '%d months',
        y: 'a year',
        yy: '%d years' },
     _months: 
      [ 'January',
        'February',
        'March',
        'April',
        'May',
        'June',
        'July',
        'August',
        'September',
        'October',
        'November',
        'December' ],
     _monthsShort: 
      [ 'Jan',
        'Feb',
        'Mar',
        'Apr',
        'May',
        'Jun',
        'Jul',
        'Aug',
        'Sep',
        'Oct',
        'Nov',
        'Dec' ],
     _week: { dow: 0, doy: 6 },
     _weekdays: 
      [ 'Sunday',
        'Monday',
        'Tuesday',
        'Wednesday',
        'Thursday',
        'Friday',
        'Saturday' ],
     _weekdaysMin: [ 'Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa' ],
     _weekdaysShort: [ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat' ],
     _meridiemParse: /[ap]\.?m?\.?/i,
     _abbr: 'en',
     _config: 
      { calendar: [Object],
        longDateFormat: [Object],
        invalidDate: 'Invalid date',
        ordinal: [Function],
        ordinalParse: /\d{1,2}(th|st|nd|rd)/,
        relativeTime: [Object],
        months: [Object],
        monthsShort: [Object],
        week: [Object],
        weekdays: [Object],
        weekdaysMin: [Object],
        weekdaysShort: [Object],
        meridiemParse: /[ap]\.?m?\.?/i,
        abbr: 'en' },
     _ordinalParseLenient: /\d{1,2}(th|st|nd|rd)|\d{1,2}/ },
  _a: [ 2016, 10, 25, 0, 0, 0, 0 ],
  _d: Fri Nov 25 2016 00:00:00 GMT+0900 (JST),
  _z: null,
  _isValid: true }

まとめ

同じ _d というプロパティの扱いが異なっているのでバグったみたいでした。

ライブラリのバージョンの混在は本当に危険なので、絶対に真似しないでくださいね。

[JavaScript] iframe で読み込み完了後に処理を実行するには onload イベントを使おう

iframe 内のコンテンツの読み込みが完了したら何か処理をさせたい場合 onload イベントを使うことで実現できます。

例えば以下のコードは、インラインフレーム内で frame.html の読み込み完了後に alert(‘complete’); という JavaScript の処理を実行しています。

<iframe src="frame.html" onload="alert('complete');">

(例) iframe 内のリンクをクリック禁止にする

もう少し具体的なテーマとして iframe 内のリンクをクリック禁止にする処理の実装について考えてみました。

onload を利用して以下のようなコードになります。

HTML

<iframe id="js-iframe" src="frame.html" onload="replaceIframeHref();">

JavaScript

window.replaceIframeHref = function(){
  $("#js-iframe").contents().find("a").each(function(){
    $(this).attr("href", "javascript:void(0);");
  });
};

以上です。

moment-timezone のバージョンが古くてサマータイムの切り替わりで日付処理がバグった

Moment Timezone の古いバージョンを使っていて、先日のサマータイムの終了日時 2016年11月6日(日)02時00分 PDT を境目に UTC から PDT/PST への変換がバグってしまう問題にぶち当たった。

結果的には、使っていた moment-timezone のバージョンが古かったので、バージョンを最新にしたことで解決しました。

moment-timezone@0.0.3 を使った場合

超古いバージョン 0.0.3 を利用した場合、タイムゾーン America/Los_Angeles で 2016-11-07 のその日の終わりの日時を正しく取得することができません。

> var now = new Date("2016-11-09 17:30:00")
> var moment = require("moment-timezone")
 
> moment(now).tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-10 07:59:59'
> moment(now).subtract(1, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-09 07:59:59'
> moment(now).subtract(2, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-08 07:59:59'
> moment(now).subtract(3, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-06 07:59:59'
> moment(now).subtract(4, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-06 06:59:59'
> moment(now).subtract(5, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-05 06:59:59'

moment-timezone@0.5.9 を使った場合

最新バージョン 0.5.9 を利用した場合、2016-11-07 07:59:59 というように 2016-11-07 のその日の終わりの日時を正しく取得することができています。

> var now = new Date("2016-11-09 17:30:00")
> var moment = require("moment-timezone")
 
> moment(now).subtract(1, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-09 07:59:59'
> moment(now).subtract(2, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-08 07:59:59'
> moment(now).subtract(3, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-07 07:59:59'
> moment(now).subtract(4, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-06 06:59:59'
> moment(now).subtract(5, "days").tz("America/Los_Angeles").endOf("day").utc().format('YYYY-MM-DD HH:mm:ss')
'2016-11-05 06:59:59'

moment-timezone は定期的に最新バージョンにアップデートしないと

moment-timezone は IANA Time Zone Database + Moment.js なライブラリなので、IANA Time Zone データベースを最新に更新するためにもバージョンのアップデートは定期的にしないといけませんね。

海外向けサービスを開発しているので、定期的にタイムゾーンに苦しんでいる気がする・・・。

[jQuery] カルーセルライブラリ slick.js の lazyLoad オプションを理解する

jQuery のカルーセルライブラリ slick.js をそのまま使うと slick.js file の読み込みと .slick() method の実行が完了するまで、カルーセルのレイアウトにならず ul/li タグで囲んでいる画像が全て表示されてしまうという問題に直面しました。

これを解決するには 1 枚目の画像だけ src で画像 URL を指定します。

<img src="http://example.com/image1.png">

2 枚目以降は data-lazy attribute に image url を指定します。

<img data-lazy="http://example.com/image2.png">

そして .slick() method に lazyLoad オプションを設定してあげれば OK です。

$('#js-banner-area').slick({
  lazyLoad: 'progressive'
});

Lazy Loading のサンプルは slick – the last carousel you'll ever need に載っています。

ちなみに lazyLoad オプションは ondemand と progressive の 2 つの設定があります。挙動の違いは Stack Overflow に詳しく説明されている投稿があったので引用しておきます。

ondemand: Loads the visible image as soon as the page is displayed and the other ones only when they’re displayed. (“[…] loads slides on demand. When a slide becomes visible (or on the before slide callback) the load is fired.”) Should be used if the other images of the carousel are displayed very rarely.

progressive: Loads the visible image as soon as the page is displayed and the other ones after everything else is loaded in the background (“loads the visible slides on init, and then progressively loads the rest of the slides on window.load().”). Should be used if the other images will be used most (or all) of the times the page is displayed.

デフォルトは画像が表示されるタイミングで load 処理が動く ondemand ですが、個人的には background で画像を load しておいてくれる progressive オプションを使うのが好みです。

lazyLoad オプションに設定値はカルーセルで 2 枚目以降の画像が表示される頻度で ondemand を使うか progressive を使うかを決めるとよさそうですね。

参考情報

[JavaScript] Date.now と new Date().getTime() と +new Date のパフォーマンス比較

JavaScript の Date.now と new Date().getTime() と +new Date の実行時間を jsPerf で確認してみました。

date-now-vs-new-date-gettime

結果は Date.now が最速でした。

Date.now を使いましょう!