30
Effective Javascript 1: 자바스크립트에 익숙해지기 (2014. 09. 20. / 류원경)

140920 Effective Javascript Study (Chapter 001)

Embed Size (px)

Citation preview

Effective Javascript

1장: 자바스크립트에익숙해지기(2014. 09. 20. / 류원경)

아이템1:

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

● 각 Javascript 엔진 / 브라우저별로사용가능한기능및문법이다름!! (2009년에공개된ECMAScript 5 기준과도상이)○ e.g. 상수를나타내는 const 키워드는사실 ES5 표준이아님.

● 따라서, 모든환경에서 ‘잘’ 작동하게하려면표준 ES5 문법을사용해야함.

● Parser에서표준 ES5 문법만허용케하려면“use strict” 명령을사용하여야함.

아이템1:

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

● “use strict” 사용법○ 소스파일최상단, 혹은특정 function 최상단에 “use

strict” 리터럴을넣음.

■ 키워드가아니라왜리터럴?: 하위호환을위해서.

예를들어 ES3 엔진으로소스를해석하더라도, 키워드가아니기때문에에러가발생하지않음.

○ 주의할점!

■ 여러파일을병합하는경우, strict 모드가아니어야하는데 strict 모드로인식되거나, 파일중간에포함되어 strict 모드여야하는데아니게인식되는등의오류가발생할가능성다분.

아이템1:

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

● (계속)

○ (계속)

■ 이러한이슈를미리예방하기위해, 실행할내용을즉시실행되는함수표현식(Immediately

Invoked Function Expressions, IIFEs)로감싼다음, 그안에서 “use strict”를선언.

● e.g. (function() { “use strict”; console.log(“hi”); })();

■ 위와같은해결방법에도불구하고, 개발자는작성하는코드가 strict 모드, 혹은일반모드일것이라미리가정하고코드를작성해서는안됨. ⇒의도적으로어떤모드에있건동일하게동작하도록코드를작성하는것이중요함.

아이템2:

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

● Javascript 에서는정수형, 부동소수점숫자모두 “number”형으로표현됨.

● 사실 Javascript의 number형은 IEEE 754 표준으로정의된 64비트배정밀도의부동소수점, 즉 “double”형임.○ 이러한체계하에정수는 53비트의정확도로표현됨.

-9,007,199,254,740,992(-2^53) ~

9,007,199,254,740,992(2^53), 즉정수는별개의데이터형이아니라, double의부분집합.

● 따라서, 대부분의산술연산(4칙및 mod) 역시정수와부동소수점의구분없이이루어짐.

아이템2:

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

● 그러나비트연산은차이가있음.○ 비트연산을위해피연산자들은 32비트 Big-Endian

정수로변환됨.

○ 연산을완료하고, 다시부동소수점으로재변환한뒤저장됨.

○ 그냥알고만있으면될듯..

● double, 즉 64비트의실수표현정확도는충분이넓지만, 여전히유한한숫자만표현할수있기에부동소수점산술연산은근사값을나타냄. 즉가장가까운표현가능한실수로반올림을함.○ e.g. 0.1 + 0.2; // 0.30000000000000004

아이템2:

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

● (계속)

○ 심지어결합법칙에의해그결과가달라지기까지함.

■ e.g. (0.1 + 0.2) + 0.3; // 0.60000000000000001

0.1 + (0.2 + 0.3); // 0.6

○ 보기에는미미한차이같지만, 이러한연산이누적되면결국엉뚱한결과가나타나기도함.

○ 대안: 단위를크게해서(e.g. 달러=>센트) 정수연산을할수있도록고안함.

아이템3:

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

● Javascript는형변환에매우관대함.

○ e.g. 3 + true; // 4

● 산술연산들은일반적으로숫자형으로형변환한후에연산함.

● 단, “+” 연산자는리터럴우선.

○ e.g. “2” + 3; // “23”

1 + 2 + “3”; // “33” => 좌측결합성(left-

associative)

1 + “2” + 3; // “123”

● 비트, 시프트연산은숫자로변환할뿐만아니라, 앞에서본것과같이 32비트정수로변환됨을유의.

아이템3:

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

● 단, 이러한편리한형변환은치명적인오류를숨길수도있음.

○ null 변수가산술연산에의해조용히 0으로변환되거나, undefined가조용히 NaN(Not a Number)로변환되어예상치못한결과를나타낼수있음.

