JS 공식문서 스터디 4. 함수 ~ 표현식과 연산자

CHO WanGi·2025년 9월 4일

Javascript

목록 보기
15/20

함수

함수 정의

function square(number) {
  return number * number;
}

변경 사항은 전역적으로 또는 해당 함수를 호출한 코드에 반영되지 않습니다.

  • 원시 값 변경시(pass by value)
function square(number) {
  number = number + 10; // 매개변수 값을 바꿔도
  return number * number;
}

let x = 5;
console.log(square(x)); // 225
console.log(x);         // 여전히 5 (안 바뀜)

위와 같이 함수안에서 매개변수의 값을 10으로 바꾸어도,
함수 밖에 있는 원래의 x에는 영향을 주지 않는다.

  • 객체 값 변경시(pass by Reference)
function myFunc(theObject) {
  theObject.make = "Toyota"; // 객체 속성 수정
}

const mycar = { make: "Honda" };

console.log(mycar.make); // "Honda"
myFunc(mycar);
console.log(mycar.make); // "Toyota"

객체 같은 경우 참조가 전달되기에, 함수 안에서 내용을 바꾸게 되면,
밖에서도 그 바뀐 내용이 반영됨.

함수 표현식(expression)

  • 익명 함수
    그냥 이름을 갖지 않는 함수
const square = function (number) {
  return number * number;
};
const x = square(4); // `x` 의 값은 16 입니다.
  • 함수 표현식에서 이름을 지정하는 것을 권장하는 이유
    디버거의 스택 추적에서 함수를 보다 더 쉽게 식별가능함
    함수를 다른 함수의 매개변수로 전달할때 편리함

함수 호출

함수를 호출 해야 지정된 매개변수를 갖고 작업을 수행
단, 함수는 호출될 때 스코프 내에 위치해야함.
이때 호이스팅이 될 수 있음

console.log(square); // square는 초기값으로 undefined를 가지고 호이스트된다.
console.log(square(5)); // ???
square = function (n) {
  return n * n;
};

위 코드는 함수 표현식이기 때문에 변수에 할당하는 방식으로 진행.
이때 저 square가 var 인지 let인지에 따라 달라짐

  • var 인 경우
    변수 선언 자체는 호이스팅, 초기화는 undefined로 됨.
    따라서 선언만 위로 올라오고 값은 나중에 할당되기에 undefined가 출력

  • let 인경우
    TDZ에 걸리게되어 ReferencError가 발생

스코프와 함수 스택

재귀

함수는 자신을 참조하고 호출하는 것이 가능.
1. 함수 이름
2. arguments.callee
3. 함수를 참조하는 스코프 내 변수

var foo = function bar() {
  // 여기에 구문이 들어갑니다.
};

위 함수를 재귀호출하라면
bar()
arguemtes.callee()
foo()

이 3가지가 있다.

중첩된 함수와 클로저

함수 내 함수를 끼워넣는데, 내부 함수는 그것을 포함하는 외부함수에서만 사용 가능
즉 외부 함수의 명령문에서만 내부 함수에 엑세스 가능

function outerFunction() {
    let outerVariable = 'I am outside!';
    
    function innerFunction() {
        console.log(outerVariable);
    }
    
    return innerFunction;
}

const innerFunc = outerFunction();
innerFunc();  // 'I am outside!' 출력

가장 기본적 동작 방식으로, innerFunction은 outerVariable을 참조 가능.

클로저의 주요 특징

1.데이터 은닉(Data Encapsulation)

클로저 사용시, 함수 내부의 변수를 외부에서 직접 접근할 수 없도록 만들 수 있음.
데이터 은닉을 통해 코드의 보안성과 유지보수성을 높입

function createCounter() {
    let count = 0;
    
    return function() {
        count++;
        return count;
    }
}

const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

위 코드에서 counter라는 함수가 아닌 count라는 변수에 직접 접근하면
ReferenceError: count is not defined 에러를 뱉는다.

counter라는 함수만 count 값에 접근할 수 있도록 하여 데이터 은닉이 가능하다.

  1. 데이터 캐싱

