※スマホ対応はしてません。

タグ: Advent Calendar 2017

累乗演算子 ** って知ってる?(現代的JavaScriptおれおれアドベントカレンダー2017 – 09日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 09日目

累乗あるいは冪(べき)乗。

概要

** です。

“1024” といえばもちろん2の10乗ですが、そういう計算です。

console.log(2 ** 10);  // => 1024

= とくっついてる版もあり。

var n = 2;
n **= 6;
console.log(n);  // => 64

仕様

基本的にはただの累乗です。

で、細かい例外的なところもちゃんと決まってます。まあ普通の内容なんだけど。

NaN ** 0 は 1

何かの0乗は1になる、と中学校で教わった記憶があるんですが、その原則は NaN にも適用されます。

console.log(2 ** 0);  // 右辺が 0 なら答えは 1
console.log(NaN ** 0);  // 左辺がNaNでも!

NaN にまつわる計算で答えが NaN にならないの、珍しくない?

ちなみに 0 ** NaN は NaN です。

左辺に負数を使う場合

計算順序に注意。カッコつけましょう。

console.log((-2) ** 3);  // => -8
console.log( -2  ** 3);  // SyntaxError

エラーメッセージはこんな感じでした。

  • Chrome: Uncaught SyntaxError: Unexpected token **
  • Edge: Invalid unary operator on the left hand side of exponentiation (**) operator
  • Firefox: SyntaxError: unparenthesized unary expression can’t appear on the left-hand side of ‘**’
  • Safari: SyntaxError: Unexpected token ‘**’. Amiguous unary expression in the left hand side of the exponentiation expression; parenthesis must be used to disambiguate the expression.

Safariなげえ。

ES2016

ES2015 (ES6) じゃなくてその翌年のES2016で追加されました。

その他

Math.pos()

も同じ機能です。

console.log(Math.pow(2, 10));  // => 1024

内部的に ** を実行しているので、 NaN の扱いとかも全部一緒です。

演算子の優先順位

って仕様書でexpressionの定義見ながらじわじわ探す感じ? MDNだと単項演算子より下位にあるんだけど、 -2 ** 2 がエラーになるあたり、同点だったりしない?

ExponentiationExpressionの定義はこう。同じ、だよね?

ExponentiationExpression[Yield]:
    UnaryExpression[?Yield]
    UpdateExpression[?Yield] ** ExponentiationExpression[?Yield]

参考

引数の初期値って便利だよね。(現代的JavaScriptおれおれアドベントカレンダー2017 – 08日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 08日目

概要

= で初期値を設定できます。

いままでこう↓してたのを、

function findRestaurant(type) {
  if (type === undefined) {
    type = 'sushi';
  }

  // ...
}

こう↓書けます。

function findRestaurant(type = 'sushi') {
  // ...
}

初期値が適用される場面

元のコードで条件として if (type === undefined) を示しましたが、新しい書き方でも条件は同じです。

書かなかった場合だけでなく、明示的に undefined を指定した場合も適用されます。

function say(message = 'Yo!') {
  console.log(message);
}

say();  // => "Yo!"
say(undefined);  // => "Yo!"
say(null);  // => null

単純に元の if (type === undefined) の置き換えって感じっすねー。

引数の設定

途中のものだけでも

Javaとかだと初期値って仮引数群の後ろからじゃないと与えられないと思うんですけど、JavaScriptだとそんなことなくて、なんと、途中にだけ付けることができます。

function makeSushi(neta, shari = 'sumeshi', wasabi) {
  console.log(neta, shari, wasabi);
  // ...
}

makeSushi();  // => undefined, "sumeshi", undefined
makeSushi('tuna');  // => "tuna", "sumeshi", undefined
makeSushi('tuna', 'katame', true);  // => "tuna", "katame", true

初期値のないもの wasabi は当然 undefined になります。

これも単純に元の if (type === undefined) の置き換えって感じっすねー。

引数から引数を利用する

左から順に評価されていくので、左側の引数を使った初期値を設定することができます。

function numbers(n = 1, m = n * n, l = n + m) {
  console.log(n, m, l);
}

numbers();  // 1, 1, 2
numbers(3);  // 3, 9, 12
numbers(3, 5);  // 3, 5, 8

うひゃあ。

改めて単純に元の if (type === undefined) の置き換えって感じっすねー。

分割代入と組み合わせる

大変便利な分割代入については別稿参照。

便利だけどなかなか読みづらいので、ご利用は計画的に。

function makeDog({ type = 'shiba' } = { type: 'mix' }) {
  console.log(type);
}

makeDog();  // "mix"
makeDog({});  // "shiba"
makeDog({ type: 'samoyed' });  // "samoyed"

右辺 { type: 'mix' } が引数自体の初期値で、左辺の type = 'shiba' は分割代入時の初期値です。

正直ここちょっとよくわかってないです。

右辺は実行時に評価

関数を実行するとき、 undefined が与えられた際、初期値(右辺)が評価されます。

この例↓だと毎回実行時の時刻になります。

function sayTime(now = new Date()) {
  console.log(`It is ${now.getHours()}:${now.getMinutes()}:${now.getSeconds()} now.`);
}

やっぱり単純に元の if (type === undefined) の置き換えって感じっすねー。

引数未指定時に例外

前項の仕様を使うと、必ず引数を書かせるようにもできます。

function throwNewError(name) {
  throw new Error(name);
}

function sayTime(now = throwNewError('Now is required')) {
  // ...
}

sayTime();  // Exception: Error: Now is required

どこで見かけた話だったかな。例外発生個所が変な場所になっちゃうのであまり実用的ではない気がする。

ちなみに = の右辺に throw は書けないです。

参考

初期値を適用する条件( undefined を与えられたとき)の定義は見つけられませんでした。

動的にプロパティ名を設定できるようになりました。(現代的JavaScriptおれおれアドベントカレンダー2017 – 07日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 07日目

概要

オブジェクト定義時に、プロパティ名に変数を使えるようになりました。

今までこう↓分けて書かなくちゃいけなかったのを、

const id = '12345';

const obj = {};
obj[id] = { name: 'Newcomer!' },

console.log(obj[id]);

こう↓書けます。

const id = '12345';

const obj = {
  [id]: { name: 'Newcomer!' },
};

console.log(obj[id]);

プロパティ名

この動的なプロパティ名、名前を “computed property name” といいます。「計算されるプロパティ名」。

オブジェクト初期化の際に使える「名前」は、以下の四種類ということになります。

  • 識別子名 { foo: xxx }
  • 文字列リテラル { "foo": xxx }
  • 数字 { 123: xxx }
  • computed property name { [Date.now()]: xxx }

鉤括弧 [ ~ ] の中身

は基本的に何でも書けます。

ただし、計算結果を文字列に変換されます。例えば以下はどれも 10になります。

const obj0 = {
  10: {},
};
const obj1 = {
  [11 + 2 - 3]: {},
};
const obj2 = {
  ['1' + '0']: {},
};
// 10を返す関数を即時実行
const obj3 = {
  [function(){ return 5 * 2; }()]: {},
};
// 文字列にすると"10"になるオブジェクト
const objOf10 = { toString: () => 10 };
const obj4 = {
  [objOf10]: {},
};

テンプレート記法

文字列リテラルは使えるけどテンプレート記法は使えません。まあそうだよね。

使いたい場合は、今回のcomputed property nameと組み合わせます。

const obj = {
  [`id-${id}`]: { },
};

その他

計算されるプロパティ名?

なんかもっと良い表現ないものか。

参考

メソッドをメソッドっぽく定義しよう。(現代的JavaScriptおれおれアドベントカレンダー2017 – 06日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 06日目

JavaScriptにおけるメソッドってのはオブジェクト(receiver)をコンテキスト this に設定した状態で実行する関数なのであって、本質的にはただの関数であったわけです。

なので、今までは普通のプロパティと同様、値の種類が関数であるプロパティとして定義してきました。

var obj = {
  foo: function() {
    // ...
  },
};

このメソッドの書き方に新しい、もっと見やすいものが追加されました。

var obj = {
  foo() {
    // ...
  },
};

とまあこういう↑書き方で書けるようになりました。

これめっちゃ見やすくないですか? まあカンマ , は残るんだけど。

特殊な関数

非同期関数やジェネレータも、接頭辞みたいにキーワードを加えつつ、同じように書くことができます。

非同期関数 async

const obj = {
  async sleep(ms) {
    return new Promise(resolve => {
      setTimeout(resolve, ms);
    });
  },

  async doLater() {
    console.log('Sleep...');
    await this.sleep(1000);
    console.log('and wake up!')
  },
}; 

obj.doLater();

別稿参照。

ジェネレータ *

const obj = {
  * sayPhrases() {
    yield 'Hello';
    yield 'World';
  },
};

for (let phrase of obj.sayPhrases()) {
  console.log(phrase);
}

別稿参照。

こっちはなんか気持ち悪い感じが……。

非同期かつジェネレータ async *

というのもアリらしい。

コンストラクタとして利用不可

メソッド定義の書式で書いた場合、コンストラクタとして利用できません。

const obj = {
  Foo() { },
};

const foo = new obj.Foo();  // Exception: TypeError: obj.Foo is not a constructor

アロー関数と同じ状態です。といってもこちらは this が固定されないですけど。

普通の関数オブジェクトをプロパティに与える場合は可能です。

const obj = {
  Foo: function() { },
};

const foo = new obj.Foo();  // OK

この場合は、一度普通の関数オブジェクト(コンストラクタとして利用可能)を作成した後に、プロパティの値として設定してる、ってな感じでしょうか。

「関数」vs「メソッド」

ちょっと細かい話。

FunctionCreate() という内部処理の仕様によると、この処理で作成できる関数は以下の三種類です。

  • Normal
  • Method
  • Arrow

Arrowは文字通りアロー関数で、Methodは今回紹介したメソッドの書き方をした場合です。それ以外がNormalになります。(このうちNormalだけがコンストラクタとして利用できます。) ですので、メソッド記法で記述したものはまさに「メソッド」になるようです。

だから何だ

そうはいってもその情報が関数オブジェクトに残るわけでもなし。実際は実行オブジェクト (receiver) の有無で分けて、同じ関数オブジェクトでも利用形態に合わせて呼ぶのが一番良いかなと思います。

functin foo() {}

// 関数
foo();

// メソッド
const obj = { method: foo };
obj.method();

ふつう、ふつう、わりと普通。

その他

receiver?

obj.method() の obj のことです。

末尾カンマ

古いIEだとオブジェクトプロパティの最後の定義にカンマが付いてると文法エラーになったんですが(バージョン忘れたけどたしか6あたり)、今はもうとっくにそうはならないようになってます。

個人的にはいっそ全部に付けるようにしてます。続きに追加した際に、カンマ変更分の差分が出るのもなんか見づらいし。

参考

  • ECMAScript 2015 Language Specification – ECMA-262 6th Edition
    • 12.2.6 Object Initializer … オブジェクトリテラルのやつ
    • 14.3 Method Definitions
    • 14.3.8 Runtime Semantics: DefineMethod … FunctionCreate に与える kind を Method に設定したり
    • 9.2.5 FunctionCreate (kind, ParameterList, Body, Scope, Strict, prototype) … 関数オブジェクトを作成する処理。メソッドの場合は非コンストラクタとしてマークされる
  • メソッド定義 – JavaScript | MDN

華麗にgetterとsetterを使いたい。(現代的JavaScriptおれおれアドベントカレンダー2017 – 05日目)

カテゴリー: JavaScript

現代的JavaScriptおれおれアドベントカレンダー2017 – 05日目

getter/setterとは、普通のプロパティのように見えて実は関数、てなやつです。 document.cookie がそれ的な感じですね。最近あんまり使わない気がするけど。

使い方

まずこんな感じで get xxx と set xxx を定義しまして。

var obj = {
  get name() {
    console.log('get()', this._name);
    return this._name;
  },

  set name(value) {
    const sValue = String(value);
    const name = sValue.charAt(0).toUpperCase() + sValue.slice(1).toLowerCase();  // capitalize
    console.log('set()', this._name, '->', name);
    this._name = name;
  },
};

使う側は普通のプロパティと同様にアクセスします。

obj.name = 'alice';
console.log('Name:', obj.name);  // "Alice"

obj.name = 'BOB';
console.log('Name:', obj.name);  // "Bob"

値保持用のプロパティ

を別途、関数の外に持っていないといけないのがちょっとアレ。

JavaScript村では「アンダースコアから始まるプロパティはprivateなものとみなす」という昔からの風習があるので、それを利用するとよろしいかと存じます。

obj._name とか書いちゃうのは駄目ということになっている、ってことです。

delete で削除も

できます。

delete obj.name;
obj.name = 'cHARLIE';
console.log(obj.name);  // "cHARLIE"

普通のプロパティと一緒ですね。

後から追加する

既に存在するオブジェクトでも、 Object.defineProperty() を使ってgetter/setterを追加できます。

var obj = {};
Object.defineProperty(obj, 'name', {
  get() {
    console.log('get()', this._name);
    return this._name;
  },

  set(value) {
    const sValue = String(value);
    const name = sValue.charAt(0).toUpperCase() + sValue.slice(1).toLowerCase();  // capitalize
    console.log('set()', this._name, '->', name);
    this._name = name;
  },
});
obj.name = 'alice';
console.log('Name:', obj.name);  // "Alice"

obj.name = 'BOB';
console.log('Name:', obj.name);  // "Bob"

他にも enumerable とか、いろいろ機能があります。個人的にはget/setだけ知ってれば良いかなーとか思ってます。

あ、ちなみに value というのもあるんだけど、get/setで扱う値を保持する用途ではないです。

メソッドの書き方

さりげなく書きましたけど、 function の記述を省略することができます。

var obj = {
  foo() {},
  function bar() {},
};

別稿参照。

クラスで使う

クラスでもget/setを書けます。

class Person {
  constructor(name) {
    this.name = name;
  }

  get name() {
    console.log('get()', this._name);
    return this._name;
  }

  set name(value) {
    const sValue = String(value);
    const name = sValue.charAt(0).toUpperCase() + sValue.slice(1).toLowerCase();  // capitalize
    console.log('set()', this._name, '->', name);
    this._name = name;
  }
}
var obj = new Person('alice');
console.log('Name:', obj.name);

obj.name = 'bob';
console.log('Name:', obj.name);

その他

ES2015 (ES6) じゃなくてES5.1(2011年)で追加されました。

でもどうしてもお伝えしたくて。ごめんね。

あと両方を作る必要はないです。setterだけとか全然アリです。

参考