Friday, June 10, 2016

ECMAScrtip2015 ( ES2015, ES6 )

아래의 내용은 codeschool의 ES2015를 요약한 것이다. 실제로 강의에서는 더 많은 예와 사용사례를 들지만 필요한 부분만 정리하였다.

let 변수
1. let 변수는 hoisting이 되지 않고 block 영역 내에서만 유효하게 된다.
function showUsers (users) {
  if (users.length >= 2) {
    var messagePlural = 'the plural';
  } else {
    var messageSingular = 'the singular';
  }
  console.log(messageSingular);}

var users = ['a', 'b', 'c'];
showUsers(users);

위 코드를 돌려보면 프로그램이 에러가 발생하지 않고 undefined가 콘솔에 찍히게 된다. 이는 javascript의 hoisting 이라는 특성때문인데 var 가 있는 변수를 프로그램이 시작하기 전에 가장 가까이에 있는 function의 최상단에 변수를 초기화 하기 때문이다. 곧 아래와 같이 작동하게 된다.

function showUsers (users) {
  var messagePlural, messageSingular;
  if (users.length >= 2) {
    messagePlural = 'the plural';
  } else {
    messageSingular = 'the singular';
  }
  console.log(messageSingular);}

var users = ['a', 'b', 'c'];
showUsers(users);

ES2015 에서는 let 이라는 키워드를 이용해서 선언된 변수는 function이 기준이 아닌 block ('{', '}' 으로 둘러쌓여 있는 코드 영역, 곧 if, else, for, while 등) 영역내에서 유의한 변수가 된다. 그래서 block 영역 이외에서 let 변수가 사용되어 지면 ReferenceError 가 발생하게 된다.

보통 for 문을 사용할 때 index에 var변수를 많이 사용하였는데 이는 아래와 같은 문제가 발생하게 된다. 
function changeProfile(profiles) {
  for (var i in profiles) {
    _update('users/' + profiles[i], function () {
      console.log(profiles[i]);
    });
  }}

changeProfile(['a', 'b', 'c'])
// c
// c
// c 가 출력됨

위 코드에서 var i 는 hoisting이 되어 for 문이 실행되기 전에 함수 상단에 선언되어 지고 for 문에 의해 _update의 인자로 주어진 각각의 콜백함수가 실행되기 전에 동일한 i를 갖게 끔 등록되어 진다. 이 후 실행이 되어지기 때문에 i가 갖는 최종값 2를 공유하게 된다.

이럴 경우 for 문에 var 대신 let을 사용하면 위와 같은 문제가 해결된다.

2. let 변수는 재선언을 해서는 안된다.
let 변수에 새로운 값을 할당하는 reassign은 가능하지만 재선언을 하면 TypeError 가 발생

let variable = 'good';
variable = 'best'; // reassigning 은 가능

let variable = 'good';
let variable = 'bad'; // redeclaring 은 불가능



Const 변수

const 변수는 읽기만을 위한 변수이다.
선언시 변수에 값을 할당(정의)을 해야 한다.
reassign이 안된다.
또한 let 변수와 마찬가지로 function 단위가 아닌 block 단위에서 유의한 변수이다.


functions
1. default parameter

javascript 는 parameter를 필요로 하는 함수에 paramter가 없이 호출을 해도 에러를 발생시키지 않는다. 그렇기 때문에 아래 countName1과 같은 방법으로 default paramter를 설정하였으나 ES2015 부터는 countName2 처럼 default paramter를 설정할 수 있다.

function countName1 (userNames) {
  let names = typeof userNames !== 'undefined' ? userNames : [];
  console.log(names.length);}

countName1();


function countName2 (userNames = []) {
  console.log(names.length);}

countName2();


2. named parameter

아래 코드에서 show1 함수의 두번째 인자 options은 object 이다. show1 코드에 options 에는 어떠한 property가 들어가야 하는지 직관적이지 않다. 이는 ES2015에서 show2 처럼 named parameter 를 사용하는 걸로 바꿀 수 있다. 

function show1 (name, options) {
  let color = options.color;
  let size = options.size;
  console.log(color);
  console.log(size);}

