본문 바로가기

Front-end/Javascript

Effective Javascript(1,2장)

1. 자바스크립트에 익숙해지기


item1. 어떤 자바스크립트를 사용하고있는지 알아야한다

  • 자바스크립트의 다양한 구현체들 때문에 어떤 기능이 어떤 플랫폼에서 사용가능한지 추적이 어려움
  • 이유는 주요 생태계인 웹브라우저에서 개발자가 작성한 코드를 실행하기 위해 어떤 버전의 js를 사용할수 있는 제어할 방법이 없어서
  • 따라서, 웹 프로그램은 모든 브라우저에서 지속적으로 동작할 수 있도록 주의

 

strict 모드

 오류를 일으키기 쉽거나 문제를 일으킬만한 기능들을 사용할수 없게 만드는 방법
"use strict" 라는 고정문자열 추가로 활성화가 가능하므로 하위호환에도 문제가 없음

!스크립트 병합시 주의해야할 케이스

  1. strict 모드는 스크립트 또는 함수 상단에 선언되었을 때만 인식 -> 병합 순서에 따라 전부 적용되지 않을수 있음
  2. strict 모드와 일반 파일을 병합하지 않고 분리하여 배포
  3. 즉시 실행함수 표현식을 사용해 파일들의 본문을 감싸 각 파일의 내용을 함수 스코프 내부로 위치시켜 해당파일 내용에만 영향을 미치도록함 (다만, 이 방법은 전역 스코프에 변수 유지가 불가함)
  4. 어떤 모드에 있건 동일하게 동작하도록 코드를 작성해야함(호환성을 위해 strict모드로 작상하는것이 바람직함)

 


item2. 자바스크립트의 부동 소수점 숫자 이해하기

  • js에서는 number 타입 단 하나로 숫자 데이터를 다룸
  • double형 으로 알려진 부동 소수점 숫자임
  • 비트단위 연산자는 부동소수점 대신 32비트 정수로 변환함
  • 비트단위 연산자 연산 과정
    • 입력값을 정수형으로 변환
    • 정수 비트 패턴에서 각 연산 수행
    • 표준 자바스크립트 부동소수점 숫자로 변환하여 결과 반환
  • 부동소수점 산술연산은 근사값만 만들어내고 가장가까운 실수로 반올림함
  • 따라서 정확도를 위해 정수값을 사용하여 반올림 없이 표현하는것이 좋음

item3. 암묵적인 형변환을 주의하라

  • 잘못된 데이터형으로 오류를 즉시 보여주는 경우
    • 함수가 아닌데 호출하려는 경우 ex) "hello"()
    • null의 prop에 접근하려는 경우 ex) null.x
  • 연산자 [ - , *, /, % ] 는 계산전에 인자들을 숫자형으로 변환함
  • 연산자 [ + ] 는 인자들의 데이터형에 따라서 덧셈 또는 병합으로 오버로딩함(문자열로 변환을 우선시 함)

NaN테스트

자신을 동등하지 않다고 처리함
var x = NaN;
x === NaN; // false

isNaN함수도 인자를 숫자로 바꾸기때문에 신뢰할 수 없음
isNaN(NaN); // true

NaN은 자기자신과 동일하지 않은 유일한 값임
var a = NaN
a !== a; // true

 

조용히 이뤄지는 강제 형변환은 오류를 숨기고 분적하기 어렵게하고 디버깅하기 곤란하게 만듦
  • 객체또한 원시 데이터형으로 강제 형변환 될수있음
    • toString() 으로 문자열로 변환
    • valueOf() 로 숫자로 변환
  • 둘다 포함할 경우 toString()이 valueOf()의 문자열 표현을 나타내지않는다면 valueOf는 피하는것이 최선임

Falsy한 값

  • 0
  • -0
  • ""
  • NaN
  • null
  • undefined

*undefined를 테스트할땐 typeof를 사용

 


item4. 객체 래퍼보다 원시 데이터형을 우선시하라

원시데이터형

  • 불리언
  • 숫자
  • 문자열
  • null
  • undefined

객체 래퍼는 원시데이터와 다르게 객체다

var s = new String("hello")

typeof "hello" // string
typeof s // object

var s1 = new String("hello")

s === s1 // false

객체 래퍼가 존재하는 주된 이유는 유틸리티 메서드 때문 ex) "hello".toUpperCase()

이런 암묵적 래핑으로 원시 데이터값에 아무영향없이 prop 설정이 가능

암묵적 래핑은 실행될때마다 매번 새로운 객체를 생성하기때문에 갱신되지 않음

 


item5. 혼합된 데이터형을 ==로 비교하지 마라

형변환이 연관되어있지 않다는 점을 확실히 보여주기위해선 ===으로 엄격하게 동일비교를 사용

  • == 연산자는 인자들이 서로 다른 데이터형일때, 암묵적 강제 형변환을 적용함
  • 비교할 값이 서로 다른 데이터형이면 명시적 강제 형변환을 사용