○ 심지어 NaN은비교불가능한값이기에(e.g. NaN

=== NaN; // false) 혼란은더욱가중됨.

■ 단, NaN은 isNaN()으로비교가능하니불행중다행.

■ 그렇지만, 형변환으로인해 NaN으로바뀌기전값들은 isNaN()으로미리구별할수없으므로다행중불행.

아이템3:

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

● (계속)

○ 대안: “자기자신 !== 자기자신”을이용해서, NaN으로바뀌었는지아닌지를검출해라! ⇒ NaN으로바뀌었으면 true을뱉을것이다. 아니면 false를뱉을것이고.

● 객체는암묵적으로 toString()이호출되어원시데이터형중 ‘리터럴’로변환될수있지만,

valueOf()를통해 ‘숫자’로변환될수도있다.

○ 만약 “+” 연산과결합되는경우 toString()을통해 ‘리터럴'이반환될까? 아니면 valueOf()를통해 ‘숫자'가반환될까?

○ 사실 Javascript는보이지않게 valueOf()를먼저실행하고그값을 toString()하여불확실함을해소한다.

아이템3:

암묵적인형변환을주의하라● (계속)

○ 하지만문제는계속된다.

e.g.

var obj = {

toString: function() { // 오버라이딩하는거야..알지?

return “[object MyObject];

},

valueOf: function() {

return 17;

}};

“object: ” + obj; // 이러면 “object: 17” 이된다고!!

○ 왜? 앞에리터럴과의 “+”연산이모호하기에, valueOf()

를먼저콜하고그값을 toString() 하기때문에!

아이템3:

암묵적인형변환을주의하라● (계속)

○ 대안: 오버로딩된 “+”가항상일관성있게작동할수있도록, 객체가정말숫자형추상이아니라면 valueOf()가사용되지않도록한다.

● 트루시니스(truthiness): Javascript의거의모든값은논리연산시 ‘true’로나타나짐. 즉암묵적으로 ‘true’로강제형변환되는것임.(truthy)

○ 참고: ‘false’로나타나는것들 false, 0, -0, “”, NaN, null,

undefined

○ 따라서, ‘undefined’와 같은값을명확히구분하기위해서는 truthiness를이용하기보단 typeof를사용해야함.

(e.g. typeof x === “undefined” 혹은 x === undefined)

아이템4:

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

● Javascript에는여섯가지의원시데이터형이있음: 객체(Object), 불리언, 숫자, 문자열,

null, undefined

○ 참고: 헷갈리게도 typeof 연산자는 null의데이터형을“object”라고반환하지만, ES5 표준에서는 null을별도의데이터형으로기술함.

● 동시에 Javscript는불리언, 숫자, 문자열을객체처럼래핑하는생성자를제공함.

○ e.g. var str = new String(“Hello”);

str + “ World!”; // “Hello World!”

str[4]; // “o”

아이템4:

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

● 무슨차이냐고?

typeof “Hello”; // “string”

typeof str; // “object”

● 따라서..

var str1 = new String(“Hello”);

var str2 = new String(“Hello”);

str1 === str2; // false

이렇게된다는거지..

심지어 String객체는개별객체이기때문에자기자신과만동일하기에 str1 == str2; //

false

아이템4:

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

● 근데왜래퍼클래스를쓰냐고?

var str = new String(“Hello”);

str.someProperty = 17;

str.someProperty; // 17

이짓하려고… 객체니까!

● 단,

“Hello”.someProperty = 17;

“Hello”.someProperty; // undefined

이렇게되겠지.

● 그래도수많은오류를나타낼수있는객체래퍼보다는원시데이터를쓰자!

아이템5:

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

● ‘==’ 연산자는인자들이서로다른데이터형일때, 혼돈스러운형변환을진행한다!

○ e.g.

“1.0e0” == {valueOf: function(){return true;}};

// 1 == 1 ⇒ true

● 비교가어떠한암묵적인강제형변환과도연관이없다는사실을코드를읽는 ‘사람'에게명시적으로나타내기위해서 ‘===’를사용해야한다.

● 더불어비교할값이서로다른데이터형이라면, 프로그램의동작을더명백히하기위해직접명시적인강제형변환을해주는것이좋다.

아이템6:

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

● Javascript에서는문장종료를의미하는 “;”을생락할수있음.

● 엔진이자동으로 “;”을삽입시켜주기때문인데, 어떠한상황에서는삽입이되고, 어떠한상황에서는삽입이되지않는지가아주명확하게 ES5 표준스펙에기술되어있기에이규칙을잘알고있다면 ‘편리함’과 ‘명확한실행’

을둘다잡을수있음.

아이템6:

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

● 세미콜론삽입규칙○ 세미콜론은한줄이상의새로운행이나, 프로그램입력의마지막이나, “}” 토큰전에만삽입된다.

○ 세미콜론은다음입력토큰을파싱할수없을때에만삽입된다.

■ e.g. a = b

(f());

⇒세미콜론이삽입되지않아 a = b(f()); 로파싱되지만,

a = b

f();

⇒여기서는두개의구분된선언으로파싱된다.

왜냐하면 a = b f(); 는문법오류이기때문이다.

아이템6:

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

● (계속)

○ (계속)

