ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript_javascript スキルの言語機能の分析に関する簡単な説明

JavaScript_javascript スキルの言語機能の分析に関する簡単な説明

WBOY
WBOYオリジナル
2016-05-16 17:37:32942ブラウズ

はじめに
JavaScript では、スコープ、コンテキスト、クロージャー、関数などが最高のものです。初心者の JSers にとっては、上級レベルの必需品です。フロントエンドのシージエンジニアは、落ち着いてこれらの本質を理解することによってのみ、エレガントなコードを書くことができます。

この記事は、忘れがちな重要な知識を要約することを目的としており、基本的な概念については説明しません。基本的な知識がよくわからない場合は、「JavaScript の決定版ガイド」を読んでください~


言語特徴関数式

まずコード スニペットを見てください:

コードをコピー コードは次のとおりです:

[javascript] view plaincopyprint?
var f = function foo(){
return typeof foo; // foo は内部スコープ内で有効です
};
// foo は外部で使用されると非表示になります
typeof foo; // " "
f(); // "function"
var f = function foo(){
return typeof foo; // foo は内部スコープ
};
// foo 外部で使用する場合は非表示
typeof foo // "unknown"
f() // "function"
;

ここで言いたいのは、関数式内の foo は関数内でのみ参照でき、関数外では参照できないということです。


json

多くの JavaScript 開発者は、JavaScript オブジェクト リテラル (JSON オブジェクト) を誤って呼んでいます。 JSON はデータ交換形式を記述するように設計されており、JavaScript のサブセットである独自の構文も備えています。

{ “prop”: “val” } このような宣言は、使用されるコンテキストに応じて、JavaScript オブジェクト リテラルまたは JSON 文字列の場合があります。文字列コンテキスト (一重引用符または二重引用符で囲まれるか、テキスト ファイルから読み取られる) で使用される場合、それは JSON 文字列です。オブジェクト リテラル コンテキストで使用される場合、それはオブジェクト リテラルです。

コードをコピー コードは次のとおりです:

[javascript] view plaincopyprint?
// これは JSON 文字列です
var foo = '{ "prop": "val" }'
// これはオブジェクト リテラルです
var bar = { "prop": "val" } ;
// これは JSON 文字列です
var foo = '{ "prop": "val" }';
// これはオブジェクト リテラルです
var bar = { "prop": "val" };

もう 1 つ知っておくべきことは、JSON.parse は JSON 文字列をオブジェクトに逆シリアル化するために使用され、JSON.stringify はオブジェクトを JSON 文字列にシリアル化するために使用されるということです。古いバージョンのブラウザはこのオブジェクトをサポートしていませんが、json2.js を通じて同じ機能を実現できます。


プロトタイプ

コードをコピー コードは次のとおりです。

function Animal (){
/ / .. .
}
function cat (){
// ...
}
cat.prototype = new Animal();//このメソッドは、コンストラクタ。
cat.prototype = Animal.prototype;//このメソッドはコンストラクターを継承しません。
//もう 1 つ注意すべき重要な点は、独自のプロトタイプ チェーンを保守する必要があるということです。初心者はこれを常に忘れてしまいます。
cat.prototype.constructor = cat;

(新しいオブジェクトを割り当てることによって) 関数のプロトタイプ プロパティを完全に変更すると、作成するオブジェクトにはコンストラクター プロパティが含まれないため、元のコンストラクターへの参照が失われます。

コードをコピー コードは次のとおりです。

function A() {}
A.prototype = {
x: 10
};
var a = new A();
alert(a.x) // 10
alert(a.constructor === A) ); // false!

MDN のコンストラクターの説明を見てみましょう。 プロトタイプ: インスタンスのプロトタイプを作成したオブジェクト関数への参照を返します。 したがって、関数へのプロトタイプ参照は手動で復元する必要があります。

コードをコピー コードは次のとおりです。

function A() {}
A.prototype = {
コンストラクター: A,
x: 10
};
var a = new A();
alert(a.x); // 10
alert( a.constructor === A) // true

