ホームページ  >  記事  >  ウェブフロントエンド  >  JS データ型、プリコンパイル、実行コンテキストなど、JS の基礎となるメカニズムについての深い理解

JS データ型、プリコンパイル、実行コンテキストなど、JS の基礎となるメカニズムについての深い理解

WBOY
WBOY転載
2021-12-27 18:48:522430ブラウズ

JavaScript は、ドキュメント オブジェクト モデル DOM、ブラウザ オブジェクト モデル BOM、およびそのコア ECMAScript の 3 つの部分で構成されています。この記事は、JavaScript の基礎となる原則についての知識をもたらし、すべての人に役立つことを願っています。

JS データ型、プリコンパイル、実行コンテキストなど、JS の基礎となるメカニズムについての深い理解

JavaScript は、動的で、弱い型指定で、プロトタイプベースの、リテラルに解釈されるスクリプト言語です。 JavaScript は私たちが使用する Web ブラウザーに根ざしており、そのインタープリターはブラウザーの JavaScript エンジンです。クライアント側で広く使用されているこのスクリプト言語は、以前はサーバー側の言語で処理されていたいくつかの入力検証操作を処理するために初めて使用されました。Web 時代の発展とともに、JavaScript は成長を続け、完全に機能するプログラミング言語になりました。 。その使用はもはや単純なデータ検証に限定されず、ブラウザ ウィンドウとそのコンテンツのほぼすべての側面と対話する機能を備えています。 JavaScript は非常に単純な言語であると同時に、非常に複雑な言語でもあるため、JavaScript を真に使いこなしたい場合は、その基礎となる設計原則のいくつかを深く理解する必要があります。この記事では、『JavaScript Advanced Programming』シリーズと『あなたの知らない JS』シリーズを参照して、JavaScript に関する基礎知識を説明します。

データ型

JavaScript のデータ型は保存方法に応じて分類できます。プリミティブデータ型(プリミティブ値)と参照データ型(参照値)の2種類。

現在、数値、文字列、ブール、Null、未定義、シンボル (ES6) を含む 6 つのプリミティブ データ型があり、これらの型は直接操作できる変数に格納される実際の値です。プリミティブ データ型はスタックに格納され、データ サイズが決定され、値によって直接格納されるため、値によって直接アクセスできます。

参照データ型は Object です。JavaScript では、配列、関数、正規表現など、プリミティブ データ型を除くすべてがオブジェクト型であり、すべてオブジェクトです。参照型はヒープ メモリに格納されているオブジェクトであり、変数はヒープ メモリ内のオブジェクトを指す、スタック メモリに格納されている参照アドレスです。変数が定義され、参照値に初期化されるとき、その変数が別の変数に割り当てられている場合、2 つの変数は同じアドレスを保存し、ヒープ メモリ内の同じメモリ空間を指します。いずれかの変数を使用して参照データ型の値を変更すると、他の変数もそれに応じて変更されます。

特殊な null を除くプリミティブ データ型の場合 (null は空のオブジェクト参照とみなされます)、その他の場合は、typeof を使用して正確な判断を行うことができます:

##true のタイプnull の型未定義変数)##タイプ オブ {}'オブジェクト'##typeof []##typeof(/[0-9,a-z]/) (

戻り値

タイプ 123

'番号'

#typeof "abc"

'string'

##「ブール値」

##'オブジェクト'

未定義のタイプ

「未定義」

不明変数の種類(

#'未定義'

#シンボルの種類()

#'シンボル'

##関数の種類() {}

#'関数'

'オブジェクト'

#'object'

## Null 型の場合は、等価演算子の Make判定を使用できます。宣言されているが初期化されていない変数値は、JavaScript ではデフォルトで未定義

を手動で未定義に割り当てることもできます) に割り当てられます。 use 等価演算子 == は null と未定義を区別できず、ECMA-262 では等価テストで true を返さなければならないと規定されています。 2 つの値を正確に区別するには、合同演算子 === を使用する必要があります。 参照データ型については、メソッド設計に特殊で typeof で正確に判断できる function を除き、その他すべてはオブジェクト型を返します。参照型の値を判断するには、instanceof を使用できます。 instanceof は、オブジェクト A が別のオブジェクト B のインスタンスであるかどうかを検出します。最下位レベルでは、オブジェクト A#(

) のプロトタイプ チェーンにオブジェクト B が存在するかどうかを確認します。 # #インスタンスとプロトタイプ チェーンについては、この記事の後半で説明します)

