TIL

WorldWannyWeb.·2020년 10월 19일
0
post-thumbnail

2020-10-19

About Scope

Scope에 대해서 알아보자.

scope는 변수의 값(변수에 담긴 값)을 찾을 때 확인하는 곳을 말한다.
block scope은 {} 이 부분을 생각하면되는데, block scope이 생기면 그 안에 있는 데이터를 밖에서 호출할 수 가 없다. 즉, 썬탠을 한 창문처럼 밖에서 안을 들여다 볼 수 없는것이다. 그러나 이것을 완전히 무시하는 키워드 var는 block scope에 상관없이 var hoisting에 의해서 문서의 맨상단으로 호출이 되기 때문에, 아무데서나 var 키워드로 선언된 변수명을 부르면 값이 나온다. var 대신 let 과 const를 써야하는 이유이다.

그럼 일단 함수선언식(Function Declaration) / 함수표현식(Function Expression)의 차이를 알아보자.

function funcDeclared(){
  return 'this is a function declaration';
} // 함수선언식
funcExpressed = function(){
  return 'this is a function expression';
}; // 함수표현식

함수선언식은 hoisting에 영향을 받는다. 함수선언식은 코드를 구현한 위치와 관계없이 hoisting에 따라 브라우저가 js를 해석할때 맨 위로 끌어올려진다. 함수표현식은 호이스팅에 영향을 받지 않는다. 함수표현식은 hoisting에 영향을 받지 않는것 뿐만 아니라, 클로저로 사용할수도 있고,콜백으로도 사용할 수 있다.

Scope는 Local Scope와 Global Scope로 나눌 수 있다. 지역변수와 전역변수라고 말하는데, global scope은 어느 곳에서든 참조가 가능하지만, local scope은 말그대로 지역한정이어서 정의된 함수 안에서만 참조가 가능하다. Global scope와 Local scope에서 scope chain이라는 개념이 나온다. 내부함수에서 외부함수변수에 접근이 가능하지만, 외부함수에서는 내부함수의 변수에 접근할 수 없다. 내부함수 안에 변수가 없으면 안에서부터 밖으로 차례차례 올라가며 변수를 찾는 관계를 Scope chain이라고 이해하면 되겠다.

여기 유의할 점은 Scope는 함수호출 이 아니라 함수선언 을 할 때 생긴다.

Lexical Scope 를 말하기 전에 토큰에 대해 알아보자. 프로그래밍 언어에서 토큰은 더이상 나눌 수 없는 최소한의 단위를 말하며, 키워드나 변수이름(식별자), 지정어, 연산자, 숫자, 그리고 공백, 구두점, 여는괄호, 콜론, 세미콜론 등과 같은 특수 기호등을 일컫는다. 렉싱타임문자열을 나누어 토큰조각으로 나누는 토크나이징 과정을 거치고, 토큰을 분석하고, 생성된 토큰에 의미를 부여하는 렉싱과정을 렉스타임이라고 한다.
코드 작성시, 변수를 어디에 작성하는가를 바탕으로, 렉싱타임에 토큰이 분석되면서, 스코프가 결정되는데 이때 구성된 scope을 뜻한다.

처음 브라우저가 스크립트를 로딩해서 실행하는 순간 모든 것을 포함 하는 전역컨텍스트 가 생긴다. 모든것을 관리하는 환경이라고 생각하면된다. 이는 페이지가 종료될 때까지 유지된다. 전역컨텍스트 말고도 함수컨텍스트 도 있는데, 함수를 호출할 때마다 함수컨텍스트가 하나씩 더 생긴다.

컨텍스트의 4가지 원칙

컨텍스트는 직역하면 문맥인데, 코드의 실행환경이라고 생각하면 된다.
1) 전역컨텍스트를 하나 생성한 후, 함수호출을 할 때마다 컨텍스트가 생긴다.
2) 컨택스트 생성 시, 컨텍스트 안에 변수객체(arguments, variable),ScopeChain, this가 생성된다.
3) 컨텍스트 생성 후, 함수가 실행이되는데, 사용되는 변수들은 변수객체 안에서 값을 찾고, 없다면 ScopeChain을 따라 올라가며 찾는다.
4) 함수실행이 마무리되면, 해당컨텍스트는 사라진다. (클로저는 제외!) 페이지가 종료되면, 전역컨텍스트가 사라진다.

