즉시 실행 함수


  • 즉시 실행 함수 패턴은 함수가 선언되자마자 실행되도록 하는 문법이다.
  • 즉시 싱행 함수 함수 패턴은 다음의 부분들로 구성된다.
  • 함수를 함수 표현식으로 선언한다. (함수 선언문으로는 동작하지 않는다.)
  • 함수가 즉시 실행될 수 있도록 마지막에 괄호쌍으로 추가한다.
  • 전체 함수를 괄호로 감싼다. (함수를 변수에 할당하지 않을 경우에만 필요하다.)
  • 즉시 실행 함수는 모든 코드를 지역 유효범위로 감싸고 어떤 변수도 전역 유효범위로 새어나가지 않게 한다.

(function(){
var days = ['sun', 'mon', 'tue', 'wed', 'the', 'fri', 'sat'],
today = new Date();
msg = 'today is ' + days[today.getDay()] + ', ' + today.getDate();

console.log(msg)

}()) // 'today is tue, 6'



즉시 실행 함수의 매개변수


즉시 실행 함수에 인자를 전달할 수도 있다.

// 출력결과:
// 'i met joe black on Tue Aug 06 2019 14:59:48 GMT+0900 (대한민국 표준시)'
(function(who, when){

console.log('i met ' + who + ' on ' + when);

}('joe black', new Date()));


전역 객체를 global로 참조가 가능하다.


(function(global){

// 전역 객체를 'global' 로 참조
}(this));



즉시 실행 함수의 반환 값


다른 함수와 비슷하게 즉시 실행 함수도 값을 반환할 수 있고 반환된 값은 변수에 할당될 수 있다.

var result1 = (function(){
return 1 + 1;
}());

// 괄호를 생략해서 같은 동작을 구현할 수 있다.
var result2 = function(){
return 2 + 2;
}();

// 즉시실행 함수에서 미리 계산하여 클로저에 저장해둔 res라는 값을 반환
var getResult = (function(){
var res = 2+2;
return function(){
return res;
}
}())

console.log(getResult());


즉시 실행 함수는 객체 프로퍼티를 정의할 때에도 사용할 수 있다.


var o = {
message : (function(){
var who = 'me',
what = 'call';
return what + ' ' + who;
}()),
getMsg : function(){
return this.message;
}
};

// 사용방법
o.getMsg(); // call me
o.message; // call me


자기 자신을 정의하는 함수


함수는 동적으로 정의할 수 있고 변수에 할당할 수 있다. 어떤 면에서는 이전의 함수 포인터가 새로운 함수를 가리키도록 재사용하는 것이다.

var scareMe = function(){
console.log("boo");
scareMe = function(){
console.log("double boo");
};
};

// 자기 자신을 정의하는 함수를 사용
scareMe(); // boo
scareMe(); // double boo


이 패턴은 함수가 어떤 초기화 준비 작업을 단 한 번만 수행할 경우 유용하다.


단점은 자기 자신을 재정의한 이후에는 이전에 원본 함수에 추가했던 프로퍼티들을 모두 찾을 수 없게 된다는 점이다. 또한 함수가 다른 이름으로 사용된다면 재정의된 부분이 아니라 원본 함수의 본문이 실행된다.

1. 새로운 프로퍼티가 추가된다.

2. 함수 객체가 새로운 변수에 할당된다.

3. 함수는 메서드로도 사용된다.


var scareMe = function(){
console.log("boo");
scareMe = function(){
console.log("double boo");
};
};

// 자기 자신을 정의하는 함수를 사용
// scareMe(); // boo
// scareMe(); // double boo

// 1. 새로운 프로퍼티가 추가된다.
scareMe.property = "property";

// 2. 함수 객체가 새로운 변수에 할당된다.
var prank = scareMe;

// 3. 함수는 메서드로도 사용된다.
var spooky = {
boo : scareMe
}

// 새로운 이름으로 호출한다.
prank(); // boo
prank(); // boo
console.log(prank.property); // property

// 메서드로 호출한다.
spooky.boo(); // boo
spooky.boo(); // boo
console.log(spooky.boo.property); // property

