blog

基本的なおさらい:jsの種類と検出方法

string、boolean、number、null、undefinedはプリミティブ型と呼ばれ、js規定の最も基本的で原始的な型を表します。 symbolはES6で追加された新しいデータ型で、一意な...

May 10, 2020 · 6 min. read
シェア

クイックナビゲーション

種類

データ型はどのようなコンピューター・プログラミング言語にとっても基本的なもので、jsには主に基本型と参照型の2種類があります。

undefined、null、Symbol、boolean、string、numericです。

備考

  1. string、boolean、number、null、undefinedはプリミティブ型と呼ばれ、jsの最も基本的でプリミティブな規定型を表します。

  2. symbolはES6の新しいデータ型で、一意な値を表します。シンボルの初期化にはnewは使えません。

  3. nullとundefinedはjsでは特別な値で、nullは空、undefinedは未定義を意味します。

参照型:一般的に言えば、他の型の基本的な型に加えて、関数(クラスの定義)、配列、日付、オブジェクトなどの参照型です。

参照型と基本型の違い

  1. 保存場所:基本型の値はスタックメモリに直接保存され、参照型の実記憶はメモリのペアのアドレスに保存され、スタックメモリに保存され、その値はヒープメモリに保存されます。

  2. クローン化の状況:基本型は変数を別の変数に直接代入することでクローン化できますが、参照型はスタック・メモリ内の値参照のアドレスを別の変数に直接代入することでクローン化され、その結果、2つの変数は同じスタック・メモリ内の同じアドレスを指すことになります。したがって、一方の変数を操作すると、もう一方の変数も変更されます。

タイプ判定

なぜタイプ判定なのですか?

個人的には、jsは弱い型付け言語なので、宣言する変数がどんな型に置き換わっても型チェックされないため、非常に柔軟に使える反面、操作によってはエラーになることがあると思います。例えば、文字列を大文字に変換する関数を宣言しておきながら、その関数を数値で呼び出した場合。これはエラーとなり、プログラムが終了してしまいます。

 let test= function(name){
 name.toUpperCase()
 }
 test(11)

そのため、プログラムをより堅牢にするために型判定を追加する必要があります。

 let test= function(name){
 if(typeof name ==='string'){
 name.toUpperCase()
 }
 }
 test(11)

typeof

 console.log(typeof 'a'); //->string
 console.log(typeof 1); //->number
 console.log(typeof undefined); //->undefined
 console.log(typeof Symbol()); //->symbol
 console.log(typeof false); //->boolean
 console.log(typeof function(){}); //->function
 console.log(typeof null); //->object
 console.log(typeof new function(){}); //->object
 console.log(typeof {}); //->object
 console.log(typeof []); //->object

上で見たように、typeofはnullとオブジェクトの正確な型を区別することができません。この問題を解決するために、jsは別の型検出演算子であるinstanceofを提供しています。

instanceof

typeofとは対照的に、instanceofはオブジェクトの型のみを検出します;

 console.log( 'a' instanceof String) //false
 console.log( new String('a') instanceof String) //true
 console.log( 1 instanceof Number) //false
 console.log( function(){} instanceof Function) //true
 console.log( new function(){} instanceof Function) //false
 console.log( new function(){} instanceof Object) //true
 
 console.log( [] instanceof Array) //true
 console.log( {} instanceof Object) //true
 console.log( {} instanceof Map) //false
 console.log( new Map() instanceof Map) //true
 console.log( new Map() instanceof Object) //true ここで真である理由は、すべてのオブジェクトはグローバルなObjectオブジェクトを継承しているからである。
 console.log( new Set() instanceof Set) //true
 console.log( null instanceof null) // 
 console.log( undefined instanceof undefined) // 

上記からわかるように、instanceofは変数が型のインスタンスとして使われます。instanceofはプロトタイプを検出し、プロトタイプ・チェインにその型のプロトタイプがあるかどうかを判断するのが内部的なメカニズムです。つまり、BがAのサブクラスである限りtrueを返します。このため、変数がAのインスタンスなのかBのインスタンスなのかを正確に判断することはできません。つまり、BがAのサブクラスであればtrueを返します。このため、変数がAのインスタンスかBのインスタンスかを正確に判断することは不可能です。同時に、instanceofの右辺はオブジェクトでなければならず、そうでなければエラーを報告するため、その検出には限界があることもわかります。

constructor

