[javascript] var functionName = function () {} vs function functionName () {}

최근에 다른 사람의 JavaScript 코드를 유지 관리하기 시작했습니다. 버그를 수정하고 기능을 추가하며 코드를 정리하고 일관성을 유지하려고합니다.

이전 개발자는 함수를 선언하는 두 가지 방법을 사용했으며 그 뒤에 이유가 있는지 알아낼 수 없습니다.

두 가지 방법은 다음과 같습니다.

var functionOne = function() {
    // Some code
};
function functionTwo() {
    // Some code
}

이 두 가지 방법을 사용하는 이유는 무엇이며 각각의 장단점은 무엇입니까? 다른 방법으로는 할 수없는 방법으로 할 수있는 것이 있습니까?



답변

차이점은 functionOne함수 표현식이므로 해당 줄에 도달했을 때만 정의되는 반면 functionTwo함수 선언이며 주변 함수 또는 스크립트가 실행 되 자마자 정의됩니다 ( 호이 스팅 으로 인해 ).

예를 들어 함수 표현식은 다음과 같습니다.

// TypeError: functionOne is not a function
functionOne();

var functionOne = function() {
  console.log("Hello!");
};

그리고 함수 선언 :

// Outputs: "Hello!"
functionTwo();

function functionTwo() {
  console.log("Hello!");
}

역사적으로 블록 내에 정의 된 함수 선언은 브라우저간에 일관되지 않게 처리되었습니다. 엄격한 모드 (ES5에서 도입)는 함수 선언을 둘러싸는 블록으로 범위를 지정하여이를 해결했습니다.

'use strict';    
{ // note this block!
  function functionThree() {
    console.log("Hello!");
  }
}
functionThree(); // ReferenceError


답변

먼저 Greg : function abc(){}도 범위 abc가 정해져 있습니다 . 이름 이이 정의가있는 범위에 정의되어 있습니다. 예:

function xyz(){
  function abc(){};
  // abc is defined here...
}
// ...but not here

둘째, 두 스타일을 결합 할 수 있습니다.

var xyz = function abc(){};

xyz가는 평소와 같이 정의 할 수있다, abc모든 브라우저하지만 Internet Explorer에서 정의되지 않는다 -이 정의되는에 의존하지 않습니다. 그러나 그것은 몸 안에 정의 될 것입니다 :

var xyz = function abc(){
  // xyz is visible here
  // abc is visible here
}
// xyz is visible here
// abc is undefined here

모든 브라우저에서 함수의 별명을 지정하려면 다음과 같은 선언을 사용하십시오.

function abc(){};
var xyz = abc;

이 경우 둘 다 xyzabc같은 객체의 별칭입니다.

console.log(xyz === abc); // prints "true"

결합 된 스타일을 사용해야하는 강력한 이유 중 하나는 Internet Explorer에서 지원하지 않는 함수 객체의 “name”속성입니다 . 기본적으로 다음과 같은 함수를 정의 할 때

function abc(){};
console.log(abc.name); // prints "abc"

이름이 자동으로 할당됩니다. 그러나 당신이 그것을 정의 할 때

var abc = function(){};
console.log(abc.name); // prints ""

이름이 비어 있습니다. 익명 함수를 만들어 변수에 할당했습니다.

결합 된 스타일을 사용하는 또 다른 좋은 이유는 짧은 내부 이름을 사용하여 자체를 참조하고 외부 사용자에게 긴 상충되지 않는 이름을 제공하는 것입니다.

// Assume really.long.external.scoped is {}
really.long.external.scoped.name = function shortcut(n){
  // Let it call itself recursively:
  shortcut(n - 1);
  // ...
  // Let it pass itself as a callback:
  someFunction(shortcut);
  // ...
}

위의 예에서 외부 이름으로 동일한 작업을 수행 할 수 있지만 너무 다루기 힘들고 느립니다.

(자체를 참조하는 다른 방법 arguments.callee은 여전히 ​​비교적 길며 엄격 모드에서는 지원되지 않는를 사용하는 것입니다.)

자바 스크립트는 두 문장을 다르게 취급합니다. 이것은 함수 선언입니다.

function abc(){}

abc 여기 현재 범위의 모든 곳에 정의되어 있습니다.

// We can call it here
abc(); // Works

// Yet, it is defined down there.
function abc(){}

// We can call it again
abc(); // Works