function show2 (name, {color, size}) {
  console.log(color); // color is a local variable.
  console.log(size); //}

show2('Tom', {color: 'blue'}) // 'size' will be undefined.
show2('Tom') // this will be error.


function show3 (name, {color, size} = {}) {
  console.log(color); // color is a local variable.
  console.log(size);
}

show2 를 사용하는데 있어 options 인자를 아예 주지 않고 사용하면 TypeError가 발생하게 되는데 이를 방지하기 위해 show3 처럼 default parameter를 지정할 수 있다.


Rest params, Spread op, Arrow Func
1. rest parameters
variadic function 이란 parameter의 수가 유동적인 함수를 의미한다. 이는 보통 아래 코드의 showUser1 처럼 구현한다. 그러나 이는 직관적이지 않고 또한 showUser1에 추가적으로 parameter가 필요할 경우 문제가 생길수 있다. 이를 대체하기 위한 방법으로 ES2015에서는 rest paramter를 사용한다.

function showUsers1 () {
  for (let i in arguments) {
    console.log(arguments[i]);
  }}

showUsers1('user1', 'user2', 'user3');

function showUser2 (...users) {
  for (let i in users) { // users == ['user1', 'user2', 'user3']
    console.log(users[i]);
  }}

showUser2('user1', 'user2', 'user3');

showUser2에서 처럼 인자 앞에 점 3개를 붙이면 이는 rest parameter를 의미하게 되고 넘겨받은 인자들을 array로 받게 된다. rest paramter 는 항상 마지막 인자로 사용되어야 한다.

2. spead operator 
위의 rest parameter 에서는 함수 정의에 사용된 점 3개는 분리된 인자를 하나의 array로 받겠다는 의미가 되고 반대로 함수의 호출에서 넘겨주는 인자에 점 3개를 사용하면 array를 분리된 인자로 변형시키겠다는 의미가 된다. 

function showUser (...users) { // users is an array.
  for (let i in users) {
    console.log(users[i]);
  }}

var users = ['user1', 'user2', 'user3']
showUser(...users);

3. arrow function 
javascript에서 함수는 호출이 되는 scope에 bind 하게 되어 지는데 arrow function 를 이용하면 함수가 정의된 scope에 bind하게 된다.
arrow function 의 형태는 아래와 같다. ES5에서의 보통의 함수 정의에서 function 키워드를 제거하고 "=>"를 붙인 형태로 나타낸다. 아래 코드는 동일한 기능을 한다. parameter가 한개일 때는 괄호를 생략할 수 있다.

// function 1
function (param) {
  statements
}

// arrow function 1
(param) => {
  statements
}

// arrow function 2
param => {
  statements
}

아래의 코드는 동일한 기능을 하는 코드이다. arrow function의 특징 중 한가지는 함수의 내용이 한줄의 표현식 밖에 없을 경우에는 대괄호 "{}" 를 생략 가능하고 이 경우에는 자동적으로 표현식 앞에 암묵적으로 return이 삽입된다. 그렇기 때문에 아래 코드의 arrow function 2와 같이 return을 생략해도 되는 것이다.

// function 1
function (param) {
  return expression
}

// arrow function 1
(param) => {
  return expression
}

// arrow function 2
param => expression

arrow function은 보통의 함수와는 달리 this와 arguments 객체를 갖지 않는다. 그렇기 때문에 lexial this 를 부모 함수와 공유하게 된다.


Objects and strings

Object initializer shorthand
object 의 property 명과 값을 가지고 있는 변수의 식별자가 동일한 경우 이를 축약해서 표현 가능하다. 아래 코드에서 name1 함수와 name2함수의 동일한 형태의 object 를 return하게 된다.

function name1 (first, last) {
  let fullname = first + ' ' + last;
  return {first: first, last: last, fullname: fullname};
}

function name2 (first, last) {
  let fullname = first + ' ' + last;
  return {first, last, fullname};
}

let first = "Good";
let last ="Boy";
let fullname = name1(first, last);

someoneName1 = {first: first, last: last, fullname: fullname};
someoneName2 = {first, last, fullname};

마찬가지로 someoneName1과 someoneName2는 동일한 형태의 object 를 갖는다.

function을 property로 갖을 경우 아래 코드의 makeName2에서 function 키워드를 생략하여 표현할 수 있다.

function makeName1 (first, last) {
  let fullname = first + ' ' + last;
  return {
    first,
    last,
    fullname,
    showName: function () {
      console.log(fullname)
    };
  };
}

function makeName2 (first, last) {
  let fullname = first + ' ' + last;
  return {
    first,
    last,
    fullname,
    showName () {
      console.log(fullname)
    };
  };
}

object destructuring
아래 코드와 같이 object에서 property 값을 받을 경우 {} 괄호를 써서 object 의 property를 풀어서 받을 수 있다. 아래 코드의 맨 아래 표현식에서 보듯이 전체 property가 아닌 특정 property만을 받을 수도 있다. 

function makeName (first, last) {
  let fullname = first + ' ' + last;
  return {first, last, fullname};
}

let someone = makeName('good', 'boy');
let first = someone.first;
let last = someone.last;
let fullname = someone.fullname;

let {first, last, fullname} = makeName('good', 'boy');

let {first, fullname} = makeName('good', 'boy');

template strings
template string은 back-ticks ( `` )에 의해 표현된다. 아래 코드의 fullname은 template string 으로 표현한 문자열이다. 또한 template string 를 이용하면  multiline line text를 쉽게 표현할 수 있다.

let first = 'good';
let last = 'boy';
let fullname = `${first} ${last}`;

let sentence = `
Hi, ${first} ${last}.
Nice weather !
`;

Object.assign
Object.assign 함수를 이용하면 object를 merge할 수 있다. parameter 중 뒷부분의 object값이 앞 object 의 property를 덮어 쓰게 된다. 그리고 첫번째 parameter인 object에 값을 변경하여 return하게 된다. 

let option1 = {first: 'good', last: 'girl'};
let option2 = {last: 'boy'};

let finalOption = Object.assign({}, option1, option2);
// finalOption = {first:'good', last:'boy'}와 동일

이는 함수의 default값을 설정할 경우 유용하게 사용되어 질 수 있다.

Array
array destructuring
아래 코드와 같이 array의 값을 개별 변수에 할당하는 방법이 있다.

let names = ['name1', 'name2', 'name3'];

let name1 = names[0];
let name2 = names[1];
let name3 = names[2];

let [name1, name2, name3] = names;

let [name1, , name3] = names;

위 코드의 마지만 표현식에서와 같이 불필요한 값은 skip할 수 있다.

또한 아래의 코드와 같이 위에서 알아본 rest params를 이용하여 나머지 값들을 하나의 변수에 array로 받을 수 있다.

let names = ['name1', 'name2', 'name3'];

let [name1, ...rest] = names
// rest = ['name2', 'name3'] 와 동일


for …of (array loop)
for of 문을 이용하여 array 의 값에 직접 접근 가능하다.

let names = ['name1', 'name2', 'name3'];

// for1 
for (let index in names){
  console.log(names[index]);
}

//for2
for (let name of names){
  console.log(name);
}

for …of는 plain javascript object(일반 객체) 에서는 사용할 수 없다. for …of는 Symbol.iterator라는 속성에 특별한 함수가 있는 객체에서만 사용가능하다. 

find element
아래 코드와 같이 array의 find함수에 test 함수를 인자로 호출하면 test 함수에서 true 값을 return하는 첫번째 element를 찾을 수 있다.

let users = [
  {name: 'name1', admin: false},
  {name: 'name2', admin: true},
  {name: 'name3', admin: true},
];

let admin = users.find( (user) => user.admin );

// admin = {name: 'name2', admin: true}; 와 동일

Maps
map 은 key/value 쌍의 집합인 자료 구조이다. 물론 object를 map 처럼 사용할 수 있으나 key 값을 string으로 처리하기 때문에 아래와 같은 문제가 생긴다.

let user1 = {name: 'Sam'};
let user2 = {name: 'Bill'};

let userProfile = {};
userProfile[user1] = 'student';  // userProfile['object Object'] = 'student'
userProfile[user2] = 'teacher';  // userProfile['object Object'] = 'teacher'

console.log(userProfile[user1]);  // teacher
console.log(userProfile[user2]);  // teacher

위 문제는 map 자료구조를 통해 해결할 수 있다.

let user1 = {name: 'Sam'};let user2 = {name: 'Bill'};

let userProfile = new Map();
userProfile.set(user1, 'student');
userProfile.set(user2, 'teacher');

console.log(userProfile.get(user1));  // student
console.log(userProfile.get(user2));  // teacher

특히나 runtime시 key 값이 결정되는 자료구조의 경우 map을 사용하는 것이 좋다. 또한 key들의 자료형이 동일하고 마찬가지로 value들의 자료형이 동일할 경우 map을 사용하는 것이 일관성있어서 좋다.

map을 사용할 경우 아래와 같이 for ..of 구문을 사용할 수 있다.

let mapData = new Map();

mapData.set('key1', 'value1');
mapData.set('key2', 'value2');
mapData.set('key3', 'value3');

for (let [key, value] of mapData) {
  console.log(key. value);}

WeakMap
key로 object 만 허용된 map 으로 primitive data type (stirng, number, boolean) 은 key로 사용할 수 없다. weakmap은 for ..of 구문을 사용할 수 없다.
weakmap의 장점은 메모리 효율에 있다. weakmap은 key로 사용하는 object의 garbage collection을 막지 않는다. 곧 weakmap에서 key로 사용되는 object 가 다른 곳에서 사용이 다 되어서 garbage collection이 되어 사라진다 하더라도 weakmap은 여전히 사용가능하다.

Sets
array와는 달리 unqiue한 element만 저장한다. 중복된 element는 대표인 하나의 element만 저장하는 자료 구조이다.

for ..of 구문을 사용할 수 있고 array destructuring이 가능하다.

let setData = new Set();
setData.add('Sam');
setData.add('Mike');
setData.add('Bill');
setData.add('Bill');

console.log(setData.size); // 3

for (let name of setData) {
  console.log(name);}

WeakSet
object만 저장할수 있다. WeakMap과 마찬가지로 gabage collection을 막지 않으며 for ..of 문을 사용할 수 없다.

Class
class syntax는 constructor function을 이용한 prototype-based inheritance의 syntactical sugar로 새로운 형태의 객체가 아닌 기존의 객체를 다른 방식으로 생성하는 것 뿐이다.

아래 코드에서 constoructor function 을 통한 객체 생성과 class 문법을 사용한 객체는 동일하다.

// Constructor function
function Person1 (name, age) {
  this.name = name;
  this.age = age;}

Person.prototype.action = function (something) {
  console.log(something);}

person1 = new Person1('Tom', 12);


// Class syntax
class Person2 {
  constructor (name, age) {  // new 키워드로 객체 생성 시 항상 실행되는 함수
    this.name = name;
    this.age = age;
  }
  action (something) { // instance method
    console.log(something);
  }
}

person2 = new Person2('Tom', 12);

_ (underscore)로 시작하는 이름의 method 는 암묵적으로 객체 내부에서만 사용하는 method를 의미하기 때문에 객체 외부에서는 호출하지 않는것이 일반적이다. 

prototype-based inheritance 는 extends 라는 키워드로 아래와 같은 방식으로 상속받는다.

class Human {
  constructor (name, age) {
    this.name = name;
    this.age = age;
  }
  eat () {
    console.log('eat')
  }}

class Student extends Human {
  constructor (name, age, grade) {
    super(name, age);  // 상속을 받았으면 항상 super를 호출해야 함.
    this.grade = grade;
  }
  study () {
    console.log('study')
  }
  eat() {
    super.eat(); // 부모 클래스의 메소드 호출
    console.log('eat a lot');
  }
  gettingOld () {
    this.age += 1;
  }}

let student1 = new Student('Tom', 14, 2);
student1.eat()
student1.gettingOld();
student1.age  // 15

Module
이전에 ECMAScript 에서 modularization을 하는 방법은 global variable을 사용하는 것이였다(html 파일에 <script> 태그를 이용하여 링크시킴). ECMAScript2015에서도 html에 <script> 를 통해 링크를 시키는 것은 동일하지만 아래 코드의 print-message.js 와 같이 global namespace를 사용하지 않기 때문에 부작용이 일어나지 않는다.

// print-message.js
export default function (message) {  // default 로 export 했기 때문에 임의의 이름으로 import 가능
  console.log(message);
}

// root.js
import print from './print-message';  // .js 확장자 생략 가능
print('good');

export default 로 정의된 module은 그 밖의 함수들은 외부에서 접근 불가능 하다.

// print-message.js
export default function (message) {  // default 로 export 했기 때문에 임의의 이름으로 import 가능
  console.log(message);
}

function print2 (message) { // export default 때문에 이 함수는 외부에서 호출 할 수 없다.
  console.log(message);
}

이럴 경우 default 키워드를 사용하지 말고 외부에서 접근 가능하게 하고자 하는 함수 앞에 export 키워드를 붙인다. 모듈을 호출하는 파일에서는 import 시 함수이름을 모듈에서 정의한 함수 이름과 동일하게 해야 한다.

// print-message.js
export function print1 (message) {
  console.log(message);
;}

export function print2 (message) {
  console.log(message);
}

// root.js
import {print1, print2} from './print-message'; // 호출하는 함수의 이름이 함수 정의에 사용된 이름과 동일해야 함.
print1('good');
print2('best');

// anotherRoot.js
import * as print from './print-message';
print.print1('good');
print.print2('best');

아래와 같은 방법으로 module의 함수들을 export 할 수도 있다.

// print-message.jsfunction print1 (message) {
  console.log(message);}