。存在する場合は true を返し、存在しない場合は false を返します。 ##式

##戻り値##[1,2,3] 配列のインスタンス'true'## ####'真実'##################

参照型の値はすべて Object のインスタンスなので、instance 演算子を使って Object と判定すると結果も true を返します。

#function foo(){ } インスタンスオブ Function

#'true'

##/[0-9,a-z]/instanceof RegExp

'true'

new Date() インスタンス of Date

'true'

{名前:「Alan」、年齢:「22」} オブジェクトのインスタンス

#function foo(){ } Object のインスタンス'true'##/[0-9,a-z]/ オブジェクトのインスタンスnew Date() オブジェクトのインスタンス
#式

戻り値

#[1,2,3] オブジェクトのインスタンス

'true'

'true'

'true'

もちろん、JavaScript のデータ型を正確に判定できる、より強力なメソッドがあります。それが Object.prototype.toString.call() メソッドです。 ES5 では、すべてのオブジェクト (ネイティブ オブジェクトとホスト オブジェクト) に内部プロパティ [[Class]] があり、その値はオブジェクトのタイプを記録する文字列です。現在含まれているのは、「配列」、「ブール値」、「日付」、「エラー」、「関数」、「数学」、「数値」、「オブジェクト」、「正規表現」、「文字列」、「引数」、「JSON」、 "シンボル"。この内部プロパティは Object.prototype.toString() メソッドを通じて表示できますが、他に方法はありません。

Object.prototype.toString() メソッドが呼び出されると、次の手順が実行されます。 1. このオブジェクトの [[Class]] 属性値を取得します。( このオブジェクトについては記事の後半で説明します)。 2. 2 つの文字列「[object」と「]」の間に値を配置し、それらを連結します。 3. 結合された文字列を返します。

this の値が null の場合、Object.prototype.toString() メソッドは直接「[object Null]」を返します。 thisの値が未定義の場合は、「[オブジェクト未定義]」をそのまま返します。

##Object.prototype.toString.call(“abc”)#[オブジェクト シンボル]##[オブジェクト関数]Object.prototype.toString.call([1,2,3])##Object.prototype.toString .call(RegExp())[オブジェクト RegExp]Object.prototype.toString.call (window.JSON)
#式

戻り値

Object.prototype.toString.call(123)

##[オブジェクト番号]

##[オブジェクト文字列]

##Object.prototype.toString.call(true)

[ object Boolean]

Object.prototype.toString.call(null)

[オブジェクト Null]

##Object.prototype.toString.call(未定義)

#[オブジェクト未定義]

##Object.prototype.toString.call (Symbol())

Object.prototype.toString .call(function foo(){})

#[オブジェクト配列]

Object.prototype.toString.call({name:”Alan” })

[オブジェクト オブジェクト]

##Object.prototype.toString.call(new Date())

#[オブジェクトの日付]

[オブジェクト JSON]

Object.prototype.toString.call(Math)

[オブジェクト Math]


call() メソッドは、Object.prototype.toString() メソッドを呼び出すときに this のポイントを変更して、渡したオブジェクトを指すようにできるため、[[Class]] 属性を取得できます。 (Object.prototype.toString.apply() を使用しても同じ効果が得られます)

JavaScript のデータ型変換も可能 データ型変換には、明示的な型変換と暗黙的な型変換の 2 つの方法があります。

表示タイプの変換のために呼び出すことができるメソッドには、Boolean()、String()、Number()、parseInt()、parseFloat()、および toString() ( null および未定義の値にはこのメソッドはありません). それぞれの用途は一目瞭然なので、ここでは一つ一つ紹介しません。

JavaScript は弱い型指定言語であるため、算術演算子を使用する場合、Java や C 言語とは異なり、演算子の両側のデータ型は任意になります。同じ型である場合、エンジンはそれらに対して暗黙的な型変換を自動的に実行します。暗黙的な型変換は、明示的な型変換ほど直感的ではありません。主な変換方法は 3 つあります:
1. 値をプリミティブ値に変換します: toPrimitive()