// 자기 자신을 재정의한 함수를 사용한다.
scareMe(); // 'double boo'
scareMe(); // 'double boo'
console.log(scareMe.property); // undefined


예제에서 보는 것처럼 함수가 새로운 변수에 할당되면 예상과 달리 자기 자신을 정의하지 않는다.

콜백 패턴


함수는 객체다. 즉 함수를 다른 함수에 인자로 전달할 수 있다. introduceBugs() 함수를 writeCode() 함수의 인자로 전달하면, 아마도 writeCode()는 어느 시점에 introduceBugs()를 실행(또는 호출)할 것이다. 이때 introduceBugs()를 콜백함수 또는 간단하게 콜백이라고 부른다.

function writeCode(callback){
// 어떤작업을 수행한다.
callback();
// ...
}

function introduceBugs(){
console.log('bug');
}

writeCode(introduceBugs); // bug


introduceBugs()가 writeCode()의 인자로 괄호없이 전달된 사실을 눈여겨 보자. 괄호를 붙히면 함수가 실행되는데 이 경우에는 함수의 참조만 전달하고 실행은 추후 적절한 시점에 writeCode()가 해줄 것이기 때문에 괄호를 덧붙이지 않았다.



콜백 예제


아래 함수는 findNodes()와 같은 형식으로 호출되며 DOM 트리를 탐색해 필요한 엘리먼트의 배열을 반환한다.

var findNodes = function(callback){
var i = 100000, // 긴루프
nodes = [], // 결과를 저장할 배열
found; // 노드 탐색 결과

// 콜백 함수를 호출할 수 있는지 확인한다.
if(typeof callback !== "function"){
callback = false;
}

while(i){
i -= 1;
// 이 부분에 복잡한 로직이 들어간다.

// 여기서 콜백을 실행
if(callback){
callback(found)
}
nodes.push(found);
}
return nodes;
};

var hide = function(node){
nodes[i].style.display = "node";
}

// 노드를 찾아서 바로 숨긴다.
findNodes(hide);


이미 존재하는 함수를 콜백으로 함수로 쓸 수도 있지만 findNodes() 함수를 호출할 때 익명 함수를 생성해서 쓸 수도 있다.


// 익명함수를 콜백으로 전달한다.
findNodes(function(node){
node.style.display = "block";
})



콜백과 유효범위


만약 콜백 메서드가 자신이 속해있는 객체를 참조하기 위해 this를 사용하면 예상치 않게 동작할 수도 있다.
myapp이라는 객체의 메서드인 paint()함수를 콜백으로 사용한다고 가정해보자.

var myapp = {};
myapp.color = 'green';
myapp.paint = function(node){
node.style.color = this.color;
};

// findNodes() 함수는 이런 식으로 동작한다.

var findNodes = function(callback){
// ...
if(typeof callback === "function"){
callback(found)
}
}


findNodes(myapp.paint)를 호출하면 this.color가 정의되지 않아 예상대로 동작하지 않는다. findNodes()가 전역 함수이기 때문에 객체 this는 전역 객체를 참조한다. findNodes()가 (dom.findNodes()처럼) dom 이라는 객체의 메서드라면 콜백 내부의 this는 예상과는 달리 myapp이 아닌 dom을 참조하게 된다.


이 문제를 해결 하기 위해서는 콜백함수와 함께 콜백이 있는 객체를 전달하면 된다.


findNodes(myapp.paint, myapp);



var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}


다음과 같이 바꿀수도 있다. 메서드와 객체를 전달할 때 메서드를 문자열로 전달하게 되면 객체를 두번 반복하지 않아도 된다.


findNodes("paint", myapp);

var findNodes = function(callback, callback_obj){
// ...
if(typeof callback === "string"){
callback = callback_obj[callback];
}

// ...
if(typeof callback === "function"){
callback.call(callback_obj, found)
}
}



비동기 이벤트 핸들러


페이지의 엘리먼트에 이벤트 리스너를 붙이는것도 콜백이 발생하는 것이므로 document 이벤트 리스너에 콜백을 전달 할 수 있다.

document.addEventListener("onload", console.log('resdy'), false);