클로저를 이용하여 계산된 값을 저장하고, 같은 계산을 반복적으로 하지 않도록 할 수 있음

function createFibonacci() {
    const cache = {};
    
    function fibonacci(n) {
        if (n in cache) {
            return cache[n]; // memoization
        }
        
        if (n <= 1) {
            return n;
        } else {
            const result = fibonacci(n - 1) + fibonacci(n - 2);
            cache[n] = result;
            return result;
        }
    }
    
    return fibonacci;
}

const fib = createFibonacci();
console.log(fib(10)); // 55
console.log(fib(20)); // 6765

피보나치 수열을 구하는 함수로,
cache 객체를 계속 기억하고 참조하게된다.
fib(10)을 계산하게 되면 fib(9), fib(8) 을 저장하게 됨.

따라서 fibonacci 함수 첫줄의 조건문을 통해 반복 계산을 하지 않는다.

클로저 활용 실전 예제

함수 Factory

function multiplier(factor) {
    return function(number) {
        return number * factor;
    }
}

const double = multiplier(2);
console.log(double(5)); // 10

클로저 함수를 활용하여 double이라는 값을 두배로 만드는 함수를 동적으로 생성

콜백 함수

function fetchDataWithStatus(url) {
    const status = 'Fetching data...';

    fetch(url)
        .then(response => response.json())
        .then(data => {
            console.log(status); // 외부 변수인 status를 참조
            console.log(data);
        });
}

fetch 함수 내부에서 외부 함수의 status 변수를 참조하여 클로저의 특성을 나타낸다.
혹은 비동기 작업에서 특정 변수나 상태를 기억하게 할때 사용 가능한데

function sendDelayedMessage(message) {
  const sender = '관리자';

  setTimeout(function() {
    console.log(`[${sender}]님으로부터 도착: ${message}`);
  }, 2000);
}

sendDelayedMessage('안녕하세요! 2초 후에 도착합니다.');

setTimeout으로 전달된 익명함수가 바로 콜백함수로,
sender와 message에 대한 참조를 계속 갖고 있다.

따라서 2초 후 콜백함수가 실행시, 이미 종료된 sendDelayedMessage 함수에 스코프에 접근하여
sender와 message의 값을 읽어오는 것

화살표 함수

함수 표현식에 대한 간결한 대안으로 등장

var a = ["Hydrogen", "Helium", "Lithium", "Beryllium"];

var a2 = a.map(function (s) {
  return s.length;
});
console.log(a2); // logs [8, 6, 7, 9]

// 화살표 함수 사용
var a3 = a.map((s) => s.length);
console.log(a3); // logs [8, 6, 7, 9]

this

함수의 블록 스코프 내에서 선언되어야 작동.
즉 this가 어디서 호출되는가에 따라 값이 달라짐.

let group = {
  title: "1모둠",
  students: ["보라", "호진", "지민"],

  title2 : this.title,
  title3() { console.log(this.title) }
};

console.log(group.title2); //undefined
group.title3(); // 1모둠
  • group.title2는 왜 undefined 일까?
    group는 객체, 객체 리터럴 내 this는 전역 객체를 가리키게 됨.
    따라서 window.title을 가리키는데 window 객체는 이런 속성이 없어서 undefined가 출력

  • group.title3는 왜 1모둠일까
    function() {...} 처럼 메서드로 선언된 함수 안에서 this는 해당 메서드를 호출한 객체를 가르키게 된다.
    즉 this는 group을 가리키게 되어 1모둠이 출력되는 것.

함수 호출 방식과 this 바인딩

Javascript는 함수 호출 방식에 의해 this에 바인딩할 객체가 동적으로 결정된다.
즉 선언 시가 아니라 호출 시 결정 된다.

  1. 함수 호출

기본적으로 this는 전역객체에 바인딩 된다.
일반 전역함수,
내부함수 역시 외부함수가 아닌 전역객체에 바인딩

function foo() {
  console.log("foo's this: ",  this);  // window
  function bar() {
    console.log("bar's this: ", this); // window
  }
  bar();
}
foo();
  1. 메서드 호출

