ホームページ >ウェブフロントエンド >jsチュートリアル >me_javascript スキルから JavaScript 関数と関数式を学びます

me_javascript スキルから JavaScript 関数と関数式を学びます

WBOY
WBOYオリジナル
2016-05-16 15:32:20984ブラウズ

1. 関数宣言と関数式

ECMAScript では、関数を作成する最も一般的な 2 つの方法は、関数式と関数宣言です。ECMA 仕様では、関数宣言には識別子 (識別子) が必要であるという 1 つの点だけが明確にされているため、この 2 つの違いは少しわかりにくいです。 ) (これは、誰もがよく関数名と呼ぶものです)、この識別子は関数式では省略できます:

関数宣言: 関数 関数名 (パラメータ: オプション){関数本体}

関数式: 関数 関数名 (オプション) (パラメータ: オプション) { 関数本体 }

つまり、関数名が宣言されていない場合は式であることがわかりますが、関数名が宣言されている場合、それが関数宣言であるか関数式であるかをどのように判断するのでしょうか。 ECMAScript はコンテキストによって区別されます。関数 foo(){} が代入式の一部である場合、関数 foo(){} が関数本体内に含まれている場合、またはプログラムの先頭にある場合、それは関数式です。関数宣言。

function foo(){} // 声明,因为它是程序的一部分
var bar = function foo(){}; // 表达式,因为它是赋值表达式的一部分

new function bar(){}; // 表达式,因为它是new表达式

(function(){
 function bar(){} // 声明,因为它是函数体的一部分
})();

式と宣言の間には非常に微妙な違いがあります。まず、関数の宣言は、コードの最後の行にある場合でも、式が解析および評価される前に解析および評価されます。同じスコープ内の式は前に解析/評価されます。次の例を参照してください。関数 fn はアラートの後に宣言されますが、アラートの実行時には fn がすでに定義されています。

alert(fn());

function fn() {
 return 'Hello world!';
}

さらに、関数宣言は条件文内で使用できますが、標準化されていないため、環境によって実行結果が異なる可能性があります。式: 条件文にはブロックレベルのスコープの概念がないため、関数式を使用するのが最善です。

// 千万别这样做!
// 因为有的浏览器会返回first的这个function,而有的浏览器返回的却是第二个

if (true) {
 function foo() {
 return 'first';
 }
}
else {
 function foo() {
 return 'second';
 }
}
foo();

// 相反,这样情况,我们要用函数表达式
var foo;
if (true) {
 foo = function() {
 return 'first';
 };
}
else {
 foo = function() {
 return 'second';
 };
}
foo();

関数宣言の実際のルールは次のとおりです:

関数宣言はプログラムまたは関数本体内でのみ使用できます。構文的には、ブロック ({ … }) 内、たとえば if、while、または for ステートメント内にこれらを使用することはできません。 Block には Statement ステートメントのみを含めることができ、関数宣言などのソース要素は含めることができないためです。一方、ルールを詳しく見てみると、ブロック内で式を使用できる唯一の方法は、それが式ステートメントの一部である場合であることがわかります。ただし、仕様では、式ステートメントをキーワード関数で始めることはできないと明確に述べられています。これが実際に意味するのは、関数式を Statement ステートメントまたはブロック内に使用できないということです (ブロックは Statement ステートメントで構成されているため)。

2. 名前付き関数式

名前付き関数式の場合は、もちろん名前が必要です。前の例 var bar = function foo(){}; は、名前付き関数式としては有効ですが、覚えておくべきことが 1 つあります。仕様では、周囲のスコープ内では識別子を有効にできないと規定されているため、新しく定義された関数のスコープ内でのみ有効です:

var f = function foo(){
 return typeof foo; // function --->foo是在内部作用域内有效
};
// foo在外部用于是不可见的
typeof foo; // "undefined"
f(); // "function"

これは必須なので、名前付き関数式はどのように使用できるのでしょうか?なぜ名前なのでしょうか?

最初に述べたように、名前を付けるとデバッグ プロセスがより便利になります。デバッグするときに、コール スタック内の各項目にそれを説明するための独自の名前があれば、デバッグ プロセスがより効果的になるからです。 、感覚が違います。