또한 return성명서를 통해 호이스트되었습니다 .

// We can call it here
abc(); // Works
return;
function abc(){}

이것은 함수 표현식입니다.

var xyz = function(){};

xyz 여기는 과제 지점에서 정의됩니다.

// We can't call it here
xyz(); // UNDEFINED!!!

// Now it is defined
xyz = function(){}

// We can call it here
xyz(); // works

함수 선언과 함수 표현은 Greg에 의해 입증 된 차이점이있는 실제 이유입니다.

재미있는 사실:

var xyz = function abc(){};
console.log(xyz.name); // Prints "abc"

개인적으로 저는 “함수 표현”선언을 선호합니다. 이런 식으로 가시성을 제어 할 수 있기 때문입니다. 내가 함수를 정의 할 때

var abc = function(){};

함수를 로컬로 정의했음을 알고 있습니다. 내가 함수를 정의 할 때

abc = function(){};

나는 abc범위 체인의 어느 곳에서도 정의하지 않았 음을 전 세계적으로 정의했음을 알고 있습니다. 이 스타일의 정의는 inside 내부에서도 사용될 수 있습니다 eval(). 정의하는 동안

function abc(){};

상황에 따라 달라지며 실제로 정의 된 위치, 특히 다음과 같은 경우에 추측 할 수 있습니다 eval(). 브라우저에 따라 다릅니다.


답변

다음은 함수를 생성하는 표준 양식에 대한 요약입니다. (원래 다른 질문을 위해 작성되었지만 정식 질문으로 이동 한 후 수정되었습니다.)

자귀:

빠른 목록 :

  • 함수 선언

  • “익명” function표현 (이 용어에도 불구하고 때때로 이름을 가진 함수를 생성 함)

  • 명명 된 function표현

  • 액세서 기능 이니셜 라이저 (ES5 +)

  • 화살표 함수 표현식 (ES2015 +) (익명 함수 표현식과 같이 명시적인 이름은 포함하지 않지만 이름을 가진 함수는 작성할 수 있음)

  • 객체 이니셜 라이저에서 메소드 선언 (ES2015 +)

  • class(ES2015 +)의 생성자와 메소드 선언

함수 선언

첫 번째 형식은 다음과 같은 함수 선언입니다 .

function x() {
    console.log('x');
}

함수 선언은 선언입니다 . 진술이나 표현이 아닙니다. 따라서, 당신은 그것을 따르지 않습니다 ;(그러나 그렇게하는 것은 무해합니다).

함수 선언은 단계별 코드가 실행 되기 전에 실행이 표시되는 컨텍스트에 들어갈 때 처리됩니다 . 그것이 생성하는 함수는 적절한 이름 ( x위의 예에서 )으로 주어지며 , 그 이름은 선언이 나타나는 범위에 놓입니다.

동일한 컨텍스트에서 단계별 코드 전에 처리되기 때문에 다음과 같은 작업을 수행 할 수 있습니다.

x(); // Works even though it's above the declaration
function x() {
    console.log('x');
}

ES2015까지 사양은 같은 제어 구조 안에 함수 선언을 넣을 경우 자바 스크립트 엔진이 무엇을해야 적용되지 않았다 try, if, switch, while같은, 등 :

if (someCondition) {
    function foo() {    // <===== HERE THERE
    }                   // <===== BE DRAGONS
}

또한 단계별 코드가 실행 되기 전에 처리되므로 제어 구조에있을 때 수행 할 작업을 아는 것이 까다 롭습니다.

이 작업은 ES2015까지 지정 되지 않았지만 블록에서 함수 선언을 지원 하는 확장이 가능 했습니다. 불행하게도 (그리고 필연적으로) 다른 엔진은 다른 일을했습니다.

ES2015 기준으로 사양에는 수행 할 작업이 나와 있습니다. 실제로 세 가지 별도의 작업을 수행합니다.

  1. 웹 브라우저가 아닌 느슨한 모드 인 경우 JavaScript 엔진은 한 가지 작업을 수행해야합니다.
  2. 웹 브라우저에서 느슨한 모드 인 경우 JavaScript 엔진은 다른 작업을 수행해야합니다
  3. 의 경우 엄격한 모드 (브라우저 또는하지 않음), 자바 스크립트 엔진은 또 다른 일을하도록되어

