>웹 프론트엔드 >JS 튜토리얼 >me_javascript 스킬을 통해 자바스크립트 함수와 함수 표현을 배워보세요

me_javascript 스킬을 통해 자바스크립트 함수와 함수 표현을 배워보세요

WBOY
WBOY원래의
2016-05-16 15:32:20985검색

1. 함수 선언 및 함수 표현

ECMAScript에서 함수를 생성하는 가장 일반적인 두 가지 방법은 함수 표현식과 함수 선언입니다. ECMA 사양에서는 함수 선언에 식별자(식별자)가 있어야 한다는 한 가지 사항만 명확하기 때문에 이 둘의 차이점은 약간 혼란스럽습니다. )(모든 사람이 흔히 함수 이름이라고 부르는 이름)이며 이 식별자는 함수 표현식에서 생략될 수 있습니다.

함수 선언: 함수 함수 이름(매개변수: 선택){함수 본문}

함수 표현식: 함수 함수 이름(선택 사항) (매개 변수: 선택 사항) { 함수 본문 }

그렇다면 함수명이 선언되지 않았다면 반드시 표현식이어야 한다고 볼 수 있는데, 함수명이 선언되어 있다면 함수 선언인지 함수 표현식인지 어떻게 판단할 수 있을까요? ECMAScript는 문맥에 따라 구별됩니다. function foo(){}가 할당 표현식의 일부인 경우 이는 함수 표현식입니다. function 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 문 내)에 나타날 수 없습니다. 블록은 명령문만 포함할 수 있고 함수 선언과 같은 소스 요소는 포함할 수 없기 때문입니다. 반면, 규칙을 자세히 살펴보면 표현식이 블록에 나타날 수 있는 유일한 방법은 표현식 문의 일부인 경우라는 것을 알 수 있습니다. 그러나 사양에는 표현식 문이 키워드 function으로 시작할 수 없다고 명시되어 있습니다. 이것이 실제로 의미하는 바는 함수 표현식이 명령문이나 블록에 나타날 수 없다는 것입니다(블록은 명령문으로 구성되기 때문입니다).

2. 명명된 함수 표현식

물론 명명된 함수 표현식의 경우에는 이름이 있어야 합니다. 이전 예제 var bar = function foo(){}는 유효한 명명된 함수 표현식이지만 기억해야 할 것이 하나 있습니다. 사양에서는 식별자가 주변 범위 내에서 유효할 수 없다고 규정하고 있으므로 새로 정의된 함수의 범위 내에서만 유효합니다.

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로 설정하면 중복된 함수를 가비지 수집할 수 있습니다.

var f = function g(){
 return 17;
}
var 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으로 문의하세요.