ヒント:ここに小さな質問があります。ES3 では、名前付き関数式のスコープ オブジェクトも Object.prototype のプロパティを継承します。これは、関数式に名前を付けるだけで、Object.prototype のすべてのプロパティもスコープに取り込まれることを意味します。結果は驚くべきものになるかもしれません。

var constructor = function(){return null;}
var f = function f(){
 return construcor();
}
f(); //{in ES3 环境}

このプログラムは null を生成するように見えますが、実際には新しいオブジェクトを生成します。名前付き関数式はそのスコープ内で Object.prototype.constructor (つまり、Object のコンストラクター) を継承するためです。 with ステートメントと同様に、このスコープは Object.prototype への動的な変更の影響を受けます。幸いなことに、ES5 ではこのバグが修正されています。

この動作に対する合理的な解決策は、関数式と同じ名前のローカル変数を作成し、それに null の値を割り当てることです。関数式宣言を誤ってホイストしない環境でも、var を使用して変数を再宣言すると、変数 g がバインドされたままになります。変数 g を null に設定すると、重複した関数をガベージ コレクションできるようになります。

3、调试器(调用栈)中的命名函数表达式

刚才说了,命名函数表达式的真正用处是调试,那到底怎么用呢?如果一个函数有名字,那调试器在调试的时候会将它的名字显示在调用的栈上。有些调试器(Firebug)有时候还会为你们函数取名并显示,让他们和那些应用该函数的便利具有相同的角色,可是通常情况下,这些调试器只安装简单的规则来取名,所以说没有太大价值,我们来看一个例子:不用命名函数表达式

function foo(){
 return bar();
}
function bar(){
 return baz();
}
function baz(){
 debugger;
}
foo();

// 这里我们使用了3个带名字的函数声明
// 所以当调试器走到debugger语句的时候,Firebug的调用栈上看起来非常清晰明了 
// 因为很明白地显示了名称
baz
bar
foo
expr_test.html()

通过查看调用栈的信息,我们可以很明了地知道foo调用了bar, bar又调用了baz(而foo本身有在expr_test.html文档的全局作用域内被调用),不过,还有一个比较爽地方,就是刚才说的Firebug为匿名表达式取名的功能:

function foo(){
 return bar();
}
var bar = function(){
 return baz();
}
function baz(){
 debugger;
}
foo();

// Call stack
baz
bar() //看到了么? 
foo
expr_test.html()

然后,当函数表达式稍微复杂一些的时候,调试器就不那么聪明了,我们只能在调用栈中看到问号:

function foo(){
 return bar();
}
var bar = (function(){
 if (window.addEventListener) {
 return function(){
  return baz();
 };
 }
 else if (window.attachEvent) {
 return function() {
  return baz();
 };
 }
})();
function baz(){
 debugger;
}
foo();

// Call stack
baz
(?)() // 这里可是问号哦,显示为匿名函数(anonymous function)
foo
expr_test.html()

另外,当把函数赋值给多个变量的时候,也会出现令人郁闷的问题:

function foo(){
 return baz();
}
var bar = function(){
 debugger;
};
var baz = bar;
bar = function() { 
 alert('spoofed');
};
foo();

// Call stack:
bar()
foo
expr_test.html()

这时候,调用栈显示的是foo调用了bar,但实际上并非如此,之所以有这种问题,是因为baz和另外一个包含alert(‘spoofed')的函数做了引用交换所导致的。

归根结底,只有给函数表达式取个名字,才是最委托的办法,也就是使用命名函数表达式。我们来使用带名字的表达式来重写上面的例子(注意立即调用的表达式块里返回的2个函数的名字都是bar):

function foo(){
 return bar();
}
var bar = (function(){
 if (window.addEventListener) {
 return function bar(){
  return baz();
 };
 }
 else if (window.attachEvent) {
 return function bar() {
  return baz();
 };
 }
})();
function baz(){
 debugger;
}
foo();

// 又再次看到了清晰的调用栈信息了耶!
baz
bar
foo
expr_test.html()

好的,整个文章结束,大家对javascript的认识又近了一步,希望大家越来越喜欢小编为大家整理的文章,继续关注跟我学习javascript的一系列文章。

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