function print2 (message) {
  console.log(message)}

export {print1, print2};

const 변수와 class 도 위의 function과 같은 방법으로 export 할 수 있다.

Promise
Javascript 에서 서버와의 통신등으로 인해 발생할 수 있는 "main thread 의 block" 상황을 막기 위해서 callback 함수를 인자로 함수를 호출하는 식의 continuation passing style로 구현을 하는 경우가 많다. 이럴 경우 callback 함수에서 또 다른 callback을 던지는 경우 굉장히 복잡한 nested code가 된다. 또한 던져진 callback 함수에서 매번 error을 체크하는 코드를 넣어야 하는 불편함이 있다.

아래와 같이 return을 promise로 받으면 then 메소드를 이용하여 코드를 단순화 할 수 있다

// Define promise function getDataFromServer (name) {
  return new Promise(resolve, reject) {
    let url = `user/${name}`;
    let request = new XMLHttpRequest();
    request.open('GET', url, true);

    request.onload = function () {
      if (request.status >= 200 && request.status <400) {
        resolve(JSON.parse(request.response)); 
      } else { 
        reject(new Error(request.status)); // error 를 던지면 promise의 catch 메소드로 직행 
      }
    };
    request.onerror = function () {
      reject(new Error('error'));
    };
  }
}

// use promise 
getDataFromServer('Sam') 
  .then(someFilter) // promise의 then은 또 다른 promise를 return. 
  .then(doSomething) 
  .catch(function (err) { console.log(err); }); // promise가 error를 던지면 catch 메소드로 직행

Iterators
array, map, set 같은 경우 for ..of 구문을 사용하면 iterator object를 반환하게 된다. 아래 코드에서 보듯이 for ..of 문은 실제로주석에 표시되어 있는 것처럼 각 자료형의 Symbol.iterator 속성의 호출로 iterator 객체가 생겨서 일어나는 것이다.

let names = ['Jack', 'Bill', 'Tom'] ;

for (let name of names) { // let iterator = names[Symbol.iterator]()
  console.log(name);        // let firstRun = iterator.next()  //   firstRun == {value: 'Jack', done: false}
}                                     // let name = firstRun.value
                                      // let secondRun = iterator.next() ...

iterator의 next 메소드를 호출하면 done과 value 속성을 갖는 객체가 return 되고 done이 false 이면 for 문이 계속 돌게 되는 것이다.

plain javascript object의 경우 Symbol.iterator 속성이 없기 때문에 iterator object를 생성할 수가 없다. 그러나 Symbol.iterator를 구현해 주면 iterator 객체를 return 할수 있게 되고 for ..of 구문과 array destructuring에서 사용가능해 진다.

Generators
함수의 이름 앞에 * 기호를 사용하면 generator function이 된다. generator function은 return 대신 yield를 사용할 수 있다 (* 기호는 함수 이름앞에 붙여도 되고 띄어도 상관없다).

