TIL 20230601(함수 실행 과정,실행컨텍스트,this,Closure,Rest, Spread 연산자)

뿌링클 치즈맛·2023년 6월 3일
0

Elice AI트랙 8기

목록 보기
14/28

함수 실행 과정

JS에 아무 코드가 없어도 this,변수들(Variable Object), Scope chain은 초기화된다.

this는 코드가 실행되는 환경에서 가리키는 객체로, 아무 코드가 없는 경우에는 최상위 스코프에 위치한 객체인 window를 가리키게 된다.

Variable Object는 어떤 변수도 선언되지 않았기에 빈 객체가 된다. 스코프 체인 또한 최상위 스코프 이외에 연결된 스코프가 없기 때문에 빈 객체가 된다.

//최상위 
function myFunc() {
	let a = 10
	let b = 20
	function add(first, second) {
		return first + second
    }
	return add(a, b)
}
myFunc();

global 실행 컨텍스트 = this:window,Variable Object: myFunc(){},스코프 체인:[]
myFunc 함수 실행 컨텍스트 = this:undefined, Variable Object:a,b,function add(){},스코프체인:[global] (myFunc 이전의 실행 컨텍스트)
add 함수 실행 컨텍스트 = this:undefined, Variable Object:first,second, 스코프체인 [global,myFunc]

  • 함수가 실행되면,함수 스코프에 따라 환경이 만들어진다.
  • this, 함수 스코프의 변수들, 그리고 스코프 체인이 형성된다.
  • 스코프 체인을 따라 글로벌 환경에 도달한다.
let o = {
	name: 'Daniel',
	method: function(number) {
		return this.name.repeat(number)
	}
}
function myFunc() {
	let n = 10
	return o.method(n)
}
myFunc()

global 실행 컨텍스트 = this:window, Variable Object: o{}(객체),myFunc(){}, 스코프체인=[]
myFunc가 호출되었으므로 전역 실행 컨텍스트 위에 myFunc 실행 컨텍스트가 쌓임
myFunc 함수 실행 컨텍스트 = this:undefined,Variable Object: n,스코프 체인:[global]
myFunc 함수 내에서 객체 o의 method를 호출했으므로 객체 o의 method 실행 컨텍스트가 myFunc 위에 쌓인다.
객체 o 의 method (number) 실행 컨텍스트 = this: o,Variable Object: function의 인자로 n을 받았으므로 number:10이 변수객체이다, 스코프 체인: [global,myFunc]

객체의 method의 경우, method 환경의 this는 해당객체를 가리키게 된다.(o에 포함된 method는 console.log(this)를 했을 때 o를 가리킴)

실습에서 배운 것!
객체와 클래스가 좀 헷갈리기 시작했다.

const DrinkPass={
	drink:'데자와',//데자와의 멋짐을 모르는 당신은 불쌍해요
  	selectDrink:function(new){
      this.drink=new;
      console.log('저쪽 손님께서 보내신',new,'입니다');
    }
};

객체 내의 메서드에 인자로 들어가는 것은 객체 내 프로퍼티 이름과 같아야 한다!
위와 같이 실행하면 오류가 발생한다.

const DrinkPass={
	drink:'데자와', //데자와의 멋짐을 모르는 당신은 불쌍해요
  	selectDrink:function(drink){
      this.drink=drink;
      console.log('저쪽 손님께서 보내신',drink,'입니다');
    }
};
DrinkPass.selectDrink('콜라');


selectDrink에 인자로 받는 값을 프로퍼티 이름인 drink로 했더니 정상적으로 실행이 된다.

실행 컨텍스트

프링글스를 만들기로 이해하는 실행 컨텍스트

let a = 10
function f1() {
	let b = 20
	function print(v) { console.log(v) }
	function f2() {
		let c = 30
		print(a + b + c)
       }
	f2()
   }
f1()

전역 컨텍스트의 Variable 객체: a(10),f1(){}
f1 실행 컨텍스트의 Variable 객체: b(20),print(v){},f2(){}
f2 실행 컨텍스트의 Vairable 객체: c(30)