2. 値を数値に変換します: toNumber( )

3. 値を文字列に変換します: toString()

一般的に、数値と文字列を加算する場合、数値は文字列に変換されます; 真偽値判定を行う場合 ( if、||、&& など) の場合、パラメータはブール値に変換されます。比較演算、算術演算、または自動インクリメントおよびデクリメント演算を実行する場合、パラメータは数値に変換されます。オブジェクトを暗黙的に型変換中に、オブジェクトの toString() メソッドまたは valueOf() メソッドの戻り値が取得されます。

NaN について:

NaN は、数値以外の値を表す特別な数値です。まず、NaN を含む算術演算はすべて NaN を返します。第 2 に、NaN は、NaN 自体を含め、どの値とも等しくありません。 ECMAScript では、パラメータが「非数値」であるかどうかをテストするために使用できる isNaN() 関数が定義されています。まず、引数を暗黙的に数値に変換しようとし、数値に変換できない場合は true を返します。


まず typeof を使用して Number 型かどうかを判断し、次に isNaN を使用して現在のデータが NaN であるかどうかを判断します。

文字列について:

JavaScript の文字列は不変であり、一度作成された文字列の値は変更できません。変数に保持されている文字列を変更するには、まず元の文字列を破棄し、次に新しい値を含む別の文字列を変数に入力します。このプロセスはバックグラウンドで実行されるため、一部の古いブラウザーでは文字列を連結するときに速度が非常に遅くなります。

実際、基本的な型値の操作を容易にするために、ECMAScript は 3 つの特別な参照型 (Boolean、Number、String) も提供します。プリミティブ データ型にはプロパティやメソッドがありません。プリミティブ型値を読み取るためにメソッドを呼び出すと、アクセス プロセスは読み取りモードになり、対応するプリミティブ ラッパー型オブジェクトがバックグラウンドで作成され、次のことが可能になります。このデータを操作するには、いくつかのメソッドを呼び出します。このプロセスは 3 つのステップに分かれています。 1. 元のパッケージ タイプのインスタンスを作成します。 2. インスタンス上で指定されたメソッドを呼び出します。 3. インスタンスを破棄します。
参照型とプリミティブ パッケージング タイプの主な違いは、オブジェクトのライフ サイクルです。自動的に作成されたプリミティブ パッケージング タイプのオブジェクトは、コード行の実行時にのみ存在し、その後すぐに破棄されるため、実行時にプリミティブ型の値を使用し、プロパティとメソッドを追加します。

プリコンパイル

「あなたが知らない JavaScript」という本の中で、著者は、JavaScript は「動的言語」または「」として分類されていると述べました。インタープリタ型実行言語」と呼ばれていますが、実際にはコンパイルされた言語です。 JavaScript の実行は、1. 構文解析 2. プリコンパイル 3. 解釈と実行の 3 つのステップに分かれています。構文解析と解釈の実行は理解するのが難しくありません。1 つはコードに構文エラーがあるかどうかを確認することであり、もう 1 つはプログラムを 1 行ずつ実行することです。ただし、JavaScript のプリコンパイル段階は少し複雑です。

JavaScript コードは実行前にコンパイルする必要があります。ほとんどの場合、コンパイル プロセスはコードが実行される前に数マイクロ秒以内に行われます。コンパイル フェーズ中に、JavaScript エンジンは現在のコード実行スコープから開始され、コードに対して RHS クエリを実行して変数の値を取得します。次に、実行フェーズ中に、エンジンは LHS クエリを実行し、変数に値を割り当てます。

コンパイル段階での JavaScript エンジンの仕事の一部は、すべての宣言を検索し、それらを適切なスコープに関連付けることです。プリコンパイル プロセス中に、グローバル スコープ内にある場合、JavaScript エンジンは最初にグローバル オブジェクト # (GO オブジェクト、グローバル オブジェクト) を作成し、プロモートします。変数宣言と関数宣言。プロモートされた変数は最初にデフォルトで未定義に初期化され、関数は関数本体全体をプロモートします。(If it is関数が関数式の形式で定義されている場合、変数昇格のルールが適用されます) 後、グローバル変数に格納されます。関数宣言の昇格は、変数宣言の昇格よりも優先されます。変数宣言の場合、繰り返される var 宣言はエンジンによって無視され、後続の関数宣言は前の関数宣言を上書きできます ( 新しいES6 の変数宣言構文は少し異なるため、ここでは説明しません)