느슨한 모드에 대한 규칙은 까다 롭지 만 엄격 모드에서는 블록의 함수 선언이 쉽습니다. 블록에 대해 로컬 이며 (ES2015에서 새로운 블록 범위 를 가지며) 맨 위로 올라갑니다. 블록의. 그래서:

"use strict";
if (someCondition) {
    foo();               // Works just fine
    function foo() {
    }
}
console.log(typeof foo); // "undefined" (`foo` is not in scope here
                         // because it's not in the same block)

“익명” function표현

두 번째 일반적인 형식을 익명 함수 표현식 이라고합니다 .

var y = function () {
    console.log('y');
};

모든 표현식과 마찬가지로 단계별 코드 실행에서 도달하면 평가됩니다.

ES5에서이 함수는 이름이 없습니다 (익명). ES2015에서는 가능한 경우 컨텍스트에서 유추하여 함수에 이름이 지정됩니다. 위의 예에서 이름은입니다 y. 함수가 속성 이니셜 라이저 값일 때 비슷한 작업이 수행됩니다. (이 상황과 규칙에 대한 자세한 내용 은 사양SetFunctionName 에서 검색 하십시오. 모든 곳에서  나타납니다 .)

명명 된 function표현

세 번째 형식은 명명 된 함수 표현식 ( “NFE”)입니다.

var z = function w() {
    console.log('zw')
};

이 함수는 적절한 이름을 가지고 있습니다 ( w이 경우). 모든 표현식과 마찬가지로 단계별 코드 실행에서 도달하면 평가됩니다. 함수 이름은 표현식이 나타나는 범위에 추가 되지 않습니다 . 이름 함수 자체의 범위 내에 있습니다.

var z = function w() {
    console.log(typeof w); // "function"
};
console.log(typeof w);     // "undefined"

NFE는 종종 JavaScript 구현을위한 버그의 원인이되었습니다. 예를 들어 IE8 및 이전 버전에서는 NFE를 완전히 잘못 처리 하여 두 가지 다른 시간에 두 가지 다른 기능을 만듭니다. 초기 버전의 Safari에도 문제가있었습니다. 좋은 소식은 최신 버전의 브라우저 (IE9 이상, 현재 Safari)에는 더 이상 이러한 문제가 없다는 것입니다. (그러나이 글을 쓰는 시점에서 슬프게도 IE8은 널리 사용되므로 웹 코드를 사용하여 NFE를 사용하는 것은 여전히 ​​문제가됩니다.)

액세서 기능 이니셜 라이저 (ES5 +)

때로는 기능이 크게 눈에 띄지 않게 몰래 들어갈 수 있습니다. 접근 자 함수 의 경우입니다 . 예를 들면 다음과 같습니다.

var obj = {
    value: 0,
    get f() {
        return this.value;
    },
    set f(v) {
        this.value = v;
    }
};
console.log(obj.f);         // 0
console.log(typeof obj.f);  // "number"

함수를 사용할 때 사용하지 않았습니다 ()! 속성에 대한 접근 자 기능 이기 때문 입니다. 우리는 일반적인 방법으로 속성을 가져오고 설정하지만, 뒤에서 함수가 호출됩니다.

당신은 또한에 접근 기능을 만들 수 있습니다 Object.defineProperty, Object.defineProperties그리고에 덜 알려진 두 번째 인수를 Object.create.

화살표 함수 표현 (ES2015 +)

ES2015는 화살표 기능을 제공 합니다. 예를 들면 다음과 같습니다.

var a = [1, 2, 3];
var b = a.map(n => n * 2);
console.log(b.join(", ")); // 2, 4, 6

그 참조 n => n * 2의 일이 숨어 map()전화를? 그것은 기능입니다.

화살표 기능에 대한 몇 가지 사항 :

  1. 그들에게는 자신의 것이 없습니다 this. 대신, 그들은 가까운 이상this 가 정의하고있는 문맥. (또한 닫고 arguments관련이있는 경우 super.) 이는 this내부 this가 작성 위치 와 동일 하며 변경할 수 없음을 의미합니다.

  2. 위에서 알 수 있듯이 키워드를 사용하지 않습니다 function. 대신을 사용 =>합니다.

n => n * 2위 의 예는 그중 하나입니다. 함수를 전달할 인수가 여러 개인 경우 parens를 사용합니다.

var a = [1, 2, 3];
var b = a.map((n, i) => n * i);
console.log(b.join(", ")); // 0, 2, 6

