ホームページ  >  記事  >  ウェブフロントエンド  >  JavaScript 応用プログラミング(第 3 版)学習ノート 8 js 関数(その 2)_基礎知識

JavaScript 応用プログラミング(第 3 版)学習ノート 8 js 関数(その 2)_基礎知識

WBOY
WBOYオリジナル
2016-05-16 17:49:25973ブラウズ
6. 実行環境とスコープ

(1) 実行コンテキスト: すべての JavaScript コードが実行環境で実行されます。 コードを実行すると、実行が開始されます。環境。アクティブな実行環境は論理的にスタックを形成します。グローバル実行環境は常にこのスタックの一番下の要素であり、スタックの一番上の要素は現在実行中の実行環境です。各関数には独自の実行環境があります。実行フローが関数に入ると、この関数の実行環境がスタックの先頭にプッシュされ、関数の実行後に実行環境がポップアップされ、制御が前の状態に戻ります。実行環境。

(2) 変数オブジェクト: 各実行環境には対応する変数オブジェクトがあり、実行環境で定義されたすべての変数と関数はこの変数オブジェクトに格納されます。この変数オブジェクトはバックグラウンド実装のオブジェクトであり、コード内でアクセスすることはできませんが、実行環境とスコープに関連する概念を理解するのに役立ちます。

(3) スコープ チェーン: コードが実行環境で実行されると、変数オブジェクトで構成されるスコープ チェーンが作成されます。このチェーンのフロントエンドは、現在のコードが配置されている環境の変数オブジェクトであり、チェーンの終端はグローバル環境の変数オブジェクトです。実行環境で識別子を解析する場合、現在の実行環境の対応する変数オブジェクトが検索され、見つからない場合はスコープ チェーンに沿ってレベルごとに検索されます。グローバル環境の変数オブジェクトが存在しない場合は、参照例外がスローされます。

(4) 起動オブジェクト: 実行環境が関数実行環境の場合、変数オブジェクトは起動オブジェクトとも呼ばれます。アクティブ オブジェクトには、最初は変数オブジェクトである引数オブジェクトが 1 つだけ含まれています (このオブジェクトはグローバル環境の変数オブジェクトには存在しません)。

これら 4 つの概念はやや抽象的ですが、それでも比較的自然なものであり、『JavaScript 上級プログラミング (第 3 版)』の例で詳しく体験できます。
コードをコピー コードは次のとおりです:

// グローバル スコープに入り、グローバル変数オブジェクトを作成します
var color = " blue";

function changeColor(){
//changeColor スコープに入り、changeColor に対応する変数オブジェクトを作成します
var anotherColor = "red";

function swapColors( color1 , color2){
// swapColors スコープに入り、swapColors の対応する変数オブジェクトを作成します。
anotherColor = color;
color = tempColor; 🎜> * swapColors スコープ内でアクセスできるオブジェクトは次のとおりです:
* グローバル変数オブジェクトの color、changeColor
* anotherColor、changeColor 関数の対応する変数オブジェクトの swapColors
* 対応する変数オブジェクトの tempColor swapColors 関数の
*/
}
swapColors('white');
/*
* changeColor スコープ内でアクセスできるオブジェクトは次のとおりです:
* の色、changeColorグローバル変数オブジェクト
* 対応する changeColor 関数 anotherColor、変数オブジェクトの swapColors
*/
}

changeColor()
/*
*グローバル スコープでアクセスできるのは次のとおりです:
* グローバル変数 object color,changeColor
*/


ここでのプロセス全体は次のとおりです:

(1) グローバル環境に入り、グローバル変数オブジェクトを作成し、グローバル環境をスタックの最上位にプッシュします (これも同様です)スタックの一番下)。宣言のプロモーションに関する前述の結論によると、ここでグローバル変数オブジェクトを作成するために考えられるプロセスは、最初にグローバル変数オブジェクトを作成し、次に関数宣言を処理して属性 changeColor を対応する関数に設定し、次に変数宣言を処理して、属性の色を未定義に設定します。