//번호순대로 실행된다.
var name = 'zero'; // (1)변수 선언 (6)변수 대입
function wow(word) { // (2)변수 선언 (3)변수 대입
  console.log(word + ' ' + name); // (11)
}
function say () { // (4)변수 선언 (5)변수 대입
  var name = 'nero'; // (8)
  console.log(name); // (9)
  wow('hello'); // (10)
}
say(); // (7)

위의 코드를 가지고 실행해보자.
전역컨텍스트 가 생성된 후, 2)에 따라서 variable,scopechain,this가 들어오고, 함수인자 즉, arguments는 없다. 여기서 variable은 해당스코프의 변수들(name,wow,say)이다.
여기서 scope chain(자신과 상위스코프들의 변수객체) 은 자기 자신인 전역변수객체이다. this는 따로 설정되어있지 않으면, window이다. new를 호출하면 this를 바꿀 수 있다.(or 함수에 다른 this값을 bind 할 수 있다.) 원래 기분적으로 window이고, new나 bind같은 상황에서 this가 바뀌는 것이다.

위의 코드를 객체형식으로 표현해보면,

'전역 컨텍스트': {
  변수객체: {
    arguments: null,
    variable: ['name', 'wow', 'say'],
  },
  scopeChain: ['전역 변수객체'],
  this: window,
}

코드를 위에서부터 실행하는데, wow와 say는 Hoisting 때문에 선언과 동시에 대입이 된다. 그 후에 variable 의 name 에 'zero'가 대입된다. 간단히 먼저 언급하자면, hoisting이란 변수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상 을 의미하는데,초기화 or 변수대입부분을 그대로 남아있다. 함수표현식이 아닌 함수선언식일 때는 식자체가 통째로 끌어올려진다.

(7)번에서 say(); 를 하는순간, say 함수컨텍스트가 생기게된다(위의 1)에 따라서). 전역컨텍스트는 그대로 존재하고, arguments 는 없으며, variable 은 name뿐이다. scope chain은 say변수객체와, 상위 전역변수객체이다. this는 따로 설정해준적이 없으니 window이다.

'say 컨텍스트': {
  변수객체: {
    arguments: null,
    variable: ['name'], // 초기화 후 [{ name: 'nero' }]가 됨
  },
  scopeChain: ['say 변수객체', '전역 변수객체'],
  this: window,
}

say 함수를 호출한 후, (8)~(10)실행한다. var의 name에 nero를 대입 후, console.log(name); 이 있다. name변수는 say컨텍스트 안에서 찾으면 된다. variable에 name이 nero라고 되어있고, name이 콘솔에 찍힌다. 그다음 wow('hello')가 있는데, say 컨텍스트 안에 wow변수를 찾을 수 있다. 찾을 수 없다면, scope chain을 따라서 상위 변수객체에서 찾는다. 그러면 전역변수객체의 variable에 wow함수가 있으므로, 그것을 호출하면된다.

(10)번에서 wow함수가 호출되어서 wow컨택스트가 생긴다. arguments는 word='hello'이고, scope chain은 wow scope, 전역scope이다. wow함수의 스코프체인은 선언시에 이미 정해져있다. 그러므로 say scope는 wow컨텍스트의 scope chain이 아니다. variable은 없고, this는 window이다.

'wow 컨텍스트': {
  변수객체: {
    arguments: [{ word : 'hello' }],
    variable: null,
  },
  scopeChain: ['wow 변수객체', '전역 변수객체'],
  this: window,
}

컨텍스트가 생긴 후 , 함수가 실행된다. say함수는 아직 종료되지 않았다. wow 함수 안에서 console.log(word + ' ' + name); 이 있다. word랑 name 변수는 wow컨텍스트에서 찾을 수 있다. word는 arguments에서 찾을 수 있고, name은 wow 변수객체에는 값이 없으니, scopechain을 따라 전역 스코프에서 찾으면 된다. 전역변수객체로 올라가니 var에 name이 zero이기 때문에 hello zero가 된다. wow컨텍스트에 따르면 wow함수는 say컨텍스트와 일절 관련이 없었다.
wow함수가 종료되면, wow컨텍스트가 사라지고, say함수의 실행이 마무리된다. 그러면 say컨텍스트도 사라지고, 마지막전역텍스트도 사라지게 된다.

