LoginSignup
TaikiTkwkbysh
@TaikiTkwkbysh (若林 泰希)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

TypescriptでObject.prototypeを拡張できないのはなぜですか?

TypescriptでObject.prototypeがエラーになる理由を教えてください。

Typescriptを学習中のものです。
prototypeについて触れる機会があり、以下のサイトで下記のようなコードを目にしました。

const foo = { a: 1 };
const date = new Date();
const arr = [1, 2, 3];
 
// どのオブジェクトもhiプロパティが無いことを確認
console.log(foo.hi, date.hi, arr.hi);
// undefined undefined undefined
 
// プロトタイプにプロパティを追加する
Object.prototype.hi = "Hi!";   // ←  問題の箇所
 
// どのオブジェクトもhiプロパティを持つようになる
console.log(foo.hi, date.hi, arr.hi);
// Hi! Hi! Hi!
Object.prototype.hi = "Hi!";

/**
 * 【エラー内容】
 * プロパティ 'hi' は型 'Object' に存在しません。ts(2339)
 */

この処理はJavascriptでは問題なく実行までできるのですが、
Typescriptで書くとコンパイルエラーが起きます。

エラーの意味は理解できるのですが、
'hi'が存在しないのはJavascrpitも同様であるように思えます。
なぜJavascrpitでは実行でき、Typescriptではできないのでしょうか。

Javascrpitがインスタンスを生成した後にプロパティやメソッドを自由に追加できるように、なければシステム側で追加するのと同じ原理でしょうか。

const ObjA = {
    name: "xxx",
    age: 26,
}

let oA = Object.create(ObjA);

oA.gender = "men";

console.log(oA.gender); // men

何卒ご教示の程、宜しくお願いいたします。

0

3Answer

なぜJavascrpitでは実行でき、Typescriptではできないのでしょうか。

この質問が一番重要な点ですが、Typescriptという言語が産み出された経緯を知ると理解しやすいかもしれません。

Javascriptは比較的柔軟な操作ができる一方で、その柔軟さがかえって多くの不具合の原因ともなってきました。

例えば、Object をprototypeに保つ変数は toString() というメソッドが必ず存在しますが、

const object = {}
console.log(obj.toString())  // '[object Object]'
console.log(obj.toSrting())  // Uncaught TypeError: obj.toSrting is not a function

のようにtypoしていたとしても、実際にコードを動かしてみるまで不具合に気づけません。

あるいは、

const num1 = 0
const num2 = "1"

console.log(num1 + num2) // '01'  // 2を期待していた

といった暗黙の型変換による間違いもあります。

これらの特性はJavascriptが悪いという話ではなく、言語ごとの特性であり、柔軟さ・手軽さを好む人にとってはメリット、堅牢さを好む人にとってはデメリットともいえるものです。

普通であれば、「じゃあ堅牢さを好む人は別の言語を選択しよう!」ということになるわけですがJavascriptだけは特殊で、ブラウザ上では基本的にJavascriptぐらいしか動かない(今はWasmなどもありますが)ため別の言語に置き換えることができません。
そこで、
Javascripの柔軟さの一部を捨ててでも、もっと堅牢で、例えば開発中に(実行前に)よくある間違いには気づけるような言語で開発できて、それでいてブラウザ上では動く言語はないものか」
として開発されたのがTypescriptです。
そのため、Typescriptは元来
「Javascriptでは確かに許されている操作なんだけど、間違いのもとだからあえてできなくしよう」
というルールを集めて実行前に検知する(そして最終的にはjavascriptに変換する)ための言語なのです。

一例として、Typescriptで

const num1: number = 0
const num2: string = "1"

console.log(num1 + num2)

と書くと、number型とstring型の加算は Javascriptでは許されているのにも関わらず トランスパイル時にエラーとなります。
(=それによって、実行前に間違えに気づけるかもしれない)
知っててわざとやってるのかもしれませんが、こういった演算は多くの場合ただのヒューマンエラーであることが多いからです。

また、

const obj: {a: number} = {a: 0}
obj.a = "hoge"

という操作も、obj.aにhogeを代入することは Javascriptでは許されているのにも関わらず トランスパイル時にエラーとなります。
「aにはnumberをいれる"つもり"だ」と宣言しているのにも関わらずstringをいれようとしているのは、知っててわざとやっているのかもしれませんが、多くの場合はヒューマンエラーだからです。

====
前置きが長くなってしまいましたが、ここで本題に戻ると、上記と同様に、Typescriptは
「Object型は toStringvalueOf といったプロパティがあるよ(=しかないよ)」
と宣言しているのにも関わらず hi を代入しようとする行為は、Javascriptでは確かに許されているが、あえて禁止されているのです
何故かというと、Typescriptはもともとそういった行為を禁止するために開発された言語だから、です

もし分かっててやりたい場合には、@_y_s さんがご紹介してくださっているリンクにもある通り、まずはObject型の型宣言を自分で上書きして、
「Object型には toStringvalueOf hi があるよ」
と宣言しなおすことによって、Typescriptに「これは分かっててやってるんだよ」と伝えてあげる必要があるというわけです