関数本体内は独立したスコープであり、プリコンパイルフェーズも関数本体内で実行されます。関数本体内では、まずアクティブ オブジェクト (AO オブジェクト、アクティブ オブジェクト) が作成され、仮パラメータと変数が宣言されるだけでなく、関数本体内の関数。宣言はプロモートされ、仮パラメータと変数は未定義に初期化され、内部関数は内部関数本体のままで、アクティブ オブジェクトに格納されます。

コンパイルフェーズが完了すると、JavaScript コードが実行されます。実行プロセスでは、変数または仮パラメータに値が順番に割り当てられます。エンジンはスコープ内で対応する変数宣言または仮パラメータ宣言を探し、見つかった場合はそれらに値を割り当てます。非厳密モードの場合、変数が宣言なしで割り当てられると、エンジンはグローバル環境で変数の宣言を自動的かつ暗黙的に作成します。ただし、厳密モードの場合、宣言されていない変数に変数が割り当てられるとエラーが報告されます。価値。 。 JavaScript の実行はシングルスレッドであるため、代入演算 (LHS クエリ) が実行される場合は、最初に変数を取得する必要があります (RHS クエリ) と出力すると、この時点では変数に値が割り当てられていないため、未定義の結果が得られます。

#実行環境とスコープ

##各関数は Function オブジェクトです。たとえば、JavaScript では、 、すべてのオブジェクトには、JavaScript エンジンのみがアクセスできる内部プロパティ [[Scope]] があります。関数の場合、[[Scope]] 属性には、関数が作成されたスコープ (スコープ チェーン) 内のオブジェクトのコレクションが含まれます。グローバル環境で関数が作成されると、関数のスコープ チェーンは、グローバル スコープで定義されたすべての変数を含むグローバル オブジェクトを挿入します。

#内部スコープは外部スコープにアクセスできますが、外部スコープは内部スコープにアクセスできません。範囲エリア。 JavaScript にはブロックレベルのスコープがないため、if ステートメントまたは for ループステートメントで定義された変数にはステートメントの外部からアクセスできます。 ES6 より前の JavaScript にはグローバル スコープと関数スコープしかありませんでしたが、ES6 では新しいブロック レベルのスコープ メカニズムが追加されました。

関数が実行されると、実行環境 (実行コンテキスト、実行コンテキストとも呼ばれます) という実行環境が実行関数用に作成されます。内部オブジェクト。各実行環境には独自のスコープ チェーンがあります。実行環境が作成されると、まずそのスコープ チェーンの先頭が、現在実行中の関数の [[Scope]] 属性のオブジェクトに初期化されます。その直後、関数実行時のアクティブ オブジェクト (すべてのローカル変数、名前付きパラメーター、引数パラメーター セット、およびこれを含む) も作成され、プッシュされます。有効 ドメインチェーンの最上位。

対応する実行環境は関数が実行されるたびに一意となり、同じ関数が複数呼び出されます。これにより、複数の実行環境が作成されます。関数の実行が完了すると、実行環境は破棄されます。実行環境が破棄されると、アクティブなオブジェクトも破棄されます#(Web ページやブラウザを閉じるなど、アプリケーションが終了するまで、グローバル実行環境は破棄されません)

関数の実行中、変数が見つかるたびに、識別子の解析プロセスが実行され、データを取得または保存する場所が決定されます。識別子の解決は、スコープ チェーンに沿ってレベルごとに識別子を検索するプロセスです。グローバル変数は常にスコープ チェーンの最後のオブジェクト