타임아웃


setTimeout()과 setInterval()도 콜백 함수를 받아서 실행 한다.

var thePlotThickens = function(){
console.log('500ms later...');
};
setTimeout(thePlotThickens, 500);


함수의 name 프로퍼티


함수를 정의하는 패턴을 선택할 때는 읽기 전용인 name 프로퍼티를 쓸 일이 있는지도 고려해보아야 한다. name 프로퍼티는 표준이 아니지만 많은 실행 환경에서 사용이 가능하다.

function foo(){} // 함수 선언문
var bar = function(){} // 함수 표현식
var baz = function baz(){} // 기명 함수 표현식

console.log(foo.name); // foo
console.log(bar.name); // ""
console.log(baz.name); // baz



함수 호이스팅


모든 변수는 함수 본문 어느 부분에서 선언되더라도 내부적으로 함수의 맨 윗부분으로 끌어올려 진다. 함수 또한 결국 변수에 할당되는 객체이기 때문에 동일한 방식이 적용된다. 함수 선언문을 사용하면 변수 선언뿐 아니라 함수 정의 자체도 호이스팅되기 대문에 자칫 오류를 만들어내기 쉽다.

// 안티패턴이다.
// 전역 함수
function foo(){
console.log("global foo");
}

function bar(){
console.log("global bar");
}

function hoistMe(){

console.log(typeof foo); //function
console.log(typeof bar); // undefined

foo(); //local foo"
bar(); // type error : bar is not a function

// 함수 선언문;
// 변수 'foo'와 정의된 함수 모두 호이스팅 된다.
function foo(){
console.log('local foo');
}

// 함수 표현식
// 변수 'bar' 는 호이스팅 되지만 정의된 함수는 호이스팅되지 않는다.
var bar = function(){
console.log('local bar');
}
}
hoistMe();


보다시피, hoistMe() 함수 내에서 foo와 bar를 정의하면, 실제 변수를 정의한 위치와 상관없이 끌어올려져 전역변수인 foo와 bar를 덮어쓰게 된다. 그런데 지역변수 foo()는 나중에 정의되어도 상단으로 호이스팅되어 정상 동작하는 반면, bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅 된다. 때문에 bar()의 정의는 호이스팅 되지 않고 선언문만 호이스팅된다. 때문에 bar()의 정의가 나오기 전까지는 undefined 상태이고, 따라서 함수로 사용할 수도 없다. 또한 선언문 자체는 호이스팅 되었기 때문에 유효범위 체인 내에서 전역 bar()도 보이지 않는다.

에러 객체


자바스크립트에는 Error(), SyntaxError(), TypeError() 등 여러 가지 에러 생성자가 내장되어 있으며 throw 문과 함께 사용된다.
이 생성자들을 통해 생성된 에러 객체들은 다음과 같은 프로퍼티를 가진다.

name
- 객체를 생성한 생성자 함수의 name 프로퍼티. 범용적인 'Error' 일 수도 있고 'RangError'와 같이 좀더 특화된 생성자일 수도 있다.

message
- 객체를 생성할 때 생성자에 전달된 문자열

throw문은 어떤 객체와도 함께 사용할 수 있다. 이 객체는 'name', 'message' 외에도 임의의 프로퍼티를 가질수 있기 때문에 catch문에서 처리할 정보를 담아 전달하면된다.

var num = 2;

try{
// 에러를 발생시킨다.
if(num === 2){
throw{
name : "MyErrorType", // 임의의 에러타입
message : "user message",
extra : "extra message",
remedy : genericErrorHandler // 에러 처리 함수
};
}
}catch(e){
// 사용자에게 공지
console.log(e.name);
console.log(e.message);
console.log(e.extra);

// 에러처리 함수 호출
e.remedy();
}
function genericErrorHandler(){
console.log('genericErrorHandler 입니다.');
}

/*
'MyErrorType'
'user message'
'extra message'
'genericErrorHandler 입니다.'
*/



아래와 같이 Error type 별로 세분화 할 수 있다.

