JS 프로그래밍이 처음이신가요? 그렇다면 당황스러울 수도 있습니다. 모든 언어에는 고유한 특징이 있습니다. 그러나 강력한 타이핑을 기반으로 하는 서버 측 언어에서 이동하는 개발자는 혼란스러울 수 있습니다. 저는 몇 년 전 전업 JavaScript 개발자가 되려고 했을 때 그랬습니다. 처음에 알았더라면 좋았을 것들이 많았습니다. 이 기사에서 나는 나의 특이한 점 중 일부를 공유할 것이며, 나에게 많은 골칫거리를 안겨준 몇 가지 경험을 여러분과 공유할 수 있기를 바랍니다. 이것은 전체 목록이 아닙니다. 부분 목록일 뿐입니다. 하지만 바라건대 이 언어의 힘과 한때 방해라고 생각했던 것들에 대해 눈을 뜨게 되기를 바랍니다.
다음 기술을 살펴보겠습니다.
I 초 이 어둠의 마법? 정수 1은 문자열 "1"과 어떻게 동일합니까? 자바스크립트에는 동등(==)과 완전 동등(===)이 있습니다. 같음 연산자는 두 피연산자를 동일한 유형으로 강제 변환한 후 엄격한 같음 비교를 수행합니다. 따라서 위의 예에서 문자열 "1"은 정수 1로 변환됩니다. 이 프로세스는 뒤에서 발생하며 변수 x와 비교됩니다.
엄격한 동등성은 유형 변환을 수행하지 않습니다. 피연산자 유형이 다른 경우(예: 정수 및 문자열) 일치하지 않습니다(완전히 동일함).
var x = 1;
// 엄격한 평등, 유형은 동일해야 합니다
-
if(x === " 1") {
console.log("슬프게도 저는 이것을 콘솔에 쓰지 않을 것입니다.");
}
if(x === 1) {
console.log("예! 엄격한 평등 FTW.")
}
너 가능한 캐스트로 인해 발생하는 모든 공포에 대해 생각할 수 있습니다. 이러한 캐스트가 참조에서 발생한다고 가정하면 문제가 있는 위치를 찾는 것이 매우 어려울 수 있습니다. 이는 놀라운 일이 아니며 숙련된 JavaScript 개발자가 항상 엄격한 평등 사용을 권장하는 이유입니다.
2.) 마침표 대 괄호 당신이 어떤 다른 언어에서 왔는지에 따라 이 작업을 본 적이 있을 수도 있고 보지 못했을 수도 있습니다(말도 안 되는 소리입니다).
// person 개체의 firstName 값 가져오기
var name = person.firstName;
-
// 세 번째 요소 가져오기 배열 요소
var theOneWeWant = myArray[2]; // 기억하세요, 0 기반 인덱스 첫 번째 요소의 인덱스가 0
이라는 점을 잊지 마세요. 괄호를 사용할 수도 있습니다. 개체의 구성원을 참조합니까? 예:
var name = person["firstName"];
왜 유용한가요? 대부분의 경우 점 표기법을 사용하지만 특정 메서드에서 점 표기법을 사용하지 못하게 만드는 괄호가 몇 가지 있습니다. 예를 들어, 나는 종종 대규모 스위치 문을 일정으로 리팩터링하므로 다음과 같습니다.
이 작업이 왜 작동하나요? 이전에 점을 사용하는 것이 더 익숙했을 수도 있지만 대괄호 표기만 사용할 수 있는 몇 가지 특수한 경우가 있습니다. 예를 들어, 나는 종종 switch 문을 (더 빠른) 조회 테이블로 리팩토링하는데, 실제로는 다음과 같습니다:
var doSomething = function(doWhat) {
switch(doWhat ) {
케이스 "doThisThing":
// 추가 코드...
Break;
사례 "doThatThing": ing":
Break;
// 여기에 추가 사례 등
기본값:
-
// 기본 동작
break;
}
}
은 다음과 같이 변형될 수 있습니다.
- var thingsWeCanDo = {
dothisthing : function () { / * 행동 * /},
dothatthing : function () { / * zoving * /},
dothisotherthing : function () { /* 동작 */ },
기본값 : function() { /* 동작 */ }
};
var doSomething = function(doWhat) {
var thingToDo = thingsWeCanDo.hasOwnProperty(doWhat) ? doWhat : "default"
-
thingsWeCanDo[thingToDo]();
}
스위치를 사용하는 데는 아무런 문제가 없습니다(그리고 많은 경우 스위치가 여러 번 반복되고 성능이 큰 관심사인 경우 조회 테이블보다 더 나은 성능을 발휘할 수 있습니다). 그러나 조회 테이블은 코드를 구성하고 확장하는 훌륭한 방법을 제공하며 괄호를 사용하면 속성을 천천히 평가할 수 있습니다.
3.) 함수 컨텍스트 JavaScript에서 이 컨텍스트를 올바르게 이해하는 훌륭한 블로그가 이미 있지만(포스트 마지막에 멋진 링크를 제공하겠습니다) 실제로 추가해야 합니다. "알고 싶었어요" 목록. 코드를 이해하고 어떤 위치에서든 이것의 가치를 자신있게 아는 것은 정말 어렵습니다. 단지 일련의 규칙을 배우면 됩니다. 불행하게도, 내가 초기에 읽었던 많은 설명은 나의 혼란을 가중시켰을 뿐입니다. 그래서 간단하게 설명하려고 노력했습니다.
먼저 - 전역 상황(Global)을 먼저 고려하세요. 기본적으로 어떤 이유로 실행 컨텍스트가 변경될 때까지 this 값은 전역 객체를 가리킵니다. 브라우저에서는 window 객체(또는 node.js의 전역 객체)가 됩니다.
두 번째 - 메소드의 this 값 함수 멤버가 있는 객체가 있고 상위 객체에서 이 메소드를 호출하면 this 값이 상위 객체를 가리킵니다. 예:
var marty = {
firstName: "Marty",
lastName: "McFly",
시간여행: 함수(연도) {
console.log(this.firstName + " " + this.lastName + "는 " + 연도로 시간 이동합니다);
}
}
-
marty.timeTravel(1955);
-
// 마티 맥플라이는 1955년으로 시간여행을 합니다
아마도 marty 개체의 timeTravel 메서드를 참조하고 다른 개체에 대한 새 참조를 생성할 수 있다는 것을 이미 알고 계실 것입니다. 이는 실제로 JavaScript의 매우 강력한 기능으로, 다양한 인스턴스의 동작(함수 호출)을 참조할 수 있게 해줍니다.
var doc = {
firstName: "Emmett",
-
lastName: "Brown",
}
-
doc.timeTravel = marty.timeTravel;
그래서 doc.timeTravel(1885)을 호출하면 어떻게 될까요? ㅋㅋㅋ 글쎄요, 그렇지 않아요. 메소드를 호출할 때 이 컨텍스트는 호출된 함수의 상위 객체의 상위 객체라는 점을 기억하세요.
marty.TimeTravel 메소드에 대한 참조를 저장한 다음 저장된 참조를 호출하면 어떻게 되나요? 살펴보겠습니다:
- var getBackInTime = marty.timeTravel;
getBackInTime(2014);
// 정의되지 않음 2014년으로의 시간여행
왜 "정의되지 않음"인가요? ! "Matry McFly" 대신?
중요한 질문을 해보자: getBackInTime 함수를 호출할 때 상위/컨테이너 개체는 무엇입니까? getBackIntTime 함수가 윈도우에 존재하면 이를 객체 메서드가 아닌 함수로 호출합니다. 컨테이너 개체 없이 이와 같은 함수를 호출하면 이 컨텍스트가 전역 개체가 됩니다. David Shariff는 이에 대해 훌륭하게 설명했습니다.
함수를 호출할 때마다 즉시 괄호의 왼쪽을 봐야 합니다. 대괄호 왼쪽에 참조가 있는 경우 호출 함수에 전달된 this 값은 참조가 속한 객체로 결정되고, 그렇지 않으면 완전한 객체입니다.
getBackInTime의 이 컨텍스트는 창이므로 firstName 및 lastName 속성이 없습니다. 이는 "undefunction undefine"이 표시되는 이유를 설명합니다.
컨테이너 개체 없이 함수를 직접 호출하면 이 컨텍스트의 결과가 전역 개체라는 것을 알 수 있습니다. 그러나 나는 또한 getBackInTime 함수가 창에 존재한다는 것을 이미 알고 있다고 말했습니다. 어떻게 알 수 있나요? 좋아, 위에서 getBackInTime을 다른 컨텍스트로 래핑한 것과 달리(우리는 함수 표현식을 즉시 실행하는 것에 대해 이야기했습니다), 제가 선언한 모든 변수가 창에 추가되었습니다. Chrome 콘솔에서 확인:
이제 이것이 발생하는 주요 영역 중 하나인 구독 이벤트 처리에 대해 논의할 시간입니다.
세 번째(#2의 확장) - 메서드에서 이 값을 비동기식으로 호출 그러므로 누군가 버튼을 클릭할 때 marty.timeTravel 메서드를 호출한다고 가정해 보겠습니다.
var flux = document.getElementById ("flux-capacitor") code , 사용자가 버튼을 클릭하면 "정의되지 않은 정의는 [객체 MouseEvent]로 이동하는 시간입니다."가 표시됩니다. 무엇? 좋습니다. 우선 가장 분명한 문제는 timeTravel 메소드에 연도 매개변수를 제공하지 않았다는 것입니다. 대신 이 메서드를 이벤트 핸들러로 직접 구독하고 MouseEvent 매개변수가 이벤트 핸들러의 첫 번째 인수로 전달됩니다. 이 문제는 해결하기 쉽지만 실제 문제는 "정의되지 않은 정의되지 않음"이 다시 표시된다는 것입니다. 절망하지 마십시오. 왜 이런 일이 발생하는지 이미 알고 있습니다(아직 깨닫지 못하더라도). 사실을 파악하는 데 도움이 되도록 timeTravel 함수를 수정해 보겠습니다.
marty.timeTravel = function(year) {
console.log(this.firstName + " " + this.lastName + "는 " + year);
console.log(this);
};
이제 이 버튼을 클릭하면 브라우저 콘솔에 다음과 유사한 출력이 표시됩니다.
로그 출력은 다음과 같습니다. 이 컨텍스트 - 실제로는 이벤트를 구독하는 버튼 요소입니다. 놀랐나요? 이전과 마찬가지로 marty.timeTravel을 getBackInTime 변수에 할당하면 marty.timeTravel에 대한 참조가 이벤트 핸들러에 저장되고 호출되지만 컨테이너 개체는 더 이상 marty 개체가 아닙니다. 이 경우 버튼 인스턴스의 클릭 이벤트에서 비동기적으로 호출됩니다.
그래서 이것을 우리가 원하는 결과로 설정할 수 있습니까? 전적으로! 이 경우 해결 방법은 매우 간단합니다. 이벤트 핸들러에서 marty.timeTravel을 직접 구독하는 대신 익명 함수를 이벤트 핸들러로 사용하고 익명 함수에서 marty.timeTravel을 호출하십시오. 이는 또한 연도 매개변수 누락 문제를 해결할 수도 있습니다.
flux.addEventListener("click", function(e) {
marty.timeTravel(someYearValue);
}) ;
버튼을 클릭하면 콘솔에 다음과 유사한 정보가 출력됩니다.
성공! 그런데 이게 왜 괜찮은 걸까요? timeTravel 메소드를 어떻게 호출하는지 생각해 보세요. 버튼 클릭의 첫 번째 예에서는 이벤트 핸들러에서 메서드 자체에 대한 참조를 구독했으므로 부모 marty에서 호출되지 않았습니다. 두 번째 예에서 이것은 버튼 요소의 익명 함수이며, marty.timeTravel을 호출할 때 부모 개체 marty에서 호출하므로 이것이 marty입니다.
넷째 - 생성자의 이 값 생성자를 사용하여 객체 인스턴스를 생성할 때 함수 내부의 this 값은 새로 생성된 객체입니다. 예:
var TimeTraveler = function(fName, lName) {
this.firstName = fName;
-
this.lastName = lName;
// 생성자 함수는
-
// 새로 생성된 객체를 반환합니다. };
- var marty = new TimeTraveler("마티", "McFly");
- console.log(marty.firstName + " " + marty.lastName);
// 마티 맥플라이
Call, Apply and BindCall 위의 예에서 런타임 시 호출 함수의 이 값을 지정할 수 있는 언어 수준 기능이 없는지 궁금하실 것입니다. 당신 말이 맞아요. 함수 프로토타입에 있는 호출 및 적용 메서드를 사용하면 함수를 호출하고 this 값을 전달할 수 있습니다.
호출 메소드의 첫 번째 매개변수는 this이고, 그 뒤에는 호출된 함수의 매개변수 시퀀스가 옵니다:
someFn.call(this, arg1, arg2, arg3);
apply의 첫 번째 매개변수도 this이고, 그 뒤에 나머지 매개변수 배열이 옵니다:
someFn.apply(this, [arg1, arg2, arg3]);
우리의 것 Doc과 Marty 인스턴스는 스스로 시간 여행을 할 수 있지만, 시간 여행을 완료하려면 아인슈타인(Einstein)의 도움이 필요합니다. 이제 문서가 시간 여행을 통해 Einstein을 도울 수 있도록 문서 인스턴스에 메서드를 추가해 보겠습니다.
doc.timetravelfor = function (인스턴스, 연도) {
th this.timeTravel.Call (인스턴스, 연도);
// Apply를 사용하는 경우 다음 구문을 사용하세요.
// this.timeTravel.apply(instance, [year]);
};
이제 Einstein을 전송할 수 있습니다.
var einstein = {
firstName: "Einstein",
lastName: "(개)"
-
};
doc.timeTravelFor(einstein, 1985);
// 아인슈타인(개)은 1985년으로 시간 여행을 하고 있습니다
이 예는 다소 터무니없다는 것을 압니다. 하지만 그것만으로도 충분합니다. 다른 객체에 기능을 적용하는 것이 얼마나 강력한지 살펴보겠습니다.
이 방법에는 아직 발견하지 못한 또 다른 용도가 있습니다. this.timeTravel(1985)에 대한 바로 가기로 marty 인스턴스에 goHome 메소드를 추가해 보겠습니다. Omarty.gohome = 함수() {
This.timetravel(1985)
}
하지만 marty.goHome을 버튼의 클릭 이벤트 핸들러로 구독하면 이 값이 버튼이 되며 안타깝게도 버튼에는 timeTravel 메서드가 없다는 것을 알고 있습니다. 위의 방법으로 문제를 해결할 수 있습니다. 익명 함수를 이벤트 핸들러로 사용하고 그 안에서 위의 메서드를 호출합니다. 하지만 또 다른 옵션인 바인드 함수도 있습니다.
flux.addEventListener( "click", marty.goHome.bind(marty));
bind 함수는 실제로 새 함수를 반환하며, 새 함수의 this 값은 제공한 매개변수에 따라 설정됩니다. 낮은 버전의 브라우저(예: ie9 이하 버전)를 지원해야 하는 경우 바인드 기능의 shim이 필요할 수 있습니다(또는 jQuery를 사용하는 경우 대신 $.proxy를 사용할 수 있으며 밑줄과 lodash는 모두 _.바인드 방법) .
프로토타입에서 직접 바인딩 메서드를 사용하면 프로토타입 메서드의 장점을 우회하는 인스턴스 메서드가 생성된다는 점을 기억하는 것이 중요합니다. 이것은 실수가 아닙니다. 단지 마음속으로 분명히 하십시오. 이 문제에 대한 자세한 내용은 여기에 썼습니다.
4.) 함수 표현식 vs 함수 선언 함수 선언에는 var 키워드가 필요하지 않습니다. 실제로 Angus Croll이 말했듯이 "이들을 변수 선언의 형제로 생각하는 것이 도움이 됩니다." 예:
function timeTravel(year) {
console.log(this.firstName + " " + this.lastName + "는 " + year)로 시간 여행을 합니다.
} 위 예제에서 함수 이름 timeTravel은 선언된 범위뿐만 아니라 함수 자체 내에서도 표시됩니다(이는 재귀 함수 호출에 매우 유용합니다). 함수 선언은 본질적으로 명명된 함수입니다. 즉, 위 함수의 name 속성은 timeTravel 입니다.
함수 표현식은 함수를 정의하고 이를 변수에 할당합니다. 일반적인 응용 프로그램은 다음과 같습니다.
var someFn = function() {
console.log("I like to express myself...");
}; 也可以对函数表达式命名——然而,不像函数声明,命名函数表达式的名字仅在它自身函数体内可访问:
var someFn = function iHazName() {
console.log("I like to express myself...");
if(needsMoreExpressing) {
iHazName(); // 函数的名字在这里可以访问
}
};
// 你可以在这里调用someFn(),但不能调用iHazName()
someFn(); 로그인 후 복사
"호이스팅"을 언급하지 않고는 함수 표현식과 함수 선언을 논할 수 없습니다. 함수와 변수 선언은 컴파일러에 의해 범위의 맨 위로 이동됩니다. 여기서는 호이스팅을 자세히 설명할 수 없지만 Ben Cherry와 Angus Croll의 두 가지 훌륭한 설명을 읽을 수 있습니다.
5.) 명명된 함수와 익명 함수 지금의 토론을 바탕으로 "익명" 함수는 실제로 이름이 없는 함수라고 짐작하셨을 것입니다. 대부분의 JavaScript 개발자는 첫 번째 매개변수를 익명 함수로 빠르게 식별합니다.
someElement.addEventListener("click", function(e) {
// 저는 익명입니다!
});
그러나, 마찬가지로 marty.timeTravvel 메소드도 익명 함수입니다.
var marty = {
firstName: "Marty",
lastName: ",
timeTravel: function(연도) {
Console.log(this.firstName + " " + this.lastName + "는 " + 연도로 시간 이동);
}
}
함수 선언에는 고유한 이름이 있어야 하므로 함수 표현식에만 이름이 없을 수 있습니다.
6.) 즉시 실행되는 함수 표현식 함수 표현식에 대해 이야기하고 있으니, 제가 알았으면 좋았을 한 가지가 있습니다: 즉시 실행되는 함수 표현식(IIFE)입니다. IIFE에 대한 좋은 글들이 많이 있는데(글 마지막에 나열하겠습니다) 한 문장으로 설명하자면, 함수 표현식을 스칼라에 할당하고 나중에 실행하는 식으로 함수 표현식을 실행하는 것이 아니라, 실행. 브라우저 콘솔에서 이 프로세스를 볼 수 있습니다.
먼저 - 함수 표현식을 입력해 보겠습니다. 단, 변수를 할당하지 마세요. 그러면 무슨 일이 일어나는지 확인하세요.
구문 오류 - 이것은 함수 선언으로 간주되어 함수 이름이 없습니다. 그러나 이를 표현식으로 만들려면 간단히 괄호로 묶으면 됩니다.
이를 표현식으로 만들면 콘솔은 익명 함수를 반환합니다. (기억하세요. 값이 할당되지는 않았지만 표현식은 값을 반환합니다). 그래서 우리는 "함수 표현"이 "즉시 호출 함수 표현"의 일부라는 것을 알고 있습니다. 실행 대기 기능을 얻으려면 표현식 뒤에 다른 괄호를 추가하여 (다른 함수를 호출하는 것과 마찬가지로) 반환된 표현식을 호출합니다.
" 하지만 잠깐만요, Jim!(참조 작성자) 이런 통화는 본 적 있는 것 같아요." 실제로 본 적이 있을 것입니다. 이것은 합법적인 구문입니다(Douglas Crockford가 선호하는 구문으로 알려짐)
두 가지 방법 모두 효과적이지만 여기에서 읽어 보시기를 적극 권장합니다.
좋아요. 이제 IIFE가 무엇인지 알았습니다. 왜 IIFE를 사용해야 할까요?
범위를 제어하는 데 도움이 됩니다. 이는 모든 JavaScript 튜토리얼에서 매우 중요한 부분입니다! 앞서 본 예제 중 상당수는 전역 범위에서 생성되었습니다. 이는 창(환경이 브라우저라고 가정) 개체에 많은 속성이 있음을 의미합니다. 우리 모두가 이런 방식으로 JavaScript 코드를 작성한다면 전역 범위에 엄청난 양의 변수 선언이 빠르게 축적되고(과장된) 창 코드가 오염될 것입니다. 최상의 상황에서도 전역 변수에 많은 세부 정보를 노출하는 것은 좋지 않은 조언입니다. 하지만 변수 이름이 기존 창 속성 이름과 같으면 어떻게 될까요? 창 속성이 재정의됩니다!
예를 들어, 즐겨찾는 "Amelia Earhart" 웹사이트가 전역 범위에서 네비게이터 변수를 선언하는 경우 설정 전후의 결과는 다음과 같습니다.
죄송합니다!
분명히 - 오염된 전역 변수는 나쁩니다. JavaScript는 함수 범위(블록 범위가 아님, C#이나 Java를 사용하는 경우 매우 중요함)를 사용하므로 코드를 전역 범위와 분리하는 방법은 IIFE를 사용하여 수행할 수 있는 새 범위를 만드는 것입니다. 그 내용이 자체 기능 범위 내에 있기 때문에 구현됩니다. 아래 예에서는 콘솔의 window.navigator 값을 보여준 다음 Amelia Earhart의 동작과 데이터를 래핑하기 위해 IIFE(즉시 실행되는 함수 표현식)를 생성하겠습니다. IIFE가 끝나면 객체가 "프로그램 네임스페이스"로 반환됩니다. IIFE 내부에 선언한 navigator 변수는 window.navigator의 값을 재정의하지 않습니다.
추가 보너스로 위에서 만든 IIFE는 JavaScript의 모듈 패턴을 깨달은 것입니다. 마지막에는 제가 살펴본 일부 모듈 패턴에 대한 링크를 포함하겠습니다.
7.) 'typeof' 연산자 및 'Object.prototype.toString' 결국 어떤 경우에는 함수에 전달된 매개변수의 유형이나 기타 유사한 사항을 확인해야 할 수도 있습니다. typeof 연산자는 확실한 선택이지만 만병통치약은 아닙니다. 예를 들어, 객체, 배열, 문자열 또는 정규 표현식에 대해 typeof 연산자를 호출하면 어떻게 될까요?
나쁘지 않습니다. 적어도 문자열을 객체, 배열 및 정규식과 구별할 수는 있습니다. 그렇죠? 다행히도 다양한 방법을 통해 더 정확한 유형 정보를 얻을 수 있습니다. Object.prototype.toString 메소드를 사용하고 앞서 언급한 호출 메소드를 적용하겠습니다.
Object.prototype에서 toString 메소드를 사용하는 이유는 무엇입니까? 타사 라이브러리나 자체 코드가 인스턴스의 toString 메서드를 재정의할 수 있기 때문입니다. Object.prototype을 통해 인스턴스의 원래 toString 동작을 강제할 수 있습니다.
어떤 typeof가 반환될지 안다면 추가 검사를 할 필요가 없습니다(예를 들어, 그것이 문자열인지 아닌지만 알면 됩니다). 이 경우 typeof를 사용하는 것이 매우 좋습니다. 그러나 배열과 객체, 정규식과 객체 등을 구별해야 한다면 Object.prototype.toString을 사용하세요.
|