# (つまり、ウィンドウ オブジェクト)# ####。 JavaScript には、実行中にスコープ チェーンを一時的に変更できるステートメントが 2 つあります。 1つ目はwith文です。 with ステートメントは、パラメーターで指定されたオブジェクトのすべてのプロパティを含む可変オブジェクトを作成し、そのオブジェクトをスコープ チェーンの最初の位置にプッシュします。これは、関数のアクティブなオブジェクトがスコープ チェーンの 2 番目の位置に押し込まれることを意味します。スコープチェーン。これにより、可変オブジェクトのプロパティへのアクセスは非常に高速になりますが、ローカル変数などへのアクセスは遅くなります。実行環境のスコープ チェーンを変更できる 2 番目のステートメントは、try-catch ステートメントの catch 句です。 try コード ブロックでエラーが発生すると、実行プロセスは自動的に catch 句にジャンプし、例外オブジェクトが変数オブジェクトにプッシュされ、スコープの先頭に配置されます。 catch コード ブロック内では、関数のすべてのローカル変数が 2 番目のスコープ チェーン オブジェクトに配置されます。 catch 句が実行されると、スコープ チェーンは以前の状態に戻ります。

コンストラクター

JavaScript のコンストラクターを使用して、特定のタイプのオブジェクトを作成できます。他の関数と区別するために、コンストラクターは通常大文字で始まります。ただし、JavaScript にはコンストラクターを定義するための特別な構文がないため、これは必要ありません。 JavaScript では、コンストラクターと他の関数の唯一の違いは、その呼び出し方法です。 new 演算子を介して呼び出される限り、任意の関数をコンストラクターとして使用できます。

#JavaScript

関数には 4 つの呼び出しモードがあります。 1. 独立した関数呼び出しモード。 foo (引数)。 2. obj.foo(arg) などのメソッド呼び出しモード。 3. new foo(arg) などのコンストラクター呼び出しモード。 4.call/apply 呼び出しモード。foo.call(this,arg1,arg2) や foo.apply(this,args) など (ここでの args は配列です)。 コンストラクターのインスタンスを作成し、コンストラクターの役割を果たすには、new 演算子を使用する必要があります。 new 演算子を使用してコンストラクターをインスタンス化すると、コンストラクター内で次の手順が実行されます。 1. 空の this オブジェクトを暗黙的に作成します。 2. コンストラクター内のコードを実行します (現在の this オブジェクトに属性を追加します)。 object )

3. 現在の this オブジェクトを暗黙的に返します




コンストラクターが明示的にオブジェクトを返す場合は、インスタンスが返されます。オブジェクト。それ以外の場合は、暗黙的に返される this オブジェクトです。

コンストラクターを呼び出してインスタンスを作成すると、インスタンスにはコンストラクターのすべてのインスタンス属性とメソッドが含まれます。コンストラクターを通じて作成されたさまざまなインスタンスの場合、そのインスタンスのプロパティとメソッドは独立しています。同じ名前の参照型の値であっても、異なるインスタンスは相互に影響を与えません。

プロトタイプとプロトタイプ チェーン

プロトタイプとプロトタイプ チェーンは JavaScript の中核です言語 本質の一つは、この言語の難しさの一つでもあります。プロトタイプ プロトタイプ (明示的プロトタイプ) は関数の一意の属性です。関数が作成されるたびに、関数は自動的にプロトタイプ属性を作成し、関数のプロトタイプ オブジェクトを指します。すべてのプロトタイプ オブジェクトは自動的にコンストラクター (コンストラクター、コンストラクターとも翻訳可能) 属性を取得します。この属性には関数 ## へのポインターが含まれています。 # (つまり、コンストラクター自体) へのポインター。コンストラクターを通じてインスタンスを作成すると、インスタンスには [[Prototype]] (暗黙的なプロトタイプ) の内部プロパティが含まれます。これはコンストラクターのプロトタイプ オブジェクトも指します。 Firefox、Safari、Chrome では、各オブジェクトは __proto__ 属性を通じて [[Prototype]] プロパティにアクセスできます。他のブラウザの場合、この属性はスクリプトからはまったく見えません。

コンストラクターのプロトタイプ属性とインスタンスの [[Prototype]] は両方ともコンストラクターのプロトタイプ オブジェクトを指します。そして [[ インスタンスの Prototype]] プロパティとコンストラクターの間に直接の関係はありません。インスタンスの [[Prototype]] プロパティが特定のコンストラクターのプロトタイプ オブジェクトを指しているかどうかを確認するには、isPrototypeOf() メソッドまたは Object.getPrototypeOf() メソッドを使用できます。