(2) グローバル環境でコードを実行します。まずカラー変数の初期化を実行し、値を「blue」に割り当ててから、changeColor() 関数を呼び出します。

(3) changeColor() 関数を呼び出し、changeColor 関数の実行環境に入り、この環境の対応する変数オブジェクト (つまり、アクティブ オブジェクト) を作成し、この環境をスタックの先頭にプッシュします。アクティブ オブジェクトを作成するための可能なプロセスは、まずアクティブ オブジェクトを作成し、内部関数宣言を処理して属性 swapColors を対応する関数に設定し、関数パラメータを処理してアクティブ オブジェクトの属性引数オブジェクトを作成し、内部関数を処理することです。変数宣言を使用して、属性anotherColorを未定義に設定します。

(4)changeColor()関数コードを実行します。まずanotherColorを実行して「red」に初期化してから、swapColors()関数を呼び出します。

(5) swapColors()関数を呼び出し、swapColors関数実行環境に入り、対応する変数オブジェクト(アクティブオブジェクト)を作成し、swapColors実行環境をスタックの先頭にプッシュします。ここでアクティブ オブジェクトを作成するために考えられるプロセスは、最初にアクティブ オブジェクトを作成し、関数パラメータを処理し、仮パラメータをアクティブ オブジェクトの属性として使用し、それらを未定義に割り当て、アクティブ オブジェクトの属性引数オブジェクトを作成し、初期化することです。実パラメータに応じた仮パラメータと引数。値と属性(属性color1とarguments[0]を「white」に初期化します。2番目の実パラメータがないため、color2の値は未定義です。引数の数は 1 だけです)。関数のパラメータを処理した後、関数の内部変数宣言を処理し、tempColor をアクティブ オブジェクトのプロパティとして使用し、それを未定義に割り当てます。

(6) swapColors()関数コードを実行します。まず初期化して tempColor に値を割り当て、次に値交換関数を実装します (ここでは color と anotherColor の値がスコープ チェーンに沿って読み取られます)。

(7) swapColors() 関数コードが実行されると、未定義が返され、対応する実行環境がスタックからポップされて破棄されます (実行環境はここで破棄されますが、対応するアクティブ オブジェクトは削除されることに注意してください)実行環境の実行環境が必ずしも破棄されるわけではありません)、現在の実行環境は、changeColor() 関数の実行環境に復元されます。 swapColor() 関数が実行を完了して戻ると、changeColor() も実行を完了し、未定義を返し、changeColor() 関数の実行環境はスタックからポップされて破棄され、現在の実行環境はグローバルに復元されます。環境。処理プロセス全体が終了し、ページが終了するまでグローバル環境は破棄されます。

スコープ チェーンは、関数が内部でそれ自体を再帰的に呼び出すことができる理由も説明しています。関数名は、関数が定義されている実行環境、および関数の内部実行環境の対応する変数オブジェクトの属性です。関数の場合、スコープをたどることができます。チェーンは 1 レベル上に上がって、関数名が指す関数オブジェクトにアクセスします。関数名が関数内の新しい関数を指している場合、再帰呼び出しは正しくありません:
コードをコピー コードは次のとおりです。 :

function fn(num){
if(1 == num){
return 1;
}else{
fn = function(){
return 0;
};
return num * fn(num - 1)
}
}
console.info(fn(5));//0

スコープと宣言のホイスティングについて、別の例を見てみましょう:
コードをコピーします コードは次のとおりです。 :

var name = 'linjisong';
function fn(){
console.info(name);//未定義
var name = 'oulinhai'; >console.info( name);//oulinhai
}
fn();
console.info(name);//linjisong

ここで最も直感的でないのは、 name はグローバルに定義されているため、出力の 3 行目は未定義になりますが、上記の解析手順に従って一度解析すると、正しい結果が得られます。さらに、ECMAScript にはグローバル実行環境と関数実行環境のみが存在し、それに応じてグローバル スコープと関数スコープのみが存在し、ブロック ステートメントは存在しますが、ブロック スコープは存在しないことを強調したいと思います。