ただし、プロトタイプ属性を送信しても、すでに作成されているオブジェクトのプロトタイプには影響しません (コンストラクターのプロトタイプ属性が変更された場合にのみ影響を受けます)。つまり、新しく作成されたオブジェクトのみが新しいプロトタイプを持ちます。すでに作成されたオブジェクトには、元の古いプロトタイプへの参照が引き続き残ります (このプロトタイプは変更できなくなります)。

コードをコピー コードは次のとおりです。

function A() {}
A.prototype .x = 10;
var a = new A();
alert(a.x); // 10
A.prototype = {
コンストラクター: A,
x: 20
y: 30
};
//オブジェクト a は、暗黙的な [[Prototype]] 参照
alert(a.x) を通じて原油のプロトタイプから取得された値です。 🎜>alert (a.y) // unknown
var b = new A();
// ただし、新しいオブジェクトは新しいプロトタイプから取得された値です
alert(b.x) // 20alert(b.y) // 30


したがって、「プロトタイプを動的に変更すると、すべてのオブジェクトに新しいプロトタイプが影響する」という考えは誤りです。新しいプロトタイプは、プロトタイプが変更された後に新しく作成されたオブジェクトに対してのみ有効になります。ここでの主なルールは、オブジェクトのプロトタイプはオブジェクトの作成時に作成され、その後は新しいオブジェクトに変更することはできません。同じオブジェクトを参照している場合は、コンストラクター内の明示的なプロトタイプを介して参照できます。オブジェクトの作成後は、プロトタイプの属性のみを追加または変更できます。

変数オブジェクト 関数実行コンテキストでは、VO(変数オブジェクト)に直接アクセスすることはできません。このとき、活性化オブジェクト(活性化オブジェクト)がVOの役割を果たします。 アクティブ オブジェクトは、関数コンテキストに入るときに作成され、関数の argument 属性を通じて初期化されます。 argument 属性の値は Arguments オブジェクトです:


function foo(x, y , z) {
// 宣言された関数パラメータの数 引数 (x, y, z)
alter(foo.length); // 3
// 実際に渡されるパラメータの数 (x, のみ) y)
alter(arguments.length); // 2
// パラメータの呼び出し先は関数自体です
alter(arguments.callee === foo); // true
}


実行コンテキストに入るとき (コード実行前)、VO にはすでに次の属性が含まれています。 1. 関数のすべての仮パラメーター (関数実行コンテキストにいる場合)

•すべての関数宣言 (FunctionDeclaration、FD);

•すべての変数宣言 (var、VariableDeclaration)


もう 1 つの典型的な例:

コードをコピーします コードは次のとおりです。
alert(x); // function
var x = 10;
alert(x); // 10
x = 20;
function x() {};
alert(x); 🎜>

仕様によれば、コンテキストに入るときに関数宣言が入力されます。また、コンテキストに入るときに変数宣言「x」があり、その後、上で述べたように、変数宣言は関数宣言とフォームの後に続きます。この入力コンテキスト段階では、変数宣言は、VO にすでに存在する同じ名前の関数宣言または仮パラメータ宣言を妨げません。単純な属性と比較して、変数には {DontDelete} という属性があります。この属性の意味は、変数属性を delete 演算子を使用して直接削除できないことです。

コードをコピー

コードは次のとおりです。a = 10;alert( window.a ); // 10
alert(delete a); // true
alert(window.a); // 未定義
var b = 20;
alert(window.b) ; // 20
alert(delete b); // false
alert(window.b); // 20 のままです。 b は変数であり、プロパティではありません!
var a = 10; // グローバル コンテキストの変数
(function () {
var b = 20; // 関数コンテキストのローカル変数
} )( );
alert(a); // 10
alert(b); // グローバル変数 "b" が宣言されていません。



This 関数コンテキストでは、this は呼び出し元によって提供され、関数の呼び出し方法によって決定されます。呼び出し括弧 () の左側が参照型の値である場合、これは参照型値の基本オブジェクトに設定されます。それ以外の場合 (参照型とは異なるその他のプロパティ)、この値は null になります。 。ただし、this の値が null である実際の状況はありません。this の値が null の場合、その値は暗黙的にグローバル オブジェクトに変換されるからです。

コードをコピー