item6. 세미콜론 삽입의 한계에 대해서 알아두자

세미콜론의 생략은 자동으로 삽입되기 때문임

 

세미콜론 삽입규칙

  • 한줄이상의 새로운행
  • 프로그램 입력의 마지막
  • } 토큰 전에만 삽입
  • 다음 입력토큰을 파싱할 수 없을 때에만 삽입

 

조심해야할 다섯개의 문자 

 -> 문맥에 따라 표현식의 연산자 또는 선언의 접두어로 사용될 수 있기때문

 -> 해당 문자로 시작하면 ; 없이 하나의 선언으로 파싱될수 있음

  1. (  => 함수 실행
  2. [  => 배열 리터럴
  3. -
  4. / => 정규 표현식 토큰 or 나눗셈 연산

 

* 생략된 세미콜론에 대한 인지, 세미콜론 비활성화 시키는 토큰이 있는지 확인 필요

 

대체방법

  • 조심해야할 다섯개의 문자로 시작하는 선언문 앞에 세미콜론을 접두어로 추가 ex) ;['a', 'b' ,'c']
  • 스크립트 병합시에도 위 다섯개의 문자 앞에 접두어를 붙이는것이 좋음

 

return문 뒤에오는 새로운 행은 세미콜론이 강제 삽입됨(동일한 케이스의 키워드들)

  • throw
  • 명시적 이름표가 있는 break, continue
  • ++ --

 

for루프의 head부분에 반드시 명시적으로 세미콜론을 포함해야함

for (var i= 0, total = 1 // parsing error
     i < n
     i++) {
     total +=i
}

function infiniteLoop() { while (true) } // parsing error


function infiniteLoop() { while (true); } // pass

item7. 문자열을 16비트 코드 단위의 시퀀스로 간주하라

유니코드는 유일한 정수값(코드 포인트) 에 세상 모든 글자가 문자단위로 할당된것

ASCII와 다른점 -> 각 인덱스가 유일한 바이너리 표현에 매핑됨

 

  • 유니코드 코드 포인트가 아니라 16비트 코드 유닛으로 구성됨
  • 코드포인드 2^16이상은 대리쌍인 두개의 코드 유닛으로 표현됨
  • 대리쌍은 문자열 요소 개수를 반환, length, charAt, charCodeAt 메서드와 정규표현식 패턴에 영향을 미침
  • 코드포인트를 다루는 문자열을 조작하기위해서는 서드파티 라이브러리 사용 권장

 

2. 변수 스코프


item8. 전역 객체의 사용을 최소화 하라

전역 변수를 정의하는것

  • 공통의 네임스페이스에서의 충돌가능성을 만듦
  • 불필요한 결합을 초래함
  • 모듈성에 반대
  • 구분된 요소들이 상호작요할수 있는 유일한 방법

 

전역객체

  • this키워드로 접근
  • 브라우저에서는 window변수로 바인딩

 

전역변수 정의방법

  1. 전역 scope에서 변수 정의 -> 선언되지않은 변수 참조시 오류발생 등으로 예상가능한 환경이 됨
  2. 전역객체에 prop으로 추가 -> 전역환경을 동적으로 반영하기때문에, 사용가능한 기능 탐지를 위해 사용
if (!this.JSON) {
    this.JSON = {
        parse: ...,
        stringify: ...
    };
}

item9. 항상 지역 변수를 선언하라

  • 새로운 지역변수 선언시 키워드 사용하여 의도치않게 전역변수로 선언되지 않도록함
  • 바인딩 되지않은 변수를 확인한데 도움되는 도구 lint를 사용

 


item10. with를 사용하지 마라

객체에서 프로퍼티를 추출해 내고 블록내의 지역변수로 바인딩
function status(info) {
    var widget = new Widget();
    with (widget) {
        setBackground("blue");
        setText("hello");
        show();
    }
}

 

with 블록 내부에서 주어진 변수이름을 가진 프로퍼티부터 찾기 시작함

with에 제공된 객체에 어떤 프로퍼티가 있는지 확신할 수 없으므로 변수 스코프와 객체 네임스페이스 사이의 충돌이 가능

with블록은 본문의 모든 변수를 찾기위해 객체의 prototype 체인을 탐색하게 되고 일반블록보다 현저히 느리게 실행됨

 

대안

  1. 인스턴스명을 짧게짓고 변수로 여러번 메서드를 호출하는것
  2. 명시적으로 지역변수를 객체 프로퍼티에 바인딩
Math.x = 0
Math.y = 0

item11. 클로저에 익숙해져라

  1. 현재함수 외부에서 선언된 변수를 참조할 수 있다
  2. 함수는 외부 함수가 무언가를 리턴한 후에도 이 외부 함수에 선언된 변수를 참조할 수 있다.
    1. => 함수를 리턴한 변수에서 외부 함수의 변수를 참조하는경우 이미 리턴된 후에도 해당 변수를 참조할수있음
    2. 함수 호출시 실행코드 및 해당 스코프에서 참조할수 있는 변수를 내부적으로 보관
    3. 함수 자신이 포함하는 스코프의 변수들을 추적하는 함수를 클로저라고함
  3. 클로저가 외부변수의 값을 변경할 수 있다

 

클로저를 생성하기 위한 편리한 방법 -> 함수 표현식

function test(stringTest) {
    return function(filling) { // 호출이 아닌 새로운 함수값 평가 용도로만들어져 익명 가능
        return stringTest + filling
    }
}

item12. 변수 호이스팅에 대해 이해하라

선언, 할당 두부분으로 나누어서 이해

  • 함수의 맨 윗부분으로 선언이 호이스팅됨
  • 변수의 재선언은 하나의 변수처럼 처리
  • 혼란을 막기위해 지역변수를 직접 호이스팅하는것을 고려

item13. 지역 변수 스코프를 만들기 위해 즉시 실행 함수 표현식을 사용하라

 

클로저는 외부 변수의 값이 아니라 참조를 저장함

따라서 클로저로 참조하는 변수가 호이스팅 되는경우 마지막 값만을 가지게 되므로

이를 피하기 위해 강제로 지역스코프를 만들고 즉시 실행하는 방법으로 이문제를 해결함

 

  • 바인딩과 할당의 차이점을 이해
  • 클로저는 외부 변수의 값이 아닌 참조를 저장
  • 지역 스코프를 만들기 위해 즉시 실행 함수 표현식을 사용 -> 현재는 블록으로도 스코프를 만들수있지않나?

item14. 기명 함수 표현식의 스코프에 주의하라

function double(x) { return x *2 } // 함수 선언문

var f = function double(x) { return x *2 } // 기명 함수 표현식

var f = function(x) { return x *2 } // 익명 함수 표현식

* 기명함수표현식은 익명 함수 표현식과 달리 그 이름을 함수내의 지역변수로 바인딩한다는 차이가 있음

이를 이용해 재귀함수 표현식이 작성 가능함

var f = function find(tree, key) {
	if (!tree) {
    	return null;
    }
    retrun find(tree.left, key);
};

* find는 그 함수 자신 내부에서만 스코프가 적용되고 외부에서는 참조할 수 없음

 

  • Error객체와 디버거에서 스택 추적 개선을 위해 기명함수 표현식 사용
  • 기명함수 표현식 사용을 자제하고 배포전에 제거
  • es5 환경이면 걱정 ㄴㄴ

item15. 블록-지역 함수 선언문의 스코프에 주의하라

  • 실행 환경에 따라 다르게 동작할 여지가 있으므로 함수선언문은 가장 바깥에 두어야함
function f() {return 'global';}
function test(x) {
	var g = f, result = []; // 선언문을 지역블록이나 하위명령에 두지않음
    if(x) {	// 지역블록
    	g = function() {reutrn 'local';
    }
}
  • 조건적인 함수 선언보다는 조건적 할당문을 통해 var 선언을 사용

item16. eval을 이용해 지역변수를 생성하지 마라

function test(x) {
	eval("var y = x;");
    return y
}
test("hello"); // hello


var y = 'global';
function test(x) {
	if (x) {
    	eval("var y = 'local';"); //동적 바인딩
    }
    return y;
}
test(true); //local
test(false); //global

*var 선언문이 test 함수 본문에 포함된것과는 다름

-> var선언문은 오직 eval함수가 호출될때에만 실행

 

외부 호출자가 함수내부의 스코프를 변경할수 있으면 안됨

-> 선언문을 인자로 받는경우

 

대안

 -> eval 이 외부 스코프에 영향을 끼치지 않도록 즉시실행함수로 감싸 스코프를 제한해줌

 

  • 호출자의 스코프를 어지럽힐수 있는 eval을 통한 변수 생성 자제
  • eval 코드가 전역변수 생성 가능성 있으면 함수 내에서 호출

 


item17. 직접적인 eval 보다 간접적인 eval을 사용하라

 

직접적인 eval

var x = 'global';
eval("x");	// 직접적인 eval

var f = eval;
f('x')	//간접적인 eval

(0, eval)(src);	//간접적인 eval을 만들어낼수 있는 유일한 방법

 

직접적인 eval 을 포함한 함수는 가장 바깥에 위치한 함수까지 모든 함수를 느리게함

지역스코프를 조사해야하는 추가적인 능력이 확실히 필요한 경우에만 사용

 

 

  • eval을 무의미한 리터럴과 함께 연속 표현식으로 감싸서 간접적으로 사용하도록 강제
  • 간접적 eval 사용 권장

'Front-end > Javascript' 카테고리의 다른 글

Effective Javascript(5,6,7장)  (0) 2023.07.31
Effective Javascript(3,4장)  (0) 2023.07.30
Core javascript(02-실행 컨텍스트)  (0) 2023.05.02
Core javascript(01-데이터 타입)  (0) 2022.11.23