単純な数値型: Unknown、Null、Boolean、Number、String があります。ここでの説明にある英語の単語はデータ型の名前のみを指しており、JS のグローバル オブジェクト (Nan、Boolean、Number、String など) を具体的に指すものではないことに注意してください。それらの概念的な違いは比較的大きいです。
オブジェクト: 値が単純な数値型、オブジェクト、または関数である順序のない属性のコレクション。上記と同様に、ここでのオブジェクトはグローバル オブジェクト Object を具体的に指すものではありません。
関数: 関数はオブジェクトの型です。実装では、内部属性 [[Class]] の値は「関数」であり、オブジェクトの内部属性メソッドに加えて、関数型であることを示します。 [[Construct]]、[[Call]]、[[Scope]] およびその他の内部プロパティもあります。関数呼び出しとしての関数の処理メカニズムは、コンストラクターの処理メカニズム (new キーワードを使用してインスタンス オブジェクトを作成する) とは異なります (Function オブジェクトを除く) コンストラクターのロジックを実装するために内部メソッド [[Construct]] が使用されます。メソッド [[Call]] は、ロジックを関数呼び出しとして実装します。上記と同様に、ここでの関数はグローバル オブジェクト Function を具体的に参照するものではありません。
関数は、JS のプロトタイプ言語におけるオブジェクト指向言語のクラスとみなすことができ、オブジェクトのインスタンスを構築するために使用できます。関数はクラスとして見ることができるため、各関数は拡張データ型として見ることができます。
組み込みデータ型 (組み込みオブジェクト)
関数: 関数型のユーザー インターフェイス。
オブジェクト: オブジェクト タイプのユーザー インターフェイス。
Boolean、Number、String: これら 3 つの単純な数値型のオブジェクト ラッパーは、概念的には C# の Box/Unbox に似ています。
Date、Array、RegExp: これらは、いくつかの組み込み拡張データ型とみなすことができます。
まず第一に、Function、Object、Boolean、Number、String、Date、Array、RegExp などはすべて JavaScript 言語の組み込みオブジェクトであり、すべて関数の派生型とみなすことができます。たとえば、関数のインスタンスの数は true です。オブジェクトのインスタンスの数は true です。この意味では、これらはユーザー定義関数と同じように扱うことができます。
第 2 に、それぞれはデータ型を表すことができ、ネイティブ コードまたは組み込み JS コードを使用して JS エンジンによって実装され、これらの組み込みデータ型を操作するために開発者に公開されるインターフェイスです。この意味で、それらはすべて抽象的な概念であり、その背後に具体的な実装メカニズムが隠されています。
数値、関数などの単語に言及するたびに、それらを頭の中で上記の 2 つの状況のいずれかに即して具体化する必要があります。
データ型実装モデルの説明
ビルトイン *** データ構造: *** 型を実装するために JS で内部的に使用されるデータ構造を指します。これらの構造には、基本的には直接アクセスできません。
ビルトイン *** オブジェクト: JS のビルトイン Number、String、Boolean およびその他のオブジェクトを指します。これは、JS が内部実装されたデータ型を開発者に公開するためのインターフェイスです。
ビルトイン *** コンストラクター: JS に組み込まれたいくつかのコンストラクターを指し、対応する型のオブジェクト インスタンスを構築するために使用されます。これらは関数オブジェクトとしてパッケージ化され、公開されます。たとえば、次のメソッドを使用してこれらの関数オブジェクトにアクセスできます。
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
//access the build-in number constructor
var number = new Number(123);
var numConstructor1 = number.constructor; //or
var numConstructor2 = new Object(123).constructor;
//both numConstructor1 and numConstructor2 are the build-in Number constructor
numConstructor1 == numConstructor2 //result: true
//access the build-in object constructor
var objConstructor1 = {}.constructor; //or
var objConstructor2 = new Object().constructor;
//both objConstructor1 and objConstructor2 are the build-in Object constructor
objConstructor1==objConstructor2 //result: true
具体实现上,上图中横向之间可能也存在关联,例如对于build-in data structure和constructor,Function、 Date、 Array、 RegExp等都可以继承Object的结构而实现,但这是具体实现相关的事情了。
关于简单数值类型的对象化这是一个细微的地方,下面描述对于Boolean, String和Number这三种简单数值类型都适用,以Number为例说明。
JS规范要求: 使用var num1=123;这样的代码,直接返回基本数据类型,就是说返回的对象不是派生自Number和Object类型,用num1 instanceof Object测试为false;使用new关键字创建则返回Number类型,例如var num2=new Number(123); num2 instanceof Number为true。
将Number当作函数调用,返回结果会转换成简单数值类型。下面是测试代码:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var num1 = new Number(123); //num1 derived from Number & Object
num1 instanceof Number //result: true
num1 instanceof Object //result: true
//convert the num1 from Number type to primitive type, so it's no longer an instance of Number or Object
num1 = Number(num1);
num1 instanceof Number //result: false
num1 instanceof Object //result: false
var num2 = 123; //num2 is a primitive type
num2 instanceof Number //result: false
num2 instanceof Object //result: false
単純な数値型を取得しましたが、これは Object と、対応する型のすべてのプロパティとメソッドを備えた JS オブジェクト オブジェクトのように見えます。基本的に使用方法に違いはありません。唯一の違いは、instanceof のテスト結果です。
プロトタイプ継承
プロトタイプすべてのオブジェクトには [[Prototype]] の内部プロパティがあり、その値はnull または別のオブジェクト。関数オブジェクトには明示的なプロトタイプ属性がありますが、これは内部 [[Prototype]] 属性ではありません。さまざまな JS エンジン実装者は、内部 [[Prototype]] 属性に任意の名前を付け、その可視性を JS エンジン内でのみ使用できるように設定できます。内部 [[Prototype]] には JS コードではアクセスできませんが (FireFox ではアクセスできます。Mozilla が公開しているため、名前は __proto__ です)、オブジェクトの isPrototypeOf() メソッドをテストに使用できることに注意してください。判定はプロトタイプチェーンで実行されます。
obj.propName を使用してオブジェクトのプロパティにアクセスする場合は、次の手順に従います (obj の内部 [[Prototype]] プロパティの名前が __proto__ であると仮定します):
1. obj に propName プロパティがある場合は、プロパティの値、それ以外の場合は
2. obj.__proto__ が null の場合は、未定義を返します。それ以外の場合は、
3. obj.__proto__.propName を返します
オブジェクトの呼び出し方法は、属性検索プロセスにアクセスする場合と同じです。なぜなら、メソッド An object の関数はオブジェクトの属性値であるからです。
ヒント: 上記の手順は再帰的なプロセスを意味します。手順 3 では、obj.__proto__ も propName 属性の検索に使用されます。
たとえば、次の図に示すように、object1 には属性 prop1、prop2、prop3 とメソッド fn1、fn2、fn3 があります。図の点線の矢印はプロトタイプ チェーンを表します。
これは Prototype に基づく継承と共有です。 object1 のメソッド fn2 は object2 から派生したものであり、概念的には、object2 は object3 のメソッド fn2 をオーバーライドします。
JavaScript オブジェクトはすべて、プロトタイプ チェーンを通じて関連付けられている必要があります。最上位は Object です。つまり、オブジェクトはすべて Object 型から派生します。
C などのオブジェクト指向言語は、メソッドを運ぶためにクラス (抽象型) を使用し、属性を運ぶためにオブジェクト (インスタンス化されたオブジェクト) を使用します。プロトタイプ言語は、メソッドと属性を運ぶためにインスタンス化されたオブジェクトのみを使用します。本質的な違いは、前者はメモリ構造の記述に基づいて継承を実装するのに対し、後者は特定のメモリ ブロックに基づいて継承を実装することです。
オブジェクト作成プロセスJS では関数オブジェクトのみクラスの概念があるため、オブジェクトを作成するには関数オブジェクトを使用する必要があります。関数オブジェクト内には [[Construct]] メソッドと [[Call]] メソッドがあり、 [[Construct]] はオブジェクトの構築に使用され、 [[Call]] は関数の呼び出しにのみ使用されます。 new 演算子を使用する場合。
var obj=new Object(); は、組み込みの Object 関数 object を使用して、インスタンス化されたオブジェクト obj を作成します。 var obj={}; および var obj=[]; この種のコードは、JS エンジンによるオブジェクトと配列の構築プロセスをトリガーします。 function fn(){}; var myObj=new fn(); は、ユーザー定義型を使用してインスタンス化されたオブジェクトを作成します。
新しい Fn(args) の作成プロセスは次のとおりです (つまり、関数オブジェクトの [[Construct]] メソッドがロジックとオブジェクトの作成プロセスを処理します)。また、関数オブジェクト自体の作成処理(関数を定義したり、Functionを使って関数オブジェクトを作成することなどを指します)も以下の処理ロジックを使用しますが、特殊な箇所がありますので、それについては後述します。
1. 組み込みオブジェクト object obj を作成し、それを初期化します。
2. Fn.prototype が Object 型の場合、obj の内部 [[Prototype]] を Fn.prototype に設定します。それ以外の場合、obj の [[Prototype ]] はその値 (つまり、Object.prototype) を初期化します
3. obj をこれとして使用し、args パラメータを使用して Fn の内部 [[Call]] メソッドを呼び出します
3.1 内部 [[Call]] メソッドは、現在の実行コンテキスト
3.2 F の関数本体を呼び出します
3.3 現在の実行コンテキストを破棄します
3.4 F の関数本体の戻り値を返します。F の関数本体に戻り値がない場合は、未定義を返します
4. [[ Call の戻り値が ]] の場合はこの値が返され、それ以外の場合は obj
ステップ 2 では、prototype はオブジェクトによって表示されるプロトタイプ プロパティを参照することに注意してください。 ]] は、オブジェクトの内部 Prototype プロパティ (暗黙的) を表します。
オブジェクトのプロトタイプ チェーンを構成するのは、内部の暗黙的な [[Prototype]] であり、オブジェクトの明示的なプロトタイプ属性ではありません。表示されるプロトタイプは、関数オブジェクト上でのみ意味を持ちます。上記の作成プロセスからわかるように、関数のプロトタイプは、プロトタイプに従って、暗黙的に派生オブジェクトの [[Prototype]] 属性に割り当てられます。ルール、派生オブジェクトのプロトタイプオブジェクト、関数の間には、プロパティとメソッドの継承・共有関係のみが存在します。
コードを使用して検証を行います:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){}
//the value of implicit [[Prototype]] property of those objects derived from fn will be assigned to fn.prototype
fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new fn();
document.write(obj.attr1 + "
"); //result: aaa
document.write(obj.attr2 + "
"); //result: bbb
document.write(obj instanceof fn); //result: true
document.write("
");
//I change the prototype of fn here, so by the algorithm of Prototype the obj is no longer the instance of fn,
//but this won't affect the obj and its [[Prototype]] property, and the obj still has attr1 and attr2 properties
fn.prototype={};
document.write(obj.attr1 + "
"); //result: aaa
document.write(obj.attr2 + "
"); //result: bbb
document.write(obj instanceof fn); //result: false
关于创建过程返回值的验证:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(){
//according to step 4 described above,
//the new fn() operation will return the object { attr1: 111, attr2: 222 }, it's not an instance of fn!
return { attr1: 111, attr2: 222 };
}
fn.prototype={ attr1:"aaa", attr2:"bbb"};
var obj=new fn();
document.write(obj.attr1 + "
"); //result: 111
document.write(obj.attr2 + "
"); //result: 222
document.write(obj instanceof fn); //result: false
做个练习经过上面的理解应,请写出下面这幅图的实现代码。图中CF是一个函数,Cfp是CF的prototype对象,cf1, cf2, cf3, cf4, cf5都是CF的实例对象。虚线箭头表示隐式Prototype关系,实线箭头表示显示prototype关系。
供参考的实现方案:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function CF(q1, q2){
this.q1=q1;
this.q2=q2;
}
CF.P1="P1 in CF";
CF.P2="P2 in CF";
function Cfp(){
this.CFP1="CFP1 in Cfp";
}
CF.prototype=new Cfp();
var cf1=new CF("aaa", "bbb");
document.write(cf1.CFP1 + "
"); //result: CFP1 in Cfp
document.write(cf1.q1 + "
"); //result: aaa
document.write(cf1.q2 + "
"); //result: bbb
ローカル プロパティと継承されたプロパティオブジェクトは暗黙的なプロトタイプ チェーンを通じてプロパティとメソッドを継承できますが、プロトタイプも通常のオブジェクトです。つまり、通常のインスタンス化されたオブジェクトです。純粋に抽象的なデータ構造の説明ではありません。したがって、ローカルプロパティと継承プロパティの問題があります。
まず、オブジェクトのプロパティを設定するプロセスを見てみましょう。 JS は、オブジェクトのプロパティを記述するために使用される一連の属性を定義し、JavaScript コードでプロパティを設定できるかどうか、for in などで列挙できるかどうかを示します。
obj.propName=value の代入ステートメントを処理する手順は次のとおりです。
1. propName の属性が設定できない場合は、
2 を返します。 、 obj の属性を作成し、その名前は propName
です。 obj.propName の値を value
に設定します。理由は明らかです。 obj の内部 [[プロトタイプ]] はインスタンスであり、obj とプロパティを共有するだけでなく、他のオブジェクトとプロパティを共有する可能性があります。
上記の CF と Cfp の例を使用して説明すると、インスタンス オブジェクト cf1 にはローカル属性 q1、q2 と継承された属性 CFP1 があり、cf1.CFP1="" が実行されると、テスト結果は cf1 にローカル属性 CFP1 が設定されます。は次のとおりです:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var cf1=new CF("aaa", "bbb");
var cf2=new CF(111, 222);
document.write(cf1.CFP1 + "
"); //result: CFP1 in Cfp
document.write(cf2.CFP1 + "
"); //result: CFP1 in Cfp
//it will result in a local property in cf1
cf1.CFP1="new value for cf1";
//changes on CF.prototype.CFP1 will affect cf2 but not cf1, because there's already a local property with
//the name CFP1 in cf1, but no such one in cf2
CF.prototype.CFP1="new value for Cfp";
document.write(cf1.CFP1 + "
"); //result: new value for cf1
document.write(cf2.CFP1 + "
"); //result: new value for Cfp
意味上の混乱がありますか? 上記の CF、Cfp サンプル シナリオをまだ使用しています。
Prototype の仕組みによれば、オブジェクト cf1、cf2 などはすべてオブジェクト Cfp のプロパティやメソッドを継承していると言え、それらの間には継承関係があると言えます。属性の継承/共有は暗黙のプロトタイプ チェーンに沿って機能するため、継承関係もこのチェーンに沿ったものとして理解される必要があります。
instanceOf 操作をもう一度見てみましょう。cf1 のinstanceOf CF のみが true であり、cf1 が Cfp のインスタンス オブジェクトであると言うのではなく、CF がクラスの役割を果たします。 cf1 は CF から継承すると言うべきでしょうか? しかし、CF はサードパーティ ファクトリとして機能するだけであり、cf1 との間に属性の継承関係はありません。
CF と Cfp を全体として理解するのも無理があります。
プロトタイプはプロトタイプです。JavaScript とオブジェクト指向の概念を無理に組み合わせる必要はありません。別の観点から見ると、JavaScript は関数型言語または動的言語とみなすことができます。複数の言語機能を組み込んだ合理化されたバージョンです。
オブジェクト モデルここはどこですか?1. JavaScript のデータ型を理解します。 Number のようなシステム組み込みオブジェクトには複数の ID があることは明らかです。a) それら自体は関数オブジェクトであり、エンジンによって内部的にのみ実装されます。b) それらはデータ型を表し、それらを使用してオブジェクトを定義および操作できます。対応するデータのタイプ、c) エンジンの内部実装メカニズムは、内部データ構造、JavaScript オブジェクトにパッケージ化されたさまざまなコンストラクターなど、その背後に隠されています。
2. プロトタイプのメカニズム、オブジェクトがそれらを介してプロパティとメソッドを継承する方法、およびオブジェクト作成プロセス中に JS エンジン内でプロトタイプ関係がどのように設定されるかを理解します。
ユーザー定義関数オブジェクト自体の作成プロセスを理解すると、JavaScript オブジェクト モデルの包括的な概要を把握できます。
Function オブジェクトの作成プロセスJavaScript コードで関数を定義するとき、または Function を呼び出して関数を作成するとき、Function 関数は最終的に次のような形式で呼び出されます: var newFun=関数(funArgs, funBody);関数オブジェクトを作成する主な手順は次のとおりです。
1. 組み込みオブジェクト fn を作成します
2. fn の内部 [[Prototype]] を Function.prototype
に設定します。内部 [[ Call]] 属性。論理参照オブジェクト作成プロセスのステップ 3 を処理する内部実装メソッドです。
4. 内部 [[Construct]] 属性。論理参照を処理する内部実装メソッドです。オブジェクト作成プロセスのステップ 1、2、3、4
5 では、fn.length を funArgs.length に設定します。関数にパラメーターがない場合は、fn.length を 0
6 に設定します。 new Object() Object オブジェクト fnProto
を作成します。 7. fnProto.constructor を fn
に設定します。 fn.prototype を fn
に設定します。 ステップ 1 とステップ 6 の違いは次のとおりです。そのステップ 1 は、Object オブジェクトの実装に使用される内部データ構造 (組み込みオブジェクト構造) を作成し、必要な内部初期化作業を完了するだけですが、その [[Prototype]]、[[Call]]、[[Construct]]他の属性は null または内部初期化値である必要があります。つまり、オブジェクトを指していない ([[Prototype]] などのプロパティの場合)、または処理が含まれていない ([[Call などのプロパティの場合]) と理解できます。 ]]、[[Construct]] メソッド)。ステップ6では、前述のオブジェクト作成処理に従って新しいオブジェクトを作成し、その[[プロトタイプ]]などを設定します。
上記の処理手順からわかるように、関数を定義するときは常に、そのプロトタイプは Object インスタンスであるため、カスタム関数のインスタンス オブジェクトを作成するとき、デフォルトでは、そのプロトタイプ チェーンは Object.prototype を指します。
さらに、Function の特別な機能は、[[Call]] と [[Construct]] の処理ロジックが同じであることです。
JavaScript オブジェクト モデル
赤い点線は、暗黙的なプロトタイプ チェーンを表します。
このオブジェクト モデル図には多くの内容が含まれているため、注意深く理解する必要があります。検証用にいくつかのテスト コードを作成します。この図を完全に理解すると、JavaScript 言語の理解もほぼ同じになります。以下に追加の手順を示します。
1. 図内には、組み込み関数コンストラクターが記載されている場所がいくつかあり、これは同じオブジェクトであり、テストおよび検証できます。
Actipro CodeHighlighter (フリーウェア) によって生成されたコードの強調表示//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
Function==Function.constructor //result: true
Function==Function.prototype.constructor //result: true
Function==Object.constructor //result: true
//Function also equals to Number.constructor, String.constructor, Array.constructor, RegExp.constructor, etc.
function fn(){}
Function==fn.constructor //result: true
これはいくつかの問題を示しています: Function はシステムの組み込み関数コンストラクター (組み込み Function コンストラクター) を指します。Function はブートストラップされます。システム内のすべての関数は Function から構築されます。
2. 左下隅の obj1、obj2...objn スコープは、次のようなコードで作成されたオブジェクトを参照します。 function fn1(){};
オブジェクトはローカルのコンストラクター メソッドを持ちませんが、プロトタイプ チェーンから継承されたコンストラクター メソッド、つまり fn.prototype.constructor を取得します。関数オブジェクトの構築プロセスから、それが fn 自体であることがわかります。
右下隅の obj1、obj2...objn の範囲は、var obj1=new Object(); または var obj1={} または var obj1=new Number(123); のようなコードで作成されたオブジェクトを参照します。 ; または obj1=/w /; など。したがって、これらのオブジェクトのプロトタイプ チェーンのポイント、プロトタイプ チェーンから継承されたコンストラクターの値 (コンストラクターが組み込みの Number コンストラクターであるか、組み込みの Object コンストラクターであるかなどを参照) は、特定のオブジェクトによって異なります。オブジェクトタイプ。また、var obj=new Object(123); この方法で作成されたオブジェクトのタイプは依然として Number であり、これもパラメータ値のタイプに基づいて決定する必要があることに注意してください。
同様に、ローカル コンストラクターはありませんが、プロトタイプ チェーンから継承されたコンストラクター メソッド、つまり組み込み *** コンストラクターを取得します。特定のコンストラクターはデータ型によって決まります。
3. 図のプロトタイプ チェーンに関する追加説明:
Object.prototype はチェーン全体の終点であり、その内部の [[Prototype]] は null です。
すべての関数のプロトタイプ チェーンは Function.prototype を指します。
関数のプロトタイプ チェーンは Function.prototype を指します。これは、設計者が関数をブートストラップされるように設計したため、仕様で必須です。 Function プロトタイプ チェーンがこの方法で設計された後は、Function.constructor==Function と Function instanceOf Function が両方とも true になります。さらに、Function はすでにトップレベルのコンストラクターですが、Function 自体も関数オブジェクトであり、何かによって作成される必要があるため、ブートストラップは意味的に合理的です。
Function.prototype のプロトタイプ チェーンは Object.prototype を指しますが、これも仕様で義務付けられています。まず、Function.prototype は Function のインスタンス オブジェクトです (typeof Function.prototype はそれが Function であることを知ることができますが、instanceOf は内部で追加で Prototype チェーンを設定しているためテストを通過できません)。そのため、Prototype の規則に従って、 Function.prototype の内部関数 [ [Prototype]] 値は Function.prototype オブジェクトである必要があります。つまり、その Prototype チェーンはそれ自体を指します。これにより、プロトタイプ チェーンに無限ループが作成される一方で、それ自体がエンドポイントになり、その結果、すべての関数オブジェクトが Object から派生しなくなります。この必須要件を追加すると、プロトタイプ チェーンのエンドポイントは 1 つだけになります。
4. Function.prototype は関数オブジェクトであるため、表示されるプロトタイプ属性、つまり Function.prototype.prototype を持つ必要がありますが、これは FireFox でのみアクセスでき、IE、Opera、またはサファリ。したがって、絵の中には存在しないことを示す記号が使われています。
5. デフォルトでは、ユーザー定義関数の [[Prototype]] 値は Object.prototype です。つまり、その暗黙のプロトタイプ チェーンは Object.prototype を指すため、図に示されています。ただし、これが常に当てはまるわけではありません。ユーザーがカスタム関数のプロトタイプ属性を設定する場合は状況が異なります。
実行モデル実行コンテキストの概要JavaScript コードが実行される場所 実行があるコンテキストは、JavaScript 実行時のスコープや有効期間などの処理を完了するために使用される概念およびメカニズムです。実行コンテキストには、変数オブジェクト、変数の初期化、スコープ/スコープ チェーンなどの概念が含まれます。異なるシナリオ/実行環境での処理にはいくつかの違いがあります。これらのシナリオについては、以下で説明します。
関数オブジェクトは、ユーザー定義関数オブジェクトとシステム組み込み関数オブジェクトに分けられ、ユーザー定義関数オブジェクトは以下の仕組みに従って処理されますが、組み込み関数オブジェクトは特定の実装に関連します。コンテキストの処理に関する要件はありません。つまり、コンテキストは一般的にこのセクションで説明する内容には適していません。
実行される JavaScript コードは 3 つのタイプに分けられ、これら 3 つのタイプの処理の違いについては後で説明します。
1. グローバル コード、つまり、どの関数にも含まれていないグローバル コード。 js ファイル、HTML ページに埋め込まれた js コードなど。
2. Eval コード。eval() 関数を使用して動的に実行される JS コードです。
3. 関数コード。ユーザー定義関数内の関数本体の JS コードです。
基本原則ユーザー定義関数では、関数内でパラメーターを渡し、ローカル変数を定義できます。関数本体のコードでは、これらの入力パラメーターとローカル変数を使用できます。その背後にあるメカニズムは何ですか?
JS 実行フローが関数に入ると、JavaScript エンジンは変数オブジェクトと呼ばれるオブジェクトを内部的に作成します。関数の各パラメータに対応して、変数オブジェクトに属性を追加します。属性の名前と値は、パラメータの名前と値と同じです。函数中每声明一个变量,也会在Variable Object上添加一个属性,名字就是变量名,因此为变量赋值就是给Variable Object对应的属性赋值。在函数中访问参数或者局部变量时,就是在variable Object上搜索相应的属性,返回其值。
一般情况下Variable Object是一个内部对象,JS代码中无法直接访问。规范中对其实现方式也不做要求,因此它可能只是引擎内部的一种数据结构。
大致处理方式就这样,但作用域的概念不只这么简单,例如函数体中可以使用全局变量、函数嵌套定义时情况更复杂点。这些情况下怎样处理? JavaScript引擎将不同执行位置上的Variable Object按照规则构建一个链表,在访问一个变量时,先在链表的第一个Variable Object上查找,如果没有找到则继续在第二个Variable Object上查找,直到搜索结束。这就是Scope/Scope Chain的大致概念。
下面是各个方面详细的处理。
Global ObjectJavaScript的运行环境都必须存在一个唯一的全局对象-Global Object,例如HTML中的window对象。Global Object是一个宿主对象,除了作为JavaScript运行时的全局容器应具备的职责外,ECMA规范对它没有额外要求。它包Math、 String、Date、parseInt等JavaScript中内置的全局对象、函数(都作为Global Object的属性),还可以包含其它宿主环境需要的一些属性。
Variable Object上面简述了Variable Object的基本概念。创建Variable Object,将参数、局部变量设置为Variable Object属性的处理过程叫做Variable Instatiation-变量实例化,后面结合Scope Chain再进行详细说明。
Global CodeVariable Object就是Global Object,这是Variable Object唯一特殊的地方(指它是内部的无法访问的对象而言)。
var globalVariable = "WWW";
document.write(window.globalVariable); //result: WWW
上記のコードは、変数オブジェクトの処理に従って、変数 globalVariable が定義されると、この属性がグローバル オブジェクト (つまり、ウィンドウ) オブジェクトに追加されるため、出力は値 WWW になります。
関数コード変数オブジェクトは、起動オブジェクトとも呼ばれます (いくつかの違いがあるため、違いを示すために仕様で新しい名前が付けられています。変数オブジェクトと呼ばれます)グローバル コード/評価コードでは、関数コードではアクティベーション オブジェクトと呼ばれます)。
関数の実行に入るたびに、新しいアクティベーション オブジェクト オブジェクトが作成され、次に引数オブジェクトが作成されてアクティベーション オブジェクトのプロパティとして設定され、変数のインスタンス化が処理されます。
関数を終了すると、アクティベーション オブジェクトは破棄されます (メモリ解放ではありませんが、ガベージ コレクションされる可能性があります)。
引数オブジェクトの属性: 長さ: 渡されるパラメータの実際の数です。関数オブジェクトの作成プロセスを参照すると、関数オブジェクトの長さは関数の定義時に必要なパラメーターの数です。
callee: は実行される関数オブジェクト自体です。その目的は、たとえば再帰呼び出しが必要な場合に、関数オブジェクトが自身を参照できるようにすることです。
function fnName(...) { ... } はこの方法で関数を定義し、その再帰呼び出しは関数本体で fnName を使用して完了できます。 var fn=function(...) { ... } この方法で匿名関数を定義します。関数本体で名前を使用して自分自身を参照することはできません。再帰呼び出しを実装するには、arguments.callee を通じて自分自身を参照できます。
パラメータ リスト: 呼び出し元によって実際に渡されるパラメータ リスト。このパラメータ リストは、インデックスを使用して実際のパラメータにアクセスする方法を提供します。関数の宣言時に指定されたパラメーター リストがある場合、変数のインスタンス化では、処理中にプロパティがアクティベーション オブジェクト オブジェクトに追加されます。関数宣言でパラメーター リストが指定されていない場合、または実際のパラメーターの数が関数宣言のパラメーターの数と異なる場合は、引数を介して各パラメーターにアクセスできます。
引数のパラメータ リストとアクティベーション オブジェクトのパラメータ属性は、同じパラメータ オブジェクトを参照します (変更された場合、両方の場所に反映されます)。仕様では引数が配列オブジェクトである必要はありません。ここでテストします:
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
var argumentsLike = { 0: "aaa", 1: 222, 2: "WWW", length: 3, callee: function() { } };
document.write(argumentsLike[2] + "
"); //result: WWW
document.write(argumentsLike[1] + "
"); //result: 222
//convert the argumentsLike to an Array object, just as we can do this for the arguments property
var array = [].slice.apply(argumentsLike);
document.write(array instanceof Array); //result: true
document.write("
");
document.write(array.reverse().join("|")); //result: WWW|222|aaa
Eval CodeVariable Object就是调用eval时当前执行上下文中的Variable Object。在Global Code中调用eval函数,它的Variable Object就是Global Object;在函数中调用eval,它的Variable Object就是函数的Activation Object。
//Passed in FF2.0, IE7, Opera9.25, Safari3.0.4
function fn(arg){
var innerVar = "variable in function";
eval(' \
var evalVar = "variable in eval"; \
document.write(arg + "
"); \
document.write(innerVar + "
"); \
');
document.write(evalVar);
}
fn("arguments for function");
输出结果是:
arguments for function
variable in function
variable in eval
说明: eval调用中可以访问函数fn的参数、局部变量;在eval中定义的局部变量在函数fn中也可以访问,因为它们的Varible Object是同一个对象。
Scope/Scope Chain首先Scope Chain是一个类似链表/堆栈的结构,里面每个元素基本都是Variable Object/Activation Object。
其次存在执行上下文的地方都有当前Scope Chain,可以理解为Scope Chain就是执行上下文的具体表现形式。
Global CodeScope Chain只包含一个对象,即Global Object。在开始JavaScript代码的执行之前,引擎会创建好这个Scope Chain结构。
Function Code函数对象在内部都有一个[[Scope]]属性,用来记录该函数所处位置的Scope Chain。
创建函数对象时,引擎会将当前执行环境的Scope Chain传给Function的[[Construct]]方法。[[Construct]]会创建一个新的Scope Chain,内容与传入的Scope Chain完全一样,并赋给被创建函数的内部[[Scope]]属性。在前面函数对象创建过程一节中,这个处理位于步骤4和5之间。
进入函数调用时,也会创建一个新的Scope Chain,包括同一个函数的递归调用,退出函数时这个Scope Chain被丢弃。新建的Scope Chain第一个对象是Activation Object,接下来的内容与内部[[Scope]]上存储的Scope Chain内容完全一样。
Eval Code进入Eval Code执行时会创建一个新的Scope Chain,内容与当前执行上下文的Scope Chain完全一样。
实例说明Scope Chain的原理就上面这些,必须结合JS代码的执行、Variable Instantiation的细节处理,才能理解上面这些如何产生作用,下面用一个简单的场景来综合说明。假设下面是一段JavaScript的Global Code:
var outerVar1="variable in global code";
function fn1(arg1, arg2){
var innerVar1="variable in function code";
function fn2() { return outerVar1+" - "+innerVar1+" - "+" - "+(arg1 + arg2); }
return fn2();
}
var outerVar2=fn1(10, 20);
执行处理过程大致如下:
1. 初始化Global Object即windo0,0)">20
);
実行プロセスは大まかに次のとおりです。
1. ウィンドウ オブジェクトであるグローバル オブジェクトとウィンドウ オブジェクト自体である変数オブジェクトを初期化します。ウィンドウ オブジェクトのみを含むスコープ チェーン オブジェクトをscope_1 であると仮定して、スコープ チェーン オブジェクトを作成します。
2. JS ソース コードをスキャンします (ソース コードを読みます。字句解析および構文解析のプロセスがある場合があります)。その結果から、定義された変数名と関数オブジェクトを取得できます。スキャン順序に従って:
2.1 変数 externalVar1 を検出し、outerVar1 属性をウィンドウ オブジェクトに追加します。値は未定義です。
2.2 関数 fn1 の定義を検出し、この定義を使用して関数オブジェクトを作成します。作成プロセスに渡されるスコープ チェーンはscope_1です。結果を window プロパティに追加します。名前は fn1 で、値は返された関数オブジェクトです。 fn1 の内部 [[Scope]] はscope_1 であることに注意してください。なお、作成処理では関数本体内のJSコードに対して特別な処理は行わず、関数本体内のJSコードのスキャン結果を関数オブジェクトの内部プロパティに保存するだけであることが分かります。関数の実行時に処理されます。これは、関数コード、特に入れ子関数定義における変数のインスタンス化を理解するための鍵です。
2.3 変数 externalVar2 を検出し、outerVar2 属性をウィンドウ オブジェクトに追加します。値は未定義です。
3.そして値を「グローバルコード内の変数」に割り当てます。
4. 関数 fn1 を実行し、戻り値を取得します。
4.1 アクティベーション オブジェクトを作成します (スコープ_2 であると仮定し、スコープ チェーンの最初のオブジェクトがアクティベーション 1 であると仮定します)。 object はウィンドウ オブジェクトです (fn1 の [[Scope]]、つまり、scope_1 の内容から取得されます)。
4.2 プロセス パラメーター リスト。 activity_1 の属性 arg1 と arg2 にそれぞれ値 10 と 20 を設定します。引数オブジェクトを作成して設定し、引数を activity_1 の属性として設定します。
4.3 fn1 の関数本体で手順 2 と同様のプロセスを実行します。
4.3.1 変数 innerVar1 を検出し、activation_1 に innerVar1 属性を追加します。オブジェクト、値は未定義です。現在の実行コンテキストの)。結果を、fn2 という名前と返された関数オブジェクトの値を使用して、activation_1 の属性に追加します。 fn2 の内部 [[スコープ]] はscope_2 であることに注意してください。
4.4 innerVar1 代入ステートメントを実行し、値を「関数コード内の変数」に代入します。
4.5 fn2 を実行します。
4.5.1 アクティベーション オブジェクトを作成します (activation_2 であると仮定します)。 新しいスコープ チェーンを作成します (scope_3 と仮定します)。scope_3 の最初のオブジェクトは activity_2 で、次のオブジェクトは activity_1、window です。オブジェクト (fn2 の [[Scope]]、つまりscope_2 から取得);
4.5.2 プロセスパラメータリスト。 fn2 にはパラメーターがないため、引数オブジェクトを作成し、それを activity_2 の属性として設定するだけです。
4.5.3 fn2の関数本体に対して手順2と同様の処理を行うと、変数定義や関数宣言が見つかりません。
4.5.4 関数本体を実行します。変数参照の場合、scope_3 から検索します。この例では、outerVar1 は window で見つかり、innerVar1、arg1、および arg2 は activity_1 で見つかります。
4.5.5 スコープ_3 とアクティベーション_2 を破棄します (ガベージ コレクションできることを意味します)。
4.5.6 fn2 の戻り値を返します。
4.6 activity_1 とscope_2 を破棄します。
4.7 結果を返します。
5. 結果をouterVar2に代入します。
他の場合でも、スコープチェーンと変数のインスタンス化は、上記のプロセスと同様に分析できます。
上記の事実によると