3

Comments

  1. @TaikiTkwkbysh

    Questioner
    @bigen1925様
    この度はご丁寧にかつ大変分かりやすい説明を頂き、誠にありがとうございます。
    「typescriptを型宣言がついたJavascript」くらいの認識で留めてしまっていましたが、背景をご教示頂いたことでその後の内容がより納得させられました。

    >Typescriptは
    >「Object型は toString や valueOf といったプロパティがあるよ(=しかないよ)」
    >と宣言しているのにも関わらず hi を代入しようとする行為は、Javascriptでは確かに許されているが、あえて禁止されているのです

    ここのご説明は、前の@uasi様のご教示の際に自分の中の仮説で考えた内容と同じでした。
    改めて@bigen1925様からもご説明頂けたので、より確証が持てて何よりです。

    >Object型の型宣言を自分で上書きして、
    >「Object型には toString や valueOf や hi があるよ」
    >と宣言しなおすことによって、Typescriptに「これは分かっててやってるんだよ」と伝えてあげる必要があるというわけです。

    やはりそういう処理が必要になるわけですね。
    実際に上書きしてみようとしましたが、lib.es5.d.ts と言う型定義ファイルで既に宣言されていると言うことでできませんでした...w
    prototypeに関してはreadonlyで宣言されていたのでこちらも無理そうですね。

    lib.es5.d.ts ファイルにそのまま書き加えてしまうか、別で型定義ファイルを自作してそこで宣言をする必要があるのかなと感じました。

    少し外れてしまいましたが、本件を自分の中でしっかり落とし込むことができました。
    また機会がございましたら、何卒ご教示の程宜しくお願い致します!

Comments

  1. @TaikiTkwkbysh

    Questioner
    @_y_s様
    この度は参考資料のご教示を頂き、誠にありがとうございます。
    本件を解決する上で、参考にさせていただきます。

    以上、宜しくお願い致します。

TypeScript で Object.prototype には Object 型がついています。 Object 型の値に新しいプロパティを生やす操作は型エラーになります。

必要なら Object.prototype as anyany 型にキャストすることでプロパティを生やせるようになります。

(Object.prototype as any).hi = "Hi!";
1

Comments

  1. @TaikiTkwkbysh

    Questioner
    @uasi様

    この度は詳しいご説明を頂き、誠にありがとうございます。
    少しご返信が遅くなってしまい、申し訳ございません。

    ご教示頂いた内容を自分なりにまとめてみました。
    ※間違っていましたら、是非とも訂正頂きたいです!💦

    ① Typescriptの Object.prototype には Object型 がついている。

    ② Object.prototypeプロパティには toString() や valueOf() といったプロパティが既に定義されている。

    ③ Typescriptでは、予めオブジェクトに期待しているプロパティがなかったり、期待していないプロパティが代入されてしまうとエラーを起こす。

    ④ hiは Object.prototype のプロパティには無い、 期待されていないプロパティ に該当するので、Typescriptの仕様上、 型エラー と判定される。

    一つ気になった点がございます。
    TypescriptではObject.prototype には Object 型がついているとのことですが、
    Javascriptでは明確な型はないのでしょうか?
    調べてはみたのですが中々出てこず...

    https://tc39.es/ecma262/multipage/fundamental-objects.html#sec-object-objects

    宜しければご教示の程、宜しくお願いいたします。
  2. 合っていると思います。

    > 予めオブジェクトに期待しているプロパティがなかったり、期待していないプロパティが代入されてしまうとエラーを起こす。

    プロパティの有無はコンパイル時に型を見て判定されるので、実行時にプロパティが存在しても型に定義されていなければエラーになることに注意してください。

    const obj: {} = JSON.parse(`{"hi":"hello"}`);
    console.log(obj.hi); //error: Property 'hi' does not exist on type '{}'.

    > Javascriptでは明確な型はないのでしょうか?

    JavaScript は値の型はありますが、変数やプロパティには型がつきません。 Object.prototype に元から入っている値は Object 型です。

    The Object Type https://tc39.es/ecma262/multipage/ecmascript-data-types-and-values.html#sec-object-type
  3. @TaikiTkwkbysh

    Questioner
    @uasi様
    ご確認及び補足説明まで頂き、誠にありがとうございます!
    自分の認識が間違っていなかったのでよかったです。

    >プロパティの有無はコンパイル時に型を見て判定されるので、実行時にプロパティが存在しても型に定義されていなければエラーになることに注意してください。

    ここの部分は全く理解しておりませんでした。

    >JavaScript は値の型はありますが、変数やプロパティには型がつきません。 Object.prototype に元から入っている値は Object 型です。

    大変納得致しました。とても分かりやすい説明誠にありがとうございます。
    変数やプロパティを色々な型の値で上書きできてしまう理由もここにあったのですね。

    度々ご対応頂き、大変助かっております。💦
    是非今後ともご教示の程、宜しくお願い致します!

Your answer might help someone💌