최근에 다른 사람의 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;
이 경우 둘 다 xyz
및 abc
같은 객체의 별칭입니다.
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()
. 브라우저에 따라 다릅니다.
답변
다음은 함수를 생성하는 표준 양식에 대한 요약입니다. (원래 다른 질문을 위해 작성되었지만 정식 질문으로 이동 한 후 수정되었습니다.)
자귀:
- ES5 : ECMAScript 5 판 , 2009
- ES2015 : ECMAScript 2015 ( “ES6″이라고도 함)
빠른 목록 :
-
함수 선언
-
“익명”
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 기준으로 사양에는 수행 할 작업이 나와 있습니다. 실제로 세 가지 별도의 작업을 수행합니다.
- 웹 브라우저가 아닌 느슨한 모드 인 경우 JavaScript 엔진은 한 가지 작업을 수행해야합니다.
- 웹 브라우저에서 느슨한 모드 인 경우 JavaScript 엔진은 다른 작업을 수행해야합니다
- 의 경우 엄격한 모드 (브라우저 또는하지 않음), 자바 스크립트 엔진은 또 다른 일을하도록되어
느슨한 모드에 대한 규칙은 까다 롭지 만 엄격 모드에서는 블록의 함수 선언이 쉽습니다. 블록에 대해 로컬 이며 (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()
전화를? 그것은 기능입니다.
화살표 기능에 대한 몇 가지 사항 :
-
그들에게는 자신의 것이 없습니다
this
. 대신, 그들은 가까운 이상this
가 정의하고있는 문맥. (또한 닫고arguments
관련이있는 경우super
.) 이는this
내부this
가 작성 위치 와 동일 하며 변경할 수 없음을 의미합니다. -
위에서 알 수 있듯이 키워드를 사용하지 않습니다
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에는 함수를 정의하는 세 가지 방법이 있습니다.
- 첫 번째 스 니펫은 함수 표현식을 보여줍니다 . 여기에는 “함수”연산자 를 사용하여 함수를 만드는 과정이 포함됩니다. 해당 연산자의 결과는 모든 변수 또는 객체 속성에 저장할 수 있습니다. 함수 표현식은 그렇게 강력합니다. 함수 표현식은 이름을 가질 필요가 없기 때문에 종종 “익명 함수”라고합니다.
- 두 번째 예는 함수 선언 입니다. 이것은 “function”문 을 사용하여 함수를 만듭니다. 이 함수는 구문 분석시 사용 가능하며 해당 범위의 어느 곳에서나 호출 할 수 있습니다. 나중에 변수 또는 객체 속성에 저장할 수 있습니다.
- 함수를 정의하는 세 번째 방법은 “Function ()”생성자 이며 원래 게시물에는 표시되지 않습니다.
eval()
문제가 있는 것과 같은 방식으로 작동하므로 사용하지 않는 것이 좋습니다 .
답변
그렉의 대답에 대한 더 나은 설명
functionTwo();
function functionTwo() {
}
왜 오류가 없습니까? 우리는 항상식이 위에서 아래로 실행된다는 것을 배웠습니다 (??)
때문에:
함수 선언과 변수 선언은
hoisted
JavaScript 인터프리터에 의해 항상 포함 범위의 맨 위로 이동합니다 ( ). 함수 매개 변수와 언어 정의 이름은 이미 존재합니다. 벤 체리
이것은 다음과 같은 코드를 의미합니다.
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;
})();
이 패턴을 사용하면 공용 함수는 모두 할당을 사용하고 개인 함수는 선언을 사용합니다.
(선언에서는 선언이 금지하는 반면, 할당에는 명령문 뒤에 세미콜론이 필요합니다.)