instanceofとあまり変わりません。コンストラクタを使った型判定は、変数の型が参照型であるかどうかを判定するだけです。原理は、関数Fが定義されたときに、JSエンジンがプロトタイプのプロトタイプを追加し、プロトタイプにコンストラクタ属性を追加します。つまり、コンストラクタの型を使って、現在の変数がコンストラクタと同じ型かどうかを判断することができます。


 'a'.constructor== String; //true
 [].constructor== Array ; //true
 {}.constructor== Object; //true
 funtion(){}.constructor== Function; //true
 new Number(1).constructor==Number; //true 
 new Map().constructor==Map; //true 
 new Set().constructor==Set; //true 
 new Number(1).constructor==Number; //true 
 Symbol().constructor==Symbol; //true 
 1..constructor ==Number; //true(ps:(ここで呼び出しを少し多めにしているのは、1点だけ小数点扱いにしてエラーとして報告するためである)。

上記から、検出する変数にはコンストラクタ属性が必要であり、この属性がない変数の値が検出された場合はエラーになることがわかります。例えば、undefined,null.はエラーになります。また、BがAクラスをprototypeで継承した場合、new B().constructor == Bの値はfalse、new B().constructor == Aの値はtrueとなります。正しく検出したい場合は、コンストラクタのポイントを手動で修正する必要があります。

Object.prototype.toString.call()

その内部検出は以下の手順で行われます。

  1. toObject(this)を呼び出すと、thisがオブジェクトOに変換されます。 注:以下の判定に使用されるプロパティやメソッドは、入力されたオブジェクトのタイプに基づいて、ここで生成されます。

  2. オブジェクト O が配列であるかどうかを判断し、配列であれば builtinTag を宣言して値 "Array" を代入します。

    もしそうでなければ、Oに[[ParameterMap]]属性があるかどうかを判断し、もしあれば、builtinTagを宣言し、それに値 "Arguments "を割り当てます。

    もしそうでなければ、Oが[[Call]]メソッドを持っているかどうかを判断し、もし持っていればbuiltinTagを宣言し、それに値 "Function "を代入します。

    もしそうでなければ、Oが[[ErrorData]]属性を持っているかどうかを判断し、もし持っていれば、値 "Error "を持つbuiltinTagを宣言します。

    そうでなければ、Oが[[BooleanData]]属性を持っているかどうかを判断し、もし持っていれば、値 "Boolean "を持つbuiltinTagを宣言します。

    そうでなければ、Oが[[NumberData]]属性を持っているかどうかを判断し、もし持っていれば、値 "Number "を持つbuiltinTagを宣言します。

    もしそうでなければ、Oが[[StringData]]属性を持っているかどうかを判断し、もし持っていれば、値 "String "を持つbuiltinTagを宣言します。

    もしそうでなければ、Oが[[DateValue]]属性を持っているかどうかを判断し、もし持っていれば、builtinTagを宣言し、それに値 "Date "を割り当てます。

    そうでなければ、Oが[[RegExpMatcher]]属性を持っているかどうかを判断し、もし持っていれば、値 "RegExp "を持つbuiltinTagを宣言します。

    それでもない場合は、"Object "という値をbuiltinTagに代入します。

  3. Get(O,@@toStringTag)メソッドを呼び出し、その結果を変数tagに代入します。

  4. Type(tag)を呼び出し、その戻り値がStringかどうかを判定し、StringでなければtagにbuiltinTagを代入します。

  5. 文字列「[object "+ tag + "]」を生成して返します。

実験による検証は以下の通り:

 Object.prototype.toString.call(1); //"[object Number]"
 Object.prototype.toString.call('a'); //"[object String]"
 Object.prototype.toString.call(function(){}); //"[object Function]"
 Object.prototype.toString.call(new function(){}); //"[object Object]"
 Object.prototype.toString.call({}); //"[object Object]"
 Object.prototype.toString.call([]); //"[object Array]"
 Object.prototype.toString.call(new Map()); //"[object Map]"
 Object.prototype.toString.call(new WeakMap()); //"[object WeakMap]"
 Object.prototype.toString.call(new Set()); //"[object Set]"
 Object.prototype.toString.call( Symbol()); //"[object Symbol]"

上記から明らかなように、Object.prototype.toString.call();メソッドはカスタム・オブジェクトの型を検出しません。しかし、ネイティブ型の検出には、このメソッドが最も適していることは確かです。

まとめ

一般的な型検出では、ネイティブ型はObject.prototype.toString.call()、カスタム型はinstanceofかconstructorを使いますが、constructorは少々面倒です。そのため、選択の難易度と落とし穴を埋めるかどうかを天秤にかけることをお勧めします。

最終的な内容はどうしても間違ってしまいます。

Read next

axiosインターセプターをカプセル化し、ユーザーに依存しないaccess_tokenの更新を実現する。

最近、プロジェクトを行う際、シングルサインオン、つまり、プロジェクトのログインページが、会社で共有されているログインページであり、ロジックはページ内で統一的に処理されます。最終的に実現されるのは、ユーザーは一度だけログインすればよく、ログインした状態で会社のすべてのウェブサイトにアクセスできるというものです。 この記事では、ログインの管理方法とログイン後の処理について説明します。

May 9, 2020 · 10 min read