본문 바로가기
기본(프론트)/J.S(자바스크립트)

J.S 객체, 함수, 스코프/ 공유의 의한 전달? 렉시컬 스코프? 자스 -03-

by Ethan cho 2022. 9. 29.

오늘 다룰 내용:

  • 스코프
  • 객체
  • 함수

*주의: 이번에 다룰 내용들은 모던 자바스크립트 10장 부터 13장까지의 부연 설명이라고 보면 좋다. 본 글쓴이는 이 4장이 정말 중요하다고 생각 되며, 이해하기 힘들었기 때문에 기록을 남겨놓는 것이다.

나만을 위해 작성한다. 독자들 ㅃ2

“Programming isn’t about what you know; it’s about what you can figure out.”
-Chris Pine-
: 프로그래밍은 무엇을 알고 있는가에 대한 것이 아니다. 그것은 당신이 무엇을 알아낼 수 있는 가에 대한 것이다.

무엇을 모르고 무엇을 아는지는 [시행착오]에서 부터 나오니 명심.

 

스코프

스코프란 유효범위라고 할 수 있다. (자신이 선언된 위치에 따라 다른 코드가 자신을 참조할 수 있는 유효 범위)

 

구분 설명 스코프 변수
전역 코드의 가장 바깥 영역 전역 스코프 전역변수
지역 함수 몸체 내부 지역 스코프 지역변수

 

이 개념은 변수의 사용에서 굉장히 중요한데, 예를 들자면 let A 를 선언했을때 이 변수가 어디 영역에서 활용이 가능 하냐는 말이다.

 

많이 들어본 전역 변수 로컬 변수의 이야기이다. 이 변수들이 정의가 되었을때 무엇을 기준으로 스코프를 구별하는지에 따라 블록 레벨 스코프 함수 레벨 스코프로 나뉜다.

 

전역 변수와 로컬 변수가 동일한 이름을 갖고 있을 경우 자바스크립트 엔진이 어떤 변수를 선택할지를 스코프를 통해 결정하는 것 식별자 결정이라고 한다. 엔진이 식별자 결정을 위해 주변 코드를 보는 환경 렉시컬 환경이라고 하며 이를 통해 구현 한 것 실행 컨텍스트가 되는 것이다.

 

식별자 결정을 할때 엔진은 변수를 참조하는데 이 때, 사용되는 것이 바로 스코프 체인이라는 것이다. 스코프 체인은 쉽게 설명하면, 변수를 참조(사용)할 때 참조한 곳의 스코프에서 시작하여 위 스코프 방향으로 이동하며 선언된 변수를 찾는 것을 말한다.

 

밑에 표를 보고 이해를 할 수 있는 지 스스로 검증을 해보길 바란다. 표 이후에 내용에서 자세히 알아보자!

var var1 = 1;

if(true) {
	var var2 = 2;
    if(true) {
    	var var3 = 3;
    }
}

function outer() {
	var var4 = 4;
    function inner() {
   		var var5 = 5;
    }
}

console.log(var1); //1
console.log(var2); //2
console.log(var3); //3
console.log(var4); // ReferenceError: var4 is not defined
console.log(var5); // ReferenceError: var5 is not defined

 

01. 전역변수와 로컬변수 / 스코프 체인

전역변수 = 어디서든 참조할 수 있다. GLOBAL

지역변수 = 함수 몸체 내부를 뜻하는 지역은 자신의 지역 스코프와 하위 지역 스코프에서 유효한 변수이다.

스코프 체인 = 스코프에 연결과 순서를 뜻함.

var x = "global";

function outer(){
	var z = "outer's local z";
    
    console.log(x); //전역 변수 이므로 global 출력
    
    function inner(){
    	var x = "inner's local x";
        
        console.log(z); //inner의 상위 스코프인 var z의 outer's local z 출력
        console.log(x); //inner의 지역 변수인 inner's local x 출력
    }
    
 }
 
 console.log(z); //outer의 지역변수로 ReferenceError: z is not defined