コードは次のとおりです:

(function () {
alert(this); // null => global
})();

この例では、関数オブジェクトはありますが、参照型オブジェクトはありません (識別子でもプロパティ アクセサーでもありません)。したがって、 this 値は最終的にグローバル オブジェクトに設定されます。

コードをコピー コードは次のとおりです:

var foo = {
bar: function () {
alert(this);
}
};
foo.bar(); // 参照、OK => foo
(foo.bar); // 参照、OK => foo
(foo.bar = foo.bar)(); // global
(false || foo.bar)(); // global
(foo.bar) , foo .bar)() // グローバル

問題は、次の 3 つの呼び出しでは、特定の操作を適用した後、呼び出し括弧の左側の値が参照型ではなくなることです。

•最初の例は明らかです - 明らかな参照型です。結果として、これが基本オブジェクト、つまり foo になります。

• 2 番目の例では、グループ演算子は適用されません。GetValue など、参照型からオブジェクトの実際の値を取得する上記のメソッドを考えてください。同様に、グループ操作の戻りでも参照型を取得します。これが、 this 値がベース オブジェクト (foo) に再度設定される理由です。

•3 番目の例では、グループ演算子とは異なり、代入演算子は GetValue メソッドを呼び出します。返される結果は関数オブジェクト (参照型ではありません) です。つまり、これは null に設定され、結果はグローバル オブジェクトになります。

• 4 番目と 5 番目も同様です。コンマ演算子と論理演算子 (OR) が GetValue メソッドを呼び出し、それに応じて参照が失われ、関数が取得されます。そして、再度グローバルに設定します。

ご存知のとおり、ローカル変数、内部関数、仮パラメータは、指定された関数のアクティベーション オブジェクトに格納されます。

コードをコピー コードは次のとおりです。

function foo() {
function bar() {
alter(this); // グローバル
}
bar() // AO.bar()
}
と同じ

アクティブなオブジェクトは常に this として返され、値は null です (つまり、疑似コードの AO.bar() は null.bar() と同等です)。ここで、これをグローバル オブジェクトに設定して、上で説明した例に戻ります。


スコープチェーン

関数コンストラクターを通じて作成された関数のスコープ属性は、常に唯一のグローバル オブジェクトです。

重要な例外の 1 つは、関数コンストラクターを介して作成された関数に関するものです。

コードをコピー コードは次のとおりです。

var x = 10;
function foo() {
var y = 20;
function barFD() { // 関数宣言
alert(x);
alert(y);
}
var barFn = Function(' アラート(x); アラート(y);');
barFD() // 10, 20
barFn(); // 10, "y" が定義されていません
}
foo ();

また:

コードをコピー コードは次のとおりです。

var x = 10, y = 10;
with ({x: 20}) {
var x = 30, y = 30;
//x = 30 ここでは x = 20 をカバーします。 🎜 > アラート(y); // 30
}
アラート(x) // 10
アラート(y);

コンテキストを入力するとどうなりますか?識別子「x」と「y」が変数オブジェクトに追加されました。さらに、コードの実行フェーズ中に次の変更を加えます:

•x = 10, y = 10;
•オブジェクト {x:20} がスコープの先頭に追加されます;
•with 内で var 宣言が検出されますが、もちろん何もありませんコンテキストに入るときに、すべての変数が解析されて追加されているため、 が作成されます。
• 2 番目のステップでは、変数 "x" のみが変更され、実際にはオブジェクト内の "x" が解析されて追加されます。スコープチェーンの先頭、「x」は 20 で 30 になります;
• 変数オブジェクト「y」も解析された後、値が 10 から 30 に変わります;
•さらに、with ステートメントが完了すると、その特定のオブジェクトがスコープ チェーンから削除されます (変更された変数 "x" - 30 もそのオブジェクトから削除されます)。つまり、スコープ チェーンの構造が状態に復元されます。 withが強化される前。
•最後の 2 つのアラートでは、現在の変数オブジェクトの "x" は同じままですが、"y" の値は 30 に等しくなりますが、これは with ステートメントの実行中に変更されました。
関数

括弧に関する質問