간략한 실행 과정
1. 전역 컨텍스트에 a(10),f1(){함수내용포함}저장.
2. f1()호출로 f1실행 컨텍스트 생성. 전역컨텍스트 위에 쌓임
3. f1실행 컨텍스트 내에 b(20),print(v){함수내용포함},f2(){함수내용포함} 저장.
4. f2()호출로 f2실행 컨텍스트 생성.f1 실행 컨텍스트 위에 쌓임
5. f2 실행 컨텍스트 내에 c(30) 저장.
6. print(a+b+c)로 f1실행 컨텍스트에 저장된 print 함수호출. print 실행 컨텍스트 생성.
7. 전역 컨텍스트에 저장된 a(10),f1 실행 컨텍스트에 저장된 b(20), f2실행 컨텍스트에 저장된 c(30)가져와 print 함수에 인자로 전달.
8. print 실행 컨텍스트 실행 완료 후 콜스택에서 삭제.
8. f2 실행 컨텍스트 실행 완료후 콜스택에서 삭제.
9. f1 실행 컨텍스트 실행 완료 후 콜스택에서 삭제.
10. 모든 코드가 실행 완료 되었으므로 전역 컨텍스트도 콜스택에서 삭제

자바스크립트가 실행될 때 전역 실행 컨텍스트가 만들어진다.
함수가 실행될 때 함수 실행 컨텍스트가 만들어진다.

실습에서 질문할 것
return (function() { } ) ( )
콜백 함수 사용시에return (function() { } ) ( )에서 마지막 ()는 콜백함수를 호출하기 위한 괄호인가?

this

함수가 호출되는 상황
1. 함수 호출 - myFunc()
2. 객체의 메서드 호출 - student.say()
3. 생성자 호출 - function Person(){this.name='kim'} const p=new Person()
4. call,apply(function의 메소드)등의 간접 호출
5. 콜백함수 호출 - myFunc(secondFunc())

함수는 다양한 환경에서 호출될 수 있고, 호출 환경에 따라 this가 달라진다.
this가 환경에 따라 바뀌는 것을 동적 바인딩(dynamic binding) 이라고 한다.
bind,apply,call등으로 this가 가리키는 것을 조작할 수 있다.

let o = {
	name: "Daniel",
	f1: () => {
		console.log("[f1] this : ", this);
    },
	f2: function () {
		console.log("[f2] this : ", this);
    },
};
o.f1(); // global. 
o.f2(); // o. 객체의 메서드로, this는 객체를 가리킨다.
setTimeout(o.f1, 10); // global
setTimeout(o.f2, 20); // global
var name='kim';
console.log('1',this); // window. 전역 컨텍스트에서 this는 window를 가리킨다.

function print(){
  const genre='funk';
  console.log(this);//window. 일반 함수 호출에서도 this는 window를 가리킨다.
}
print();

function myFunc(name) {
  this.name = name;
  this.getName = function () {
    console.log("getName this:", this); //object
    return this.name;
  };

  console.log("myFunc this:", this); //object, myFunc
  // return this; 생략 가능합니다.
}

const o = new myFunc("elice"); // myFunc this: myFunc {...}
o.getName() // myFunc this: myFunc {...}

전역 컨텍스트에서 this는 window를 가리킨다.

화살표 함수 : 호출시 this = 함수가 생성된 환경.
일반 함수: this = 함수가 호출된 환경.
객체의 메서드: this = 객체.

최상단 스코프의 실행 컨텍스트는 전역 (global)이다.

화살표 함수와 일반 함수의 this

  • 화살표 함수의 this는 호출된 함수를 둘러싼 실행 컨텍스트를 가리킨다.
  • 일반 함수의 this는 새롭게 생성된 실행 컨텍스트를 가리킨다.

Closure

inner가 본인의 외부함수(outside)보다 더 오래 살아있다면 inner는 클로저!
렉시컬 환경은 사라지지 않았음
한 중첩함수가 상위 스코프의 식별자를 참조하고 있고 본인의 외부 함수보다 더오래 살아있다면 그 중첩함수는 클로저
클로저에 의해 참조되는 변수:자유 변수

함수와 함수가 사용하는 변수들을 저장한 공간! 내부 함수의 실행 컨텍스트가 닫혀 콜스택에서 사라져도 외부함수에서 실행 컨텍스트가 살아있다면 클로저를 사용할 수 있다.

클로저를 사용하는 이유: 상태를 안전하게 은닉하고, 보존시키기 위함.

function createCard() {
    let title= "";
    let content= "";
    
    function changeTitle(text) {
      title= text
    }
    
    function changeContent(text) {
      content= text
    }
    
    function print() {
      console.log("TITLE -", title);
      console.log("CONTENT -", content);
    } 

    return { changeTitle, changeContent, print};
}

const KimsCard=createCard('김씨의 카드','신용카드');