function * getNames () {
  yield "Jack";  // {done: false, value: 'Jack'};
  yield "Bill";  // {done: false, value: 'Bill'};
  yield "Tom";  // {done: false, value: 'Tom'};}

for (let name of getNames()) {
  console.log(name);}

let names = [...getNames()];

let [name1, name2] = getNames();

generator 함수는 iterator object와 같은 next 메소드를 갖는 객체를 return 하기 때문에 generator function에 의해 return 된 object는 for ..of, spead operator, destructuring 에서 사용가능하다.

이 genrator를 이용하면 plain javascript object 의 Symbol.iterator 속성의 구현을 쉽게 할 수 있다.




















이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Thursday, June 2, 2016

Javascript. The core.

Javascript 에 관련된 글 중 가장 인상 깊었던 글을 번역해 보려 한다. 중요 용어(object 등)는 영어를 그대로 사용하려 한다 (비록 괄호 안에 해석을 넣긴하겠지만). 원문에서는 각 섹션마다 관련이 있는 글에 대한 링크가 있으나 이 번역에서는 그 링크를 담지 않는다. 

변역해보고 나서 알게 된 사실이 이미 다른 분이 각 섹션에 링크되어 있는 detail을 번역해 놓으셨다. http://huns.me/?s=ECMA&x=0&y=0 에 보면 각 섹션의 detail 내용을 볼수 있다.


이 글을 ECMA-262-3 in detail 시리즈에 대한 개요와 서머리에 관한 것이다. 각 섹션은 거기에 해당하는 챕터를 레퍼런스로 담고 있기 때문에 더욱 자세한 내용을 알고 싶다면 관련 글을 읽어보길 바란다.

이글의 대상 독자 : 숙련된 프로그래머

우리는 ECMAscript의 기초가 되는 object의 개념에서 부터 시작하도록 한다.

An object
ECMAscript 는 객체지향 언어로서 object(객체)를 다룬다. 물론 primitives (기본자료형) 도 있지만 필요에 따라 이 역시 object로 변환된다.
object란 property(속성)의 집합으로 하나의 prototype object(프로토타입 객체) 를 가지고 있다. phototype은 object 또는 null 값이다.

object의 기본적인 예를 봐보자. 객체가 가지고 있는 prototype은 [[Prototype]] 이라는 속성으로 가르키게 된다. 그러나 그림에서 우리는 __<내부속성>__ 처럼 괄호 대신 밑줄을 이용하도록 하겠다. 특히 prototype object를 가르키는 속성으로  __proto__를 사용하도록 하겠다. 

코드를 보면 

var foo = {
  x: 10,
  y: 20};

위 코드의 foo라는 object는 두개의 속성(property) 와 prototype을 가르키는 내부적인 속성인 __proto__ 속성을 가지고 있다.


무엇을 위해 이 prototype이 필요한 것일까? 이 질문에 답하기 위하여 prototype chain (프로토타입 체인) 이라는 개념에 대해 알아보자

A prototype chain
prototype obejct는 단순히 object 이고 그렇기 때문에 그것 또한 자신만의 property(속성)을 가지고 있다. 만약에 prototype의 __proto__ 속성이 null값이 아닌 또 다른 prototype을 가리키고 있다면 이것을 prototype chain(프로토타입 체인) 이라 한다.

prototype chain은 객체들의 유한한 사슬(chain)으로서 상속(inheritance)와 속성의 공유(shared property)를 구현하는데 사용되어 진다.

거의 모든 부분이 유사하고 약간의 부분만 다른 두 object(객체)  가 있다고 할때, 좋은 디자인은 이미 하나의 object에 구현되어 있는 코드를 재사용하는 것이다. class(클래스) 기반의 언어에서는 이와같은 코드의 재활용 문법을 class-based inheritance(클래스 기반 상속) 이라고 한다. 공통되는 기능을 class A에 구현하고 class B, C가 A로 부터 상속을 받아 약간의 추가적인 코드만 넣는 것이다.

ECMAScript 에서는 class의 개념이 없다. 그러나 코드 재사용 문법은 별반 다르지 않으며(심지어 어떤면에서는 더욱 유연하다) prototype chain 을 이용한다. 이와 같은 상속을 delegation based inheritance 또는 prototype-based inheritance 라 한다. 

class A, B, C에 비슷하게 ECMAscript는 a, b, c 라는 object를 생성한다. object a가 공통된 부분을 가지고 있고 b, c가 각각의 다른 부분(속성이나 메소드)를 갖는다.

var a = {
  x: 10,
  calculate: function (z) {
    return this.+ this.+ z;
  }};

var b = {
  y: 20,
  __proto__: a
};

var c = {
  y: 30,
  __proto__: a
};

// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80

쉽지 않는가? 우리는 object b, c가 a에 정의되어 있는 속성에 접근하여 이용하는 것을 볼 수 있다. 

규칙은 단순하다. 만약에 특정 속성이 object 그 자신에게서 발견이 되지 않는다면 prototype chain에 있는 object의 속성을 확인하는 것이다. 만약 prototype의 속성에도 특정 속성이 없다면 prototype의 prototype의 속성을 확인하는 식으로 계속 속성을 찾게 된다. prototype chain에서 처음으로 찾아지는 속성을 사용하게 된다. prototype chain 에 의해 연결된 어떠한 prototype에도 찾고자 하는 특정 속성이 없으면 undefined 값을 리턴하게 된다.

주목해야 할것은 this라는 키워드는 현재 사용되어 지는 object를 가르키게 된다. a object에 있는 this 는 b.calculate에서 b object가 현재 사용되어 지는 object이기 때문에 b를 가르키게 된다. this.x 에서의 this 는 b에서 x라는 property 찾을 수 없기 때문에 prototype chain에 의해 a에 있는 x 속성을 가르키게 된다.

만약에 object의 prototype이 명시 되어 지지 않았다면 __proto__라는 속성은 Object.prototype을 가르키게 되며 Object.prototype의 __proto__ 속성은 null 값을 가르키게 되고 이는 prototype chain의 마지막 링크가 된다.

아래 그림은 위 코드의 a, b, c object간의 상속 관계를 도식화 한 것이다.


ES5 에서는 아래와 같은 방법으로 prototype-based inheritance를 구현할 수 있다.

var b = Object.create(a, {y: {value: 20}});
var c = Object.create(a, {y: {value: 30}});

때때로 갖은 property 구조를 갖지만 그 값은 다른  object를 만들 필요가 있을 때가 있다. 이와 같은 경우 constructor function (생성자 함수) 를 사용할 수 있다.

Constructor
object 생성에 있어 constructor 함수는 또다른 유용한 방법이다. constructor함수를 이용해서 object 를 생성하게 되면 새로이 생성되는 object의 prototype object를 자동으로 설정할 수 있게 된다. constructor 함수에 의해 생성되는 object가 가르키게 될 prototype object 는 constructor 함수의 prototype 속성에 연결되어 있다 (주의 할것은 여태 prototype이라는 용어를 object를 의미하는 것으로 사용하였는데 constructor 함수의 prototype 속성은 말그대로 속성의 key 값을 의미한다).

prototype chain 섹션의 코드를 아래와 같이 constructor 함수를 이용한 방법으로 변환할 수 있다(object a의 역할을 여기서는 Foo라는 constructor 함수가 대신하게 된다). 

// a constructor functionfunction Foo(y) {
  // 이것이 constructor 함수 이다.
  // constructor 함수를 이용해 object를 생성시 y 값을 받아서
  // 생성된 object 자신만의 y값을 갖게 된다.
  this.= y;}
 
// also "Foo.prototype" stores reference
// to the prototype of newly created objects,
// so we may use it to define shared/inherited
// properties or methods, so the same as in
// previous example we have:

// inherited property "x"
Foo.prototype.= 10;

// and inherited method "calculate"
Foo.prototype.calculate = function (z) {
  return this.+ this.+ z;};

// now create our "b" and "c"// objects using "pattern" Foovar b = new Foo(20);var c = new Foo(30);

// call the inherited method
b.calculate(30); // 60
c.calculate(40); // 80

// let's show that we reference// properties we expect

console.log(

  b.__proto__ === Foo.prototype, // true
  c.__proto__ === Foo.prototype, // true

  // also "Foo.prototype" automatically creates
  // a special property "constructor", which is a
  // reference to the constructor function itself;
  // instances "b" and "c" may found it via
  // delegation and use to check their constructor

  b.constructor === Foo, // true
  c.constructor === Foo, // true
  Foo.prototype.constructor === Foo, // true

  b.calculate === b.__proto__.calculate, // true
  b.__proto__.calculate === Foo.prototype.calculate // true

);

위 코드는 아래와 같이 도식화 될 수 있다.
위 그림에서와 같이 constructor 함수에 의해 생성된 모든 object는 prototype object를 갖게 된다. Foo constructor 함수도 함수 이기 때문에 javascript에서는 함수도 객체이기에 __proto__ 속성을 가지고 있고 이는 Function.prototype 이라는 object를 가르키게 된다. 이 Function.prototype의 __proto__ 속성은 Object.prototype을 가르키기 된다. Foo의 prototype 속성은 object를 생성하는 constructor로서의 역할을 수행할때 생성되는 object의 prototype이 될 object를 가르키는 속성에 지나지 않는다. 

ES6 에서는 class 의 개념을 표준화 하여 constructor 함수 위에 class 라는 syntactic sugar를 만들었다.

// ES6class Foo {
  constructor(name) {
    this._name = name;
  }

  getName() {
    return this._name;
  }}

class Bar extends Foo {
  getName() {
    return super.getName() + ' Doe';
  }}

var bar = new Bar('John');
console.log(bar.getName()); // John Doe

이제 object의 특성을 바탕으로 ECMAScript가 어떻게 runtime program execution을 구현하는지 알아보자. 이것은 execution context stack(실행 문맥 스택)으로 불리는데 이것의 모든 요소들이 (ECMAScript가 항상 그렇듯) 추상적으로 object로 표현되어 진다.