質問を見てみましょう: 「関数を作成した直後に呼び出す場合、なぜ関数を括弧で囲む必要があるのですか?」 』の答えは、表現文の制限はこうです。

標準によれば、式ステートメントはコード ブロックと区別することが難しいため、中括弧 { で始めることはできません。同様に、関数宣言と区別することが難しいため、関数キーワードで始めることもできません。つまり、作成直後にすぐに実行する関数を定義する場合は、次のように呼び出します:

コードをコピー コードは次のとおりです:

function () {
.. .
}();
// 名前があっても
function foo() {
...
}();

上記 2 つの定義では関数宣言を使用しましたが、インタープリターは解釈時にエラーを報告しますが、多くの理由が考えられます。これがグローバル コード (つまり、プログラム レベル) で定義されている場合、関数キーワードで始まるため、インタープリターはそれを関数宣言として扱います。最初の例では、関数宣言に名前がないため、SyntaxError が発生します。 (関数宣言には名前が必要であると前述しました)。 2 番目の例では、foo という名前の関数宣言が正常に作成されていますが、それでも構文エラー、つまり式のないグループ化演算子エラーが発生します。これは実際には、関数呼び出しで使用される括弧ではなく、関数宣言後のグループ化演算子です。したがって、次のコードを宣言するとします:

コードをコピー コードは次のとおりです:

// "foo" は関数宣言です、入力後 Create
alert(foo); // Function
function foo(x) {
alter(x);
}(1); // これは単なるグループ化演算子です。関数呼び出し!
foo(10); // これは実際の関数呼び出しであり、結果は 10

式を作成する最も簡単な方法は、グループ化演算子の括弧を使用することです。そのため、インタープリタがそれを解釈するときにあいまいさがなくなります。この関数はコード実行フェーズ中に作成され、すぐに実行され、その後自動的に破棄されます (参照がない場合)

コードをコピー コードは次のとおりです。

(function foo(x) {
alert(x);
})(1); // これはグループ化演算子ではなく呼び出しです

上記のコードは、式を括弧で囲み、(1) を通じて呼び出すものです。すぐに実行される次の関数の場合、関数を囲む括弧は必要ないことに注意してください。関数はすでに式の位置にあり、関数の実行フェーズで作成される必要がある FE を処理していることがパーサーに認識されているためです。 、関数が作成された直後に呼び出されます。

コードをコピー コードは次のとおりです:

var foo = {
bar:関数 (x ) {
>

ご覧のとおり、foo.bar は文字列であり関数ではありません。この関数は条件パラメータに基づいてこのプロパティを初期化するためにのみ使用され、作成されてすぐに呼び出されます。


1. したがって、「括弧について」という質問に対する完全な答えは次のとおりです。
2. 関数が式の位置にない場合、グループ化演算子の括弧が必要です (つまり、手動で)。関数はFEに変換されます。
3. パーサーが FE を扱っていることを認識している場合は、括弧を使用する必要はありません。
自由変数:

コードをコピー コードは次のとおりです。

function testFn() {
var localVar = 10 ;//innerFn 関数の場合、localVar は自由変数です。
function innerFn(innerParam) {
alert(innerParam localVar);
}
return innerFn;
}

クロージャーの静的スコープ:

コードをコピー コードは次のとおりです:

var z = 10;
function foo() {
alert(z);
}
foo() // 10 – 静的スコープと動的スコープを使用する場合
(function () {
var z = 20;
foo(); // 10 - 静的スコープを使用、20 - 動的スコープを使用
})();
// foo がパラメーターとして使用される場合、同じ
(function (funArg) {
var z = 30;
funArg(); // 10 – 静的スコープ、30 – 動的スコープ
})(foo);

理論: スコープ チェーンのため、すべての関数はクロージャです (関数の種類に関係なく、匿名関数、FE、NFE、FD はすべてクロージャです)。実用的な観点から: 以下の関数はクロージャとみなされます: * 作成されたコンテキストが破棄された場合でも、依然として存在します (たとえば、内部関数が親関数から返される)

* コード内で自由変数

を参照します

最後に:
ECMAScript は、プロトタイプベースの委任継承をサポートするオブジェクト指向言語です。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。