try {
myroutine(); // may throw three types of exceptions
} catch (e) {
if (e instanceof TypeError) {
// statements to handle TypeError exceptions
} else if (e instanceof RangeError) {
// statements to handle RangeError exceptions
} else if (e instanceof EvalError) {
// statements to handle EvalError exceptions
} else {
// statements to handle any unspecified exceptions
logMyErrors(e); // pass exception object to error handler
}
}


JSON 다루기


JSON은 자바스크립트 객체 표기법(JavaScript Object Notation)의 준말로 데이터 전송 형식의 일종이다.
JSON을 다룰때는 JSON.parse()를 사용하는것이 최선책이다.

// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

// 안티 패턴
var data = eval('(' + jstl + ')');
console.log(data.mykey);

// 권장안
var data = JSON.parse(jstl);
console.log(data.mykey);


JUI3을 사용하면 다음과 같이 쓸 수 있다.


// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

// JUI 인스턴스를 사용하여 문자열을 파싱하고 객체로 변환한다.
YUI().use('json-parse', function(Y){
var data = Y.JSON.parse(jstl);
console.log(data.mykey); // myValue
})


jquery에는 parse.JSON() 이라는 메서드가 있다.


// 입력되는 JSON 문자열
var jstl = '{"mykey": "myValue"}';

var data = jQuery.parseJSON(jstl);
console.log(data.mykey); // 'myValue'


JSON.parse() 메서드의 반대는 JSON.stringify()다. 이 메서드는 객체 또는 배열을 인자로 받아 JSON 문자열로 직렬화 한다.


var dog = {
name : "fido",
dob: new Date(),
legs: [1,2,3,4]
};

var jsonstr = JSON.stringify(dog);
console.log(jsonstr);
// '{"name":"fido","dob":"2019-08-02T07:29:35.248Z","legs":[1,2,3,4]}'


배열 리터럴


자바스크립트의 배열은 이 언어 내 다른 모든 것들과 마찬가지로 객체다. 내장 생성자인 Array()로도 배열을 생성할 수 있지만 리터럴 표기법도 존재하며, 객체 리터럴과 마찬가지로 배열 리터럴 표기법이 더 간단하고 장점이 많다.


// 세 개의 원소를 가지는 배열
// 경고 : 안티패턴이다.
var a = new Array("itary", "bitsy", "spider");

// 위와 똑같은 배열
var a = ["itary", "bitsy", "spider"];

console.log(typeof a); // 배열도 객체이기 때문에 object 가 출력된다.
console.log(a.constructor === Array); // true



배열 생성자의 특이성