위 var x  와 outer함수는 전역 변수가 되고, outer 함수 안에 있는 var z와 inner 함수는 지역변수이고, var x 는 지역변수가 된다. 아래 스코프 체인 표를 보고 방향을 유의깊게 살피며 엔진이 변수를 찾아가는 흐름과 범위에 대해서 이해하고 넘어가자.

스코프 체인

 

02. 블록 레벨 스코프와 함수 레벨 스코프

함수 레벨 스코프 = 지역은 함수 내부를 뜻하고 이 지역이 스코프가 된다. 즉 코드 블록이 아닌 함수에 의해 스코프가 생성되는 것이 함수 레벨 스코프이다. 주의: var키워만 함수 레벨 스코프를 따른다.

블록 레벨 스코프 = 함수 몸체 뿐만이 아니라 모든 코드 블록이 지역 스코프를 만든다. (if, for, while, try/catch 등)

var x = 1;

if(true){
	var x = 10;
}

console.log(x); // 10

let a = 1;
if(true){
	let a = 2;
}

console.log(a); // 1

 

03. 렉시컬 환경

var x = 1;

function foo() {
	var x = 10;
    	bar();
 }
 
 function bar() {
 	console.log(x);
 }
 
 foo();
 bar(); // bar의 x값이 무엇이냐!

렉시컬 환경은 앞에서 설명 했다시피, 문맥을 보고 변수를 판단하는 환경이다. 자바스크립트는 렉시컬 환경을 따르므로 위 코드 예제를 보았을때 bar()에서 출력되는 x의 값이 무엇이 될가를 결정하는 중요한 역할을 하는 환경인 것이다.

고로, 렉시컬 환경은 함수가 어디서 호출이 됬는가가 아닌, 함수가 어디서 정의가 됬느냐에 따라서 스코프를 생성하는 것이다.

위 예제에서 bar()가 foo()안에서 호출이 되었고 윗 줄에 있기 때문에 x가 10으로 변환된 뒤에 bar()의 console.log(x)가 출력되어 10이 될 것 같지만, 실제로는 bar가 형성이 된 시점에 스코프에서 가르키는 x의 값 1이 출력 되는 것이다. 즉, 함수가 호출된 위치는 상위 스코프 결정에 어떠한 영향도 주지 않는다.

 

객체

원시값 = "변경 불가능한 찐 데이터" like 숫자, 문자 등 Immutable value

객체값 = "변경 가능한 데이터" mutable value

리터럴 = "사람이 이해할 수 있는 문자 또는 약속된 기호"

 

자바스크립트에서 객체는 원시값을 제외한 나머지 값들 모두가 해당된다.

객체는 0개 이상의 프로퍼티로 구성된 집합이며, 아래의 그림을 보면 key 와 값으로 구성된 프로퍼티의 {}집합을 객체라 생각하면된다.

객체 구조

객체 안에는 프로퍼티 말고도 메소드가 들어갈 수 있다. function을 함수라고 부르는데 객체 안에 들어가게 되면 메소드로 불린다. 즉, 프로퍼티의 값이 함수일 경우 일반함수와 구분하기 위해 메소드라고 부른다.

var counter = {
	num:0,
    increase : function(){
    	this.num++;
   	}
};
// 여기서 num:0 은 프로퍼티
// increase: 는 메소드

객체 생성 방법

1. 겍체 리터럴
2. Object 생성자 함수
3. 생성자 함수
4. Object.create 메소드
5. 클래스(ES6)

가장 일반적인 방법은 1번 객체 리터럴이다. 위에서 활용한 방법이 객체 리터럴이고 중괄호 내에 0개 이상의 프로퍼티를 정의하는 것이다. -변수가 할당되는 시점에 자바스크립트 엔진은 객체리터럴을 해석해 객체를 생성한다 -