Execution context stack
ECMAScript 코드에는 3가지 종류의 코드가 있다: global code, function code, eval code. 모든 코드는 execution context(실행 문맥) 안에서 평가되어 진다. global context(문맥)는 오직 하나만 있고 그 안에 많은 함수의 instance와 eval execution context(실행 문맥)들이 있다. 매번 함수가 호출될 때마다 함수 실행 문맥으로 들어가게 되고 함수 코드 타입을 평가하게 된다. 마찬가지로 eval 함수가 호출되면 eval 실행 문맥으로 들어가고 그것의 코드가 평가된다.

함수가 호출 될때마다 문맥이 생성되기 때문에 하나의 함수는 수많은 문맥을 생성할 수 있으며 아래 코드와 같이 서로 다른 문맥이 서로 다른 context state(문맥 상태)를 가지고 만들어 진다.

function foo(bar) {}

// call the same function,// generate three different// contexts in each call, with// different context state (e.g. value// of the "bar" argument)

foo(10);
foo(20);
foo(30);

곧 함수가 또 다른 함수를 호출할때와 같이 실행 문맥은 또다른 실행 문맥을 활성화 시킬수 있다. 이러한 상황은 stack(스택)으로 구현되어 있으며 이를 execution context stack (실행 문맥 스택) 이라 한다.

문맥을 활성화 시킨 문맥을 caller (호출자) 라 한다.  반면에 활성화 된 문맥은 callee (피호출자) 라 한다. 피호출자가 또 다른 문맥을 활성화 시키게 된다면(다른 함수를 호출하게 된다면) 이는 또 호출자가 된다.  

호출자가 피호출자를 활성화 시키면(한 함수가 다른 함수를 호출하면) 호출자는 자신의 실행을 중단하고 제어권을 피호출자에게 넘겨준다. 이는 피호출자가 스택에 쌓이게 되는 것이고 이 피호출자의 실행문맥이 활성화 되는 것이다(active execution context). 피호출자의 문맥이 끝나게 되면 제어권이 다시 호출자에게 넘어가고 나머지 문맥이 평가된다(혹은 실행된다). 피호출자는 단순히 return을 하거나 exception과 함께 끝나면서 스택에서 빠지게 된다.

곧  모든 ECMAscript 의 program runtime (프로그램 런타임)은 아래 그림과 같이 실행문맥 스택 (execution context stack = EC stack)으로 표현되며 이 스택에서 젤 위에 있는 문맥이 활성화 상태의 문맥 (active context) 가 된다.


위 그림을 자세히 설명하면, 프로그램이 실행될때 global EC 에 들어가게 되고 이는 스택에 맨 처음 쌓이게 된다. 그러면 global code에 따라 일부 초기화 (필요한 객체와 함수들이 만들어 지는것)가 이루어 진다. global 문맥이 실행되는 동안 global code가 다른 함수를 호출(활성화)하면 그 함수의 EC로 들어가게 되고 이 EC는 스택에 쌓이게 된다. 또는 초기화된 이후 runtime system(런타임 시스템)은 특정 이벤트가 발생하기를 기다리고, 특정 이벤트가 발생하면 이벤트에 관련되 함수가 호출되고(활성화) 그 함수의 EC로 들어가게 되며 이 함수의 EC가 스택에 쌓이는 것이다.

아래는 프로그램이 시작될때 global EC가 스택에 쌓이고, global code에서 특정 함수를 호출하여 특정함수의 EC가 스택에 추가 된 이후, 함수가 실행 완료되어 스택에서 없어진 프로세스를 나타내는 그림이다.

이것이 바로 ECMAScript의 runtime system이 어떻게 코드의 실행을 다루는가를 보여준다.

위에서 말했듯 스택에 있는 모든 실행 문맥은 객체로 나타내어 진다. 이제 실행문맥을 나타내는 객체의 구조와 property(속성)에 대해 알아보자


Execution context
execution context(실행 문맥)은 객체로 표현되어 진다. 모든 실행문맥은 context's state (문맥 상태) 라 불리는 속성(property)를 갖고 있다. 이 속성들을 통해 연관된 코드의 실행을 추적하게 된다. 아래는 문맥의 구조를 나타낸 그림이다.


실행문맥은 위의 3개의 속성(variable object, this, scope chain) 외에도 구현에 따라 추가적인 속성을 갖을 수 있다.

이 속성들에 대해 자세하게 알아보자

Variable object
variable object (변수 객체)는 실행문맥과 관련이 있는 데이터를 담는 그릇이다. 이것은 문맥에 정의된 variable(변수), function(함수)를 저장하고 있는 객체이다.

주의해야 할것은 함수 표현식 (함수 선언 말고) 은 변수 객체에 포함되지 않는다.

변수 객체는 추상적인 개념이다. 다른 문맥에서는 다른 객체로 표현된다. 예를 들면 global 문맥에서는 global object(객체) 자신이 변수 객체이다(그렇기 때문에 global 변수를 global object를 통해서 직접적으로 접근이 가능한 것이다). 

아래 global 실행문맥을 살펴보자.

var foo = 10;

function bar() {} // function declaration, FD(function baz() {}); // function expression, FE