■ 특히다섯문자 (, [, +, -, /의사용시조심해야함.

문맥에따라표현식연산자로동작하거나, 선언의접두어로사용될수있기때문.

● e.g.

a = b

[“r”, “g”, “b”].forEach(function(key)){ doSomething(); }); 는a = b[“r”, “g”, “b”].forEach( … )로오해받을수있음.

아이템6:

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

● (계속)

○ (계속)

■ (계속)

● e.g.

a = b // 세미콜론이추정되어삽입됨var x // 세미콜론이추정되어삽입됨(f()) // 세미콜론이추정되어삽입됨

var x // 세미콜론이추정되어삽입됨a = b // 세미콜론이삽입되지않음!

(f()) // 세미콜론이추정되어삽입됨⇒ var x;

a = b(f()); 처럼보임.

아이템6:

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

● (계속)

○ (계속)

■ 여러 js 파일을병합하는경우내윗(앞) 파일의소스에세미콜론이생략되어에러(문법혹은논리)

가발생할여지가있으므로, 방어적으로(function(){ // do something.. })() 앞에세미콜론을붙여주는습관을들이는것이좋다.

⇒ ;(function(){

// do something..

})()

아이템6:

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

● (계속)

○ (계속)

■ “return”문은이러한이유로강제로세미콜론이추가되는제한된생성(restricted production)의대표적인키워드이다. 즉, 두토큰사이에새로운행이허용되지않는다는것.

● e.g.

return

{ };

는반드시return;

{ }

;

로파싱된다.

아이템6:

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

● (계속)

○ (계속)

■ 이러한제한은 “throw”, “break”, “continue”, “++나-- 같은단항연산접두어"에도적용된다.

● e.g

a

++

b

는반드시a; ++b; 로파싱된다.

아이템6:

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

● (계속)

○ 세미콜론은 for 반복문의구분자나빈선언문으로절대삽입되지않는다.

■ e.g. (1) for 문구분자for (var i = 0

i < 100

i++) {

sum += i

}

⇒바로문법오류행e.g. (2) 본문이비어있는루프function looooop() { while (true) }

⇒ while(true); 가되어야한다.

아이템7:

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

● 유니코드..에대해서는여기서설명할내용은아닌것같고..

● 여튼, 옛날엔유니코드중 0x0000 ~ 0xFFFF

(0 ~ 65535, 즉 2^16)의테이블을만들어놓고(이테이블을 ‘기본다중언어평면/Basic

Multilingual Plane, BMP’이라고함.) 거기에각코드포인트마다한글자씩할당해주는UCS-2가사용되었음.

‘h’ ‘e’ ‘l’ ‘l’ ‘o’

0x0068 0x0065 0x006c 0x006c 0x006f

0 1 2 3 4

아이템7:

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

● 그런데, 세상이좋아지고더많은언어나기호를추가.. 즉더많은코드포인트가필요해졌다. 그래서 UTF-16에서는 ‘대리쌍’이추가되어두개의코드포인트를조합하여하나의문자로만드는방법을사용하게되었다.

예를들어 ‘𝄞(높은음자리표)’는 0xd834와0xdd1e를조합하여만들어진다. (단, 가변)

‘𝄞’ ‘ ‘ ‘c’ ‘l’ ‘e’ ‘f’

0xd834 0xdd1e 0x0020 0x0063 0x006c 0x0065 0x0066

0 1 2 3 4 5 6

아이템7:

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

● Javascript에서의문제는여기에있다.

“𝄞 clef”.length; // 7.. 분명빈칸까지해서 6인데?

“𝄞 clef”.charCodeAt(0); // 55348 (0xd834)

“𝄞 clef”.charCodeAt(1); // 56606 (0xdd1e)

“𝄞 clef”.charAt(1) === “ “; // false

“𝄞 clef”.charAt(2) === “ “; // true

● …?‘𝄞’ ‘ ‘ ‘c’ ‘l’ ‘e’ ‘f’

0xd834 0xdd1e 0x0020 0x0063 0x006c 0x0065 0x0066

0 1 2 3 4 5 6

아이템7:

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

● 이렇게유니코드전체영역을처리하는어플리케이션을만들기위해서는정규표현식을사용하거나라이브러리의도움을받는것이좋음.

(직접구현하기는매우까다로움. 라이브러리를쓰세요.)

‘𝄞’ ‘ ‘ ‘c’ ‘l’ ‘e’ ‘f’

0xd834 0xdd1e 0x0020 0x0063 0x006c 0x0065 0x0066

0 1 2 3 4 5 6

참고문헌 / 보충자료

● 따윈없다.

● 라기보단유니코드에대해좀더알고싶으면편하게읽을수있는글들.

○ 엔하위키:

http://mirror.enha.kr/wiki/%EC%9C%A0%EB%8B%88

%EC%BD%94%EB%93%9C

○ 위키백과(에서 UCS, UTF-8, UTF-16의차이를보면좋다. 더불어 BOM이나엔디안같은아티클도..):

http://ko.wikipedia.org/wiki/%EC%9C%A0%EB%8B%8

8%EC%BD%94%EB%93%9C

○ https://gist.github.com/kimdwkimdw/a2ea13848167984

adc8f 파이썬에서처리하는법(CJK 언어들이조금많이힘들더라고요)

끝!