コードをコピー コードは次のとおりです。
function fn(){
var fnScope = ' a';

{
var blockScope = 'b';
blockScope = fnScope;
console.info(blockScope);//スコープなので問題ありません。 関数スコープ全体内で blockScope にアクセスします。
console.info(fnScope)
}
fn();//ba,a

console.info(blockScope); //ReferenceError、関数スコープ外では、内部定義された変数にアクセスできません
console.info(fnScope);//ReferenceError


スコープ チェーンの場合、with ステートメントと try-catch ステートメントの catch ブロックを使用して拡張することもできます。

•with(obj){} ステートメントを使用する場合は、obj オブジェクトを現在のスコープ チェーン フロントエンド。
•try{}catch(error){} ステートメントを使用する場合は、現在のスコープ チェーンの先頭にエラー オブジェクトを追加します。
読み取り全体のスムーズさに影響しないことを願って、より抽象的な概念を挿入しました。実際、ここでは「クロージャ」と呼ばれる概念を無視しています。関数とクロージャについては、以下のこの記事で詳しく説明します。 。

7. 関数内部オブジェクトとこれ

オブジェクト指向言語のユーザーにとって、これはコンストラクターの新規作成を指します。物体!ただし、ECMAScript では、物事はそれほど単純ではありません。new 演算子を使用して関数を呼び出す場合、これは新しく作成されたオブジェクトを指しますが、これは単に this の値を指定する方法にすぎません。 object. このオブジェクトの値を指定する方法は他にもあります。つまり、これは動的であり、自分で自由に指定できます。

(1) グローバル環境の this

グローバル環境の this は、ブラウザ内のウィンドウであるグローバル オブジェクト自体を指します。 ここで、グローバル環境の this も使用できます。グローバル実行環境に対応する変数オブジェクトとして理解されます。グローバル環境で定義された変数と関数は、この変数オブジェクトの属性です:
コピー コード コードは次のとおりです:

var vo = 'a';
vo2 = 'b';
function fn(){
return 'fn ';
}
console.info(this === window);//true
console.info(this.vo);//a
console.info(this.vo2); //b
console .info(this.fn());//fn

カスタム関数でグローバル オブジェクトを参照したい場合は、window を直接使用することもできますが、より良い方法は、パラメータが関数に渡されるときにグローバル オブジェクトを使用することです。これは、JS ライブラリで非常に一般的な方法です:
コードをコピー コードは次のとおりです:

(function(global){
console.info(global === window);//内部的に window の代わりに global を使用できます
})(this);

このメソッドは互換性が優れています (ECMAScript 実装のグローバル オブジェクトがすべてウィンドウであるとは限りません)。

(2) 関数内部属性 this

関数環境では、関数に対応するアクティブオブジェクトの属性として理解できる内部属性オブジェクトとその値この内部属性は動的です。この値はどのように動的に決定されるのでしょうか?

•newを使って関数を呼び出す場合、その関数はコンストラクタとも呼ばれます。このとき、関数内のthisは新しく作成されたオブジェクトとして指定されます。
コードをコピー コードは次のとおりです:

function fn(){
var name = ' oulinhai';//関数に対応するアクティブ オブジェクトの属性
this.name = 'linjisong';//new を使用して関数を呼び出す場合、新しく作成されたオブジェクトとして this を指定します。つまり、属性を追加します。新しく作成されたオブジェクトに
}
var person = new fn();
console.info(person.name);//linjisong

var arr = [fn]; >console.info(arr[ 0]());//未定義

関数の実行環境で定義されているプロパティ (つまり、アクティブなオブジェクトのプロパティ) を区別することに注意する必要があります) とこのオブジェクトのプロパティ。配列要素を使用して関数を呼び出す場合、関数内の this は配列自体を指すため、上記の例では最終的に unknown が出力されます。