객체리터럴 주의 사항:

  • 프로퍼티를 정의하지 않으면 {} 빈 객체가 된다.
  • 중괄호는 코드블록이 아니다.
  • 객체 리터럴은 값으로 평가되는 표현식이므로 중괄호 뒤에 자동적으로 ;가 붙는다.
  • 다른 언어와 다르게 클래스를 먼저 정의하거나 new 와 함께 생성자를 호출 할 필요가 없다.
  • 객체 리터럴에 프로퍼티를 포함시켜 객체를 생성함과 동시에 프로퍼티를만들고 객체를 생성한 이후에 프로퍼티를 동적으로 추가할 수도 있다.
  • 식별자 지정시 규칙에 따르면 (문자만 있는 그런거 알지?)  따옴표가 필요 없지만 hyphen 같은 규칙 이외의 것 (ex. 'last-name')은 따옴표를 붙여야 한다. => 'last-name' : 'Cho'
  • obj[key]  = 'world' 로 키와 값의 프로퍼티를 생성 가능하다.
var obj = {};
var key = 'hello';

// ES5: 프로퍼티 키 동적 생성
obj[key] = 'world';
// ES6: 계산된 프로퍼티 이름
// var obj = { [key]: 'world' };

console.log(obj); // {hello: 'world'}

var foo = {
	0: 1,
    1: 2,
    2: 3
 };
 
 console.log(foo); // {0: 1, 1:2, 2:3} 문자열 반환한다.

 

주의: 매개변수는 3개 이상이면 객체를 사용한 매개변수를 사용하는 것이 좋음. 매개 변수가 적으면 적을수록 이상적이다. 왜냐하면, 이상적인 함수는 한 가지 일만 해야 하는 것이기 때문이다.

 

 

객체 접근 방법

1. 마침표 프로퍼티 접근 연산자(.)를 사용하는 마침표 표기법
2. 대괄호 프로퍼티 접근 연산자 ([...])를 사용하는 대괄호 표기법
var person = {
	name : "Cho"
};

console.log(person.name); // 1번 출력: Cho
console.log(person['name']); // 2번 출력: Cho

// 동적 할당
person.age = 20;
person[height] = 170;

console.log(person); // {name : "Cho", age : "20", height : "170" }

// 프로퍼티 삭제

delete person.age;

console.log(person); // {name : "Cho", height : "170" }

// 계산된 프로퍼티 이름

var prefix = 'prop';
let i = 0;

const obj = {
	[`${prefix}-${++i}`]: i,
    [`${prefix}-${++i}`]: i,
    [`${prefix}-${++i}`]: i
};

console.log(obj); // {prop-1:1, prop-2:2, prop-3:3}

// 메서드 축약 표현
// ES5
var = obj = {
	name: "Lee",
    sayHi: function() {
    	console.log('Hi! ' + this.name);
    }
};

obj.sayHi(); // Hi! Lee

//ES6 function 표기 생략 가능
var = obj = {
	name: "Lee",
    sayHi() {
    	console.log('Hi! ' + this.name);
    }
};

obj.sayHi(); // Hi! Lee
  • 대괄호를 이용한 접근시 따옴표를 사용해야하는 것 유의 하고 쓰지 않는다면 변수로 식별한다.

01. 원시 값과 객체의 비교

변경 불가능한 값인 원시타입의 값과 객체(참조)타입의 값 즉, 변경 가능한 값의 차이점을 잘 알아 두자.

  • 값에 의한 전달: 실제 값이 복사되어 전달 (pass by value)
  • 참조에 의한 전달: 참조 값이 복사되어 전달 (pass by reference)

다시 한번 강조하면 원시타입의 값은 한번 저장되면 read only가 되어 값을 변경할 수 없지만 객체의 값은 변경이 가능하다는 것 꼭 알아두자. 또한 변경 불가능하다는 것은 변수가 아니라 값에 대한 진술이다. 고로 원시값을 바꿀때는 재할당을 이용한다. 단, 상수의 경우 재할당을 금지한다. 예) const

위 그림처럼 재할당을 할 때도 똑같이 값의 할당이 된 후 식별자가 이동하는 것이다.(이러한 불변성은 데이터의 신뢰도를 높인다는 것!)

 

특이점: 유사 배열 객체 (array-like object) read-only

문자열은 유사 배열 객체이면서 이터러블(하나하나 보는 것)이므로 배열과 유사하게 각 문자에 접근할 수 있다. 단 수정은 불가능하다.

