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

JavaScript 応用プログラミング(第 3 版)学習ノート 7 js 関数(前編)_基礎知識

WBOY
WBOYオリジナル
2016-05-16 17:49:10880ブラウズ
変数の型

関数について話す前に、まず変数の型について話しましょう。

1. 変数: 変数は基本的に名前付きのメモリ空間です。

2. 変数のデータ型: 数値型、ブール型、オブジェクト型など、変数が格納できる値のデータ型を指します。ECMAScript では、変数は動的であり、変数のデータ型を変更するときに実行時に実行できます。

3. 変数の型: 変数自体の型を指します。ECMAScript では、変数の型は値型と参照型の 2 つだけです。変数のデータ型が単純データ型の場合、変数の型は値型になります。変数のデータ型がオブジェクト型の場合、変数の型は参照型になります。曖昧さを生じさせずに、変数のデータ型を変数型と呼ぶこともできます。

では、値型と参照型の違いは何でしょうか?最も重要なことは、変数の型が値型の場合、変数は変数の値そのものを格納し、変数の型が参照型の場合、変数は変数の値ではなく、変数の値へのポインタのみを格納することです。リファレンスへのアクセス 型の変数値を取得する場合、まずポインタを取得し、そのポインタを基に変数値を取得します。参照型の変数値を別の変数に割り当てると、最終的には両方の変数が同時に同じ変数値を指すことになります:
コードをコピー コードは次のとおりです:

var a = {
name:'linjisong',
age:29
};
var b = a;// 参照型変数 a を変数 b に代入します。 a と b は両方とも、a が指し始めたオブジェクトを指します。 /b が指すオブジェクト、つまり a が指すオブジェクトを変更します。
console.info(a.name) //oulinhai
b = {//変数を再割り当てします。しかし、b が指すオブジェクトは変更されていません。つまり、a が指定したオブジェクトは変更されていません。
name:'hujinxing',
age:23
};
console.info(a.name) );//oulinhai

わかりました、まず変数の型について話しましょう。データ構造をメモリに保存し続けると、沈んで浮くことができなくなるのではないかと心配です。

関数

オブジェクトが部屋の場合、関数は魔法の効果のある部屋です。関数はまずオブジェクトであり、この関数オブジェクトにも多くの魔法の関数があります...


1. 関数
(1) 関数はオブジェクト

関数 これもオブジェクトであり、関数オブジェクト インスタンスの作成に使用される関数は組み込みの Function() 関数です (オブジェクト インスタンスの作成には関数が必要で、関数はオブジェクト インスタンスです。鶏が先か卵が先か決めるのですか?混乱しないでください、鶏が卵を産むことができ、卵が鶏を孵化させることができる限り、それは哲学者に任せましょう)しかし、オブジェクトのような機能は大きく異なります。通常のオブジェクトから 関数オブジェクト インスタンスで typeof を使用すると、オブジェクトの代わりに関数が返されます。

(2) 関数名は関数オブジェクトを指す参照型変数です

function fn(p){
console.info(p);
}
console.info(fn);//fn(p), fn を一般変数として使用できます。
var b = fn;
b('function');//関数にアクセスするには、b が指すオブジェクトを示す関数呼び出しを使用できます (つまり、fn) が指すオブジェクトは関数です

注: 関数名に関して、ES5 の strict モードでは、当然ながら、これら 2 つの eval と引数の使用は許可されなくなりました。パラメータ名には使用できません (プロのハッカーでない限り、これらを識別子としても使用しないでしょう)。


2. 関数の作成
(1) オブジェクトとしては、new を使用してコンストラクター Function() を呼び出します。任意の数のパラメーターを受け入れることができます。最後のパラメーターは関数の本体として使用され、前のすべての仮パラメーターはカンマで区切られた 1 つのパラメーターとして渡すこともできます。一般的な形式は次のとおりです:

コードをコピー コードは次のとおりです:
var fn = new Function (p1, p2, ..., pn, body);
//または
var fn = Function(p1, p2, ..., pn, body); var fn = new Function("p1, p2, ..., pn", q1, q2, ..., qn, body);
//または
var fn = Function("p1, p2, ..., pn", q1, q2, ..., qn, body);


例:


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

var add = new Function('a','b','return a b;');
console.info(add(2,1));//3
varsubtract = Function('a','b','return a - b;');
console.info(subtract(2,1));//1
var sum = new Function('a, b','c','return a b c;');
console.info(sum(1,2,3));//6