オブジェクト インスタンスのプロパティが読み取られるたびに、指定された名前のプロパティをターゲットとして検索が実行されます。検索は最初にオブジェクト インスタンス自体から開始されます。指定された名前の属性がインスタンス内で見つかった場合は、その属性の値が返されます。見つからなかった場合は、[[Prototype] が指すプロトタイプ オブジェクトの検索が続行されます。 ] オブジェクトの属性 プロトタイプでは、オブジェクト内で指定された名前のプロパティを検索し、見つかった場合はプロパティの値を返します。

#オブジェクトがどのコンストラクターの直接インスタンスであるかを判断するには、コンストラクターにアクセスします。インスタンスのプロパティに直接アクセスすると、インスタンスは [[Prototype]] を通じてプロトタイプ オブジェクトのコンストラクター プロパティを読み取り、コンストラクター自体を返します。 プロトタイプ オブジェクトの値には、オブジェクト インスタンスを通じてアクセスできますが、オブジェクト インスタンスを通じて変更することはできません。インスタンス プロトタイプ オブジェクトと同じ名前のプロパティをインスタンスに追加すると、インスタンスにプロパティが作成されます。このインスタンス プロパティにより、プロトタイプ オブジェクト内のそのプロパティにアクセスできなくなりますが、そのプロパティは変更されません。インスタンス プロパティを null に設定するだけでは、プロトタイプ オブジェクトのプロパティへのアクセスは復元されません。プロトタイプ オブジェクトのプロパティへのアクセスを復元するには、delete 演算子を使用してオブジェクト インスタンスからプロパティを完全に削除します。

hasOwnProperty() メソッドを使用して、プロパティがインスタンスに存在するかプロトタイプに存在するかを検出します。このメソッドは、指定されたプロパティがオブジェクト インスタンスに存在する場合にのみ true を返します。オブジェクト自体の列挙可能なすべてのインスタンス プロパティを取得するには、ES5 Object.keys() メソッドを使用できます。列挙可能かどうかに関係なく、すべてのインスタンス プロパティを取得するには、Object.getOwnPropertyNames() メソッドを使用できます。

プロトタイプは動的であり、プロトタイプ オブジェクトに加えられた変更はすぐにインスタンスに反映されますが、それが繰り返されると、プロトタイプ オブジェクト全体を作成すると、状況は異なります。コンストラクタを呼び出すと、元のプロトタイプ オブジェクトへの [[Prototype]] ポインタがオブジェクト インスタンスに追加されます。プロトタイプ オブジェクト全体を書き換えた後、コンストラクタは新しいプロトタイプ オブジェクトを指します。すべてのプロトタイプ オブジェクトのプロパティとメソッドは、新しいプロトタイプとともに存在します。オブジェクト上で、オブジェクト インスタンスも元のプロトタイプ オブジェクトを指しているため、コンストラクターと同じプロトタイプ オブジェクトを指している元のプロトタイプ オブジェクト間の接続は、それぞれ異なるプロトタイプ オブジェクトを指しているため切断されます。

この接続を復元するには、コンストラクターのプロトタイプを書き換えた後にオブジェクト インスタンスをインスタンス化するか、オブジェクト インスタンスを変更します。__proto__ 属性は再ポイントします。コンストラクターの新しいプロトタイプ オブジェクト。

JavaScript は、継承を実装する主な方法としてプロトタイプ チェーンを使用し、プロトタイプを使用して、ある参照型に別の参照型のプロパティとメソッドを継承させます。コンストラクターのインスタンスには、プロトタイプ オブジェクトを指す [[Prototype]] 属性があります。コンストラクターのプロトタイプ オブジェクトを別の型のインスタンスと等しくすると、プロトタイプ オブジェクトには、プロトタイプ オブジェクトを指す [[Prototype]] ポインターも含まれます。別のプロトタイプが別のタイプのインスタンスである場合...など、インスタンスとプロトタイプのチェーンが形成されます。これが、いわゆるプロトタイプチェーンの基本概念です。