var str = 'string'

console.log(str[0]); // s

console.log(str.length);// 6
console.log(str.toUpperCase()); //STRING

str[0] = 'X'; // 변경 불가능
console.log(str); // string

에 의한 전달 VS 참조에 의한 전달

var score = 80;
var copy = score;

console.log(score); // 80 오리지날
cosnole.log(copy); // 80 카피한 값으로 원시값을 복사해 왔으며 주소값이 다르다 -깊은 복사-

var grade = {value : 80};
var copyGrade = grade;

console.log(grade, copyGrade); // 값은 같지만 둘다 똑같은 메모리 값을 가리키고 있다.

copyGrade['error'] = "hi";

console.log(copyGrade)// {value: 80, error: "hi"} 
console.log(grade); // {value: 80, error: "hi"} copyGrade의 참조값이 grade의 
//참조값이 같기 때문에 copy한 변수에서 변경을 하면 grade도 변경된다. -얕은 복사-

//이를 막기 위해 spread 연산자를 사용해 원시값을 복사하는 방법을 사용한다.

deepCopyGrade = {...grade}
deepCopyGrade['game'] = "throne";

console.log(grade); // {value:80};
console.log(deepCopyGrade) // {value:80, game: "throne"}; // 참조한 메모리 주소가 다르기 때문

 

 

 

 

함수

자바스크립트의 함수는 일급 객체이다.

함수는 일련의 과정을 문으로 구현하고 코드 블록으로 감싸서 하나의 실행 단위로 정의한 것이다.

아래 그림을 보고 구성을 살펴 보자. / 함수 사용이유: 재사용, 유지보수의 코드 신뢰성

일급객체에 대한 정의이다.

일급객체(First-class Object)란 다른 객체들에 일반적으로 적용 가능한 연산을 모두 지원하는 객체를 가리킨다. [위키백과]

일급객체의 조건에 대해서 정의를 내려보겠다.

변수에 할당(assignment)할 수 있다.

다른 함수를 인자(argument)로 전달 받는다.

다른 함수의 결과로서 리턴될 수 있다.

위에 대한 조건으로 인해 알 수 있는 것은 함수를 데이터(string, number, boolean, array, object) 다루 듯이 다룰 수 있다는 점이다.

여기서 데이터를 다룬다는 의미는 변수에 할당이 가능하다는 것으로, 함수 역시 할당이 가능하다는 의미이다.

그렇다면, 함수가 일급객체이기 때문에 할 수 있는 것은 무엇인가?

고차함수(Higher order function)를 만들 수 있다.

콜백(callback)을 사용할 수 있다.

이상 주저리 주저리를 마치고 중요한 부분들을 짚어나가보자.

 

01. 함수 정의

기명 함수: 이름 있는 함수

익명 함수: 이름 없는 함수

 

함수는 표현식이 아닌 문이다. 고로 값을 갖고 있는 것이 아닌 식 인것이다. 

고로, 함수 선언문은 표현식이 아닌 문이므로 변수에 할당할 수 없다.

var add = function add(x,y){
	return x + y;
};

//함수 호출
console.log(add(2,5)); // 7

하지만 위 예제를 보면 함수가 add 변수에 할당 된 것 처럼 작동한다. 이와 같이 동작 하는 이유는 엔진이 코드 문맥을 살필 때 변수에 우변에 있는 항목을 객체 리터럴로 보기 때문이다. = {}은 중의적 표현이다.

즉, 값으로 평가되어야 할 문맥에서 피연산자로 사용된다면 이를 객체 리터럴로 본다는 것이다.

 

// 기명 함수 리터럴을 단독으로 사용하면 함수 선언문으로 해석된다.
// 함수 선언문에서는 함수 이름을 생략할 수 없다.
function foo() { console.log('foo'); }

foo(); // 출력: foo

// 함수 리터럴을 피연산자로 사용하면 함수 선언문이 아니라 함수 리터럴 표현식으로 해석된다.
// 함수 리터럴에서는 함수 이름을 생략할 수 있다
(function bar() { console.log('bar'); });