console.log(
  this.foo == foo, // true
  window.bar == bar // true);

console.log(baz); // ReferenceError, "baz" is not defined

위 코드의 global 문맥의 변수객체(variable object) 는 아래와 같은 속성을 갖는 객체이다.


그림에서와 같이 baz 함수 표현식은 변수 객체에 포함되지 않는다. 이 때문에 baz 함수 자신 이외의 곳에서 baz를 호출하면 ReferenceError가 발생하게 되는 것이다. 

주의해야 할 것은 다른 언어와는 달리 ECMAScript는 오직 function(함수) 만이 scope(스코프)를 만든다. 특정 함수 안에서 생긴 변수나 또 다른 함수는 그 함수 밖에서는 보이지 않는다. 

eval 역시 새로운 실행 문맥을 생성하나 자신만의 변수 객체를 생성하지 않고 global 변수 객체를 이용한다던지 혹은 eval을 호출한 호출자 함수의 변수 객체를 이용한다. 

그러면 함수에서는 변수 객체는 무엇인가. 함수 문맥에서는 변수 객체가 activation object (활성 객체)로 표현되어 진다.

Activation object
함수가 다른 함수 혹은 global 문맥에서 호출되어 활성화 되면 변수 객체 역할을 하는 특별한 객체인 activation object (활성 객체)가 만들어 진다. 일단 활성객체는 함수가 받는 parameter(인자)와 특별한 객체인 arguments (인자들을 array 형태로 갖는 객체) 를 속성으로 갖게 된다. 그 뒤 이 활성객체는 변수 객체로 사용되어 진다. 즉 활성 객체는 인자, aruments를 추가적으로 포함한 변수 객체인 것이다.

아래 예시 코드를 보자

function foo(x, y) {
  var z = 30;
  function bar() {} // FD
  (function baz() {}); // FE}
 
foo(10, 20);

위코드 함수의 활성 객체는 아래 그림과 같다.


다시 한번 알수 있드시 함수 표현식을 활성 객체에 포함되지 않는다.

다음 색션에서는 scope chain(스코프 체인)에 대해 알아본다. ECMAScript 에서는 함수안에 또 다른 함수인 inner function (내부함수)를 사용할때가 있으며 이 내부함수에서 parent function (부모함수)의 변수에 접근할 수 있다. variable object(변수 객체)를 문맥의 scope object(스코프 객체)라 명명하였듯이 앞서 알아본 prototype chain 또한 소위 scope chain 이라 불린다.

Scope chain
scope chain 은 문맥 안의 코드에 나타난 identifier(식별자)를 찾기 위한 객체들의 리스트이다. 

scope chain(스코프 체인) 에서의 규칙은 prototype chain 에서의 규칙과 비슷하다. 특정 변수가 자신의 scope (변수 객체 혹은 활성 객체)에서 발견이 되지 않으면 부모의 변수 객체에서 찾는 것이다. 

문맥에 따라 identifier(식별자)는 변수의 이름, 선언된 함수명, 인자 등이 될 수 있다. 문맥의 변수 객체에서 찾을 수 없는 식별자를 free varible이라고 하는데 이 free variable을 찾고자 할 때 scope chain이 사용되어진다.

일반적인 경우 scope chain은 부모의 변수 객체와 자신의 변수 객체(부모의 변수 객체보다 앞에 위치함)의 리스트를 의미한다. 그러나 문맥의 실행 동안 다른 객체(with-objects 또는 catch-clauses의 특별한 객체)가 scope chain에 추가될 수 있다.

식별자를 찾을 때(look up) prototype chain의 방식과 유사하게 활성 객체부터 시작하여(함수의 경우) 상위의 부모 함수 변수 객체를 찾아보게 된다. 

var x = 10;

(function foo() {
  var y = 20;
  (function bar() {
    var z = 30;
    // "x" and "y" are "free variables"
    // and are found in the next (after
    // bar's activation object) object
    // of the bar's scope chain
    console.log(+ y + z);
  })();})();

scope chain 의 연결이 활성 객체의 __parent__ 속성에 의해 연결되어 있다고 여긴다. 이 __parent__ 속성이 부모의 활성 객체 (혹은 변수 객체)를 가리키게 된다. 이와 같은 방법이 ES5 lexical environments 에 사용된 방식(outer link라 칭한다)이다. scope chain를 나타내는 다른 방식은 단순히 참조가 되는 활성 객체를 순서대로 array에 담는 것이다. __parent__ 속성을 이용한 구현 방식을 이용하여 우리는 위의 코드를 아래와 같이 표현할 수 있다(부모의 활성 객체는 함수의 [[Scope]] 속성을 저장되어 있다. 주의해야 할 것은 __parent__ 속성은 활성 객체의 속성이고, [[Scope]]는 함수 자체의 속성이다). 


코드의 실행 중에 with문과 catch 절 객체 (clause object)에 의해  scope chain의 객체가 추가 될수 있다. 그리고 추가되는 객체가 단순히 객체이기 때문에 prototype을 가지고 있을 수 있다(prototype chain). 이같은 사실로 인해 scope chain을 통한 identifier(식별자) 검색이 two-dimensional (이차원) 적으로 이뤄질 수도 있다. (1) 먼저 scope chain 에 의한 링크가 고려가 되어 자신의 활성 객체 부터 부모들의 활성 객체까지 하나씩 깊이가 깊어지면서 (2) 그 단계에 있는 활성 객체가 가지고 있는 prototype chain에 연결되어 있는 모든 객체를 검사하게 되는 것이다.  

Object.prototype.= 10;

var w = 20;var y = 30;

// in SpiderMonkey global object// i.e. variable object of the global// context inherits from "Object.prototype",// so we may refer "not defined global// variable x", which is found in// the prototype chain

console.log(x); // 10

(function foo() {

  // "foo" local variables
  var w = 40;
  var x = 100;

  // "x" is found in the
  // "Object.prototype", because
  // {z: 50} inherits from it

  with ({z: 50}) {
    console.log(w, x, y , z); // 40, 10, 30, 50
  }

  // after "with" object is removed
  // from the scope chain, "x" is
  // again found in the AO of "foo" context;
  // variable "w" is also local
  console.log(x, w); // 100, 40

  // and that's how we may refer
  // shadowed global "w" variable in
  // the browser host environment
  console.log(window.w); // 20

})();

위 코드는 아래와 같은 구조를 갖게 된다(__parent__ 속성에 의한 링크를 검색하기 전에 __proto__에 의한 체인을 먼저 검색한다.).



주의해야 할 것은 모든 ECMAScript들의 구현에서 꼭 global 객체가 Object.prototype을 상속받는 것은 아니다라는 것이다(특정 구현에서는 위의 구조화가 틀릴 수도 있다).

 부모의 변수 객체가 존재할 때까지 inner function (내부 함수)에서 부모의 데이터를 찾는 것은 전혀 특별한 일이 아니다. 단순히 scope chain 을 통해서 필요한 변수가 있는지를 확인하는 것이다. 그러나 문맥이 끝나게 되면 문맥의 state (상태 값) 와 그 문맥 자체가 사라지게 된다. 어떤 경우에는 inner function (내부함수) 가 부모 함수의 return 값이 될수도 있다. 이렇게 리턴된 내부 함수가  free variable (자신의 활성 객체에서 찾을 수 없는 identifier)를 가지고 있을 때 활성화(호출)된다면 어떻게 될 것인가? 이와 같은 문제를 해결하기 위한 개념을 closure라 부르며 이것은 scope chain 개념과 연관되어 있다.


Closures
ECMAScript에서 함수는 first-class 객체이다. 이 말은 함수는 다른 함수의 인자로 전달되어 질 수 있다는 것이다(이런 경우 인자로 전달되어 지는 함수를 "functional arguments", 줄여서 "funargs"라고 한다). "funargs"를 인자로 받는 함수는 high-order function (고차원 함수) 또는 수학에 가까운 용어로 operator (연산자) 라고 한다. 또한 함수가 first-class 객체이기 때문에 다른 함수의 리턴 값으로 함수가 리턴 되어 질 수 있다. 다른 함수를 리턴하는 함수를 function valued 함수이라 한다.

"funargs" 와 "functional value"에 연관된 두가지 개념적인 문제가 있다. 이 두 문제는 "Funargs problem" 이라고 불리는 문제로 일반화 될 수 있다(A problem of a functional argument). 그리고 이 "Funargs problem"을 해결하기 위해 closure(클로져)라는 개념이 나타나게 된 것이다. 이 두 개념적 문제에 대해 자세하게 알아보자(이 두 문제가 이전 섹션에서 언급한 함수의 [[Scope]] 속성에 의해 해결된다는 것을 알게 될 것이다).

"Funargs problem" 의 첫번째 문제는 "upward funargs problem" 이다. 이것은 한 함수가 다른 함수에 의해 "up"(caller의 밖으로 리턴)되어 지고 이 리턴된 함수가 free variable 사용하고 있을 경우이다. 부모의 문맥이 종료되어 사라진 이후 리턴되어진 inner function (내부함수)에서 부모 함수의 변수에 접근하기 위해서 inner function이 만들어 질 때 inner function의 [[Scope]] 속성에 부모의 scope chain을 저장해 놓는다. 그리고 이후에 리턴된 inner function이 활성화(호출)되면 inner function의 문맥의 scope chain에는 활성 객체와 [[Scope]] 속성에 저장된 부모의 scope chain이 담기게 되는 것이다. 

Scope chain = Activation object + [[Scope]]

다시 한번 강조해야 할 부분은 리턴되는 inner function이 만들어 지는 순간 부모의 scope chain이 저장되어 지는 것이다. 그래야만 나중에 부모 함수(caller)가 사라지더라도 free varible를 찾을수 (look up 혹은 resolve) 있기 때문이다. 

function foo() {
  var x = 10;
  return function bar() {
    console.log(x);
  };}

// "foo" returns also a function// and this returned function uses// free variable "x"

var returnedFunction = foo();

// global variable "x"var x = 20;

// execution of the returned function
returnedFunction(); // 10, but not 20

이같은 스타일의 scope(스코프)를 static scope 혹은 lexical scope 라고 한다. 위 코드에서 리턴된 bar 함수의 [[Scope]] 속성을 통해서 x 변수가 찾아지는 것을 확인할 수 있다. lexical scope 외에도 dynamic scope 라는 것이 있는데 이 경우 리턴된 bar 함수에서 찾아진 x 변수의 값은 10이 아니라 20이 된다. 그러나 ECMAScript에서는 이 dynamic scope는 사용되지 않는다.

"funargs problem" 중 두번째 문제는 "downward funarg problem" 이다. 부모 문맥은 존재하지만 identifier(식별자)를 찾는데 애매한 경우를 이야기 한다. 다시 말해 문제는 어느 scope(스코프)에 있는 identifier를 사용해야하는 것인가 라는 것이다. 정적으로 함수가 생성될때 저장된 scope(정의 되었을 때의 부모의 scope chain)를 사용해야 하는 건지 실행될때 자신을 호출한 함수의 문맥에 있는 scope(caller 의 scope chain)를 사용해야 하는 건지의 문제인 것이다. 이같은 모호성을 해결하고 closure를 구성하기 위해 static scope(함수가 정의될때의 부모 scope를 사용하는 것)가 사용되는 것으로 정해졌다.

// global "x"var x = 10;

// global functionfunction foo() {
  console.log(x);}

(function (funArg) {

  // local "x"
  var x = 20;

  // there is no ambiguity,
  // because we use global "x",
  // which was statically saved in
  // [[Scope]] of the "foo" function,
  // but not the "x" of the caller's scope,
  // which activates the "funArg"

  funArg(); // 10, but not 20

})(foo); // pass "down" foo as a "funarg"

closure를 가지기 위한 필수 조건은 static scope 이다. 그러나 어떤 언어들은 dynamic 과 static scope를 섞어서 사용하기도 한다. ECMAScript는 static scope를 사용하기 때문에 closure가 있고 이는 함수의 [[Scope]] 속성을 이용해서 구현되었다. 아래는 closure의 정확한 정의이다.

closure 는 code block(ECMAScript 에서는 함수를 의미)과 statically(= lexically 정적으로, 그러니까 함수가 생성될 때) 저장된 부모의 scope의 조합이다. 이 조합에서 함수는 free variable을 탐색하게 된다.

모든 함수가 생성 당시 [[Scope]] 속성을 갖게 되므로 이론적으로 모든 함수는 closure이다.

주의해야 할 것은 많은 함수가 똑같은 부모 scope를 공유할 수 있다는 것이다. global 문맥에 정의되어 있는 수많은 함수가 있는 경우가 이러한 예이다. 이 경우 한 함수의 [[Scope]] 속성에 있는 변수를 변경하면 똑같은 부모 scope를 공유하고 있는 다른 함수에도 이것이 반영된다. 곧 부모 scope가 공유되어 있는 closure 사이에는 부모 scope에 있는 변수의 변경이 전체 closure에 반영된다.

function baz() {
  var x = 1;
  return {
    foo: function foo() { return ++x; },
    bar: function bar() { return --x; }
  };}

var closures = baz();

console.log(
  closures.foo(), // 2
  closures.bar()  // 1);

위 코드를 구조화 하면 아래 그림과 같다.


이같은 특징때문에 loop 안에서 여러 함수를 만들 경우 혼란이 생길수도 있다. loop 안에서 counter를 이용하여 함수를 생성할 경우 모든 함수의 counter가 같은 값을 가지게 되된다. 이는 loop 안에 생성된 함수들이 부모 scope의 공유 하는 closure 이기 때문이다. 

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = function () {
    console.log(k);
  };}

data[0](); // 3, but not 0
data[1](); // 3, but not 1
data[2](); // 3, but not 2

이 같은 문제를 해결하기 위한 여러 가지 방법이 있다. 아래 코드와 같이 생성하고자 하는 함수 위에 부모 함수를 만들어서 새로운 scope를 scope chain에 추가하는 것이다.

var data = [];

for (var k = 0; k < 3; k++) {
  data[k] = (function (x) {
    return function () {
      console.log(x);
    };
  })(k); // pass "k" value}

// now it is correct
data[0](); // 0
data[1](); // 1
data[2](); // 2

주의해야 할 점은 ES2015 (=ES6) 에서는 block-scope 가 추가되었다는 것이다. 이전의 ECMAScript에서는 함수 기반의 scope 였는데 ES2015에서 let 과 const 라는 키워드를 추가하여 block 기반의 scope를 도입하였다. 아래 코드가 그 예이다.

let data = [];

for (let k = 0; k < 3; k++) {
  data[k] = function () {
    console.log(k);
  };}

data[0](); // 0
data[1](); // 1
data[2](); // 2

이제 실행 문맥의 마지막 속성인 "this" 에 대해 알아보자.

This value
this 값은 실행문맥과 연관이 있는 객체이다. 그러므로 this 값은 context object라고 명한다. 

그 어떤 객체도 문맥의 this 값에 의해 사용되어 질수 있다. 중요한 점은 this 는 실행 문맥의 속성이지 변수 객체의 속성이 아니라는 것이다. 

이는 매우 중요한데 변수와 달리 this 값은 절대 identifier resolution process(식별자를 찾기 위한 sope chain을 뒤지는 과정) 를 거치지 않기 때문이다. 즉 this 값에 접근할때 scope chain 검색 없이 바로 실행 문맥에서 찾는다는 것이다. 이 this 값은 오직 문맥에 들어갈때 한번만 생성된다.

주의: ES2015 에서는 this가 lexical environment의 속성이 되었다. 다시 말해 변수객체의 속성이 되었다는 것이다. 이는 arrow function을 지원하기 위한 것으로 arrow function은 부모 문맥으로 부터 상속받은 lexical this를 가지게 된다(함수가 정의 될 당시의 부모 문맥의 this를 자신의 this로 갖게 된다).

global 문맥에서는 this 값은 global object 자신을 가리키고 위 변수 객체 섹션에서 언급했드시 global object는 곧 global variable object (변수 객체) 이기 때문에 this 값은 global 변수 객체가 된다.

var x = 10;

console.log(
  x, // 10
  this.x, // 10
  window.// 10
);

함수 문맥에서는 this 값은 호출될 때마다 달라질수 있다. 

// the code of the "foo" function// never changes, but the "this" value// differs in every activation

function foo() {
  alert(this);}

// caller activates "foo" (callee) and// provides "this" for the callee

foo(); // global object
foo.prototype.constructor(); // foo.prototype

var bar = {
  baz: foo
};

bar.baz(); // bar

(bar.baz)(); // also bar(bar.baz = bar.baz)(); // but here is global object(bar.baz, bar.baz)(); // also global object(false || bar.baz)(); // also global object

var otherFoo = bar.baz;
otherFoo(); // again global object
이유를 좀더 자세하고 알고 싶으면 http://dmitrysoshnikov.com/ecmascript/chapter-3-this/ 를 읽어보길 권하다(번역: http://huns.me/development/258).
이 글에서는 this의 설명이 자세하지 않으니 위 링크를 꼭 읽어보길 바란다. 사실 hoisting 만큼 쉽게 이해가 되지 않는 부분이 this 이기 때문에 위 링크의 글을 읽어보면 조금 더 명확해 질 것이다 (쉽게 기억하는 방법은 this가 포함된 함수를 호출하는 대상 곧 "." 앞의 객체 혹은 함수(예를 들어 someVarible.callFunction() 에서 callFunction 메소드에 this가 들어 있다면 someVariable이 caller, 곧 호출하는 대상)가 있다면 this는 그 대상을 가리키게 되는 것이고 그게 없다면 그냥 global을 의미). 

이 글은 Evernote에서 작성되었습니다. Evernote는 하나의 업무 공간입니다. Evernote를 다운로드하세요.

Tuesday, March 3, 2015

Ancestry Composition in 23andMe

23andMe에서 Ancestry Composition 이라는 자신의 혈통적 뿌리가 어디서 유래 했는가를 알수 있는 서비스를 제공한다. 이는 아마도 23andMe 라는 회사가 기반을 둔 미국의 다인종 혼혈 상황에서 자신의 뿌리에 대한 사람들의 관심에 대한 결과가 아닐까 한다.

0. Contents

1. Basics

2. Wrinkle #1:  사람들은 일반적으로 여러 인종 혹은 혈통(popuy)을 조상으로 갖는다 (특히나 미국의 경우).

3. Wrinkle #2: 어떤 DNA 혹은 chromosome이 엄마, 아빠에서 온건지 알수 없다.

4. Ancestry Composition Overview

5. Prep 1: The DataSet

6. Prep 2: Population selection

7. Step 1: Phasing

8. Step 2: Window Classification

9. Step 3: Smoothing

10. Step 4: Calibration

11. Step 5: Aggregation & Reporting

12. Using Close Family Members

13. Testing & Validation

14. Ancestry Composition's Future




Basics

특정 DNA marker와 지리학적 위치의 연관성의 정보를 가지고 조상을 추측하는 것.
예를 들면 mtDNA haplogroup 의 한 종류인 haplogroup H 는 유럽에서 많이 발견되고 호주나 미국에서는 거의 발견되지 않는다. 만약 누군가 haplogroup H에 관련된 DNA marker를 가지고 있다면 그 사람은 유럽인일 확률이 크고 native american 확률은 작게 된다. 이렇듯 여러 DNA marker를 이용하여 혈통을 추측하는 것이다.

Wrinkle #1:  사람들은 일반적으로 여러 인종 혹은 혈통을 조상으로 갖는다

문제는 어떤이는 조상이 여러 위치에서 온 혹은 여러 인종이 뒤섞여 있을 수 있다는 것.
곧 admixture (the genetic mixing together of previously-separate populations), 원래는 유전적으로 분리가 되어 있던 집단이 서로 섞이는 것. 유럽인의 경우 조상이 유럽전역에 걸쳐 뒤섞인 경우.

Wrinkle #2: 어떤 DNA 혹은 chromosome이 엄마, 아빠에서 온건지 알수 없다.

chip 을 통한 genotyping은 unphased 상태, 곧 서로 다른 locus의 genotype이 같은 chromosome에서 왔지는 알 수 없는 상태이다. phased 상태의 SNP이 정보가 더 많다.

Ancestry Composition Overview

admixture and unknown phase 의 genotype 정보에서 혈통을 추측하기 위해

  1. 조상을 찾고자 하는  snp chip을 phasing을 통해 haplotype을 추정한다.
  2. 다음에 각 chromosome 을 특정 window 사이즈별로 겹치지 않게 구획을 나눠 reference genome과 비교하여 각 window 별 조상을 찾는다. 각 window는 여러 조상의 genotype이 섞인 것이 아니라 하나의 조상으로 부터 유래한다는 가정하이다. 
  3. "smoothing"을 통한 phasing correction
  4. 결과의 정확성을 위한 calibrate result 

Prep 1: The DataSet

10,000여 명의 데이터

  • 23andMe 자체 데이터 : 조부모, 부모의 출생지가 같은 회원의 정보
  • public resource : Human Genome Diversity Project, HapMap, 1000 Genomes project
reference 중 너무 genetic relationship이 가까운 개체는 제외. outlier로 판단되는 개체도 제외. 대략 10% 정보 제거됨


Prep 2: Population selection

위는 유럽인들의 유전정보만을 가지고 PCA를 그린 결과이다. 왼쪽 상단의 Finns 들의 경우 명확하게 isolation되는 것을 확인할 수 있다. 그러나 역사적으로 집단이 섞인 경우가 있어서 구분이 뚜렷하지 않은 경우도 있다. 이렇듯 구분이 되는 reference population 을 선택하였다.

Step1: Phasing

BEAGLE 프로그램을 customized 해서 Finch라는 프로그램을 개발, 이 프로그램을 통해서 haplotype phasing을 한다. BEAGLE은 phasing을 할 모든 데이터가 프로그램을 돌릴 때 갖춰져야 한다는 가정하에 있는데 추가적으로 고객이 발생하는 상황에서는 이 가정이 여의치 않기 때문에 이를 효과적으로 해결하기 위해 Finch를 개발.

Step2: Window Classification

phasing을 마친 후에 chromosome 을 100개의 marker 사이즈의 window로 쪼갠다. 보통 chromosome당 5,000 ~ 40,000 개의 marker가 존재. marker DNA 서열을 input으로 reference population에 의해 training 된 SVM 을 이용하여 각 window 의 ancestry를 결정한다.

Step3: Smoothing

SVM을 통해 ancestry 가 결정된 window의 정보는 first draft 이다. 여기에는 2가지의 에러가 포함 될 수 있다.

  1. window에 ancestry를 할당할 때 비슷한 정도의 ancestry candidate가 있을 경우 잘못된 ancestry를 할당하는 에러
  2. phasing step에서 haplotyping의 에러

위 두가지의 오류를 HMM (Hidden Markov Model)을 통해 수정하는 단계를 smoothing이라고 한다.

예를 들어 X, Y, Z 3개의 집단이 있고 SVM을 통해 아래와 같이 각 chromosome 의 window 별 ancestry 가 할당되었다고 할 때

parent1 의 뒤에서 두번째 window가  X라고 되어 있는데 이는 Z와 유사한 정도의 ancestry확률을 갖고 있어서 SVM이 잘못된 판단을 한 것일 수 있다. 또한 phasing step에서의 에러로 parent1 의 첫 4개 윈도우 X-X-Y-X는 parent 2의 Z-Z-Z-Z와 바뀌었을 확률이 높다.

이를 Hidden Markov Model 을 이용해서 correction을 하면 아래와 같게 된다.
위의 예는 smoothing을 설명하기 위해 단순화 한것이고 사실은 아래와 같이 각 window에 대해 모든 집단의 확률을 구한다.
위 그림은 African-American customer의 chromosome2에 대한 smoothing의 결과이다. 아래쪽 패널을 보게 되면 왼쪽부터 핑크-그린-핑크 (african-american-african)가 나오는 걸 확인할 수 있다. 

Step4: Calibration

위 단계에서 나온 그래프가 옳은지 우리는 어떻게 알 수 있을까? 시뮬레이션을 통해 systematic bias가 있는지 알아보고 이 systematic bias를 보정하는 recalibration단계를 추가한다.

Step5: Aggregation & Reporting

threshold를 정해서 그 threshold를 넘는 ancestry의 사이즈 에 비례해서 최종적으로 ancestry를 report하게 된다. 예를 들면 위 그림에서 오른쪽 부위의 green segment은 비록 이 region은 다른 집단에서 유래할 확률( orange segment :east asia)이 있더라도 70%의 threshold를 넘겼기 때문에 너비에 비례한 만큼 (전체 chromosome에서 0.26 % 차지) native americas 의 확률에 더하게 된다. 좌측 중간의 blue segment의 경우 threshold를 넘긴 특정 european population이 없기 때문에 report를 하지 않게 된다. 이런 경우에는 각 집단의 확률의 합이 threshold를 넘길 경우 이 집단들을 포괄하는 group 인 Broadly Northern European 으로 report를 하게 된다.

Using Close Family Members

부모나 자식의 데이터가 있다면 Finch를 통한 phasing 이 매우 정확해진다. 이는 higher-resolution result로 연결된다. 자식보다는 한명의 부모의 데이터가, 한명의 부모의 데이터보다는 두명의 부모 데이터가 있는 것이 조금 더 정확해진다.

13. Testing & Validation

14. Ancestry Composition's Future





* 위 내용은
https://www.23andme.com/ancestry_composition_guide/
http://blog.23andme.com/wp-content/uploads/2012/11/20121027_ancestry_painting_methods_poster.pdf
를 참조함