new Array(0를 멀리해야 하는 또다른 이유는 이 생성자가 품고 있는 함정을 피하기 위해서다. Array() 생성자에 숫자 하나를 전달할 경우, 이 값은 배열의 첫번째 원소 값이 되는 게 아니라 배열의 길이를 지정한다.

// 한 개의 원소를 가지는 배열
var a = [3];
console.log(a.length); // 1
console.log(a[0]) // 3

// 세개의 원소를 가지는 배열
var a = new Array(3);
console.log(a.length); // 3
console.log(typeof a[0]) // undefined


new Array()에 정수가 아닌 부동소수점을 가지는 수를 전달할 경우 더욱 예상 밖의 결과가 나온다. 부동소수점을 가지는 수는 배열의 길이로 유효한 값이 아니기 때문에 에러가 발생한다.


// 리터럴 사용
var a = [3.14]
console.log(a[0])

var a = new Array(3.14); // error: RangeError: invalid array length
conosle.log(typeof a); // undefined



배열인지 판별하는 방법


if(typeof Array.isArray === "undefined"){
Array.isArray = function(arg){
return Object.prototype.toString.call(arg) === "[object Array]";
}
}


new를 강제하는 패턴


생성자를 호출 할 때 new 를 빼먹게 되면 문법오류나 런타임에러는 발생하지 않지만 논리적인 오류가 생겨 예기치 못한 결과가 나올수 있다.
new를 빼먹으면 생성자 내부의 this가 전역 객체를 가리키게 되기 때문이다.

생성자 내부에 this.menber와 같은 코드가 있을 때 이 생성자를 new 없이 호출하게 되면 실제로는 전역 객체에 member라는 새로운 프로퍼티가 생성된다. 이 프로퍼티는 window.menber 또는 그냥 member를 통해 접근할 수 있다. 이런 동작 방식은 바람직 하지 않다.

// 생성자
function Waffle(){
this.tastes = "yummy";
}

// 새로운 객체
var good_morning = new Waffle();
console.log(typeof good_morning); //'object'
console.log(good_morning.tastes); //'yummy'

// 안티패턴
// 'new'를 빼먹을때
var good_morning = Waffle();
console.log(typeof good_morning); //'object'
console.log(window.tastes); //'yummy'


ECMAScript 5 에서는 이러한 동작 방식의 문제에 대한 해결책으로 스트릭트 모드 에서는 this가 전역 객체를 가리키지 않도록 했다.



명명 규칙


생성자 함수명의 첫글자는 대문자를 쓰고(MyConstructor), 일반적인 함수와 메서드의 첫글자는 소문자를 사용한다(myConstructor).


that 사용


생성자가 항상 생성자로 동작하도록 해주는 패턴을 살펴보자. this에 모든 멤버를 추가하는 대신, that에 모든 멤버를 추가한 후 that를 반환하는 것이다.

function Waffle(){
var that = {};
that.tastes = "yummy";
return that;
}


간단한 객체라면 that이라는 지역변수를 만들 필요도 없이 객체 리터럴을 통해 객체를 반환해도 좋다.


function Waffle(){
return {
tastes : "yummy"
}
}


위의 Waffle() 구현 중 어느 것을 사용해도 호출 방법과 상관없이 항상 객체가 반환된다.


var first = new Waffle(), second = Waffle();
console.log(first.tastes);
console.log(second.tastes);


위 패턴의 문제점도 있다. 프로토타입과의 연결고리를 잃어버리게 된다는 점이다. 즉 Waffle() 프로토타입에 추가한 멤버를 객체에서 사용할 수 없다.



스스로 호출하는 생성자


앞서 설명한 패턴의 문제점을 해결하고 인스턴스 객체에서 프로토타입의 프로퍼티들을 사용할 수 있게 하려면 다음 접근 방법을 고려하면 된다.
생성자 내부에서 this가 해당 생성자의 인스턴스인지를 확인하고 그렇지 않은 경우 new 와 함께 스스로를 재호출 하는 것이다.

function Waffle(){

if(!(this instanceof Waffle)){
return new Waffle();
}

this.tastes = "yummy";

}
Waffle.prototype.wantAnother = true;

// 호출 확인
var first = new Waffle(), second = Waffle();

console.log(first.tastes);
console.log(second.tastes);

console.log(first.wantAnother);
console.log(second.wantAnother);


인스턴스를 판별하는 또다른 범용적인 방법은 생성자 이름을 하드코딩하는 대신 arguments.callee와 비교하는 것이다.


if(!(this instanceof arguments.callee)){
return new arguments.callee();

** ES 5의 스트릭트 모드에서는 허용되지 않는다.

사용자 정의 생성자 함수


객체 리터럴이나 내장 생성자 함수를 쓰지 않고 직접 생성자 함수를 만들어 객체를 생성할 수도 있다.

var Person = function(name){
this.name = name;
this.say = function(){
return "I am " + this.name;
};
};

var adam = new Person("adam");
console.log(adam.say()); // 'I am adam"


new와 함께 생성자 함수를 호출하면 함수 안에서 다음과 같은 일이 일어난다.

  • 빈 객체가 생성된다. 이 객체는 this 라는 변수로 참조할 수 있고, 해당 함수의 프로토타입을 상속 받는다.
  • this로 참조되는 객체에 프로퍼티와 메서드가 추가된다.
  • 마지막에 다른 객체가 명시적으로 반환되지 않을 경우, this 로 참조된 이 객체가 반환 된다.

즉 다음과 같이 진행 된다고 할 수 있다.

var Person = function(name){
// 객체 리터럴로 새로운 객체를 생성한다.
// var this = {};

// 프로퍼티와 메서드를 추가한다.
this.name = name;
this.say = function(){
return "I am " + this.name;
};

// this를 반환한다.
// return this;
};


이 예제에서는 간단히 say()라는 메서드를 this에 추가했다. 결과적으로 new Persion()을 호출할 때마다 메모리에 새로운 함수가 생성된다. say()라는 메서드는 인스턴스별로 달라지는게 아니므로 이런방식은 비효율적이므로 이 메서드는 Persion의 프로토타입에 추가하는 것이  더 낫다.


var Person = function(name){
this.name = name;
};

Person.prototype.say = function(){
return "I am " + this.name;
}

var adam = new Person("adam");
console.log(adam.say()); // 'I am adam"


생성자 내부의 this는 Persion의 프로토타입을 상속 받는다. 즉 다음 코드에 더 가깝다.


var this = Object.create(Persion.prototype);



생성자의 반환 값


함수 내에 return 문을 쓰지 않았더라도 생성자는 암묵적으로 this를 반환한다. 그러나 반환 값이 될 객체를 따로 정할 수도 있다. 다음 예제에서 새로운 객체를 생성하여 that으로 참조하고 반환하는 것을 볼 수 있다.

var Objectmarker = function(){
// 생성자가 다른 객체를 대신 반환하기로 결정했기 때문에
// 다음의 'name' 프로퍼티는 무시된다.
this.name = "this is it";

// 새로운 객체를 생성하여 반환한다.
var that = {};
that.name = "And that's that";
return that;
}

// 결과
var o = new Objectmarker();
console.log(o.name); // 'And that's that'


객체 리터럴


자바스크립트에서 객체 라고 하면 단순히 이름-쌍 해시 테이블을 생각하면 된다. 다른 언어에서 연관배열이라 불리는것과 유사하다.
빈 객체를 정의해놓고 기능을 추가해 나갈 수도 있다. 객체 리터럴 표기법은 이처럼 필요에 따라 객체를 생성할 때 이상적이다.

다음 예제를 보자.
// 빈 객체에서 시작한다.
var dog = {};

// 프로퍼티 하나를 추가한다.
dog.name = "Benji";

// 이번에는 메서드를 추가한다.
dog.getName = function(){
return dog.name;
}

console.log(dog.getName());

  • 프로퍼티와 메서드 값을 변경할 수 있다.
dog.getName = function(){
// 메서드가 하드코딩된 값을 반환하도록 재정의한다.
return "Fido";
}

  • 프로퍼티나 메서드를 완전히 삭제한다.
delete dog.name;

  • 다른 프로퍼티나 메서드를 추가한다.
dog.say = function(){
return "Woof!";
}

dog.fleas = true;



반드시 빈 객체에서 시작해야 하는 것은 아니다. 객체 리터럴 표기법을 쓰면 다음 예제처럼 생성 시점에 객체에 기능을 추가할 수 있다.

var dog = {
name : "Benjo",
getName : function(){
return this.name;
}
};




객체 리터럴 문법

  • 객체를 중괄호( { 와 } )로 감싼다.
  • 객체 내의 프로퍼티와 메서드를 쉼표(,)로 분리한다. 마지막 이름-값 쌍 뒤에 쉼표가 들어가면 IE에서는 에러가 발생하므로, 마지막에는 사용하지 말아야 한다.
  • 프로퍼티명과 프로퍼티 값은 콜론으로 분리한다.
  • 객체를 변수에 할당할 때는 닫는 중괄호 뒤에 세미콜론을 빼먹지 말아야 한다.



생성자 함수로 객체 생성하기


자바스크립트에도 자바와 같은 클래스 기반 객체 생성과 비슷한 문법을 가지는 생성자 함수가 존재한다.
객체를 생성할 때는 직접 만든 생성자 함수를 사용할 수도 있고, Object(), Date(), String() 등 내장 생성자를 사용할 수도 있다.

다음 예제는 동일한 객체를 생성하는 두가지 방법을 보여준다.
// 첫 번째 방법 - 리터럴 사용
var car = {goes : "far"}

// 다른 방법 - 내장 생성자 사용
// 경고 : 이 방법은 안티패턴이다.
var car = new Object();
car.goes = "far";


+ Recent posts