(즉 기억 Array#map첫 번째 인수로 진입하고, 상기 제로서 인덱스를 통과한다.)

두 경우 모두 함수의 본문은 표현식 일뿐입니다. 함수의 반환 값은 자동으로 해당 표현식의 결과가됩니다 (명시 적을 사용하지 않음 return).

단일 표현식 이상을 수행하는 경우 평소 {}와 같이 명시 적 return(값을 반환해야하는 경우)을 사용하십시오.

var a = [
  {first: "Joe", last: "Bloggs"},
  {first: "Albert", last: "Bloggs"},
  {first: "Mary", last: "Albright"}
];
a = a.sort((a, b) => {
  var rv = a.last.localeCompare(b.last);
  if (rv === 0) {
    rv = a.first.localeCompare(b.first);
  }
  return rv;
});
console.log(JSON.stringify(a));

없는 버전 { ... }표현식 본문 또는 간결한 본문이 있는 화살표 함수라고합니다 . (또한하십시오 간결한 . 화살표 기능)와 하나의 { ... }몸체를 형성은 A의 화살표 함수 기능 체 . (또한 : 자세한 화살표 기능)

객체 이니셜 라이저에서 메소드 선언 (ES2015 +)

ES2015는 메소드 정의 라는 함수를 참조하는 속성을 선언하는 짧은 형식을 허용합니다 . 다음과 같이 보입니다 :

var o = {
    foo() {
    }
};

ES5 및 그 이전 버전과 거의 동등한 내용은 다음과 같습니다.

var o = {
    foo: function foo() {
    }
};

(자세한 것 이외의) 차이점은 메소드가을 사용할 수 super있지만 함수는 사용할 수 없다는 것입니다. 예를 들어, valueOf메소드 구문을 사용하여 정의 된 객체가있는 경우 ES5 버전이 대신 수행해야하는 반면 super.valueOf()Object.prototype.valueOf을 반환 하는 데 사용할 수 있습니다 (아마도 다른 작업을 수행하기 전에) Object.prototype.valueOf.call(this).

즉, 메소드에 정의 된 오브젝트에 대한 참조가 메소드에 있음을 의미하므로 해당 오브젝트가 임시 인 경우 (예 : Object.assign소스 오브젝트 중 하나로 전달하는 경우 ) 메소드 구문 오브젝트가 유지됨을 의미 할 수 있습니다. 그렇지 않으면 메모리에 가비지 수집되었을 수 있습니다 (JavaScript 엔진이 해당 상황을 감지하지 않고 메소드를 사용하지 않는 경우 처리하지 않는 경우 super).

class(ES2015 +)의 생성자와 메소드 선언

ES2015는 class선언 된 생성자와 메소드를 포함한 구문을 제공 합니다 :

class Person {
    constructor(firstName, lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    getFullName() {
        return this.firstName + " " + this.lastName;
    }
}

위에는 두 개의 함수 선언이 있습니다. 하나는 생성자를 Person위한 것이고 다른 하나 getFullName는에 할당 된 함수 Person.prototype입니다.


답변

var명령문과 FunctionDeclaration끝에 있는 전역 컨텍스트에 대해 말하면 전역 오브젝트에 삭제할 수없는 특성 이 작성 되지만 둘 다의 값을 겹쳐 쓸 수 있습니다 .

두 가지 방법의 미묘한 차이점은 변수 인스턴스화 프로세스가 실행될 때 (실제 코드 실행 전에)로 선언 된 모든 식별자 var가로 초기화 undefined되고의 FunctionDeclaration순간에 사용 된 식별자는 다음 과 같이 사용 가능하다는 것입니다.

 alert(typeof foo); // 'function', it's already available
 alert(typeof bar); // 'undefined'
 function foo () {}
 var bar = function () {};
 alert(typeof bar); // 'function'

할당은 bar FunctionExpression런타임까지 수행됩니다.

FunctionDeclaration변수 값과 같은 문제없이 a로 만든 전역 속성을 덮어 쓸 수 있습니다. 예 :

 function test () {}
 test = null;

두 예제의 또 다른 명백한 차이점은 첫 번째 함수에는 이름이 없지만 두 번째 함수에는 이름이 있다는 점입니다. 이는 디버깅 할 때 실제로 유용 할 수 있습니다 (예 : 호출 스택 검사).

편집 한 첫 번째 예 ( foo = function() { alert('hello!'); };)에 대해서는 선언되지 않은 과제이므로 항상 var키워드를 사용하는 것이 좋습니다 .

var명령문이 없는 할당을 사용하면 범위 체인에서 참조 된 식별자를 찾을 수 없으면 전역 객체 의 삭제 가능한 속성이됩니다.

또한 선언되지 않은 할당 ReferenceError엄격 모드 아래 ECMAScript 5 에서을 발생 시킵니다.

반드시 읽어야합니다.

참고 :이 답변은 다른 질문 에서 병합되었습니다 . OP의 주요 의심과 오해는으로 선언 된 식별자를 FunctionDeclaration덮어 쓸 수 없다는 것입니다.


답변

거기에 게시 한 두 개의 코드 스 니펫은 거의 모든 목적으로 동일한 방식으로 작동합니다.

그러나 동작의 차이점은 첫 번째 변형 ( var functionOne = function() {})을 사용하면 해당 함수는 코드에서 해당 지점 이후에만 호출 할 수 있다는 것입니다.

두 번째 변형 ( function functionTwo())을 사용하면 함수가 선언 된 위치에서 실행되는 코드에 함수를 사용할 수 있습니다.

첫 번째 변형에서는 함수가 foo런타임에 변수 에 할당되기 때문입니다 . 두 번째로, 함수는 foo구문 분석시 해당 식별자에 할당됩니다 .

더 많은 기술 정보

JavaScript에는 함수를 정의하는 세 가지 방법이 있습니다.

  1. 첫 번째 스 니펫은 함수 표현식을 보여줍니다 . 여기에는 “함수”연산자 를 사용하여 함수를 만드는 과정이 포함됩니다. 해당 연산자의 결과는 모든 변수 또는 객체 속성에 저장할 수 있습니다. 함수 표현식은 그렇게 강력합니다. 함수 표현식은 이름을 가질 필요가 없기 때문에 종종 “익명 함수”라고합니다.
  2. 두 번째 예는 함수 선언 입니다. 이것은 “function”문 을 사용하여 함수를 만듭니다. 이 함수는 구문 분석시 사용 가능하며 해당 범위의 어느 곳에서나 호출 할 수 있습니다. 나중에 변수 또는 객체 속성에 저장할 수 있습니다.
  3. 함수를 정의하는 세 번째 방법은 “Function ()”생성자 이며 원래 게시물에는 표시되지 않습니다. eval()문제가 있는 것과 같은 방식으로 작동하므로 사용하지 않는 것이 좋습니다 .

답변

그렉의 대답에 대한 더 나은 설명

functionTwo();
function functionTwo() {
}

왜 오류가 없습니까? 우리는 항상식이 위에서 아래로 실행된다는 것을 배웠습니다 (??)

때문에:

함수 선언과 변수 선언은 hoistedJavaScript 인터프리터에 의해 항상 포함 범위의 맨 위로 이동합니다 ( ). 함수 매개 변수와 언어 정의 이름은 이미 존재합니다. 벤 체리

이것은 다음과 같은 코드를 의미합니다.

functionOne();                  ---------------      var functionOne;
                                | is actually |      functionOne();
var functionOne = function(){   | interpreted |-->
};                              |    like     |      functionOne = function(){
                                ---------------      };

선언의 할당 부분은 게양되지 않았습니다. 이름 만 게양됩니다.

그러나 함수 선언의 경우 전체 함수 본문도 함께 게양됩니다 .

functionTwo();              ---------------      function functionTwo() {
                            | is actually |      };
function functionTwo() {    | interpreted |-->
}                           |    like     |      functionTwo();
                            ---------------


답변

다른 의견 제시 자들은 이미 위 두 변형의 의미 론적 차이를 다루었습니다. 스타일 차이를 언급하고 싶었습니다. “할당”변형 만 다른 개체의 속성을 설정할 수 있습니다.

나는 종종 다음과 같은 패턴으로 JavaScript 모듈을 빌드합니다.

(function(){
    var exports = {};

    function privateUtil() {
            ...
    }

    exports.publicUtil = function() {
            ...
    };

    return exports;
})();

이 패턴을 사용하면 공용 함수는 모두 할당을 사용하고 개인 함수는 선언을 사용합니다.

(선언에서는 선언이 금지하는 반면, 할당에는 명령문 뒤에 세미콜론이 필요합니다.)