この方法で関数を作成すると、コードを 2 回 (通常の解析で 1 回、関数本体で 1 回) 解析すると効率に影響しますが、関数本体を動的にコンパイルする必要がある状況にはより適しています。

(2) 関数オブジェクト自体の特殊な性質により、キーワード function を使用して関数を作成することもできます。
コードをコピー コードは次のとおりです。

function add(a, b){
return a b;
}
console.info(add( 2,1));/3
varsubtract = function(a, b){
return a - b;
console.info(subtract(2,1)); //1

上記からわかるように、function キーワードを使用して関数を作成するには、関数宣言と関数式の 2 つの方法があります。どちらの方法でも目的の効果を実現できますが、両者の違いは何でしょうか?これについては次にお話します。


3. 関数宣言と関数式
(1) ECMA-262 仕様では、次のように区別されます。

コードをコピー コードは次のとおりです: 関数宣言: 関数識別子 (パラメーター リスト (オプション)){関数本体}
関数式:関数識別子(オプション)(パラメータリスト(オプション)) {関数本体}


関数式の識別子(関数名)がオプションであること以外は違いはありませんしかし、そこから、関数名のないものはすべて関数式でなければならないこともわかります。もちろん、関数名がある場合は文脈から判断するしかありません。
(2) コンテキストと区別する。これは簡単に言うと、式の出現のみを許可するコンテキストは関数式でなければならず、ステートメントの出現のみを許可するコンテキストは関数宣言でなければなりません。いくつかの例を示します。



コードをコピー コードは次のとおりです。 function fn(){ };/ /関数宣言
//function fn(){}(); // 例外、関数宣言を直接呼び出すことはできません
var fn = function fn(){};//関数式
(function fn (){});//関数式、グループ化演算子
内の関数 fn(){console.info(1);}();//1、関数式は演算子の後に表示されます。ここでは、new
new function fn(){console.info(2);}();//2、関数式などの他の演算子を new 演算子の後に使用することもできます。
(function(){
function fn(){};//関数宣言
});


(3) 違い: なぜこれほどの労力を費やすのか関数宣言と関数式はどうなるでしょうか?当然のことながら、それらの違いによるものです。宣言のプロモーションに関しては、グローバル スコープでの宣言のプロモーションについて説明しました。
A. エンジンは解析するときに、まず関数宣言を解析し、次に変数宣言を解析し (解析中に型は上書きされません)、最後にコードを実行します。関数宣言を解析する場合、型 (関数) も同時に解析されますが、変数宣言を解析する場合は変数のみが解析され、初期化されません。

デモ (思い出してください) の例もいくつかありますが、同じ名前の宣言例はありませんので、ここに追加します:




コピーcode
コードは次のとおりです: console.info(typeof fn);//関数、宣言プロモーション、関数の対象 var fn = ' '; function fn(){
}
console.info(typeof fn);//文字列、コードが実行されたため、ここでの fn の型は string
try{
fn(); //すでに文字列型であるため、呼び出すことができません。型例外がスローされます。
}catch(e){
console.info(e);//TypeError
}
fn = function(){ console.info('fn');};// fn を呼び出したい場合は、関数式を使用して fn に割り当てることしかできません
fn();//fn 、

console.info (typeof gn);//function
function gn(){
}
var gn = ''; gn);//文字列


変数宣言が前であっても後であっても、宣言が昇格されると関数宣言が優先されることがわかりますが、宣言が昇格された後は変数の初期化を行う必要があるため、関数宣言が優先されます。は初期化されなくなりました(プロモーション中に関数型が解析された)ので、後で出力するときは String 型になります。

上記の 3 行目で関数が定義され、7 行目ですぐに呼び出されていますが、機能しません。グローバル名前空間をクリーンに保つことの重要性を理解する必要があります。そうしないと、「コード内で関数を明確に定義したのに、それを呼び出すことができない」というような問題が発生する可能性があります。もちろん、そうすることで他の人のコードを壊す危険があります。

別の質問があります。変数の型が変数宣言のプロモート時ではなく初期化中に変更されたことをどのように判断するのでしょうか。以下のコードを見てください:
コードをコピーします コードは次のとおりです:

console。 info(typeof fn) ;//function
function fn(){
}
var fn;
console.info(typeof fn);//function

ご覧のとおり、ステートメント「プロモートされた型は関数」ですが、初期化コードがないため、最終的な型は変更されていません。

関数宣言と関数式に関して、次のコードを見てください。
コードをコピーします コードは次のとおりです:

if(true){
function fn(){
return 1;
}
}else{
function fn() {
return 2;
}
}
console.info(fn());// Firefox で出力 1、Opera で出力 2、Opera でプロモーションを宣言、その後のステートメント同じレベルの以前の宣言を上書きします

if(true){
return 1;
gn = function() {
return 2;
}
console.info(gn());// 1、すべてのブラウザ出力は 1


ECMAScript 仕様では、名前付き関数式の識別子は内部スコープに属し、関数宣言の識別子は定義スコープに属します。



コードをコピー
コードは次のとおりです。 var sum = function fn(){ var total = 0,
l = argument.length;
for(; l; l--)
{
total = argument[l-1]} console.info(typeof fn);
return total;
console.info(sum(1,2,3,4));//function, 10
console.info( fn(1 ,2,3,4));//ReferenceError


上記は、FireFox で名前付き関数式を実行した結果です。この名前は関数スコープ内でアクセスできます。グローバルスコープ アクセス中に参照例外が発生しました。ただし、IE9 より前の IE ブラウザでは、名前付き関数式は関数宣言と関数式の両方として解析され、幸いなことに IE9 ではこれが修正されています。

グローバル スコープに加えて、関数スコープもあり、関数のパラメーターも宣言促進競争に参加します。まず明確にしておきたいのは、関数の定義時には関数のスコープは存在せず、関数が実際に呼び出されるときにのみ存在するということです。



コードをコピー


コードは次のとおりです:console.info(inner);// 内部
console.info(other);// その他
}
fn('param'); 🎜>// パラメータと内部関数、内部関数が優先されます。
function gn(inner){
console.info(inner);// inner() function
console.info(inner()); // 未定義
function inner(){
return other;
var other = 'other';// inner() 関数
console.info(inner ());// その他
}
gn('param');
上記の出力により、内部関数宣言 > 関数パラメータ > 内部変数宣言の優先順位がわかります。

ここでのプロセスの 1 つは次のとおりです。まず、内部関数宣言がプロモートされ、関数名の型が関数の型に設定されます。次に、関数パラメーターが解析され、実際のパラメーター値が渡されます。 in は仮パラメータに割り当てられ、最後に内部変数宣言のプロモーションは、初期化なしで宣言のみをプロモートします。重複する名前がある場合、同じ優先順位を持つものは前のものを上書きし、異なる優先順位を持つものは上書きされません。優先度の高いものは解析され、優先度の低いものは解析されなくなります)。
説明すると、これは出力結果に基づく私の推論にすぎません。バックグラウンド実装に関しては、手順がまったく逆である可能性もあり、各手順が前の手順の結果をカバーするか、最初の手順から開始することもあります。もちろん、効率の観点から、このプロセスのほうが良いと考えられます。さらに、グローバル スコープは実際には、関数パラメーターのない関数スコープの簡略化されたバージョンです。

ここでは包括的な例は示しません。この記事を以前の基本的な文法の記事と合わせて読むことをお勧めします。より良い結果が得られる可能性があります。優先順位と対象範囲については、以下で説明する問題にもつながります。

4. 関数のオーバーロード

関数はオブジェクトであり、関数名は関数オブジェクトを指す参照型変数であるため、これは不可能です。一般的なオブジェクト指向言語と同様にオーバーロードを実装します。
コードをコピーします コードは次のとおりです:

関数 fn(a){
return a;
}
function fn(a,b){
return a b;
}

コンソール。 info(fn(1)); // NaN
console.info(fn(1,2));// 3

なぜ 8 行目が NaN を出力するのか不思議ではありません。関数名は単なる変数であり、2 つの関数宣言は順番に解析されます。この変数が最終的に指す関数は 2 番目の関数であり、関数内では b が 1 つのパラメーターのみを渡します。を加算し、1 を加算すると、結果は NaN になります。関数式に置き換えるとわかりやすいかもしれません。当然、後の代入によって前の代入が上書きされます:
Copy code コードは次のとおりです。

var fn = function (a){ return a; }
fn = function (a,b){ return a b;

それでは、ECMAScript でオーバーロードを実装するにはどうすればよいでしょうか?単純なデータ型ラッパー オブジェクト (Boolean、Number、String) は、オブジェクトを作成するためのコンストラクターとして、またデータ型を変換するための変換関数として使用できることを思い出してください。これは典型的なオーバーロードです。実際、このオーバーロードについては、前の記事で説明しました。

(1) 関数に従ったオーバーロード このメソッドの一般的な形式は、
コードをコピー コードは次のとおりです:
function fn(){
if(this instanceof fn)
{
/ /関数 1
}else
{
// 関数 2
}
}

この方法は実行可能ですが、その効果は明ら​​かに限定されています。オーバーロードできるのは 2 回だけであり、オーバーロードできるのはコンストラクターのみです。もちろん、apply() や call()、あるいは ES5 の新しい binding() を組み合わせて、関数内で this 値を動的にバインドしてオーバーロードを拡張することもできますが、これはすでに関数の内部プロパティに基づいたオーバーロードを意味します。
(2) オーバーロード

コードをコピー コードは次のとおりです。
function fn (){
var length = argument.length;
if(0 == length)//比較演算子が If として記述されているため、リテラルを左側に配置するのは Java からの習慣です。演算子 (0=length) が欠落していると、コンパイラによってエラーが表示されます。このメソッドに慣れていない場合はご容赦ください。
{
return 0;
}else if(1 == length)
{
return argument[0]; else{
return (arguments[0]) (arguments[1]);
}
}

console.info(fn());//0
コンソール。 info(fn(1));//1
console.info(fn(true));//1
console.info(fn(1,2));//3
コンソール。 info(fn('1','2'));//3


ここでは、関数の内部属性引数を使用してオーバーロードを実装します。もちろん、内部的にオーバーロードする方法は数多くあり、typeof や instanceof などの演算子を組み合わせて必要な機能を実現することもできます。内部プロパティ引数とは正確には何ですか?それが以下でお話しすることです。

5. 関数の内部属性の引数

簡単に言うと、関数の内部属性は関数本体内でのみアクセスできる属性です。関数が呼び出されたときにアクセスされる 関数が呼び出されたときに実行されるため、関数の内部プロパティは関数が呼び出されたときにのみ解析されます。各呼び出しには対応する解析があり、動的特性があります。このような属性には、this と引数が含まれます。まず引数を見て、それについては次の記事で説明します。

(1) 関数定義中の引数リストを仮引数、関数呼び出し時に実際に渡される引数を実引数と呼びます。一般に、C 系言語では、関数を呼び出すときに実パラメータが仮パラメータと一致している必要があります。ただし、ECMAScript では、定義時に 2 つの仮パラメータを指定して渡すことができます。 2 つの実パラメータを入力しますが、3 つの実パラメータを渡すことも、1 つの実パラメータのみを渡すことも、パラメータを渡さないこともできます。この機能は、関数の内部プロパティを使用してオーバーロードを実装するための基礎となります。

(2) 仮パラメータは同じ名前を持つこともできますが、実際に渡されると、後の値が仮パラメータの値として使用されます (この場合、引数を使用して、前の実際のパラメータ):
コードをコピー コードは次のとおりです。

function gn(a ,a){
コンソール .info(a);
コンソール.info(引数[1]);
gn(1) ,2);//2, 1,2
gn(1);//未定義, 1, 未定義


これは、実際には、この記事の前半のステートメントのプロモーションに関する結論によって説明できます。 : 同じ優先順位を持つ後のものは前のものを上書きし、関数のパラメータが解析されるときに値も同時に解析されます。もちろんこのままではセキュリティ上非常に問題があるため、ES5のstrictモードでは重複した名前の仮パラメータは禁止されています。

(3) 実パラメータの値は仮パラメータで受け入れられますが、実パラメータと仮パラメータが一致しない場合はどうなりますか?答えは、引数を使用して格納することです。実際、実パラメータと仮パラメータが一致している場合でも、引数オブジェクトは依然として存在し、実パラメータを受け入れた仮パラメータと同期されます。この文を改良して理解してみましょう。

•arguments は配列のようなオブジェクトであり、arguments 要素には、arguments[0]、arguments[1] などの配列要素と同様に角括弧とインデックスを介してアクセスできます。
•arguments は、Object から継承されたプロパティとメソッド (一部のメソッドはオーバーライドされます) に加えて、長さ、呼び出し先、呼び出し元などの独自のプロパティも持ちます。実際のパラメータの数 (仮パラメータの数? つまり関数属性の長さ)、callee は現在の関数オブジェクトを表し、caller は関数属性の呼び出し元と区別するためにのみ定義されており、その値は未定義です。
•arguments は配列のようなオブジェクトですが、実際の配列オブジェクトではありません。引数に対して配列オブジェクトのメソッドを直接呼び出すには、まず Array.prototype.slice.call を使用します。 (引数) を使用してオブジェクトに変換します。
•arguments には、関数の呼び出し時に渡される実際のパラメータが格納されます。0 番目の要素は最初の実パラメータを格納し、1 番目の要素は 2 番目の実パラメータを格納します。
•引数は実際のパラメータ値を保存し、仮パラメータも実際のパラメータ値を保存します。一方が変更されると、他方もそれに応じて変更されます。
•引数と仮パラメータ間の同期は、仮パラメータが実際に実パラメータを受け取る場合にのみ存在します。実パラメータを受け取らない仮パラメータの場合、そのような同期関係はありません。
引数オブジェクトは非常に強力ですが、パフォーマンスがある程度低下するため、必要がない場合は、仮パラメータを優先して使用しないことをお勧めします。


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

fn(0,-1);
function fn(para1,para2,para3,para4){
console.info(fn.length);//4、仮パラメータの数
console.info(arguments.length);//2、パラメータの実際の数
console.info(arguments.callee === fn);//true、呼び出し先オブジェクトは fn 自体を指します
console.info (arguments.caller);//未定義
console.info(arguments.constructor);//Array() ではなく Object()
try{
arguments.sort();/ /Array-like 結局配列ではないので、配列メソッドを直接呼び出すことはできません
}catch(e){
console.info(e);//TypeError
}
var arr = Array.prototype.slice .call(arguments);//まず配列に変換します
console.info(arr.sort());//[-1,0]、ソート済み

console.info( para1);//0
arguments[0] = 1;
console.info(para1);//1、arguments[0] を変更すると、同時に形式も変更されますパラメータ para1

console.info(arguments[1]);//-1
para2 = 2;//2、仮パラメータを変更しますpara2 は同時に argument[1]

console.info(para3);//未定義、実際のパラメータが渡されない仮パラメータは未定義です
arguments[2] = 3;
console。 info(arguments[2]);// 3
console.info(para3);//実パラメータを受け入れない未定義の仮パラメータには同期関係がありません

console.info(arguments[3] ]);//未定義、実際のパラメータは渡されず、値は未定義です
para4 = 4;
console.info(para4);//4
console.info(arguments[3]) ;//未定義。これは渡される実際のパラメータであり、同期されません。
}

テスト後、引数と仮パラメータ間の同期は双方向ですが、「」の 66 ページJavaScript Advanced Programming (3rd Edition) には、これは一方向であると記載されており、仮パラメータを変更しても引数は変更されません。これは、元の本の別のバグである可能性があります。あるいは、FireFox が仕様を拡張した可能性があります。ただし、これは、たとえ古典的であっても、依然としてバグの可能性があり、すべてが実際の運用に従う必要があることを示しています。

• 引数とその属性の呼び出し先と組み合わせると、関数内でそれ自体を呼び出すときに関数名から分離できるため、関数が別の変数に割り当てられている場合でも、関数名 (忘れないでください) 、変数でもあります)さらに、値を割り当てることで、正しい動作を保証することもできます。典型的な例には、階乗関数、フィボナッチ数列などの検索が含まれます。

コードをコピー コードは次のとおりです:
//階乗を検索
関数階乗(num) {
if(num {
return 1;
}else{
return num *階乗(num - 1); >}
var fn = fastial;
factorial = null;
try{
fn(2);//factorial は関数内で再帰的に呼び出され、factorial には null の値が割り当てられているため、例外がスローされます
}catch(e){
console.info(e);//TypeError
}

//フィボナッチ数列
function fibonacci(num){
if(1 == num || 2 == num){
return 1;
}else{
return argument.callee(num - 1) argument.callee(num - 2);
}
}
var gn = fibonacci;
fibonacci = null;
console.info(gn(9));//34 は、arguments.callee を使用して関数オブジェクトを実現します。関数名 分離すると通常通り実行可能


再帰アルゴリズムは非常にシンプルですが、実行中のスタックを維持する必要があるため、効率はあまり良くありません。再帰的最適化に関しては、非常に充実したアルゴリズムも多数あるため、ここでは詳しく説明しません。
現時点では、arguments.callee は ES5 の厳密モードで禁止されていることに注意してください:



コードをコピー コードは次のとおりです: //フィボナッチ数列
var fibonacci = (function f(num){ return num <= 2 ? 1 : (f(num - 1) f(num - 2));
var gn = fibonacci = null; .info(gn(9));//34、名前付き関数式の使用により、関数オブジェクトと関数名の分離が実現され、通常どおりに実行できます

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