[解決済み] Cookieやローカルストレージを使用しないユーザー認識
質問
私は分析ツールを作っているのですが、現在、ユーザーのIPアドレス、ブラウザ、オペレーティングシステムをユーザーエージェントから取得することができます。
私は、Cookie やローカル ストレージを使用せずに、同じユーザーを検出する可能性があるかどうか疑問に思っています。私はここでコード例を期待しているわけではなく、さらに見るべき場所の簡単なヒントだけです。
同じコンピューター/デバイスである場合、クロスブラウザー互換である必要があることを言及するのを忘れました。基本的に、私は、ユーザーではなく、デバイスの認識の後にいます。
どのように解決するのですか?
はじめに
私が正しく理解していれば、一意な識別子を持たないユーザーを識別する必要があり、ランダムデータのマッチングによって彼らが誰であるかを把握したいとします。ユーザーのアイデンティティを確実に保存することはできないからです。
- Cookie は削除することができます。
- IP アドレスの変更
- ブラウザは変更することができます
- ブラウザのキャッシュを削除することができます
Java Applet または Com Object を使用すれば、ハードウェア情報のハッシュを使用して簡単に解決できましたが、最近の人々はセキュリティ意識が高く、この種のプログラムを自分のシステムにインストールさせるのは難しいでしょう。そのため、Cookieやその他の類似のツールを使用することになります。
Cookie やその他の類似のツール
データプロファイルを構築し、確率テストを使って 可能性のあるユーザー . このために有用なプロファイルは、以下のいくつかの組み合わせによって生成することができます。
-
IP アドレス
- 実際のIPアドレス
- プロキシIPアドレス(ユーザーは同じプロキシを繰り返し使用することが多い)
-
クッキー
- HTTP クッキー
- セッションクッキー
- サードパーティークッキー
- フラッシュクッキー ( ほとんどの人は、これらを削除する方法を知りません。 )
-
Web バグ (バグが修正されるため、信頼性は低いですが、それでも有用です)
- PDF バグ
- Flashの不具合
- Java のバグ
-
ブラウザ
- クリック追跡 (多くのユーザーが、毎回同じシリーズのページを訪問します)
- ブラウザのフィンガー プリント - インストールされているプラグイン (多くの場合、ユーザーはさまざまな、多少ユニークなプラグインのセットを持っています)
- キャッシュされた画像 (人々は時々クッキーを削除しますが、キャッシュされた画像は残します)
- Blob の使用
- URL(ブラウザの履歴やCookieには、URLの中にユニークなユーザーIDが含まれていることがあり、例えば https://stackoverflow.com/users/1226894 または http://www.facebook.com/barackobama?fref=ts )
- システムフォントの検出 (これはあまり知られていませんが、しばしばユニークなキー シグネチャです)
-
HTML5 & ジャバスクリプト
- HTML5 LocalStorage
- HTML5 Geolocation API とリバース ジオコーディング
- アーキテクチャ、OS 言語、システム時間、画面解像度、など。
- ネットワーク情報 API
- バッテリーステータスAPI
私が挙げた項目は、もちろん、ユーザーを一意に識別するために考えられるいくつかの方法にすぎません。もっとたくさんあります。
データ プロファイルを作成するためのランダムなデータ要素のセットで、次は何をするのでしょうか?
次のステップは ファジーロジック を開発すること、あるいはもっといいのは 人工ニューラルネットワーク (ファジーロジックを使用)を使用することもできます。いずれの場合も、システムを訓練し、その訓練と ベイズ推定 を組み合わせて、結果の精度を高めることです。
この ニューラルメッシュ ライブラリを使用すると、人工ニューラルネットワークを生成することができます。ベイジアン推論を実装するには、以下のリンクを参照してください。
この時点で、あなたは考えているかもしれません。
一見単純な作業に見えるのに、なぜこれほどまでに数学と論理が必要なのでしょうか?
基本的に、それは 簡単な作業ではない . あなたが実現しようとしていることは、実は 純粋な確率 . たとえば、次のような既知のユーザーがいたとします。
User1 = A + B + C + D + G + K
User2 = C + D + I + J + K + F
以下のデータを受信した場合。
B + C + E + G + F + K
あなたが本質的に求めている質問は、次のとおりです。
受信したデータ(B + C + E + G + F + K)が実際にUser1またはUser2である確率はどのくらいか?そして、その2つのマッチのうち、どちらが 最も の確率は?
この質問に効果的に答えるには、次のことを理解する必要があります。 頻度 vs 確率の形式 と、なぜ 結合確率 がより良いアプローチであるかもしれません。詳細はここでは書ききれませんが(だからリンクを貼っているのです)、良い例としては 医療診断ウィザードアプリケーション これは、症状の組み合わせから病気の可能性を特定するものです。
データプロファイルを構成する一連のデータポイント(上の例ではB + C + E + G + F + K)を次のように考えてみてください。 症状 とし、未知のユーザーを 病気 . 病気を特定することで、さらに適切な治療法を特定することができます(このユーザーをUser1として治療する)。
明らかに 病気について を1つ以上特定した場合、その 症状 は特定しやすくなります。実際、より多くの 症状 を特定できれば、診断がより簡単に、より正確になることはほぼ間違いないでしょう。
他の選択肢はないのですか?
もちろんあります。代替措置として、独自の単純なスコアリング アルゴリズムを作成し、完全一致をベースにすることもできます。これは確率ほど効率的ではありませんが、あなたにとってよりシンプルに実装できるかもしれません。
例として、この単純なスコア表を考えてみましょう。
+-------------------------+--------+------------+ | プロパティ|重み|重要度 +-------------------------+--------+------------+ | 実IPアドレス|60|5 | 使用されるプロキシIPアドレス(40)|4|。 | HTTPクッキー|80|8 | セッションクッキー|80|6 | サードパーティークッキー|60クッキー|4クッキー | フラッシュクッキー|90|7 | PDFの不具合について|20|1 | フラッシュに関する不具合|20|1 | Java不具合|20件|1件 | よく使うページ|40件|1件 | ブラウザのフィンガープリント|35|2 | インストールされたプラグイン|25|1 | キャッシュされた画像|40|3 | URL|60|4 | システムフォント検出|70|4 | ローカルストレージ|90|8 | ジオロケーション|70|6 | aoltr|70|4 | ネットワーク情報API|40|3 | バッテリーステータスAPI|20|1 +-------------------------+--------+------------+
与えられたリクエストで収集できる各情報について、関連するスコアを付与し、次に 重要度 を使用して、スコアが同じである場合の競合を解決します。
コンセプトの証明
簡単な概念実証のために、以下をご覧ください。 パーセプトロン . パーセプトロンは RNAモデル であり、一般にパターン認識のアプリケーションで使用されます。また、古くから PHP クラス がありますが、おそらくあなたの目的に合わせて変更する必要があるでしょう。
パーセプトロンは素晴らしいツールですが、複数の結果(マッチする可能性のあるもの)を返すこともあるので、スコアと差分の比較を使って ベスト を特定するのに役立ちます。
前提条件
- 各ユーザーに関するすべての可能な情報 (IP、Cookie など) を保存します。
- 結果が完全一致の場合、スコアを 1 つ増やします。
- 結果が完全一致でない場合、スコアを1つ下げます。
期待値
- RNAラベルを生成する
- データベースを模したランダムなユーザーを生成する
- 単一のUnknownユーザーを生成する
- アンノウン・ユーザーのRNAと値の生成
- RNAの情報をマージし、パーセプトロンにティーチングする。
- パーセプトロンの学習後、システムは重み付けのセットを持つことになります。
- これでUnknown userのパターンをテストすることができ、パーセプトロンは結果セットを生成します。
- すべてのPositiveなマッチを保存する
- マッチをまずスコアでソートし、次に差分でソートします (上記と同様)
- 最も近い 2 つのマッチを出力するか、マッチが見つからなかった場合は空の結果を出力します。
概念実証のためのコード
$features = array(
'Real IP address' => .5,
'Used proxy IP address' => .4,
'HTTP Cookies' => .9,
'Session Cookies' => .6,
'3rd Party Cookies' => .6,
'Flash Cookies' => .7,
'PDF Bug' => .2,
'Flash Bug' => .2,
'Java Bug' => .2,
'Frequent Pages' => .3,
'Browsers Finger Print' => .3,
'Installed Plugins' => .2,
'URL' => .5,
'Cached PNG' => .4,
'System Fonts Detection' => .6,
'Localstorage' => .8,
'Geolocation' => .6,
'AOLTR' => .4,
'Network Information API' => .3,
'Battery Status API' => .2
);
// Get RNA Lables
$labels = array();
$n = 1;
foreach ($features as $k => $v) {
$labels[$k] = "x" . $n;
$n ++;
}
// Create Users
$users = array();
for($i = 0, $name = "A"; $i < 5; $i ++, $name ++) {
$users[] = new Profile($name, $features);
}
// Generate Unknown User
$unknown = new Profile("Unknown", $features);
// Generate Unknown RNA
$unknownRNA = array(
0 => array("o" => 1),
1 => array("o" => - 1)
);
// Create RNA Values
foreach ($unknown->data as $item => $point) {
$unknownRNA[0][$labels[$item]] = $point;
$unknownRNA[1][$labels[$item]] = (- 1 * $point);
}
// Start Perception Class
$perceptron = new Perceptron();
// Train Results
$trainResult = $perceptron->train($unknownRNA, 1, 1);
// Find matches
foreach ($users as $name => &$profile) {
// Use shorter labels
$data = array_combine($labels, $profile->data);
if ($perceptron->testCase($data, $trainResult) == true) {
$score = $diff = 0;
// Determing the score and diffrennce
foreach ($unknown->data as $item => $found) {
if ($unknown->data[$item] === $profile->data[$item]) {
if ($profile->data[$item] > 0) {
$score += $features[$item];
} else {
$diff += $features[$item];
}
}
}
// Ser score and diff
$profile->setScore($score, $diff);
$matchs[] = $profile;
}
}
// Sort bases on score and Output
if (count($matchs) > 1) {
usort($matchs, function ($a, $b) {
// If score is the same use diffrence
if ($a->score == $b->score) {
// Lower the diffrence the better
return $a->diff == $b->diff ? 0 : ($a->diff > $b->diff ? 1 : - 1);
}
// The higher the score the better
return $a->score > $b->score ? - 1 : 1;
});
echo "<br />Possible Match ", implode(",", array_slice(array_map(function ($v) {
return sprintf(" %s (%0.4f|%0.4f) ", $v->name, $v->score,$v->diff);
}, $matchs), 0, 2));
} else {
echo "<br />No match Found ";
}
Possible Match D (0.7416|0.16853),C (0.5393|0.2809)
D"のPrint_rです。
echo "<pre>";
print_r($matchs[0]);
Profile Object(
[name] => D
[data] => Array (
[Real IP address] => -1
[Used proxy IP address] => -1
[HTTP Cookies] => 1
[Session Cookies] => 1
[3rd Party Cookies] => 1
[Flash Cookies] => 1
[PDF Bug] => 1
[Flash Bug] => 1
[Java Bug] => -1
[Frequent Pages] => 1
[Browsers Finger Print] => -1
[Installed Plugins] => 1
[URL] => -1
[Cached PNG] => 1
[System Fonts Detection] => 1
[Localstorage] => -1
[Geolocation] => -1
[AOLTR] => 1
[Network Information API] => -1
[Battery Status API] => -1
)
[score] => 0.74157303370787
[diff] => 0.1685393258427
[base] => 8.9
)
Debug = true の場合、次のように表示されます。 入力(Sensor & Desired)、初期重み、出力(Sensor, Sum, Network)、エラー、補正、最終的な重み .
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| o | x1 | x2 | x3 | x4 | x5 | x6 | x7 | x8 | x9 | x10 | x11 | x12 | x13 | x14 | x15 | x16 | x17 | x18 | x19 | x20 | Bias | Yin | Y | deltaW1 | deltaW2 | deltaW3 | deltaW4 | deltaW5 | deltaW6 | deltaW7 | deltaW8 | deltaW9 | deltaW10 | deltaW11 | deltaW12 | deltaW13 | deltaW14 | deltaW15 | deltaW16 | deltaW17 | deltaW18 | deltaW19 | deltaW20 | W1 | W2 | W3 | W4 | W5 | W6 | W7 | W8 | W9 | W10 | W11 | W12 | W13 | W14 | W15 | W16 | W17 | W18 | W19 | W20 | deltaBias |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
| 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 0 | -1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 19 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | -1 | -1 | 1 | -19 | -1 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | -1 | -1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | -1 | -1 | -1 | -1 | 1 | 1 | 1 |
| -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
+----+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+------+-----+----+---------+---------+---------+---------+---------+---------+---------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----------+----+----+----+----+----+----+----+----+----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----+-----------+
x1〜x20はコードによって変換された機能を表す。
// Get RNA Labels
$labels = array();
$n = 1;
foreach ( $features as $k => $v ) {
$labels[$k] = "x" . $n;
$n ++;
}
ここでは オンラインデモ
使用クラスです。
class Profile {
public $name, $data = array(), $score, $diff, $base;
function __construct($name, array $importance) {
$values = array(-1, 1); // Perception values
$this->name = $name;
foreach ($importance as $item => $point) {
// Generate Random true/false for real Items
$this->data[$item] = $values[mt_rand(0, 1)];
}
$this->base = array_sum($importance);
}
public function setScore($score, $diff) {
$this->score = $score / $this->base;
$this->diff = $diff / $this->base;
}
}
修正パーセプトロンクラス
class Perceptron {
private $w = array();
private $dw = array();
public $debug = false;
private function initialize($colums) {
// Initialize perceptron vars
for($i = 1; $i <= $colums; $i ++) {
// weighting vars
$this->w[$i] = 0;
$this->dw[$i] = 0;
}
}
function train($input, $alpha, $teta) {
$colums = count($input[0]) - 1;
$weightCache = array_fill(1, $colums, 0);
$checkpoints = array();
$keepTrainning = true;
// Initialize RNA vars
$this->initialize(count($input[0]) - 1);
$just_started = true;
$totalRun = 0;
$yin = 0;
// Trains RNA until it gets stable
while ($keepTrainning == true) {
// Sweeps each row of the input subject
foreach ($input as $row_counter => $row_data) {
// Finds out the number of columns the input has
$n_columns = count($row_data) - 1;
// Calculates Yin
$yin = 0;
for($i = 1; $i <= $n_columns; $i ++) {
$yin += $row_data["x" . $i] * $weightCache[$i];
}
// Calculates Real Output
$Y = ($yin <= 1) ? - 1 : 1;
// Sweeps columns ...
$checkpoints[$row_counter] = 0;
for($i = 1; $i <= $n_columns; $i ++) {
/** DELTAS **/
// Is it the first row?
if ($just_started == true) {
$this->dw[$i] = $weightCache[$i];
$just_started = false;
// Found desired output?
} elseif ($Y == $row_data["o"]) {
$this->dw[$i] = 0;
// Calculates Delta Ws
} else {
$this->dw[$i] = $row_data["x" . $i] * $row_data["o"];
}
/** WEIGHTS **/
// Calculate Weights
$this->w[$i] = $this->dw[$i] + $weightCache[$i];
$weightCache[$i] = $this->w[$i];
/** CHECK-POINT **/
$checkpoints[$row_counter] += $this->w[$i];
} // END - for
foreach ($this->w as $index => $w_item) {
$debug_w["W" . $index] = $w_item;
$debug_dw["deltaW" . $index] = $this->dw[$index];
}
// Special for script debugging
$debug_vars[] = array_merge($row_data, array(
"Bias" => 1,
"Yin" => $yin,
"Y" => $Y
), $debug_dw, $debug_w, array(
"deltaBias" => 1
));
} // END - foreach
// Special for script debugging
$empty_data_row = array();
for($i = 1; $i <= $n_columns; $i ++) {
$empty_data_row["x" . $i] = "--";
$empty_data_row["W" . $i] = "--";
$empty_data_row["deltaW" . $i] = "--";
}
$debug_vars[] = array_merge($empty_data_row, array(
"o" => "--",
"Bias" => "--",
"Yin" => "--",
"Y" => "--",
"deltaBias" => "--"
));
// Counts training times
$totalRun ++;
// Now checks if the RNA is stable already
$referer_value = end($checkpoints);
// if all rows match the desired output ...
$sum = array_sum($checkpoints);
$n_rows = count($checkpoints);
if ($totalRun > 1 && ($sum / $n_rows) == $referer_value) {
$keepTrainning = false;
}
} // END - while
// Prepares the final result
$result = array();
for($i = 1; $i <= $n_columns; $i ++) {
$result["w" . $i] = $this->w[$i];
}
$this->debug($this->print_html_table($debug_vars));
return $result;
} // END - train
function testCase($input, $results) {
// Sweeps input columns
$result = 0;
$i = 1;
foreach ($input as $column_value) {
// Calculates teste Y
$result += $results["w" . $i] * $column_value;
$i ++;
}
// Checks in each class the test fits
return ($result > 0) ? true : false;
} // END - test_class
// Returns the html code of a html table base on a hash array
function print_html_table($array) {
$html = "";
$inner_html = "";
$table_header_composed = false;
$table_header = array();
// Builds table contents
foreach ($array as $array_item) {
$inner_html .= "<tr>\n";
foreach ( $array_item as $array_col_label => $array_col ) {
$inner_html .= "<td>\n";
$inner_html .= $array_col;
$inner_html .= "</td>\n";
if ($table_header_composed == false) {
$table_header[] = $array_col_label;
}
}
$table_header_composed = true;
$inner_html .= "</tr>\n";
}
// Builds full table
$html = "<table border=1>\n";
$html .= "<tr>\n";
foreach ($table_header as $table_header_item) {
$html .= "<td>\n";
$html .= "<b>" . $table_header_item . "</b>";
$html .= "</td>\n";
}
$html .= "</tr>\n";
$html .= $inner_html . "</table>";
return $html;
} // END - print_html_table
// Debug function
function debug($message) {
if ($this->debug == true) {
echo "<b>DEBUG:</b> $message";
}
} // END - debug
} // END - class
結論
一意な識別子なしでユーザーを識別することは、簡単な作業ではありません。それは、さまざまな方法でユーザーから収集できる十分な量のランダム データを収集することに依存します。
人工ニューラルネットワークを使用しない場合でも、少なくとも優先順位と可能性を持つ単純な確率行列を使用することをお勧めします。
関連
-
vueの補間表現とv-textディレクティブの違いについて
-
vueのプロジェクトでモックを使用する方法を知っていますか?
-
[解決済み] ページを再読み込みせずにURLを変更するにはどうすればよいですか?
-
[解決済み] jQueryを使用しない$(document).ready相当
-
[解決済み] 新しい配列を作成せずに、既存のJavaScript配列を別の配列で拡張する方法
-
[解決済み] PHPでユーザー入力をサニタイズするにはどうすればよいですか?
-
[解決済み] ローカルファイルの読み込み時に "Cross origin requests are only supported for HTTP." というエラーが発生する。
-
[解決済み] npmのユーザーインストール済みパッケージの一覧を表示する方法は?
-
[解決済み] HTML5 ローカルストレージとセッションストレージの比較
-
[解決済み] HTML5のローカルストレージのアイテムはいつ期限切れになりますか?
最新
-
nginxです。[emerg] 0.0.0.0:80 への bind() に失敗しました (98: アドレスは既に使用中です)
-
htmlページでギリシャ文字を使うには
-
ピュアhtml+cssでの要素読み込み効果
-
純粋なhtml + cssで五輪を実現するサンプルコード
-
ナビゲーションバー・ドロップダウンメニューのHTML+CSSサンプルコード
-
タイピング効果を実現するピュアhtml+css
-
htmlの選択ボックスのプレースホルダー作成に関する質問
-
html css3 伸縮しない 画像表示効果
-
トップナビゲーションバーメニュー作成用HTML+CSS
-
html+css 実装 サイバーパンク風ボタン
おすすめ
-
JavaScriptの関数この指摘の問題を説明
-
Vue+ElementUIによる大規模なフォームの処理例
-
[解決済み】リソースの読み込みに失敗した:Bind関数でサーバーが500(Internal Server Error)のステータスで応答した【非公開
-
[解決済み】React Nativeアプリをターミナルから実行するとエラーが発生する(iOS)
-
[解決済み] 期待される代入または関数呼び出し: 未使用式なし ReactJS
-
[解決済み】ERROR エラーです。スイッチのname属性が指定されていないフォームコントロールの値アクセッサがない
-
[解決済み】React Uncaught Error: 対象コンテナが DOM 要素でない [重複]。
-
[解決済み】「.addEventListener is not a function」なぜこのエラーが発生するのか?
-
JavaScriptのStringに関する共通メソッド
-
JavaScriptのgetElementById()メソッド入門