creditCard 함수에는 title,content 변수와 chageTitle,changeContent,print 함수가 있다.
title,content 변수는 함수 내에 있는 지역변수이기에 외부에서 접근할 수 없다. 따라서, chageTitle,changeContent를 통해 그 값을 수정할 수 있게 해주었다.
이 모든 함수를 객체로 묶어 createCard 함수에서 return하고, const myCard=createCard()의 형태로 인스턴스를 만들어 사용할 수 있다.

ES6 Rest, Spread 연산자

Rest Operator

함수 인자 rest operator
함수의 인자, 배열, 객체에서 나머지 값을 묶어 사용하는 연산자이다.

function findMin(...rest) {
	return rest.reduce((a, b) =>a < b ? a : b)
}
findMin(7, 3, 5, 2, 4, 1) // 1

rest연산자가 7,3,5,2,4,1을 배열로 묶어 함수의 인자로 받는다.
배열의 0과1번 요소를 비교하고 1,2번을 비교하는 함수를 reduce로 적용해 그 값을 반환한다.
가변 인자 함수를 구현할 수 있다.(array의 길이가 변하는 것 같은 건가?)

객체 rest operator

const o = {
	name: "Daniel",
	age: 23,
	address: "Street",
	job: "Software Engineer",
};
const { age, name, ...rest } = o;
findSamePerson(age, name);

...rest는 address:'Street' 와 job:'Software Engineer'를 객체로 묶은 형태로 가져온다.
필요한 변수를 제외할 수 있다.
왜 주소가 길이죠

배열 rest operator

function sumArray(sum, arr) {
	if (arr.length === 0) return sum;
	const [head, ...tail] = arr;
	return sumArray(sum + head, tail);
}
sumArray(0, [1, 2, 3, 4, 5]);

tail 앞에 rest 연산자가 붙었으므로 배열의 인덱스 0에 위치한 head를 sum에 더해주고, 나머지 값들을 다시 배열로 묶어준다. arr에 head를 제외한 tail이 그대로 들어가므로 [2,3,4,5]가 들어간다.
배열에 인자가 하나도 없더라도 rest operator는 빈 배열을 반환한다.


arra라는 배열을 선언하고, 구조분해 할당을 사용해 a에는 1,b에는 2, tail에 3,4,5를 배열로 묶어 할당했다.
꼭 tail일 필요는 없고 원하는 변수명 앞에 ...만 붙여주면 된다.

spread operator

1.객체 Spread operator

let o = {
	name: "Daniel",
	age: 23,
	address: "Street",
	job: "Software Engineer",
}
let o2 = { ...o, name: "Tom", age:
24 }
let o3 = { name: "Kim", age: 22,
...o }
o2.name // Tom
o2.job  //Software Engineer
o3.name // Daniel

spread operator의 등장 순서에 따라,객체의 필드가 덮어씌워 질 수 있다.
o2.name은 ...o가 먼저 등장해 o객체를 덮어썼으나 이후에 name에 Tom을 줘서 Tom이 출력된다.
o2.job은 덮어쓴 o의 job인 Software Engineer를 가져온다.
o3.name은 name에 'Kim'을 줬으나, ...o가 뒤에 나와 앞의 값이 모두 덮어씌워졌으므로 Daniel이 출력된다.

객체의 필드들을 배열에 spread operator로 합치면 에러가 발생한다. 일반 객체는 iterable(반복 가능한) 성질이 없으므로, 배열같은 iterable 객체에 합치려고 하면 에러가 발생한다.

2.배열 spread operator

function findMinInObject(o) {
	return Math.min(
	...Object.values(o)
	)
}
let o1 = { a: 1 }
let o2 = { b: 3 }
let o3 = { c: 7 }
findMinInObject(
	mergeObjects(o1, o2, o3)
) // 1

...Object.values(o)는 mergeObjects에서 인자로 받은 객체값들(1,3,7)을 하나의 객체로 만들고, 배열로 만들어 반환한다[1,3,7].

배열의 필드들을 객체에 spread operator로 합치면 에러가 발생한다. 배열은 iterable 성질을 가진 객체이므로 객체에 spread operator로 합쳐도 에러가 발생하지 않는다. 배열의 각 index에 있는 값들이 '0':1,'1':2처럼 객체에 합쳐진다.

spread operator로 객체를 복사하면 객체나 배열 필드의 경우 참조복사된다.
객체나 배열 필드는 단순히 reference만 복사된다. 따라서 deep copy를 위한 별도의 작업이 필요하다.

profile
뿌링클 치즈맛

0개의 댓글