# プロトタイプ チェーンは、プロトタイプ検索メカニズムを拡張します。インスタンス属性を読み取るとき、その属性は最初にインスタンス内で検索されます。属性が見つからない場合、インスタンス [[Prototype]] が指すプロトタイプ オブジェクトの検索が続行されます。プロトタイプ オブジェクトは、別のコンストラクタのインスタンスにもなります。プロトタイプ オブジェクトが見つからない場合、検索は続行されます. プロトタイプ オブジェクト [[Prototype]] は、別のプロトタイプ オブジェクトを指しています... 検索プロセスは、プロトタイプ チェーンに沿って上向きに検索を続けます。指定された属性またはメソッドが見つからない場合、検索プロセスは、次のプロトタイプ オブジェクトまで 1 つずつ実行されます。プロトタイプチェーンの最後で止まります。

関数のプロトタイプ オブジェクトが変更されていない場合、すべての参照型には、既定で Object のプロトタイプ オブジェクトを指す [[Prototype]] 属性があります。したがって、すべての関数のデフォルトのプロトタイプは Object のインスタンスです。これが、すべてのカスタム型が toString() や valueOf() などのデフォルト メソッドを継承する基本的な理由です。 instanceof 演算子または isPrototypeOf() メソッドを使用して、インスタンスのプロトタイプ チェーンにコンストラクター プロトタイプが存在するかどうかを確認できます。

プロトタイプ チェーンは非常に強力ですが、いくつかの問題もあります。最初の問題は、プロトタイプ オブジェクトの参照型の値がすべてのインスタンスで共有されていることです。これは、異なるインスタンスの参照型のプロパティまたはメソッドが同じヒープ メモリを指していることを意味します。1 つのインスタンスのプロトタイプの参照値を変更すると、影響を受けることになります。他のすべてのインスタンスを同時にプロトタイプ オブジェクト上のインスタンスの参照値。プライベート プロパティまたはメソッドがプロトタイプではなくコンストラクターで定義されるのはこのためです。プロトタイプチェーンの 2 番目の問題は、コンストラクターのプロトタイプ プロトタイプを別のコンストラクターのインスタンスと同等視する場合、この時点で属性値を設定するためにパラメーターを別のコンストラクターに渡すと、すべてのプロパティが元のコンストラクターに基づいてしまうということです。 constructor will インスタンスのこの属性には、プロトタイプ チェーンにより同じ値が割り当てられますが、これが望ましい結果ではない場合があります。

クロージャ

クロージャは JavaScript の最も強力な機能の 1 つです。JavaScript では、クロージャ、は、別の関数のスコープ内の変数にアクセスする権利を持つ関数を指します。これは、関数がローカル スコープ外のデータにアクセスできることを意味します。クロージャを作成する一般的な方法は、別の関数の中に関数を作成し、この関数を返すことです。

一般的に、関数が実行されると、ローカルのアクティブ オブジェクトは破棄され、グローバル スコープのみが保存されます。想い出。ただし、閉店の場合は状況が異なります。

クロージャ関数の [[Scope]] 属性は、それをラップする関数のスコープ チェーンに初期化されます。クロージャには、実行環境と同じスコープ チェーン内のオブジェクトへの参照が含まれます。一般的に、関数のアクティブなオブジェクトは実行環境とともに破棄されます。ただし、クロージャが導入されると、参照がクロージャの [[Scope]] 属性にまだ存在するため、元の関数のアクティブ オブジェクトを破棄することはできません。これは、クロージャ関数は非クロージャ関数よりも多くのメモリ オーバーヘッドを必要とするため、より多くのメモリ リークが発生することを意味します。さらに、クロージャが元のラッピング関数のアクティブ オブジェクトにアクセスする場合、まずスコープ チェーン内の自身のアクティブ オブジェクトの識別子を解決し、上位層を見つける必要があるため、元のラッピング関数の変数を使用するクロージャはパフォーマンスにも悪影響を及ぼし、大きな影響を及ぼします。

タイマー、イベント リスナー、Ajax リクエスト、クロスウィンドウ通信、Web ワーカー、またはその他の非同期または同期タスクでコールバック関数を使用するときは常に、実際にはクロージャを使用していることになります。

一般的なクロージャの問題は、タイマーを使用して for ループ内でループ変数を出力することです。

このコードは、JavaScript に詳しくない人向けです。たとえば、結果が 0、1、2、3 と順番に出力されるのは当然だと思っているかもしれませんが、実際には、このコードが出力する 4 つの数値はすべて 4 です。

