Webの開発を長らくやっていると、新しい知識を増やすことなく知っている知識だけを頼って惰性でモノづくりをしてしまいがちです。それでも何とかなっているかも知れません。そもそもサポートしないといけないブラウザに古いIEがあったりすると「新しい機能を使っていけない」になるため、新しい技術を学ぶモチベーションも上がりません。しかし、ブラウザの性能も日々向上しており、JavaScriptにも知らぬ間に新しい仕様が追加されています。クラウドサービスでNode.jsでの実装を指定してある場合もあります。どんな分野でも、たまに知識の底上げをしないといつの間にか時代に取り残されてしまいます。
という事で、ECMAScriptの勉強会を実施しようかな、と思い、その時に使うネタを公開しようと思った次第です。
ECMAScript(エクマスクリプト)とは?
JavaScriptの標準のこと。
Ecma Internationalという情報通信システムの分野における国際的な標準化団体で標準化手続きが進められている。
バージョンについて(ECMAScript6?ES2015?)
ECMAScriptは、もともとバージョン1、2、3…といった感じでバージョンアップしていた。
ECMAScript6を策定している途中で、バージョンの付け方を西暦にして1年毎に小出しにしようと決まった。
だから、ECMAScript2015はECMAScript6とも呼ばれる。ES2015と短縮して呼ばれることもある。
バージョン | 呼ばれ方(ググるキーワード) | 公開日 |
1 | ECMAScript1 | 1997年6月 |
2 | ECMAScript2 | 1998年6月 |
3 | ECMAScript3 | 1999年12月 |
4 | ECMAScript4 | 放棄 |
5 | ECMAScript5 = ES5 | 2009年12月 |
5.1 | ECMAScript5.1 | 2011年6月 |
2015 | ECMAScript6 = ECMAScript2015 = ES2015 | 2015年6月 |
2016 | ECMAScript2016 = ES2016 | 2016年6月 |
2017 | ECMAScript2017 = ES2017 | 策定中 |
標準だから使って大丈夫?
ブラウザの実装は各ベンダーに任されているので必ずしも標準に沿っているとは限らない。昔はIEとFireFoxとSafariとで動きやサポートしている機能が違っていたりしてけっこう地獄だった。最近は昔ほど方言はひどくない。
ただ、すべてのブラウザが最新の仕様をキャッチアップしているわけでもなく、また、逆に標準化前に先行して機能を実装しているブラウザなどもある。さらに、ガラケーやゲーム機などのブラウザやIEなどは進化が止まったりするので、どのJavaScriptの機能を使用するかはターゲットユーザの動作環境を考慮する必要がある。
ブラウザ毎のサポート状況は次のページが分かりやすい。
ECMAScript 6 compatibility table(http://kangax.github.io/compat-table/es6/)
以下、細かいのは略しつつ、主だったものを紹介。JavaScriptのthisとかprototypeとかコンストラクタ関数とかの認識が怪しい人は別途、勉強しましょう。
ECMAScript2015以前
use strict
"use strict"; // ↑コードの先頭
function hoge() { "use strict"; // ↑関数の先頭 return 0; }
構文チェックが厳格になる。
ES5以降の仕様に従ってコーディングするなら書いた方が良い。
getter、setter
var person = (function() { var _age = 0; return { get age() { return _age; }, set age(value) { _age = value; } } }());
文字でプロパティにアクセス
var obj = { hoge: "ほげ"}; console.log(obj.hoge); console.log(obj["hoge"]);
Objectに追加
// Object.defineProperty var obj = {}; Object.defineProperty(obj, "age", {value : 37, writable : true, enumerable : true, configurable : true}); // {age: 37} // Object.defineProperties var person = {}; Object.defineProperties(person, { age: {value : 37, writable : true, enumerable : true, configurable : true}, name: {value : "ken", writable : true, enumerable : true, configurable : true}}); // {age: 37, name: "ken"} // Object.getPrototypeOf Object.getPrototypeOf(new Object()) === Object.prototype; // true // Object.create Object.create(Object.prototype, { age: {value : 37, writable : true, enumerable : true, configurable : true}, name: {value : "ken", writable : true, enumerable : true, configurable : true} }); // {age: 37, name: "ken"} var o = Object.create(null); o.prop = 1; console.out(o.prop); // 1 o.toString(); // ↑ Uncaught TypeError: o.toString is not a function /* new Hoge()はコンストラクタ関数の呼び出し。 Object.createは第一引数のプロトタイプを引き継いだobjectを作る。第二引数でプロパティも追加できる。 Object.createの第一引数にnullを渡すとprototypeを持っていないobjectが出来るので、toStringすら呼べない。 */ // Object.keys, Object.getOwnPropertyNames Object.keys({key1: 123, key2: "abc"}); // ["key1", "key2"] Object.keys(["apple", "orange", "banana"]); // ["0", "1", "2"] Object.getOwnPropertyNames({key1: 123, key2: "abc"}); // ["key1", "key2"] Object.getOwnPropertyNames(["apple", "orange", "banana"]); // ["0", "1", "2", "length"] Object.keys(Object); // [] Object.getOwnPropertyNames(Object.prototype); // ["__defineGetter__", "__defineSetter__", "hasOwnProperty", "__lookupGetter__", "__lookupSetter__", "propertyIsEnumerable", "toString", "valueOf", "__proto__", "constructor", "toLocaleString", "isPrototypeOf"] // Object.seal, Object.isSealed /* sealするとプロパティを消したり増やしたりできなくなる。値の変更は可能 */ var obj = { a: "x", b: "y" }; Object.isSealed(obj); // false Object.seal(obj); Object.isSealed(obj); // true obj.a = "f"; obj.c = "z"; delete obj.b; // false Object.getOwnPropertyNames(obj); // ["a", "b"] obj.a // "f" // Object.freeze, Object.isFrozen var obj = { a: "x", b: "y" }; Object.isFrozen(obj); // false Object.freeze(obj); Object.isFrozen(obj); // true obj.a = "f"; obj.c = "z"; delete obj.b; // false Object.getOwnPropertyNames(obj); // ["a", "b"] obj.a // "x" /* ちなみにstrictモードだと操作できないプロパティを操作しようとしたところで例外が発生する。 Uncaught TypeError: Cannot assign to read only property 'a' of object '#<Object>' Uncaught TypeError: Cannot add property c, object is not extensible Uncaught TypeError: Cannot delete property 'b' of #<Object> */ // Object.preventExtensions, Object.isExtensible var obj = {}; Object.isExtensible(obj); // true Object.defineProperty(obj, "one", { value: 1 }); // {one: 1} Object.preventExtensions(obj); Object.isExtensible(obj); // false Object.defineProperty(obj, "two", { value: 2 }); // ↑ Uncaught TypeError: Cannot define property two, object is not extensible
Arrayに追加
// Array.isArray(obj) Array.isArray([1]); // true Array.isArray(new Array()); // true Array.isArray(Array.of(1, 2, 3)); // true // Array.prototype.indexOf // Array.prototype.lastIndexOf [1, 2, 3].indexOf(2); // 1 [1, "hoge", "piyo"].indexOf("piyo"); // 2 [1, 1, 2, 2, 3].indexOf(2); // 2 [1, 1, 2, 2, 3].lastIndexOf(2); // 3 // Array.prototype.every // Array.prototype.some var member = [{name: "tom", age: 18}, {name: "ken", age: 21}, {name: "jessie", age: 28}]; function isAdult(v) { return v.age >= 20; } member.some(isAdult); // true member.every(isAdult); // false // Array.prototype.forEach member.forEach(function(item) { console.log(item.name); } ); //tom //ken //jessie // Array.prototype.map member.map(function(item) { return "name: " + item.name + ", age: " + item.age; } ); //["name: tom, age: 18", "name: ken, age: 21", "name: jessie, age: 28"] // Array.prototype.reduce // Array.prototype.reduceRight member.reduce(function(accumulator, item) { return accumulator + item.age; }, 0); //67 member.reduce(function(accumulator, item) { return accumulator.concat(item.name); }, []); //["tom", "ken", "jessie"] member.reduceRight(function(accumulator, item) { return accumulator.concat(item.name); }, []); //["jessie", "ken", "tom"] // Array.prototype.filter member.filter(isAdult); //[{name: "ken", age: 21}, {name: "jessie", age: 28}] member.filter(function(item) { return !isAdult(item); } ); // 大人じゃない、はこうするしかない? //[{name: "tom", age: 18}]
JSON
var jsonstr = JSON.stringify({hoge:"piyo"}); // "{"hoge":"piyo"}" var obj = JSON.parse(jsonstr); // {hoge:"piyo"}
Dateに追加
var now = Date.now(); // new Date().getTime(); var isoString = new Date().toISOString(); // "2017-09-12T16:46:24.647Z" var json = new Date().toJSON(); // "2017-09-12T16:48:30.553Z"
Stringに追加
var trim = " aaa ".trim(); // "aaa"
Functionに追加
// fun.bind(thisArg[, arg1[, arg2[, ...]]]); // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind function f() { console.log(this); } f(); // Window f.bind(new Array())(); // [] function f() { "use strict"; console.log(this); } f(); // undefined
undefined,NaN,Infinityが不変に
ECMAScript2015
末尾再帰の最適化
仕様としては盛り込まれているけどSafari以外は未だに実装されていない。
"use strict"; (function f(n){ if (n <= 0) { return "foo"; } return f(n - 1); }(1e6)) === "foo"; // ↑ Uncaught RangeError: Maximum call stack size exceeded // at f (:2:12) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10) // at f (:6:10)
デフォルト引数
function f(a, b=1000) { return a + b; } f(1, 2); // 3 f(1); // 1001
可変長引数と繰り返しの展開
function a(...a) { return a.length; } a(1, 2, 3); // 3 function b(a, b, c) { return a + b + c; } b(...[1, 2, 3]); // 6
オブジェクトリテラルの拡張
var a = 'foo', b = 42, c = {}; {a, b, c}; // {a: "foo", b: 42, c: {…}} var o = { method(arg) { return arg + 1 } }; o.method(5); // 6 var prop = "p"; var o = { [prop] : 8 }; // o.prpo; undefined o.p; // 8
テンプレートリテラル
var s = "sunday"; var t = "tuesday"; var w = `${s}\nmonday\n${t}`; console.log(w); // sunday // monday // tuesday function tag(strings, ...values) { console.log(strings[0]); console.log(strings[1]); console.log(values[0]); console.log(values[1]); return "hogehoge"; } tag`${s}\nmonday\n${t}`; function tag2(strings, ...values) { console.log(strings[0]); // "saturday " console.log(strings[1]); // " monday " console.log(strings[2]); // " wednesday" console.log(values[0]); // "sunday" console.log(values[1]); // "tuesday" return "hogehoge"; } tag2`saturday ${s} monday ${t} wednesday`; // "hogehoge"
for ofループ
iterableオブジェクトに対してループする。
let iterable = [10, 20, 30]; for (let value of iterable) { console.log(value); } // 10 // 20 // 30 for (const str of "こんにちは") { console.log(str); } // こ // ん // に // ち // は
バイナリ、8進、テンプレートのリテラル
var octal = 0o10; var bin = 0b10; var name = "tom"; console.log(`my name is ${name}.`); // "my name is tom."
分割代入構文
var x = [1, 2, 3, 4, 5]; var [a, b, c] = x; console.log(`${a} ${b} ${c}`); // "1 2 3" var a, b; [a, b] = [1, 2]; console.log(`${a} ${b}`); // 1 2 var e, f; [e=5, f=7] = [1]; console.log(`${e} ${f}`); // 1 7 function f() { return [3, 4]; } var a, b; [a, b] = f(); console.log(`${a} ${b}`); // 3 4 var c; [, c] = f(); console.log(c); // 4 var o = {p: 42, q: true}; var {p, q} = o; console.log(`${p} ${q}`); // 42 true
new.target
function F() { console.log(new.target === this.constructor); } new F(); // true F(); // false
const,let
const C = "定数"; C = "a"; // ← Uncaught TypeError: Assignment to constant variable. (function() { var v = 1; let l = 2; { var v = 10; let l = 20; console.log(v); // 10 console.log(l); // 20 } console.log(v); // 10 console.log(l); // 2 })(); for (let i = 1; i <= 5; i++) { // 略 }
アロー関数
var func = (x, y) => { return x + y; }; console.log(func(11, 22)); // 33 // アロー関数は var func = function(x, y) { return x + y; } と違って、 // this, arguments, super, new.target を束縛しない。
Class
あれ?いつのまに使えるようになったんだ?AltJS要らないかも。
class Person { constructor(name, age) { this.name = name; this.age = age; } get info() { return this.speakSelfIntroduction(); } static totalAge(a, b) { return a.age + b.age; } speakSelfIntroduction() { return `My name is ${this.name}. ${this.age} years old`; } } const tom = new Person("tom", 5); const ken = new Person("ken", 10); console.log(tom.name); // "tom" console.log(tom.info); // "My name is tom. 5 years old" console.log(ken.speakSelfIntroduction()); // "My name is ken. 10 years old" console.log(Person.totalAge(tom, ken)); // 15 class Taro extends Person { constructor(age) { super("taro", age); } } const taro = new Taro(53); console.log(taro.info); // "My name is taro. 53 years old"
ジェネレータ
function* gen(i) { yield i++; yield i++; yield i++; yield i++; yield i++; } const g = gen(10); console.log(g.next()); // {value: 10, done: false} console.log(g.next()); // {value: 11, done: false} console.log(g.next()); // {value: 12, done: false} console.log(g.next()); // {value: 13, done: false} console.log(g.next()); // {value: 14, done: false} console.log(g.next()); // {value: undefined, done: true} console.log(g.next()); // {value: undefined, done: true} function* gen2(i) { yield* gen(i); yield i--; yield i--; } const g2 = gen2(10); console.log(g2.next()); // {value: 10, done: false} console.log(g2.next()); // {value: 11, done: false} console.log(g2.next()); // {value: 12, done: false} console.log(g2.next()); // {value: 13, done: false} console.log(g2.next()); // {value: 14, done: false} console.log(g2.next()); // {value: 10, done: false} console.log(g2.next()); // {value: 9, done: false} console.log(g2.next()); // {value: undefined, done: true} console.log(g2.next()); // {value: undefined, done: true}
Proxy, Reflect
var handler = { get: function(target, name){ return name === "name" && target[name] === "taro" ? "nice to meet you taro." : target[name]; } }; var p = new Proxy({}, handler); p.name = "hanako" console.log(p.name); // "hanako" p.name = "taro" console.log(p.name); // "nice to meet you taro." var p = new Proxy({}, { set: function(target, prop, value, receiver) { Reflect.set(target, prop, value, receiver); console.log('property set: ' + prop + ' = ' + value) return true } }); p.a = 10; // "property set: a = 10" console.log(handler.a); // 10 // traps // handler.getPrototypeOf() // handler.setPrototypeOf() // handler.isExtensible() // handler.preventExtensions() // handler.getOwnPropertyDescriptor() // handler.defineProperty() // handler.has() // handler.get() // handler.set() // handler.deleteProperty() // handler.ownKeys() // handler.apply() // handler.construct()
Promise
非同期処理のコールバック地獄から解放されたい時に使う。
let wait = function(millisec) { return new Promise((resolve, reject) => { setTimeout(function(){ resolve("wait"); }, millisec); } }); wait(500).then((successMessage) => { console.log(successMessage); // "wait" }); // Promise.all(iterable) // Promise.race(iterable) // Promise.reject(reason) // Promise.resolve(value) // Promise.prototype.catch(onRejected) // Promise.prototype.then(onFulfilled, onRejected)
Symbol
for ofループを実現するために制御の都合で実装されたっぽい。
あんまり開発者的には使い道は無いような。
// new Symbol(); ← やると Uncaught TypeError: Symbol is not a constructor console.log(Symbol("a") === Symbol("a")); // false console.log(Symbol.for("a") === Symbol.for("a")); // true var obj = {}; var sym = Symbol("a"); obj["a"] = ""; obj[sym] = ""; console.log(obj.hasOwnProperty("a")); // true console.log(obj.hasOwnProperty(sym)); // true Object.getOwnPropertySymbols(obj); // [Symbol(a)] for (var i in obj) { console.log(i); }; // "a" // Symbol.iterator // Symbol.match // Symbol.replace // Symbol.search // Symbol.split // Symbol.hasInstance // Symbol.isConcatSpreadable // Symbol.unscopables // Symbol.species // Symbol.toPrimitive // Symbol.toStringTag
Objectに追加
// Object.assign var o1 = { a: 1, b: 1, c: 1 }; var o2 = { b: 2, c: 2 }; var o3 = { c: 3 }; var o4 = { d: 4 }; var obj = Object.assign({}, o1, o2, o3, o4); console.log(obj); // {a: 1, b: 2, c: 3, d: 4} var o0 = { x: 5, y: 6}; var obj2 = Object.assign(o0, o1, o2, o3, o4); console.log(obj2); // {a: 1, b: 2, c: 3, d: 4, x: 5, y: 6} // Object.is Object.is('foo', 'foo'); // true 'foo' === 'foo'; // true Object.is([], []); // false [] === []; // false Object.is(null, null); // true null === null; // true Object.is(0, -0); // false 0 === -0; // true var a = []; var b = a; Object.is(a, b); // true a === b; // true // Object.getOwnPropertySymbols // → Symbolで説明済み // Object.setPrototypeOf var a = new Object(); a.a = "a"; a.hasOwnProperty("a"); // true Object.setPrototypeOf(a, null); a.hasOwnProperty("a"); // ↑ Uncaught TypeError: a.hasOwnProperty is not a function Object.setPrototypeOf(a, Object.prototype); a.hasOwnProperty("a"); // true
Stringに追加
// String.raw var s1 = `こんにちは \n さようなら`; var s2 = String.raw`こんにちは \n さようなら`; console.log(s1); // こんにちは // さようなら console.log(s2); // こんにちは \n さようなら // String.fromCodePoint String.fromCodePoint(42); // "*" String.fromCodePoint(42, 43, 44); // "*+," // String.prototype.codePointAt // String.prototype.normalize // String.prototype.repeat // String.prototype.startsWith // String.prototype.endsWith // String.prototype.includes
Arrayに追加
// Array.from Array.from(document.querySelectorAll("input")).forEach(e => console.log(e.value)); // ↑ inputのvalueを全部表示 // Array.of Array.of(1, 2, 3); // [1, 2, 3] Array(1, 2, 3); // [1, 2, 3] Array.of(1); // [1] Array(1); // [undefined] Array.of(1.5); // [1.5] Array(1.5); // Uncaught RangeError: Invalid array length // Array.prototype.copyWithin // Array.prototype.find // Array.prototype.findIndex // Array.prototype.fill // Array.prototype.keys // Array.prototype.values // Array.prototype.entries
Numberに追加
// Number.isFinite // Number.isInteger // Number.isSafeInteger // Number.isNaN // Number.parseFloat // Number.parseInt // Number.EPSILON // 最小の数値 // Number.MIN_SAFE_INTEGER // Number.MAX_SAFE_INTEGER
Mathに追加
// Math.clz32 // Math.imul // Math.sign // Math.log10 // Math.log2 // Math.log1p // Math.expm1 // Math.cosh // Math.sinh // Math.tanh // Math.acosh // Math.asinh // Math.atanh // Math.trunc // Math.fround // Math.cbrt // Math.hypot
Map, Set, WeakMap, WeakSet
名前の通りなので省略。
RegExpにyとuのフラグが追加された
TypedArray
Int8Array();
Uint8Array();
Uint8ClampedArray();
Int16Array();
Uint16Array();
Int32Array();
Uint32Array();
Float32Array();
Float64Array();
ECMAScript2016
べき乗(**)
2 ** 3 === Math.pow(2, 3); // true (-2) ** 3 === Math.pow(-2, 3); // true (-2) ** 3; // -8 -2 ** 3; // ↑ Uncaught SyntaxError: Unexpected token **
Arrayに追加
// Array.prototype.includes [1, 2, 3].includes(2); // true ["a", "b", "c"].includes("b"); // true ["a", "b", "c"].includes(2); // false
ECMAScript2017
Objectに追加
// Object.entries var obj = {a: 5, b: 7, c: 9}; console.log(Object.entries(obj)); // [Array(2), Array(2), Array(2)] // 0: ["a", 5] // 1: ["b", 7] // 2: ["c", 9] // length:3 for (var [key, value] of Object.entries(obj)) { console.log(key + ' ' + value); } // "a 5" // "b 7" // "c 9" Object.entries(obj).forEach(([key, value]) => { console.log(key + ' ' + value); }); // "a 5" // "b 7" // "c 9" // Object.values console.log(Object.values(obj)); // [5, 7, 9] // Object.getOwnPropertyDescriptors console.log(Object.getOwnPropertyDescriptors(obj)); // {a: {…}, b: {…}, c: {…}} // a:{value: 5, writable: true, enumerable: true, configurable: true} // b:{value: 7, writable: true, enumerable: true, configurable: true} // c:{value: 9, writable: true, enumerable: true, configurable: true} // __proto__:Object // shallow cloneできる。 Object.create( Object.getPrototypeOf(someobj), Object.getOwnPropertyDescriptors(someobj) );
Stringに追加
// String.prototype.padStart, String.prototype.padEnd "abc".padStart(8, "0"); // "00000abc" "abc".padEnd(8, "0"); // "abc00000"
Objectリテラル、Arrayリテラル、関数の引数の末尾のカンマ無視
var obj = { a: 1, b: "a",}; var arr = [1,2,3,]; function f(p,) {} (p,) => {}; Math.max(10, 20,); Math.max(10, 20,,,,,); // ↑ Uncaught SyntaxError: Unexpected token ,
async function
let wait = function(millisec) { return new Promise(resolve => { setTimeout(() => { resolve(millisec); }, millisec); }) }; async function call() { var a = await wait(2000); var b = await wait(3000); return a + b; } call().then(v => { console.log(v); }); // 5000 // ↑ 実行してから5000ms後に表示される
arguments.caller廃止
その他
プリミティブデータ型のラッパーオブジェクトの生成はECMAScript6ではサポートされないらしい。new Booleanやnew String、new Numberが出来るのは歴史的な理由であって出来ないのが正式になる。new String(“aaa”) === “aaa”がfalseだったのでビックリした時があったけど(まあ、落ち着いて考えればあたりまえなんだけど)、ビックリしなくて済むようになるようだ。
あと、オブジェクトが持っている関数の一覧が知りたかったらコンソールでObject.getOwnPropertyNamesを呼び出すといいと思う。
Object.getOwnPropertyNames(Array); // ["length", "name", "arguments", "caller", "prototype", "isArray", "from", "of"] Object.getOwnPropertyNames(Array.prototype); // ["length", "constructor", "toString", "toLocaleString", "join", "pop", "push", "reverse", "shift", "unshift", "slice", "splice", "sort", "indexOf", "lastIndexOf", "copyWithin", "find", "findIndex", "fill", "includes", "entries", "keys", "concat", "forEach", "filter", "map", "every", "some", "reduce", "reduceRight"]