JavaScript 名前(変数、関数、要素など)の取得ガイド:詳細な解説
はじめに
JavaScript開発において、「名前」はコードを構成する上で不可欠な要素です。変数名、関数名、クラス名、オブジェクトのプロパティ名、そしてHTML要素に関連付けられたIDやクラス名など、これらはすべてプログラム内の様々なエンティティを識別するために用いられます。
これらの「名前」を取得する必要が生じる場面は多岐にわたります。例えば、デバッグ中に特定の変数や関数の名前を確認したい、オブジェクトの構造を動的に調べたい、HTMLフォームの要素から名前と値のペアを取得したい、あるいはリフレクションやメタプログラミングのような高度なテクニックを使いたい場合などです。
しかし、JavaScriptは非常に動的な言語であり、ソースコードに書かれた通りの「名前」が常に実行時に簡単に取得できるとは限りません。特に変数名のような識別子は、実行時にはメモリ上の値への参照として扱われるため、その元の名前をプログラム自身が知ることは難しい場合があります。また、コードのミニファイ(圧縮)や難読化によって、本来の名前が失われたり変更されたりすることも一般的です。
この記事では、JavaScriptにおける様々な種類の「名前」をどのように取得できるか、それぞれの方法の仕組み、利用可能な状況、そして限界について、詳細かつ網羅的に解説します。変数、関数、クラス、オブジェクトプロパティ、DOM要素など、主要な対象ごとに掘り下げていきましょう。
JavaScriptにおける「名前」の種類
JavaScriptで「名前」として扱われる可能性のある主な対象は以下の通りです。
- 変数名 (Variable Names):
var
,let
,const
キーワードで宣言された変数の識別子。例:let count = 0;
のcount
。 - 関数名 (Function Names): 関数宣言 (
function myFunction(...)
) や名前付き関数式 (const func = function myFunction(...)
) で指定される関数の識別子。無名関数やアロー関数には通常明示的な名前がありません。例:function greet() {}
のgreet
。 - クラス名 (Class Names): クラス宣言 (
class MyClass {...}
) や名前付きクラス式 (const cls = class MyClass {...}
) で指定されるクラスの識別子。例:class Person {}
のPerson
。 - オブジェクトプロパティ名 (Object Property Names): オブジェクトが持つプロパティを識別するためのキー。文字列またはシンボル (
Symbol
) が使われます。例:let obj = { name: 'Alice' };
の'name'
。 - モジュール名 (Module Names): ES Modules (
import
,export
) において、モジュール間でインポート・エクスポートされる名前。例:import { myFunction } from './myModule';
のmyFunction
。 - DOM要素の識別子 (DOM Element Identifiers): HTML要素に関連付けられた識別情報。主に
id
属性、class
属性、name
属性、そしてタグ名 (tagName
) など。
これらの「名前」の取得方法や難易度は、その性質によって大きく異なります。次に、それぞれの種類の名前について、具体的な取得方法を見ていきましょう。
変数名・関数名・クラス名の取得
プログラミング言語における「名前」の中でも、ソースコード上で最も直接的に使われるのが変数名、関数名、クラス名です。しかし、実行時にこれらの名前をプログラム自身のコードから取得することは、JavaScriptにおいてはしばしば困難です。
実行時における取得の難しさ
なぜ実行時にこれらの名前を取得するのが難しいのでしょうか? その理由は、JavaScriptがコンパイルまたは解釈される過程と、実行時の仕組みにあります。
ソースコードがエンジンによって処理される際、変数名、関数名、クラス名といった識別子は、特定のメモリ上の場所(または値)への参照へと解決されます。実行時には、コードはこれらの参照を使って処理を進めます。元の識別子名そのものは、デバッグ情報として保持されることはあっても、通常のプログラム実行フローの中でアクセス可能な情報としては残されないことが多いのです。
特に、V8エンジンのようなモダンなJavaScriptエンジンは、パフォーマンス最適化のために様々な処理を行います。変数名などは最適化の過程で完全に捨てられたり、内部的な短い名前(レジスタ名など)に置き換えられたりすることがあります。また、本番環境で使われるコードは、サイズ削減や難読化のために変数名や関数名が1文字のような短い名前に変更されるのが一般的です。
このような性質から、JavaScriptコード内で「この変数x
の名前は’x’である」とか、「今実行している関数はmyFunction
という名前である」といった情報を、汎用的かつ信頼性高く取得する直接的な手段は限られています。
関数名の取得: function.name
プロパティ
変数名やクラス名に比べると、関数名は実行時にも比較的取得しやすい情報です。ES6以降、関数オブジェクトは name
プロパティを持つようになりました。
“`javascript
// 関数宣言
function greeting(name) {
console.log(“Hello, ” + name);
}
console.log(greeting.name); // Output: “greeting”
// 名前付き関数式
const farewell = function goodbye(name) {
console.log(“Goodbye, ” + name);
};
console.log(farewell.name); // Output: “goodbye” (変数名ではなく関数式に付けた名前)
// 無名関数式
const sayHi = function(name) {
console.log(“Hi, ” + name);
};
console.log(sayHi.name); // Output: “sayHi” (変数名から推論される)
// アロー関数
const sayHello = (name) => {
console.log(“Hello again, ” + name);
};
console.log(sayHello.name); // Output: “sayHello” (変数名から推論される)
// クラスメソッド
class MyClass {
myMethod() {
console.log(“Method called”);
}
}
console.log(new MyClass().myMethod.name); // Output: “myMethod”
// オブジェクトメソッド (ショートハンドプロパティ)
const myObject = {
myObjectMethod() {
console.log(“Object method called”);
}
};
console.log(myObject.myObjectMethod.name); // Output: “myObjectMethod”
// Functionコンストラクタから生成された関数
const dynamicFunc = new Function(‘a’, ‘b’, ‘return a + b;’);
console.log(dynamicFunc.name); // Output: “anonymous”
// bind() でバインドされた関数
const boundGreeting = greeting.bind(null, ‘World’);
console.log(boundGreeting.name); // Output: “bound greeting”
“`
function.name
プロパティは、関数の名前を文字列として返します。
- 関数宣言や名前付き関数式の場合は、その宣言/式で使われた名前を返します。
- 無名関数式やアロー関数を変数に代入した場合、ES6以降ではその変数名から関数名が推論されます。
- クラスメソッドやオブジェクトのメソッド(ショートハンドプロパティで定義されたもの)の名前も同様に取得できます。
new Function()
コンストラクタで生成された関数は、通常"anonymous"
となります。bind()
メソッドで生成された関数は、元の関数名の前に"bound "
が付加されます。
function.name
はデバッグやロギング、フレームワークでの内部処理などに役立ちます。ただし、コードのミニファイによって関数名が変わる可能性がある点、および変数名からの推論は常に可能とは限らない点に注意が必要です。
変数名の取得: 実行時取得はほぼ不可能
前述のように、JavaScriptにおいてプログラム自身のコードから実行中の特定の変数(例えば let count = 0;
の count
)のソースコード上の名前を直接取得する、汎用的かつ信頼性のある方法は存在しません。
これは言語の根本的な設計によるもので、変数名はその値や参照を識別するためのメタ情報であり、実行時にはこのメタ情報自体は通常アクセス不要とされるためです。
しかし、文脈によっては「変数名のようなもの」を取得する方法や、関連するテクニックがいくつか存在します。
-
グローバル変数を
window
またはglobalThis
オブジェクトのプロパティとして扱う:
ブラウザ環境のグローバル変数や、Node.jsのグローバル変数(ただしvar
宣言されたものなど限定的)は、それぞれwindow
またはglobalThis
オブジェクトのプロパティとしてアクセスできることがあります。この場合、それらのプロパティ名を取得することで、結果的にグローバル変数名を知ることができます。“`javascript
// ブラウザ環境またはNode.jsのグローバルスコープ (ただし var のみなど制約あり)
var globalVar = 123;
const nonGlobalVar = 456; // const/let は通常 globalThis のプロパティにならないconsole.log(globalThis.globalVar); // Output: 123
// globalThis からプロパティ名を取得
if (globalThis.globalVar !== undefined) {
// グローバルオブジェクトのプロパティを列挙して探すなどの方法が考えられるが非効率的
// 一般的には、特定の変数名を知っているから globalThis.variableName にアクセスできるのであって、
// 知らない変数名を取得することはできない。
}
``
let
これは非常に限定的で、現代のJavaScript開発では推奨されるアプローチではありません。特にや
constで宣言された変数はブロックスコープであり、グローバルスコープであっても
windowや
globalThis` のプロパティにはなりません。 -
オブジェクトのプロパティとして変数(値)を管理し、プロパティ名を取得する:
変数そのものの名前を取得することは難しくても、オブジェクトのプロパティとして値を保持している場合は、プロパティ名を取得できます。これは「変数名を取得する」のではなく、「値を保持するオブジェクトのプロパティ名を取得する」という全く異なる操作ですが、用途によっては代替手段となります。“`javascript
const data = {
userName: ‘Alice’,
userAge: 30
};// プロパティ名を取得
const keys = Object.keys(data);
console.log(keys); // Output: [“userName”, “userAge”]
“`
これは、後述する「オブジェクトプロパティ名の取得」のセクションで詳しく解説します。データを構造化して扱う際には非常に一般的なパターンです。 -
eval()
やnew Function()
を使用してコード文字列を解析する (非推奨・危険):
極端な方法として、変数を宣言しているコードを文字列として持ち、それをeval()
やnew Function()
で実行する前に文字列解析を行うというアプローチが考えられます。“`javascript
const code = “let myVariable = 10;”;
// ここで code 文字列を正規表現などで解析して “myVariable” を抽出…
// 例:const match = code.match(/let\s+(\w+)\s*=/); if (match) console.log(match[1]);eval(code); // myVariable が宣言される
// eval 後の myVariable にアクセスすることはできても、eval() が実行された
// スコープは分離されることが多く、変数名を取得する本質的な解決にはならない。
“`
この方法は、セキュリティリスクが高く、コード解析が非常に複雑で信頼性に欠けるため、決して推奨されません。ミニファイされたコードや複雑な構文には対応できません。 -
デバッグツールと Source Map:
開発中のデバッグ時には、ブラウザの開発者ツールなどがソースコード上の変数名を表示してくれます。これは、ツールが実行環境からデバッグ情報(変数名とメモリ位置のマッピングなど)を取得したり、Source Mapという仕組みを使ってミニファイ/バンドル前の元の変数名を復元したりしているためです。これは実行中のプログラム自身が名前を知る方法ではなく、外部ツールによるサポートです。 -
Proxyによるプロパティアクセス名の補足:
これも変数名そのものではなく、オブジェクトのプロパティへのアクセスに使われた名前を補足するテクニックです。後述の「Proxyを使ったプロパティアクセス名の補足」で解説します。
結論として、JavaScriptの実行時において、ローカルスコープやモジュールスコープで宣言された変数(特に let
や const
)のソースコード上の名前をプログラムコード自体から取得することは、実用的にはほぼ不可能です。もし変数名を何らかの処理で利用したい場合は、その変数をオブジェクトのプロパティとして管理し、プロパティ名を利用するなどの設計上の工夫が必要です。
クラス名の取得: class.name
プロパティ
関数と同様に、クラスも実行時に name
プロパティを使ってその名前を取得できます。
“`javascript
// クラス宣言
class Animal {
constructor(name) {
this.name = name;
}
}
console.log(Animal.name); // Output: “Animal”
// 名前付きクラス式
const Cat = class MyCat {
constructor(name) {
this.name = name;
}
};
console.log(Cat.name); // Output: “MyCat” (変数名ではなくクラス式に付けた名前)
// 無名クラス式
const Dog = class {
constructor(name) {
this.name = name;
}
};
console.log(Dog.name); // Output: “Dog” (変数名から推論される)
“`
クラス名の取得は function.name
とほぼ同様の挙動を示します。これもデバッグやロギング、あるいはフレームワークでのクラス登録や名前解決などに利用されることがあります。注意点も関数名と同様で、ミニファイや難読化によって名前が変わる可能性があります。
オブジェクトプロパティ名の取得
JavaScriptにおいて、実行時に名前を取得する操作が最も一般的で強力なのが、オブジェクトのプロパティ名(またはキー)の取得です。JavaScriptのオブジェクトは動的なキーバリューコレクションとして機能するため、その構造を実行時に調べることが容易です。
プロパティ名は、主に文字列またはシンボル (Symbol
) のどちらかです。プロパティ名の取得には、いくつかの標準メソッドが用意されており、それぞれ取得対象の範囲(自身のプロパティか、プロトタイプチェーンを含むか)や種類(列挙可能か、文字列か、シンボルか)が異なります。
列挙可能なプロパティ名の取得
「列挙可能 (enumerable)」なプロパティとは、for...in
ループなどで列挙されるようにマークされたプロパティです。多くの一般的なプロパティはデフォルトで列挙可能です。
Object.keys(obj)
Object.keys()
メソッドは、引数として与えられたオブジェクト自身の、列挙可能な 文字列 プロパティ名の配列を返します。プロトタイプチェーン上のプロパティは含まれません。
“`javascript
const myObject = {
a: 1,
b: 2,
c: 3
};
const keys = Object.keys(myObject);
console.log(keys); // Output: [“a”, “b”, “c”]
// プロトタイプ上のプロパティは含まれない
function MyClass() {}
MyClass.prototype.protoProp = ‘proto’;
const instance = new MyClass();
instance.ownProp = ‘own’;
console.log(Object.keys(instance)); // Output: [“own”]
“`
Object.keys()
は、オブジェクト自身の「データ」として保持されているプロパティ名を調べたい場合によく使われます。返される配列の順序は、数値キーの場合は昇順、それ以外の場合はプロパティが追加された順序になることが保証されます(ただし、JavaScriptエンジンのバージョンによって挙動が異なる場合があります)。
for...in
ループ
for...in
ループは、オブジェクト自身のプロパティだけでなく、プロトタイプチェーン上にある列挙可能なプロパティ名も列挙します。シンボルプロパティは列挙しません。
“`javascript
const myObject = {
a: 1,
b: 2
};
function MyClass() {}
MyClass.prototype.protoProp = ‘proto’;
const instance = new MyClass();
instance.ownProp = ‘own’;
console.log(“Using for…in on instance:”);
for (const key in instance) {
console.log(key);
}
/ Output (順序は保証されない):
own
proto
/
“`
for...in
はプロトタイプチェーンを辿るため、意図せず継承されたプロパティを処理してしまう可能性があります。オブジェクト自身のプロパティのみを処理したい場合は、ループ内で Object.prototype.hasOwnProperty.call(obj, key)
または Object.hasOwn(obj, key)
(ES2022以降) を使用してフィルタリングするのが一般的です。
“`javascript
const myObject = {
a: 1,
b: 2
};
function MyClass() {}
MyClass.prototype.protoProp = ‘proto’;
const instance = new MyClass();
instance.ownProp = ‘own’;
console.log(“Using for…in with hasOwnProperty:”);
for (const key in instance) {
if (Object.prototype.hasOwnProperty.call(instance, key)) {
console.log(key); // Output: own
}
}
console.log(“Using for…in with Object.hasOwn:”);
for (const key in instance) {
if (Object.hasOwn(instance, key)) { // ES2022
console.log(key); // Output: own
}
}
“`
for...in
はプロトタイプチェーン上のプロパティも考慮する必要があるため、通常は Object.keys()
の方がシンプルで意図した動作になりやすいことが多いです。しかし、意図的に継承されたプロパティも含めて列挙したい場面(例:特定のミックスインや基底クラスのプロパティを調べる)では有用です。
自身の全てのプロパティ名の取得
列挙可能かどうかにかかわらず、オブジェクト自身のプロパティ名を全て取得したい場合があります。
Object.getOwnPropertyNames(obj)
Object.getOwnPropertyNames()
メソッドは、引数として与えられたオブジェクト自身の、列挙可能かどうかにかかわらず 文字列 プロパティ名の配列を返します。プロトタイプチェーン上のプロパティは含まれません。
“`javascript
const myObject = {
a: 1,
b: 2
};
// 列挙不可なプロパティを追加
Object.defineProperty(myObject, ‘nonEnumerableProp’, {
value: ‘secret’,
enumerable: false
});
console.log(Object.keys(myObject)); // Output: [“a”, “b”]
console.log(Object.getOwnPropertyNames(myObject)); // Output: [“a”, “b”, “nonEnumerableProp”]
// 配列のインデックスも文字列プロパティとして取得される
const myArray = [‘x’, ‘y’, ‘z’];
console.log(Object.getOwnPropertyNames(myArray)); // Output: [“0”, “1”, “2”, “length”]
“`
Object.getOwnPropertyNames()
は、オブジェクトが自身で直接持っている文字列キーのプロパティ全てに関心がある場合に役立ちます。例えば、オブジェクトの構造を詳細に調べたい場合や、列挙不可なプロパティ(組み込みオブジェクトの多くのプロパティなど)を含めて処理したい場合に使われます。
シンボルプロパティ名の取得
ES6で導入されたシンボルは、文字列とは異なる新しいプリミティブ型です。シンボルはオブジェクトのプロパティキーとして使用でき、文字列キーとの衝突を気にせず一意なプロパティを作成できます。シンボルプロパティはデフォルトでは列挙可能ではありません(Object.keys
や for...in
で列挙されない)。
Object.getOwnPropertySymbols(obj)
Object.getOwnPropertySymbols()
メソッドは、引数として与えられたオブジェクト自身の、シンボル プロパティキーの配列を返します。列挙可能かどうかにかかわらず含まれます。プロトタイプチェーン上のプロパティは含まれません。
“`javascript
const mySymbol1 = Symbol(‘desc1’);
const mySymbol2 = Symbol(‘desc2’);
const myObject = {
a: 1,
};
Object.defineProperty(myObject, mySymbol2, {
value: ‘symbolValue2’,
enumerable: true // シンボルも列挙可能性を持つことができる
});
console.log(Object.keys(myObject)); // Output: [“a”]
console.log(Object.getOwnPropertyNames(myObject)); // Output: [“a”]
console.log(Object.getOwnPropertySymbols(myObject)); // Output: [Symbol(desc1), Symbol(desc2)]
// 列挙可能なシンボルプロパティのみを取得したい場合は? -> Reflect.ownKeys を使うか、取得後にフィルタリング
“`
シンボルプロパティはメタデータや内部的なプロパティとして使われることがあり、Object.getOwnPropertySymbols()
はこれらの内部的なキーにアクセスしたい場合に必要になります。
自身の全てのプロパティキー(文字列とシンボル)の取得
オブジェクト自身の持つプロパティキー全てに関心がある場合は、文字列キーとシンボルキーの両方を取得する必要があります。
Reflect.ownKeys(obj)
Reflect.ownKeys()
メソッドは、引数として与えられたオブジェクト自身の、列挙可能かどうかにかかわらず 文字列 プロパティキーと シンボル プロパティキーの両方を配列として返します。プロトタイプチェーン上のプロパティは含まれません。
“`javascript
const mySymbol = Symbol(‘desc’);
const myObject = {
a: 1,
};
Object.defineProperty(myObject, ‘nonEnumerableProp’, {
value: ‘secret’,
enumerable: false
});
console.log(Object.keys(myObject)); // Output: [“a”]
console.log(Object.getOwnPropertyNames(myObject)); // Output: [“a”, “nonEnumerableProp”]
console.log(Object.getOwnPropertySymbols(myObject)); // Output: [Symbol(desc)]
console.log(Reflect.ownKeys(myObject)); // Output: [“a”, “nonEnumerableProp”, Symbol(desc)] (順序は実装依存)
“`
Reflect.ownKeys()
は、オブジェクトが持つすべてのプロパティキーを網羅的に調べたい場合に最も適しています。これは、Object.getOwnPropertyNames()
と Object.getOwnPropertySymbols()
を組み合わせたような結果を提供しますが、単一のメソッド呼び出しで済むため便利です。リフレクションAPIの一部として導入されました。
プロトタイプチェーン上のプロパティも含む取得
for...in
ループはプロトタイプチェーン上の列挙可能な文字列プロパティを列挙しましたが、列挙可能かどうかにかかわらず、あるいはシンボルも含めて、プロトタイプチェーン全体を辿ってプロパティキーを取得したい場合は、自身でプロトタイプチェーンを遡る必要があります。
“`javascript
function getAllPropertyKeys(obj) {
let current = obj;
let keys = new Set(); // Setを使って重複を排除
while (current) {
// current オブジェクト自身のプロパティキーを取得(文字列とシンボル両方)
const ownKeys = Reflect.ownKeys(current);
// 取得したキーをSetに追加
for (const key of ownKeys) {
keys.add(key);
}
// プロトタイプオブジェクトを取得して、次のループで処理
current = Object.getPrototypeOf(current);
}
// Setから配列に変換
return Array.from(keys);
}
function ParentClass() {}
ParentClass.prototype.parentProp = ‘parent’;
const parentSymbol = Symbol(‘parentSymbol’);
ParentClass.prototype[parentSymbol] = ‘parent symbol’;
function ChildClass() {}
ChildClass.prototype = Object.create(ParentClass.prototype);
ChildClass.prototype.constructor = ChildClass;
ChildClass.prototype.childProp = ‘child’;
const instance = new ChildClass();
instance.ownProp = ‘own’;
const ownSymbol = Symbol(‘ownSymbol’);
instance[ownSymbol] = ‘own symbol’;
Object.defineProperty(instance, ‘nonEnumerableOwnProp’, { value: ‘secret’, enumerable: false });
console.log(getAllPropertyKeys(instance));
/ Output (順序は実装依存):
[
“ownProp”,
Symbol(ownSymbol),
“nonEnumerableOwnProp”,
“childProp”,
“parentProp”,
Symbol(parentSymbol),
“constructor” // ChildClass.prototype の constructor プロパティなど
]
/
“`
この例では、Object.getPrototypeOf()
を使ってオブジェクトのプロトタイプを順に辿り、各ステップで Reflect.ownKeys()
を使ってそのオブジェクト自身のプロパティキーを取得し、Setに加えて重複を排除しています。これは、オブジェクトの完全なプロパティ構造を調べたい場合に有用ですが、パフォーマンスに影響を与える可能性があるため、必要な場合にのみ使用すべきです。
プロパティ名を取得する際の考慮事項
- 列挙可能性 (
enumerable
): プロパティのenumerable
属性は、Object.keys()
やfor...in
ループでの振る舞いに影響します。デフォルトでは多くのプロパティは列挙可能ですが、組み込みオブジェクトのプロパティ(例:Array.prototype.forEach
)やObject.defineProperty
で明示的にenumerable: false
に設定されたプロパティは列挙されません。 - プロトタイプチェーン:
for...in
以外のObject
メソッド (keys
,getOwnPropertyNames
,getOwnPropertySymbols
) やReflect.ownKeys
は、自身のプロパティのみを対象とします。継承されたプロパティを含めたい場合は、プロトタイプチェーンを明示的に辿る必要があります。 - シンボルプロパティ: シンボルプロパティは、文字列プロパティとは異なるメソッド(
Object.getOwnPropertySymbols
,Reflect.ownKeys
)で取得する必要があります。 - 数値キー: オブジェクトのプロパティキーが数値のように見える文字列(例:
'0'
,'1'
)の場合、JavaScriptエンジンはこれらを特別扱いし、多くの場合昇順で並べます(Object.keys
,Object.getOwnPropertyNames
,Reflect.ownKeys
)。 - 計算されたプロパティ名 ([computed property names]):
let propName = 'myProp'; let obj = { [propName]: 1 };
のように計算されたプロパティ名で追加されたプロパティも、追加された後は通常の文字列またはシンボルキーとして扱われるため、上記メソッドで取得できます。
オブジェクトプロパティ名の取得はJavaScriptのメタプログラミングにおいて非常に強力な手段です。オブジェクトの状態を動的に調べたり、汎用的なデータ処理関数を作成したりするのに役立ちます。
DOM要素の識別子の取得
Webブラウザ環境では、JavaScriptはDocument Object Model (DOM) を操作します。DOM要素はHTML属性を通じて様々な識別子を持つことが多く、これらの属性値や関連するプロパティをJavaScriptから取得できます。これらは厳密には「JavaScriptの変数名やプロパティ名」とは異なりますが、HTML要素をコードから識別・操作するための「名前」として機能します。
主要な識別子とその取得方法を見ていきましょう。
IDの取得: element.id
HTML要素の id
属性は、ドキュメント内でその要素を一意に識別するためのものです。JavaScriptからは、要素オブジェクトの id
プロパティとしてアクセスできます。
“`html
“`
javascript
const element = document.getElementById('myUniqueElement');
const elementId = element.id;
console.log(elementId); // Output: "myUniqueElement"
element.id
は文字列であり、id
属性の値と一致します。
クラス名の取得: element.className
および element.classList
HTML要素の class
属性は、要素に適用されるCSSクラスを指定したり、特定の種類の要素をグループ化したりするために使用されます。複数のクラスをスペース区切りで指定できます。
クラス名を取得する方法は主に二つあります。
element.className
className
プロパティは、要素の class
属性の値をそのまま単一の文字列として返します。複数のクラスがある場合は、スペース区切りで連結された文字列になります。
“`html
“`
javascript
const element = document.getElementById('myElement');
const classNameString = element.className;
console.log(classNameString); // Output: "container hidden active"
この文字列を処理するには、手動で分割する必要があります(例: classNameString.split(' ')
)。
element.classList
classList
プロパティは、要素の class
属性の値を個々のクラス名に分解し、DOMTokenList
という特殊なオブジェクトとして提供します。DOMTokenList
は配列のようにインデックスでアクセスしたり、length
プロパティを持ったりするほか、クラスの追加 (add
), 削除 (remove
), 存在チェック (contains
), トグル (toggle
) など、クラス名を操作するための便利なメソッドを提供します。
“`html
“`
“`javascript
const element = document.getElementById(‘myElement’);
const classList = element.classList;
console.log(classList); // Output: DOMTokenList(3) [“container”, “hidden”, “active”, value: “container hidden active”]
console.log(classList.length); // Output: 3
console.log(classList[0]); // Output: “container”
console.log(classList.contains(‘hidden’)); // Output: true
// DOMTokenList を配列として取得したい場合
const classesArray = Array.from(classList);
console.log(classesArray); // Output: [“container”, “hidden”, “active”]
// または spread 構文
const classesArraySpread = […classList];
console.log(classesArraySpread); // Output: [“container”, “hidden”, “active”]
// または classList.value を使う
console.log(classList.value); // Output: “container hidden active” (className と同じ文字列)
“`
クラス名を個別に扱いたい場合や、クラスの存在チェック、追加・削除を行いたい場合は、classList
を使用するのが一般的です。クラス名を単なる文字列として扱いたい場合は className
を使うこともありますが、通常 classList
の方が便利です。
タグ名の取得: element.tagName
tagName
プロパティは、要素のタグ名(例: "DIV"
, "A"
, "TABLE"
)を大文字の文字列として返します。
“`html
Link
“`
“`javascript
const divElement = document.querySelector(‘div’);
console.log(divElement.tagName); // Output: “DIV”
const linkElement = document.querySelector(‘a’);
console.log(linkElement.tagName); // Output: “A”
const inputElement = document.querySelector(‘input’);
console.log(inputElement.tagName); // Output: “INPUT”
“`
要素の種類を判別したい場合などに利用されます。返される文字列は常に大文字であることに注意してください。要素がXMLドキュメントの一部である場合は、要素のXMLタグ名が返されます。
name属性の取得: element.name
name
属性は、主にフォーム要素 (<input>
, <select>
, <textarea>
, <button>
, <form>
) や <iframe>
, <map>
, <meta>
, <param>
などで使用され、要素に名前を付けて識別するために使われます。フォーム要素の場合、サーバーに送信されるデータのキーとして使われる重要な属性です。
JavaScriptからは、要素オブジェクトの name
プロパティとしてアクセスできます。
“`html
“`
“`javascript
const usernameInput = document.querySelector(‘input[name=”username”]’);
console.log(usernameInput.name); // Output: “username”
const passwordInput = document.querySelector(‘input[name=”password”]’);
console.log(passwordInput.name); // Output: “password”
“`
フォーム入力要素の値と名前をまとめて取得して処理する場合などに頻繁に利用されます。
その他の属性値の取得: element.getAttribute()
および element.dataset
ID、クラス、name属性以外にも、HTML要素は様々な属性を持ちます(例: href
, src
, alt
, data-*
)。これらの属性値もJavaScriptから取得できます。
element.getAttribute('attribute-name')
getAttribute()
メソッドは、指定された属性名に対応する属性値を文字列として返します。指定された属性が存在しない場合は null
を返します。
“`html
Example
“`
“`javascript
const link = document.querySelector(‘a’);
console.log(link.getAttribute(‘href’)); // Output: “https://example.com”
console.log(link.getAttribute(‘target’)); // Output: “_blank”
console.log(link.getAttribute(‘id’)); // Output: null (id属性はない)
const img = document.querySelector(‘img’);
console.log(img.getAttribute(‘src’)); // Output: “image.jpg”
console.log(img.getAttribute(‘alt’)); // Output: “An image”
const div = document.querySelector(‘div[data-user-id]’);
console.log(div.getAttribute(‘data-user-id’)); // Output: “12345”
console.log(div.getAttribute(‘data-role’)); // Output: “admin”
“`
getAttribute()
は、HTML属性として書かれている通りの値を取得したい場合に最も正確です。いくつかの属性は、対応するJavaScriptプロパティを持っていますが(例: a.href
、img.src
)、これらのプロパティはURLを絶対パスに解決するなど、元の属性値とは異なる値を返すことがあります。元の属性値を確実に取得するには getAttribute()
を使用します。
element.dataset
data-*
属性は、カスタムデータをHTML要素に関連付けるための標準的な方法です。例えば、<div data-user-id="123">
のように記述します。
JavaScriptからは、element.dataset
プロパティを使ってこれらの属性に簡単にアクセスできます。dataset
は DOMStringMap
オブジェクトを返します。data-attribute-name
という形式の属性は、dataset.attributeName
というキャメルケースのプロパティとしてアクセスできます。
“`html
“`
“`javascript
const userInfoDiv = document.getElementById(‘userInfo’);
const dataset = userInfoDiv.dataset;
console.log(dataset.userId); // Output: “12345” (data-user-id -> userId)
console.log(dataset.role); // Output: “admin” (data-role -> role)
console.log(dataset.lastLogin); // Output: “2023-10-27” (data-last-login -> lastLogin)
// dataset オブジェクト自体を列挙することも可能(プロパティ名として)
console.log(Object.keys(dataset)); // Output: [“userId”, “role”, “lastLogin”]
“`
dataset
は data-*
属性を扱うための非常に便利な方法です。属性名のハイフンがキャメルケースに変換される点に注意が必要です。
DOM要素を取得するためのセレクター
要素そのものを取得するためには、document.getElementById()
, document.querySelector()
, document.querySelectorAll()
, document.getElementsByClassName()
, document.getElementsByTagName()
などのメソッドを使います。これらのメソッドには、ID名、セレクター文字列(クラス名やタグ名を含む)、タグ名などを引数として渡しますが、これは「名前を取得する」というよりも、「名前を使って要素を見つける」という操作です。
一度要素を取得すれば、上記で説明した id
, className
, classList
, tagName
, name
, getAttribute
, dataset
といったプロパティやメソッドを使って、その要素の識別子や属性値を取得できます。
その他の状況における名前の取得
これまでに挙げた主な対象(変数、関数、クラス、オブジェクトプロパティ、DOM)以外にも、名前に関連する情報を取得したい場面があります。
関数引数の名前
関数が受け取る引数に付けられた「名前」(仮引数名、例: function greet(name) {}
の name
)を、関数の実行中にプログラム自身が知ることは、変数名と同様に困難です。
“`javascript
function processArguments(arg1, arg2, options = {}) {
// ここで arg1, arg2, options という名前をプログラム自身が知る方法?
console.log(arg1, arg2, options);
}
processArguments(‘hello’, 123, { debug: true });
“`
実行時の最適化やミニファイによって引数名は失われるか変更されます。function.toString()
を使って関数のソースコードを取得し、それを解析するという非推奨の方法は存在しますが、これは信頼性が低く、構文解析が困難であり、アロー関数やデフォルト引数、分割代入など複雑な構文には対応しにくいです。
“`javascript
function exampleFunc(a, { b, c }, d = 10) {
// 引数名を解析したい
}
console.log(exampleFunc.toString());
/ 出力例:
“function exampleFunc(a, { b, c }, d = 10) {
// 引数名を解析したい
}”
/
// この文字列を解析して [“a”, “{ b, c }”, “d”] を取得するのは難しい
“`
静的解析ツール(Babelプラグインやカスタムパーサーなど)であれば、ソースコードの段階で引数名に関する情報を抽出できますが、これは実行時ではなくビルド時の処理になります。
スタックトレースからの関数名
エラーが発生した際などに生成されるスタックトレースには、エラーが発生した時点での関数の呼び出し履歴が含まれています。このスタックトレースから、呼び出し元の関数名などの情報を得ることができます。これは主にデバッグ目的で非常に有用です。
JavaScriptのエラーオブジェクト (Error
) は、多くの環境で stack
プロパティを持っています。このプロパティは、コールスタックを示す文字列を返します。
“`javascript
function foo() {
bar();
}
function bar() {
baz();
}
function baz() {
try {
throw new Error(“Something went wrong”);
} catch (e) {
console.error(e.stack);
}
}
foo();
“`
上記のコードを実行すると、コンソールには以下のようなスタックトレースが出力されます(具体的な形式はブラウザやNode.jsのバージョンによって異なります)。
Error: Something went wrong
at baz (...)
at bar (...)
at foo (...)
at ...
この文字列を解析することで、エラー発生時の関数呼び出しの順序や関数名(baz
, bar
, foo
)を知ることができます。ただし、スタックトレースの文字列形式は標準化されていないため、解析するには正規表現などを用いて各行をパースする必要があります。また、匿名関数やアロー関数の名前は表示されなかったり、「anonymous」などと表示されたりする場合があります。最適化によってスタックトレースの情報が少なくなることもあります。
スタックトレースの解析は、カスタムエラーロギングやプロファイリングなどに利用できます。
Proxyを使ったプロパティアクセス名の補足
ES6で導入された Proxy
オブジェクトを使うと、オブジェクトに対する様々な操作(プロパティの読み書き、列挙、関数の呼び出しなど)をインターセプトできます。これにより、オブジェクトへのアクセスに使われたプロパティ名や操作の種類を知ることができます。
これは「オブジェクトが自身で持つプロパティ名を取得する」のではなく、「外部からオブジェクトに対して行われた操作に使われた名前を傍受する」というイメージです。
“`javascript
const target = {
message1: ‘hello’,
message2: ‘world’
};
const handler = {
get(target, property, receiver) {
console.log([Proxy] プロパティ "${String(property)}" が読み取られました
);
return Reflect.get(target, property, receiver);
},
set(target, property, value, receiver) {
console.log([Proxy] プロパティ "${String(property)}" に値 "${value}" が設定されました
);
return Reflect.set(target, property, value, receiver);
},
has(target, property) {
console.log([Proxy] プロパティ "${String(property)}" の存在チェックが行われました
);
return Reflect.has(target, property);
},
ownKeys(target) {
console.log([Proxy] 自身のプロパティキーの列挙が行われました
);
return Reflect.ownKeys(target);
}
};
const proxy = new Proxy(target, handler);
console.log(proxy.message1); // [Proxy] プロパティ “message1” が読み取られました -> hello
proxy.message2 = ‘proxy world’; // [Proxy] プロパティ “message2” に値 “proxy world” が設定されました
console.log(‘message1’ in proxy); // [Proxy] プロパティ “message1” の存在チェックが行われました -> true
console.log(Object.keys(proxy)); // [Proxy] 自身のプロパティキーの列挙が行われました -> [“message1”, “message2”]
“`
Proxy
の get
, set
, has
, ownKeys
などのトラップ関数には、操作の対象となったプロパティ名(キー)が引数として渡されます。これにより、そのプロパティ名を利用して追加の処理(ロギング、バリデーション、遅延評価など)を実行できます。
これは強力な機能ですが、Proxy
はあくまで「プロパティへのアクセス」をフックするものであり、例えばローカル変数へのアクセスに使われた変数名を取得することはできません。
Symbol.toStringTag
Symbol.toStringTag
は、直接的な「名前」ではありませんが、Object.prototype.toString.call(obj)
を呼び出した際に、オブジェクトのタイプを示す文字列として使われるシンボルプロパティです。組み込みオブジェクト(Array
, Map
, Set
など)はこれを持っており、独自のクラスやオブジェクトにも設定できます。
“`javascript
console.log(Object.prototype.toString.call([])); // Output: “[object Array]”
console.log(Object.prototype.toString.call(new Map())); // Output: “[object Map]”
class MyObject {
get Symbol.toStringTag {
return ‘MyCustomObject’;
}
}
const instance = new MyObject();
console.log(Object.prototype.toString.call(instance)); // Output: “[object MyCustomObject]”
“`
これはオブジェクトの「型」や「種類」を示す識別子として機能し、デバッグや型チェックのような場面で役立ちます。
実践的な使用例と注意点
JavaScriptで名前を取得する様々な方法を見てきましたが、これらのテクニックはどのような場面で役立ち、使用する上でどのような注意が必要でしょうか。
実践的な使用例
- デバッグとロギング: 関数名 (
function.name
) やスタックトレース (Error.stack
) を使って、プログラムのどの部分が実行されているか、どこでエラーが発生したかをログに出力する。オブジェクトをconsole.log({ myVariable })
のようにオブジェクトリテラルとして出力すると、一部の環境(Chrome DevTools, Node.jsなど)では変数名とその値を同時に表示してくれます(これは開発者ツールの機能であり、プログラム自身の実行結果ではありません)。 - データシリアライズ/デシリアライズ: オブジェクトをJSONなどの形式に変換する際、プロパティ名 (
Object.keys
,Reflect.ownKeys
) をキーとして使用する。サーバーから受け取ったデータとクライアントサイドのオブジェクトをマッピングする際にもプロパティ名が重要になります。 - フォーム処理: HTMLフォームから送信されるデータを収集する際に、入力要素の
name
属性をキーとして使用する (element.name
)。 - テンプレートエンジンやデータバインディング: オブジェクトのプロパティ名を使って、テンプレート内のプレースホルダーをデータで置換したり、UI要素とデータを同期させたりする。
- 汎用的なユーティリティ関数: オブジェクトのプロパティを操作する関数(例: ディープコピー、オブジェクトのマージ、特定の型のプロパティのみを抽出)を実装する際に、プロパティ名を動的に取得して処理する (
Object.keys
,Reflect.ownKeys
など)。 - メタプログラミングとフレームワーク: DIコンテナでの依存関係解決(クラス名や関数名を利用)、ORMでのオブジェクトとデータベースカラムのマッピング(プロパティ名とカラム名を対応付ける)、設定オブジェクトの処理など、フレームワークレベルでオブジェクトの構造や名前に関する情報を利用する。
- DOM操作: 要素のID (
element.id
) やクラス名 (element.classList
) を使って、特定の要素を見つけたりスタイルを適用したり、要素の状態を管理したりする。data-*
属性 (element.dataset
) にカスタムデータを保持し、JavaScriptからアクセスする。
使用上の注意点
- ミニファイと難読化: 本番環境にデプロイされるコードは、通常ミニファイ(変数名や関数名を短くする)や難読化が行われます。これにより、
function.name
の値が変わったり、変数名が完全に意味不明なものになったりします。名前の取得に依存する処理は、これらのプロセスを考慮して設計する必要があります。デバッグ時にはSource Mapを利用して元の名前を復元できますが、これは開発者ツールの機能です。 - 実行時取得の信頼性: 特に変数名の実行時取得は、前述のように言語の設計上困難です。変数名自体が必要な場合は、別の方法(オブジェクトプロパティとして管理するなど)を検討する必要があります。
eval()
とtoString()
解析の危険性: コード文字列を解析して名前を取得するような方法は、セキュリティリスク(evalインジェクションなど)が高く、パフォーマンスが悪く、信頼性も低いため、極力避けるべきです。- Symbolプロパティ: シンボルプロパティは
Object.keys()
やfor...in
では取得できません。シンボルも対象に含める場合は、Object.getOwnPropertySymbols()
やReflect.ownKeys()
を使用する必要があります。 - パフォーマンス: オブジェクトのプロパティを動的に列挙する処理は、静的に名前を指定する場合に比べてオーバーヘッドが発生する可能性があります。特に大規模なオブジェクトや頻繁に実行されるコードで利用する場合は、パフォーマンスへの影響を考慮してください。プロトタイプチェーン全体を辿る処理はさらにコストが高くなる可能性があります。
- DOM操作のパフォーマンス:
getAttribute()
やdataset
などによる属性値の取得は通常高速ですが、大量の要素に対して頻繁にDOMアクセスを行う場合はパフォーマンスに影響を与える可能性があります。適切なセレクターを使って必要な要素のみを取得し、DOM操作を最小限に抑えることが重要です。
高度なトピック
静的解析 (Static Analysis)
実行時ではなく、コードのソースコードを解析して名前や構造に関する情報を取得する手法です。これは、変数名などの実行時には失われやすい情報を得るための強力な方法であり、多くの開発ツールやフレームワークで利用されています。
静的解析では、まずソースコードをAbstract Syntax Tree (AST) という構造化されたデータに変換します。ASTはコードの構造を木構造で表現したもので、変数宣言、関数宣言、プロパティアクセスなどがノードとして表現されます。解析ツールはASTを走査し、必要な情報(変数名、関数名、それらのスコープ、参照箇所など)を抽出します。
- ツール: ESLint (コード規約チェック), Prettier (コードフォーマット), TypeScript (型チェック), Babel (トランスパイル), webpack/Rollup (バンドル) など、多くのJavaScript開発ツールが内部で静的解析を利用しています。
- ASTライブラリ:
acorn
,@babel/parser
などのライブラリを使うと、JavaScriptコードをASTにパースできます。ASTを操作するためのestraverse
,babel-traverse
といったライブラリもあります。 - 用途: 変数名の変更(ミニファイ)、未使用変数の検出、コード内の特定のパターン検索、ドキュメンテーション生成、リンティング、コード変換など。
変数名そのものを実行時に知る必要がどうしてもあるような特殊なケース(例えば、独自のDIコンテナを構築する際に、関数の引数名から依存関係を推論したいなど)では、実行時ではなくビルドプロセスの中で静的解析を行い、必要な名前情報をメタデータとして出力しておき、実行時にはそのメタデータを参照するというアプローチが取られることがあります。
Source Map
Source Mapは、ミニファイやバンドル、トランスパイルされた本番用コードと、開発用の元のソースコードとの間の対応関係を記録したファイルです。主に .map
拡張子を持ちます。
ブラウザの開発者ツールはSource Mapを読み込むことで、実行中のミニファイされたコードの特定の箇所が、元のソースコードのどのファイル・行・列に対応するかを知ることができます。これにより、開発者は本番環境で発生したエラーのスタックトレースを元のコードで確認したり、デバッグ時に元の変数名を使って値を調べたりすることが可能になります。
これはプログラム自身が名前を取得する方法ではありませんが、開発者にとって「名前」を把握する上で非常に重要な仕組みです。
まとめ
JavaScriptにおける「名前」の取得は、その対象によって利用可能な方法、容易さ、そして信頼性が大きく異なります。
- 変数名: 実行時にプログラム自身のコードから取得することは、実用的にはほぼ不可能です。ミニファイや難読化の影響も強く受けます。オブジェクトプロパティとして管理することで、プロパティ名として取得するという代替手段があります。
- 関数名・クラス名:
function.name
およびclass.name
プロパティを使って比較的容易に取得できます。ただし、無名の場合の推論や、ミニファイによる変更に注意が必要です。 - オブジェクトプロパティ名:
Object.keys()
,Object.getOwnPropertyNames()
,Object.getOwnPropertySymbols()
,Reflect.ownKeys()
,for...in
ループなど、オブジェクトの構造を調べるための様々な標準メソッドが提供されており、最も柔軟に名前を取得できる対象です。 - DOM要素の識別子:
element.id
,element.className
,element.classList
,element.tagName
,element.name
,element.getAttribute()
,element.dataset
など、要素の属性やプロパティを通じて簡単に取得できます。これらはHTML構造における「名前」として機能します。 - その他の名前: 関数引数名やスタックトレースからの関数名など、一部の情報は限定的な方法で取得可能ですが、信頼性や汎用性に欠ける場合があります。Proxyはプロパティへのアクセスに使われた名前を補足する強力な仕組みです。
実行時取得の限界を補うために、ビルドプロセスにおける静的解析や、デバッグ時のSource Mapといったツールや仕組みが活用されています。
JavaScript開発において何らかの「名前」を取得したい状況に直面した際は、まずその「名前」がどの種類のエンティティ(変数、関数、プロパティ、DOMなど)に関連しているかを明確にし、それぞれの対象に対して用意されている標準的なAPIを確認することが重要です。そして、コードのミニファイや実行環境(ブラウザかNode.jsかなど)の違い、そして取得したい名前の信頼性(デバッグ目的か、プログラムのロジックに組み込むか)を考慮して、適切な方法を選択する必要があります。
このガイドが、JavaScriptにおける様々な「名前」の取得方法を理解し、開発に役立てるための一助となれば幸いです。