•一般関数として呼び出された場合、グローバルオブジェクトを指します。
•オブジェクトのメソッドとして呼び出された場合、このメソッドを呼び出すオブジェクトを指します。
次の例を見てください:

コードをコピー コードは次のとおりです:
var name = ' oulinhai';
var person = {
name:'linjisong',
getName:function(){
return this.name;
}; 🎜>console.info(person.getName());//linjisong
var getName = person.getName;
console.info(getName());//oulinhai


関数オブジェクト自体は匿名であり、オブジェクト属性として呼び出された場合、この関数が別の関数に割り当てられて呼び出されるとき、このオブジェクトは として呼び出されます。一般的な関数。これはグローバル オブジェクトを指します。この例は、「関数がオブジェクトのメソッドとして呼び出される場合、内部属性 this は呼び出し側オブジェクトを指し、関数が一般関数として呼び出される場合、内部属性 this はグローバル オブジェクトを指す」ことを完全に示しています。また、 this の指定は動的であり、関数が個別に定義されているかオブジェクト メソッドとして定義されているかに関係なく、呼び出し時に指定されることも示しています。これは、関数がオブジェクトのメソッドとして呼び出された場合、this は呼び出し元のオブジェクトを指すため、関数内で this が返された場合にのみ、呼び出し元オブジェクトの次のメソッドを続行できるためです。つまり、連鎖操作 (a jQueryの大きな特徴)。

•apply()、call()、またはbind()を使用して関数を呼び出す場合、これは最初のパラメータオブジェクトを指します。パラメーターが渡されない場合、または null または未定義が渡された場合、これはグローバル オブジェクトを指します (ES5 厳密モードでは null に設定されます)。渡された最初のパラメータが単純型の場合、これは対応する単純型ラッパー オブジェクトに設定されます。
コードをコピーします コードは次のとおりです。

var name = 'linjisong'; >function fn (){
return this.name;
}
var person = {
name:'oulinhai',
getName:fn
}; = {name :'hujinxing'};
var person3 = {name:'huanglanxue'};
console.info(fn());//linjisong、一般的な関数呼び出し、これが指す内部属性グローバル オブジェクトなので、this.name は linjisong
console.info(person.getName()); //oulinhai、オブジェクト メソッドとして呼び出され、this はこのオブジェクトを指すので、ここでは person.name
console を返します。 info(fn.apply(person2) );//hujinxing、apply、call、bind を使用して関数を呼び出し、渡された最初のパラメーター オブジェクトを実行するため、 person2.name
console.info(fn.call(person2) );//hujinxing
var newFn = fn.bind(person3);//ES5 の新しいメソッドは、新しい関数インスタンスを作成し、それを返します。内部の this 値は、渡されたパラメーター オブジェクト
コンソールとして指定されます。 .info(newFn()); //huanglanxue


上記の例はすべて一般的な状況であり、最初のパラメーターが null または未定義である場合はリストされていません。 。この値の決定に関しては、元の本に別の例があります:


var name = 'The Window';
var object = {
name : 'My Object',
getName:function(){
return this.name; 🎜>},
getNameFunc:function(){
return function(){
return this.name;
}
}; info(object.getName ());//私のオブジェクト
console.info((object.getName)());//私のオブジェクト
console.info((object.getName = object.getName)( ));// ウィンドウ
console.info(object.getNameFunc()());// ウィンドウ


第1个是正常输出,第2个(object.getName)与object.getName的效果是相同的,而第3个(object.getName=object.getName)最终返回的是函数对象本身,也就是说第3个会作为一般函数来调用,第4个则先是调用getNameFunc这个方法,返回一个函数,然后再调用这个函数,也是作为一般函数来调用。

8、函数属性和方法

  函数是一个对象,因此也可以有自己的属性和方法。不过函数属性和方法与函数内部属性很容易混淆,既然容易混淆,就把它们放一起对照着看,就好比一对双胞胎,不对照着看,不熟悉的人是区分不了的。

  先从概念上来区分一下:

(1)函数内部属性:可以理解为函数相应的活动对象的属性,是只能从函数体内部访问的属性,函数每一次被调用,都会被重新指定,具有动态性。

(2)函数属性和方法:这是函数作为对象所具有的特性,只要函数一定义,函数对象就被创建,相应的属性和方法就可以访问,并且除非你在代码中明确赋为另一个值,否则它们的值不会改变,因而具有静态性。有一个例外属性caller,表示调用当前函数的函数,也是在函数被调用时动态指定,在《JavaScript高级程序设计(第3版)》中也因此将caller属性和函数内部属性arguments、this一起讲解,事实上,在ES5的严格模式下,不能对具有动态特性的函数属性caller赋值。

  光从概念上区分是非常抽象的,也不是那么容易理解,再把这些属性列在一起比较一下(没有列入一些非标准的属性,如name):

类别 名称 继承性 说明 备注
函数内部属性 this - 函数据以执行的环境对象 和一般面向对象语言有很大区别
arguments -

表示函数实际参数的类数组对象

arguments本身也有自己的属性:length、callee和caller

1、length属性表示实际接收到的参数个数

2、callee属性指向函数对象本身,即有:

  fn.arguments.callee === fn

3、caller属性主要和函数的caller相区分,值永远都是undefined

函数属性 caller 调用当前函数的函数 虽然函数一定义就可访问,但是不在函数体内访问时永远为null,在函数体内访问时返回调用当前函数的函数,在全局作用域中调用函数也会返回null
length 函数形式参数的长度 就是定义函数时命名的参数个数
prototype 函数原型对象 原型对象是ECMAScript实现继承的基础
constructor 继承自Object,表示创建函数实例的函数,也就是Function() 值永远是Function,也就是内置的函数Function()
函数方法 apply 调用函数自身,以(类)数组方式接受参数

这三个方法主要作用是动态绑定函数内部属性this

1、apply和call在绑定之后会马上执行

2、bind在绑定之后可以在需要的时候再调用执行

call 调用函数自身,以列举方式接受参数
bind 绑定函数作用域,ES5中新增
toLocalString 覆盖

覆盖了Object类型中的方法,返回函数体

不同浏览器实现返回可能不同,可能返回原始代码,也可能返回去掉注释后的代码

toString 覆盖
valueOf 覆盖
hasOwnProperty 直接继承自Object类型的方法,用法同Object
propertyIsEnumerable
isPropertyOf

関数のプロパティとメソッドには、Object から継承されたものに加えて、関数自体に固有のプロパティとメソッドも含まれます。最も一般的に使用されるメソッドは、当然、前のセクションで説明した apply() と call() です。メソッドは、関数の内部属性を設定して関数スコープを拡張するために使用されます。ただし、apply() が関数スコープを拡張する場合、call() は関数のパラメーターを (クラス) 配列の形式で受け入れます。関数のスコープを拡張するには、関数のパラメーターを 1 つずつリストして渡す必要があります。次の例を参照してください:

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

function sum(){
var total = 0,
l = argument.length;

for(; l; l- -){
合計 = 引数[l -1];
}
合計を返します

console.info(sum.apply(null,[1,2, 3,4]));//10
console.info(sum.call(null,1,2,3,4));//10

ただし、次のようにする必要があります。 apply と call の主な役割は関数 Scope を拡張することであることを強調してください。 apply と call はスコープを拡張するときにすぐに関数を呼び出すため、アプリケーションに大きな制限がかかります。そのため、この関数は ES5 ではスコープを拡張するためにも使用されますが、この関数は拡張する必要はありません。 Function。関数インスタンスを返し、渡された最初のパラメータを元の関数のスコープとして受け取ります。可能な実装は次のとおりです:

コードをコピー コードは次のとおりです:
function binding(scope ){
var that = this;
return function(){
that.apply(scope, argument);
Function.prototype.bind; = binding;


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