「もっとこうした方がいいよ」「ここバグあるよ」など助言、マサカリお待ちしております。
実装例
function StringArray() {
let existArgument = true;
// String型以外は許しまへんで
const isStringType = value => {
if (typeof value !== "string") {
throw new Error("Incompatible types. Please converted to String.");
};
};
if (arguments.length === 0) {
// new Array()の場合
existArgument = false;
} else if (arguments.length === 1 && typeof arguments[0] === "number") {
// new Array(arrayLength)の場合
; // 何もしない
} else {
// new Array(element0) と new Array(element0, element1[, ...[, elementN]])の場合
for (const value of arguments) {
isStringType(value);
};
};
return new Proxy((existArgument ? new Array(...arguments) : new Array()), {
get: function (obj, prop, receiver) {
return Reflect.get(obj, prop, receiver);
},
set: function (obj, prop, value, receiver) {
const convertedProp = Number(prop); // 数値型に変換
if (Number.isNaN(convertedProp) || !Number.isInteger(convertedProp) || convertedProp < 0) {
// NaN または 整数値でない または 0未満 であれば通常のプロパティ
return Reflect.set(obj, prop, value, receiver);
};
isStringType();
return Reflect.set(obj, convertedProp, value, receiver);
}
});
}
ところどころ解説
classを使わない理由は、下記のように関数実行ができないためです。
functionで定義することで 'new' なしでもオブジェクトを生成できます。
class StringArray { }
const a = new StringArray(); // a {}
const b = StringArray(); // Uncaught TypeError: Class constructor a cannot be invoked without 'new'
isStringType関数は配列に代入される要素がString型であるかを確認します。
const isStringType = value => {
if (typeof value !== "string") {
throw new Error("Incompatible types. Please converted to String.");
};
};
引数はargumentsオブジェクトから取得します。
argumentsオブジェクトの長さ(引数の個数)によって対応を変えます。
if (arguments.length === 0) {
// new StringArray() に対応します。
} else if (arguments.length === 1 && typeof arguments[0] === "number") {
// new StringArray(10) などに対応します。
// 負の数、小数の場合、Arrayオブジェクト側でエラーを吐きます。
} else {
// new StringArray("hello") や new StringArray("taro", "jiro", "subro") など対応します。
// isStringArray関数でString型以外を排除します。
};
Proxyオブジェクトを使います。
Reflectオブジェクトというものをセットで使うそうですが、今回はよく理解せずコードを書いてます(危険)。
ざっくりと、Proxyはsetter, getterのような動きをしますが、
setter, getterが定義されている・されていないに関わらず、自前の処理を噛ませることができます。
プロパティの更新検知や、特定のプロパティにはアクセスさせない処理などを追記できます。
Proxy, Reflectについては、以下の記事を拝読しました。
ECMAScript 2015 の Proxy(Proxies) / Reflect をなんとなく理解する
JavaScript(ES2015〜)のProxyで、プロパティにフックする正しいやり方
今回はString型以外の値が代入されることを防ぎたいため、setterで判定を行います。
{
get: function (obj, prop, receiver) {
return Reflect.get(obj, prop, receiver);
},
set: function (obj, prop, value, receiver) {
const convertedProp = Number(prop); // 数値型に変換
if (Number.isNaN(convertedProp) || !Number.isInteger(convertedProp) || convertedProp < 0) {
// NaN または 整数値でない または 0未満 であれば通常のプロパティ
return Reflect.set(obj, prop, value, receiver);
};
isStringType();
return Reflect.set(obj, convertedProp, value, receiver);
}
}
感想
思い付きで作ってみたところProxyという便利オブジェクトに出会いました。
これは運命ですね。
[1, "hello", true, {}]
JavaScriptの配列はなんでもかんでも代入できる子なので、特定の型しか代入できない配列を作れるのは安心ですね。