함수가 객체의 프로퍼티 값이라면 메소드로서 호출,
이때 메소드 내부의 this는 해당 메서드를 호출한 객체에 바인딩

var obj1 = {
  name: 'Lee',
  sayName: function() {
    console.log(this.name);
  }
}

var obj2 = {
  name: 'Kim'
}

obj2.sayName = obj1.sayName;

obj1.sayName(); // Lee
obj2.sayName(); // Kim
  1. constructor 안에서 사용하는 경우
function fn(){
	this.name = 'Kim';
}

JS에서 비슷한 오브젝트를 여러개 만들 경우 복사가 아닌 constructor를 사용.
이때 this는 fn으로 부터 새로 생성될 object들을 의미.

var object = new fn();

이런식으로 새로운 오브젝트를 꺼낼 수 있음.

  1. eventListener 안에서 사용시 this는 e.currentTarget
document.getElementById('버튼').addEventListener('click', function(e){
  console.log(this)
});

this는 현재 이벤트가 동작하는 곳을 의미함

this와 화살표함수

var 오브젝트 = {
  이름들 : ['김', '이', '박'];
  함수 : function(){
      오브젝트.이름들.forEach(function(){
        console.log(this)
      });
  }
}
var 오브젝트 = {
  이름들 : ['김', '이', '박'];
  함수 : function(){
      오브젝트.이름들.forEach(() => {
        console.log(this)
      });
  }
}

두 코드에서 this는 다른 것을 의미한다.
첫번째 메서드는 단순히 파라미터로 넘어간 일반함수 이기 때문에 window를,
두번째는 화살표함수를 사용하게되어, 자신만의 this를 갖지 않는다.
이때 자신이 선언된 스코프(오브젝트)의 this를 상속받아 오브젝트를 출력한다.

연산자와 표현식

JS는 단항, 이항, 삼항 연산자 모두 가짐

  • 속성 할당
    어떤 표현식이 객체로 평가시, 할당 표현식의 왼쪽 부분은 객체의 property로 할당 가능
let obj = {};

obj.x = 3;
  • 구조 분해 할당
var foo = ["one", "two", "three"];

// 구조 분해 없음
var one = foo[0];
var two = foo[1];
var three = foo[2];

// 구조 분해 사용
var [one, two, three] = foo;

JS로 코딩테스트를 풀게 되면 많이 쓰는 구조분해할당...

평가 예제

function f() {
  console.log("F!");
  return 2;
}
function g() {
  console.log("G!");
  return 3;
}
let x, y;
  1. y = x = f()

y는 x와 동일하고, x는 함수 f를 할당했기에
y도 f를 할당 받게 된다.
f()가 실행되었기에 콘솔에는 F 출력

  1. y = [f(), (x = g())];
    f() 는 2로 평가, x는 g() 평가값인 3으로 평가
    따라서 y는 [2, 3] 배열이 됨.
    콘솔에는 두 함수 모두 실행되었기에 F G 기록

  2. x[f()] = g();
    f()는 2로 평가 됨.
    x[2]에 g()의 평가값인 3이 할당
    콘솔에는 두 함수 모두 평가가 되었기에 F 기록후 G가 기록 되고
    x[2] = 3 으로 마무리.

표현식

NEW

생성자 함수나 클래스를 이용해 새로운 객체를 생성시 사용

function Person(name) {
  this.name = name;
}
const p = new Person("완기");

console.log(p.name); // "완기"

SUPER

클래스 상속시 부모클래스의 생성자나 메서드 호출 시 사용

class Animal {
  constructor(name) {
    this.name = name;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name); // Animal의 constructor(name) 호출
    this.breed = breed;
  }
}

const d = new Dog("바둑이", "진돗개");
console.log(d.name);  // "바둑이"
console.log(d.breed); // "진돗개"

Animal을 상속받은 자식 클래스인 Dog에서 Animal의 생성자인 name을 가져올때
super 키워드를 활용.

profile
제 Velog에 오신 모든 분들이 작더라도 인사이트를 얻어가셨으면 좋겠습니다 :)

0개의 댓글