これは、タイマーが非同期読み込みメカニズムであるため、for ループを通過するまで実行されないためです。タイマーが実行されるたびに、タイマーは外側のスコープで i 変数を探します。ループが終了したため、外部スコープの i 変数は 4 に更新されたため、4 つのタイマーによって取得された i 変数は、理想的な出力 0、1、2、3 ではなく、すべて 4 になります。

この問題を解決するには、即時実行関数をラップする新しいスコープを作成し、各ループの外部スコープの i 変数を新しく作成したスコープに保存して、タイマーが関数を実行できるようにします。毎回 最初に新しいスコープから値を取得します。即時実行関数を使用して、この新しいスコープを作成できます:

このようにして、ループ実行の結果は次のようになります。出力 0、1、2、および 3 の場合、この即時実行関数を簡略化し、実際のパラメータ i を即時実行関数に直接渡すこともできます。そのため、内部で j に値を割り当てる必要はありません。

もちろん、即時実行関数を使用する必要はありません。匿名でない関数を作成し、ループするたびに実行することもできますが、関数を保存するためにより多くのメモリを消費します。宣言。

ES6 より前にはブロックレベルのスコープ設定がなかったため、この問題を解決するには、新しいスコープを手動で作成するしかありません。 ES6 はブロック レベルのスコープの設定を開始しました。let を使用してブロック レベルのスコープを定義できます:

let 演算子はブロック レベルのスコープを作成し、let を通じて宣言します。変数は現在のブロック スコープに格納されるため、すぐに実行される各関数は毎回現在のブロック スコープから変数を検索します。

# LET には特別な定義もあり、変数はサイクル中に 1 回だけ宣言されるわけではありません。サイクルごとに再宣言され、新しいステートメントの値はステートメントの末尾の値で初期化されます。 for ループの先頭で let を直接使用することもできます:

#this

を指します このキーワードは JavaScript の最も複雑なメカニズムの 1 つであり、すべての関数のスコープ内で自動的に定義されます。これは関数自体を指すものとして理解されやすいですが、ES5 では関数の宣言時にはバインドされず、関数の実行時にバインドされ、関数の呼び出し方法にのみ依存します。関数が宣言される場所とは関係ありません。 (ES6 の新しいアロー関数のこれは異なり、そのポイントは関数宣言の場所によって異なります。)

前に述べたことを思い出してください。関数の 4 つの呼び出しモード: 1.独立した関数呼び出しモード (foo(arg) など)。 2.オブジェクト メソッド呼び出しモード (obj.foo(arg) など)。 3.コンストラクター呼び出しモード (new foo(arg) など)。 4.call/apply呼び出しモード (foo.call(this) や foo.apply(this) など)。

独立関数呼び出しモードの場合、非厳密モードでは、デフォルトで this はグローバル オブジェクトを指します。厳密モードでは、デフォルトでこれをグローバル オブジェクトにバインドすることは許可されていないため、未定義にバインドされます。

オブジェクト メソッド呼び出しモードの場合、関数内の this は、それを呼び出すオブジェクト自体を指します:

コンストラクター呼び出しモードの場合、コンストラクター内の実行手順は以前に紹介しました。
1. この空のオブジェクトを暗黙的に作成します。
2. コンストラクター内のコードを実行します (現在の this オブジェクトに属性を追加します)
3. 現在の this オブジェクトを暗黙的に返します

したがって、新しいメソッドを使用して関数を呼び出すとき、this はコンストラクター内で暗黙的かつ独立して作成された this オブジェクトを指します。これを通じて追加されたすべてのプロパティまたはメソッドは、最終的にははこの空のオブジェクトに追加され、コンストラクター インスタンスに返されます。

call/apply 呼び出しモードの場合、図に示すように、関数内の this は、渡した最初のパラメーターにバインドされます。

foo.apply() と foo.call() は、this が指すポイントを変更する同じ機能を持ちます。唯一の違いは、2 番目のパラメーターが配列形式で渡されるか、分散されるかです。フォーマット。

JavaScript の基本原理については、今日はここに一時的に書きますが、今後も JavaScript に関する内容は更新していきますので、引き続きご注目ください。

[関連する推奨事項:

JavaScript 学習チュートリアル

]

以上がJS データ型、プリコンパイル、実行コンテキストなど、JS の基礎となるメカニズムについての深い理解の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はcsdn.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。