Javascript

Scope

  1. 전역 스코프 (Global scope)
    코드 어디에서든지 참조할 수 있다.

  2. 지역 스코프 (Local scope or Function-level scope): var
    함수 코드 블록이 만든 스코프로 함수 자신하위 함수에서만 참조할 수 있다.

    function foo() {
      var x = "variable";
      // x는 오직 foo 함수 내부에서만 사용 가능
      console.log("Inside function");
      console.log(x);
    }
    foo();
    
    console.log(x);  // 에러 발생
    

    if 문, loop 문 등과 같은 다른 유형의 블록에서는 스코프로 간주되지 않는다.

    var age = 32;
    if (true) {
      var korAge = age + 1;
      console.log(`Your Korean Age is ${korAge}!`); // 'Your Korean Age is 33!'
    }
    console.log(korAge); // 33
    
  3. 블록 스코프(Block scope): let, const
    블록이란 여는 중괄호 '{'와 닫는 중괄호 '}'의 집합으로, 일반적인 c/c++ 에서 사용하는 규칙이다.

    var age = 32;
    if (true) {
      let korAge = age + 1;
      console.log(`Your Korean Age is ${korAge}!`); // 'Your Korean Age is 33!'
    }
    console.log(korAge); // Uncaught ReferenceError: korAge is not defined
    
  4. 렉시컬 스코프(Lexical Scope)
    렉시컬 스코프란 중첩된 함수 그룹에서 내부 함수가 상위 범위의 변수 및 기타 리소스에 액세스 할 수 있음을 의미한다.
    즉, 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다는 뜻이며,
    가장 중요한 점은 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.
    다른 말로, 정적 스코프(Static Scope)라 부르기도 한다.

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

    Javascript 는 렉시컬 스코프를 따르므로, 함수를 선언한 시점에 상위 스코프가 결정된다.
    함수를 어디서 호출하였는지는 스코프 결정에 아무런 영향을 주지 않는다.

    위 예제의 bar 함수는 전역에 선언되었기에, bar 함수의 상위 스코프는 전역 스코프이고,
    따라서 전역 변수 x의 값 1을 두 번 출력한다.

    관련 키워드: Scope, Scope chain, Execution context...


함수

일반함수

function 함수명(){
  함수 로직
}

// 예시
function SayHello(){
  console.log("hello!");
}

SayHello(); // 출력: hello!

Hoisting


익명함수


화살표 함수

This

어디서 선언했느냐가 아니라, 어디서 호출했는가에 따라 달라진다.
this는 "호출한 메서드를 소유하는 객체"

javascript 의 this는 함수의 실행방식에 따라 값이 다르다.

  1. 일반함수 실행방식
    전역공간에서 this는 전역객체를 가리킨다

    function foo() {
     console.log(this);
    }
    foo(); // window
    

    핵심은 함수가 실행되는 부분을 찾는 것이다.

    function foo() {
     console.log(this);
    }
    function bar() {
     foo();
    }
    bar();
    
  2. 메소드 실행
    메소드 실행에서의 this는 메소드를 소유하고 있는 객체를 가리킨다.

    const obj = {  
      name: 'obj',
      foo: function() {
        console.log(this);
      }
    };
    obj.foo(); // obj
    
    const obj2 = {
      name: 'obj2',
      foo: obj.foo
    }
    obj2.foo(); // obj2
    
    const obj3 = obj.foo;
    obj3(); // ???
    

    obj3: 일반함수 실행방식

  3. 생성자 실행

    • new 키워드
      • 규칙
        1. 함수 이름의 첫 글자는 대문자로 시작합니다.
        2. 반드시 new 연산자를 붙여 실행합니다.
    function User(name) {
      // this = {};  (빈 객체가 암시적으로 만들어짐)
    
      // 새로운 프로퍼티를 this에 추가함
      this.name = name;
      this.isAdmin = false;
    
      // return this;  (this가 암시적으로 반환됨)
    }
    
    • 생성자 실행에서의 this는 새롭게 만들어진 객체를 가리킨다.
    function Constructor(name) {
      this.name = name;
    }
    console.log(new Constructor('test'));
    const temp = Constructor('temp');
    console.log(temp);
    console.log(windows.name);
    
  4. 명시적 this 바인딩
    명시적 this 바인딩은 함수가 myFuc.call() 이나 myFuc.apply() 등의 메소드 호출을 통해 실행되는 것을 뜻한다.
    ( function.prototype.call / function.prototype.bind / function.prototype.apply )

    call(), apply(), bind() 호출에서 this는 첫번째 매개변수를 가리킨다.

    const object = { name: 'test' };
    function foo(a,b,c) {
      console.log(this);
    }
    
    foo(11, 12, 13);
    foo.call(object, 21, 22, 23);
    foo.apply(object, [31, 32, 33]);
    
    const bar = foo.bind(object);
    bar(41, 42, 43);
    

화살표함수와 this

Arrow function에서 this는 arrow function이 정의된 곳의 문맥을 그대로 따른다.

화살표 함수를 사용하면 안되는 경우

tip

  1. ||, && 연산자 활용

    var person = { age: 10 };
    if(
     person.name === undefined ||
     person.name === false ||
     person.name === '' ||
     person.name === null ||
     person.name === 0
    ) {
     console.log('noname');
    } else {
     console.log(person.name);
    }
    //
    console.log(person.name || 'noname');
    
    if(person.age > 19) {
     console.log('운전가능');
    } else {
     console.log(false);
    }
    //
    console.log(person.age > 19 && '운전가능')
    
  2. Optional operator .?

    let me = {}
    me.name // undefined
    
    me.name.first // error
    me.name?.first // undefined
    
    me.friends[0] // error
    me.friends?.[0] // undefined
    
    me.where(); // error
    me.where?.(); // undefined
    
  3. 값이 없을 때, 값을 할당하는 Null 병합 할당 연산자

    let config = {min: 10};
    if(config.min === undefined || config.min === null) {
     config.min = 20;
    }
    if(config.max === undefined || config.max === null) {
     config.max = 100;
    }
    console.log(config);
    // -->
    
    let config = {min: 10};
    config.min ??= 20;
    config.max ??= 100;
    console.log(config)
    
  4. ... 연산자를 이용한 복제, 원본 데이터의 유지

    const todos = [
     {
       title: '공부',
       done: true
     },
     {
       title: '게임',
       done: false
     }
    ];
    todos[1].done = true;
    todos.push({title: '운동', done: false});
    console.log(todos);
    
    // React 에서 자주 사용하는 복사
    const copyTodos = [...todos]
    copyTodos[1] = {...copyTodos[1]}
    copyTodos[1].done = true;
    copyTodos.push({title: '운동', done: false});
    
    // immer
    import produce from "immer";
    const copyTodos = produce(todos, draft => { // draft 는 todos의 임시 복제본
     draft[1].done = true;
     draft.push({title: "운동", done: false});
    });
    setTodos(copyTodos);
    

Reference