Primitive Data type & Reference data type

JavaScript에서 Primitive Data type(원시자료형)은 객체가 아니면서 method를 가지지 않는 6가지의 데이터를 말한다.
(string, number, bigint, boolean, undefined, symbol).

Primitive Data type

1) Primitive data type은 값 자체에 대한 변경이 불가능하다(immutable).

let name = 'wannyweb';
expect(name).toBe('wannyweb');
expect(name.toUpperCase()).toBe('WANNYWEB');
expect(name).toBe('wannyweb');
//새로운 값으로 재할당은 가능하다. 아래처럼!
name = name.toUpperCase();
expect(name).toBe('WANNYWEB');

Primitive data type의 큰 특징 중 하나는 변수의 크기(메모리에서 차지하는 공간)가 fixed되어있다는 것이다. 변수의 크기와 값의 크기를 헷갈리면 안된다.

아래 예시를 봐보자

const num1 = 123;
const num2 = 123456789;
// 변수 num1의 값은 num2의 값보다 작지만 
// 두 변수(num1,num2)의 크기(그릇의 크기 즉, 메모리공간크기)
// 는 같다!!

2) Primitive data type을 변수에 할당할 경우, 값자체의 복사가 일어난다.

let overTwenty = true;
let allowedToDrink = overTwenty;
//
overTwenty = false;
expect(overTwenty).toBe(false);
expect(allowedToDrink).toBe(true);
// 위에 allowedToDrink에 overTwenty를 할당했고,
// overTwenty에 true값을 할당했으므로
// allowedToDrink는 true로 값이 나온다.
let variable = 'variable';
let variableCopy = 'variableCopy';
variableCopy = variable;
variable = variableCopy;
expect(variable).toBe('variable');

자바스크립트에서 원시 자료형이 아닌 모든 것은 Object 이다. 배열([])과 객체({}), 함수(function(){})가 대표적이다. Object는 변수의 크기(값의 크기와 구분해야한다.)가 동적(dynamic)으로 변한다. 이 때문에 Object의 데이터 자체는 별도의 메모리 공간(heap)에 저장된다. 그리고 이 데이터가 위치한 곳(메모리 상 주소)을 가리키는 주소가 변수에 저장된다.
참조 타입 데이터는 크기가 정해져 있지 않고 변수에 할당될 때 값이 직접 해당 변수에 저장될 수 없으며, 변수에는 데이터에 대한 참조만 저장된다. 참조 타입은 변수의 값이 저장된 메모리 블럭의 주소를 가지고 있고, 자바스크립트 엔진이 변수가 가지고 있는 메모리 주소를 이용해서 변수의 값에 접근한다.

Object 자료형은 데이터는 heap에 저장되고, 변수에 할당을 할 경우 변수에는 주소가 저장된다.
1) [1, 2, 3]; // [1, 2, 3]이라는 데이터가 heap에 저장되지만 변수 할당이 되지 않아 주소는 어디에도 저장되지 않는다.
2) const num1 = [1, 2, 3]; // // [1, 2, 3]이라는 데이터가 heap에 저장되고, 그 주소가 변수 num1에 저장된다.
3) const num2 = [1, 2, 3]; // // [1, 2, 3]이라는 데이터가 heap에 저장되고, 그 주소가 변수 num2에 저장된다.
1), 2), 3)에서 말하는 주소는 전부 다른 주소다.

Rest Parameters

function printAll(...args){ // ...를 rest parameters라고 한다.(배열형태로 전달)
  for(let i = 0; i < args.length; i++){
    console.log(args[i]);
  }
}
printAll('dream','coding','ellie');
// printAll을 호출할때 3가지의 인자를 전달하는데 위에 
// ...args는 3개의 인자가 담긴 배열이다.
profile
와니완의 월드와이드와니웹🐥

0개의 댓글