bar(); // ReferenceError: bar is not defined

위 예 처럼 foo는 출력이 되지만 bar는 출력이 안된다. 그 이유는 앞에서 설명했 듯 "함수 이름은 함수 몸체 내에서만 참조할 수 있는 식별자다"라는 것 때문인데,

여기서 질문!

foo는 함수 몸체에서 호출한게 아닌데 왜 출력이 되나요?

이에 대한 답은 foo라는 함수가 선언이 될때 같은 이름(foo)이 변수로 선언이 되기 때문이다.

함수가 어떻게 저장되어 있는지 아래 그림을 보자.

 

 

함수가 생성될때 암묵적으로 자바스크립트 엔진이 같은 이름의 식별자를 값으로는 참조값을 넣게 되는 것이다.

 

02. 함수 생성시점과 함수 호이스팅

// 함수 참조
console.dir(add); // f add(x,y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2,5)); // 7
console.log(sub(2,5)); // TypeError: sub is not a function

//함수 선언문
function add(x,y){
	return x + y;
}

//함수 표현식
var sub = function (x,y){
	return x - y;
}

위 코드를 보면, add함수가 호출 함수 밑에 선언 되었음에도 add 함수가 잘 호출 된다는것을 볼 수 있다.

=> 런타임 이전에 함수가 "선언"이 되고 객체가 생성된다. 앞에서 말했듯이 엔진은 암묵적으로 식별자를 생성하고 참조값을 부여한다.

이와 같이, 함수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 자바스크립트 고유의 특징을 함수 호이스팅이라고 한다.

 

선언문과 다르게 표현식으로 작성된 경우

=> 런타임 이전에 undefiend 값과 함께 식별자 sub가 생성, 런타임 실행시(할당이 발휘되는 시점) 우변에 있는 함수 객체 생성.

이와 같이 하는 경우 앞의 문제인 호이스팅을 방지 할 수 있다.

 

1. 생성자 함수

var add = new Function('x', 'y', 'return x + y');
console.log(add(2,5));

// 클로저를 생성하지 않는 등 바람직하지 않다.

2.화살표 함수

// 화살표 함수
const add = (x,y) => x+y;
console.log(add(2,5)); // 7

3.즉시 실행 함수

// 즉시 실행된다 () 는 연산식으로 가장 우선시 되고 안에 있는 값은 표현식이 된다.

//익명
(function(){
	var a = 3;
    var b = 5;
    return a * b;
}());

//기명
(function foo(){
	var a = 3;
    var b = 5;
    return a * b;
}());

4. 재귀 함수

// 탈출 조건 필수
function countdown(n){
	if(n<0) return;
    console.log(n);
    countdown(n-1); //재귀 호출
}

countdown(10);

5. 중첩 함수

function outer(){
	var x = 1;
    
    // 중첩 함수
    function inner() {
    	var y = 2;
        // 외부 함수의 변수를 참조할 수 있다.
        console.log(x+y); //3
    }
    
    inner();
}

outer();

6. 콜백 함수

// 외부에서 전달받은 f를 n만큼 반복 호출한다. 여기서 f가 콜백함수 repeat이 고차 함수가 된다.
function repeat(n,f) {
	for (var i = 0; i<n; i++){
    	f(i); // i를 전달하면서 f를 호출 (콜백함수)
    }
}

var logAll = function (i) {
	console.log(i);
 };
 
 // 반복 호출할 함수를 인수로 전달한다.
 repeat(5, logAll); // 0 1 2 3 4 5

함수의 매개변수를 통해 다른 함수의 내부로 전달되는 함수를 콜백 함수라고 하며, 매개변수를 통해 함수의 외부에서 콜백함수를 전달받은 함수를 고차 함수 라고 한다.

쉽게 말해서,

콜백함수 =  다른 함수가 실행될때 끝나거나 중간에 실행되는 함수를 뜻한다.  f 콜백함수

고차 함수 = 함수를 인자로 받거나 함수를 리턴하는 것이다. repeat 고차함수

댓글