h e 1 1 o !

koans 본문

p r o g r a m m i n g

koans

hee.hee 2022. 5. 18. 15:37

 

SCOPE - 호이스팅 / 함수선언식 & 함수표현식


호이스팅이란?

변수의 사용이 정의보다 앞서 등장하지만 동작하는 현상.

자바스크립트 및 액션스크립트 코드를 인터프리터가 로드할 때, 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되어 변수, 함수의 선언을 항상 컨텍스트 내의 최상위로 끌어올려 처리하는 것을 의미한다. 

JavaScript는 함수의 코드를 실행하기 전에 함수 선언에 대한 메모리부터 할당합니다. 덕분에 함수를 호출하는 코드를 함수 선언보다 앞서 배치할 수 있습니다. (그럼 왜 호이스팅 현상이 특이한거지? 더 자세한 내용은 나중에 공부하기로)

 

 

변수, 함수 선언 방법에 따라 호이스팅이 일어나는지가 달라진다.

모든 선언에는 사실 호이스팅이 일어난다. 하지만, let, const, class를 이용한 선언문은 호이스팅이 일어나지 않는 것처럼 보임.

더보기
왜 이런 차이가 나느냐 하면,let, const, class는 변수를 생성할 때 선언과 초기화(메모리 할당)가 따로 된다. 그리고...수정할 것*
호이스팅 일어나는 변수와 함수식
var, 함수선언식 -> 호이스팅
let, const, class, 함수표현식 -> 호이스팅 X
변수 먼저, 함수 다음
할당 된 변수 먼저, 할당 안된 변수 다음

 

 

 

문제

it('함수 선언식(declaration)과 함수 표현식(expression)의 차이를 확인합니다.', function () {
    let funcExpressed = 'to be a function';

    expect(typeof funcDeclared).to.equal('function');
    expect(typeof funcExpressed).to.equal('string');

    function funcDeclared() { //함수 선언식. 호이스팅이 일어나 위로.
      return 'this is a function declaration';
    }

    funcExpressed = function () { //함수 표현식. 호이스팅이 일어나지 않아 순서대로 실행됨
      return 'this is a function expression';
    };

    // 자바스크립트 함수 호이스팅(hoisting)에 대해서 검색해 봅니다.

    const funcContainer = { func: funcExpressed };
    expect(funcContainer.func()).to.equal('this is a function expression');

    funcContainer.func = funcDeclared;
    expect(funcContainer.func()).to.equal('this is a function declaration');
  });

 

 

 

 

SCOPE - parameter

    let message = 'Outer';
function getMessage() {
      return message;
    }

    function shadowGlobal() {
      let message = 'Inner';
      return message;
    }

    function shadowGlobal2(message) {
      return message;
    }

    function shadowParameter(message) {
      message = 'Do not use parameters like this!';
      return message;
    }

    expect(getMessage()).to.equal('Outer');
    expect(shadowGlobal()).to.equal('Inner');
    expect(shadowGlobal2('Parameter')).to.equal('Parameter');
    expect(shadowParameter('Parameter')).to.equal('Do not use parameters like this!');
    expect(message).to.equal('Outer');
  });

 

 

 

 

 

SCOPE - closure


mdn에 따르면 클로저의 정의는 다음과 같습니다. 반드시 기억하시기 바랍니다.
 
클로저는 함수와 함수가 선언된 어휘적 환경의 조합을 말한다.
이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
 
여기서의 키워드는 "함수가 선언"된 "어휘적(lexical) 환경"입니다.
특이하게도 자바스크립트는 함수가 호출되는 환경와 별개로, 기존에 선언되어 있던 환경 - 어휘적 환경 - 을 기준으로 변수를 조회하려고 합니다.
유어클레스 영상에서 언급되는 "외부함수의 변수에 접근할 수 있는 내부함수"를 클로져 함수로 부르는 이유도 그렇습니다.
클로저는 내부(inner) 함수가 외부(outer) 함수의 지역 변수에 접근할 수 있습니다.
이를 유념하시고 클로저의 유즈 케이스를 검색해 보시기 바랍니다. 아래 검색 키워드를 활용합니다.
function factories
namespacing private variables/functions

 

 

문제

it('lexical scope와 closure에 대해 다시 확인합니다.', function () {
    let age = 27;
    let name = 'jin';
    let height = 179;

    function outerFn() {
      let age = 24;
      name = 'jimin';
      let height = 178;

      function innerFn() {
        age = 26;
        let name = 'suga';
        return height;
      }

      innerFn();

      expect(age).to.equal(26);
      expect(name).to.equal('jimin');

      return innerFn;
    }

    const innerFn = outerFn();

    expect(age).to.equal(27);
    expect(name).to.equal(---);
    expect(innerFn()).to.equal(---);
  });

 'jimin' 178

 

 

 

화살표 함수와 클로저

화살표 함수

const add = (x, y) => {
      return x + y
    }
    
// 아래와 같음

const add2 = (x, y) => x + y

 

화살표 함수 클로저

const adder = x => {
      return y => {
        return x + y
      }
    }
    
// 아래와 같음


const adder2 = x => y => x + y

 

 

 

원시자료형, 참고자료형

원시 자료형을 변수에 할당할 경우, 값 자체의 복사가 일어납니다.
 it('원시 자료형을 변수에 할당할 경우, 값 자체의 복사가 일어납니다.', function () {
    let overTwenty = true;
    let allowedToDrink = overTwenty;

    overTwenty = false;
    expect(overTwenty).to.equal(false);
    expect(allowedToDrink).to.equal(true);

    let variable = 'variable';
    let variableCopy = 'variableCopy';
    variableCopy = variable;
    variable = variableCopy;
    expect(variable).to.equal('variable');
  });
 
원시 자료형 또는 원시 자료형의 데이터를 함수의 전달인자로 전달할 경우, 값 자체의 복사가 일어납니다.
it('원시 자료형 또는 원시 자료형의 데이터를 함수의 전달인자로 전달할 경우, 값 자체의 복사가 일어납니다.', function () {
    let currentYear = 2020;
    function afterTenYears(year) {
      year = year + 10; //currentyear가 변수로 전달되지만, 지역변수 year로 새롭게 선언된 것
    }
    afterTenYears(currentYear);
    expect(currentYear).to.equal(---);
    function afterTenYears2(currentYear) {
      currentYear = currentYear + 10;
      return currentYear;
    }
    let after10 = afterTenYears2(currentYear);
    expect(currentYear).to.equal(---);
    expect(after10).to.equal(---);
    // 사실 함수의 전달인자도 변수에 자료(data)를 할당하는 것입니다.
    // 함수를 호출하면서 넘긴 전달인자가 호출된 함수의 지역변수로 (매 호출 시마다) 새롭게 선언됩니다.
  });
  
  console.log(currentyear)

  //2020 2020 2030 //2020

 

 

참조형 데이터 비교

const ages = [22, 23, 27];
    allowedToDrink = ages;
    expect(allowedToDrink === ages).to.equal(----);
    expect(allowedToDrink === [22, 23, 27]).to.equal(---);

//true, false

const person = {
      son: {
        age: 9,
      },
    };

    const boy = person.son;
    boy.age = 20;
    expect(person.son.age).to.equal(--);
    expect(person.son === boy).to.equal(--);
    expect(person.son === { age: 9 }).to.equal(--);
    expect(person.son === { age: 20 }).to.equal(--);

20 true false falseddd

 

 

ARRAY

it('Array의 요소(element)를 다루는 방법을 확인합니다.', function () {
    const arr = [];
    expect(arr).to.deep.equal([]);

    arr[0] = 1;
    expect(arr).to.deep.equal([1]);

    arr[1] = 2;
    expect(arr).to.deep.equal([1, 2]);

    arr.push(3);
    expect(arr).to.deep.equal([1, 2, 3]);

    const poppedValue = arr.pop();   // *
    expect(poppedValue).to.equal(3); // **
    expect(arr).to.deep.equal([1, 2]); // ***
  });

*에서 기존의 arr도 pop이되고, 그 pop으로 삭제한 값을 poppedValue라는 새로운 변수에 할당함.

그러니 arr는 pop이 반영된 [1, 2]

 

 

 

it('Array 메소드 slice를 확인합니다.', function () {
    const arr = ['peanut', 'butter', 'and', 'jelly'];

    expect(arr.slice(1)).to.deep.equal(['butter', 'and', 'jelly']);
    expect(arr.slice(0, 1)).to.deep.equal(['peanut']);
    expect(arr.slice(0, 2)).to.deep.equal(['peanut', 'butter']);
    expect(arr.slice(2, 2)).to.deep.equal();
    expect(arr.slice(2, 20)).to.deep.equal();
    expect(arr.slice(3, 0)).to.deep.equal();
    expect(arr.slice(3, 100)).to.deep.equal();
    expect(arr.slice(5, 1)).to.deep.equal();

    // arr.slice는 arr의 값을 복사하여 새로운 배열을 리턴합니다.
    // 아래의 코드는 arr 전체를 복사합니다. 자주 사용되니 기억하시기 바랍니다.
    expect(arr.slice(0)).to.deep.equal(['peanut', 'butter', 'and', 'jelly']);
  });

[], ['and', 'jelly'], [], ['jelly'], []

 

slice는 새로운 배열을 반환한다. slice(a,b)

  • a>b  ->  빈 배열
  • a = b ->  빈 배열
  • a>arr.length  ->  빈배열
  • b>arr.length  ->  a부터 다 나옴

 

 

Array를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.

slice메서드로 배열을 할당하면 복사됨. 배열을 인자로 전달하고 재할당하면 원본도 바뀜

더보기
it('Array를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
// call(pass) by value와 call(pass) by reference의 차이에 대해서 학습합니다.
const arr = ['zero', 'one', 'two', 'three', 'four', 'five'];

function passedByReference(refArr) {
refArr[1] = 'changed in function';
}
passedByReference(arr);
expect(arr[1]).to.equal('changed in function');

const assignedArr = arr;
assignedArr[5] = 'changed in assignedArr';
expect(arr[5]).to.equal('changed in assignedArr');

const copiedArr = arr.slice();
copiedArr[3] = 'changed in copiedArr';
expect(arr[3]).to.equal('three');
});


 

 

 

shift, unshift, pop 메서드는 mutable

 it('Array 메소드 shift와 unshift를 확인합니다.', function () {
    const arr = [1, 2];

    arr.unshift(3);
    expect(arr).to.deep.equal([3, 1, 2]);

    const shiftedValue = arr.shift(); //pop처럼 삭제된 값이 담기고 원본이 변화
    expect(shiftedValue).to.deep.equal(3);
    expect(arr).to.deep.equal([1, 2]);
  });

mutable Array method

  • prototype. pop()
  • prototype. push()
  • prototype. shift()
  • prototype. unshift()
  • prototype. reverse()
  • prototype. sort()
  • prototype. splice()

 

 

 

객체의 길이는 undefined

const emptyObj = {};
emptyObj.length // undefined

const emptyarr = [];
emptyarr.length // 0

 

 

 

object property 다루는 방법. 'key' in objectname

 it('Object의 속성(property)를 다루는 방법을 확인합니다.', function () {
    const megalomaniac = { mastermind: 'Agent Smith', henchman: 'Agent Smith' };

    expect('mastermind' in megalomaniac).to.equal(true);

    megalomaniac.mastermind = 'Neo';
    expect(megalomaniac['mastermind']).to.equal('Neo');

    expect('secretary' in megalomaniac).to.equal(false);

    megalomaniac.secretary = 'Agent Smith';
    expect('secretary' in megalomaniac).to.equal(true);

    delete megalomaniac.henchman;
    expect('henchman' in megalomaniac).to.equal(false);
  });

 

this에 관한 문제

이해하고 있으나 this에 대해 더 자세히 공부할 때 정리하기

더보기
it("'this'는 method를 호출하는 시점에 결정됩니다.", function () {
const currentYear = new Date().getFullYear(); //2022
const megalomaniac = {
mastermind: 'James Wood',
henchman: 'Adam West',
birthYear: 1970, // 2000 //2010
calculateAge: function (currentYear) {
return currentYear - this.birthYear;
},
changeBirthYear: function (newYear) {
this.birthYear = newYear;
},
};

 

expect(currentYear).to.equal(2022);
expect(megalomaniac.calculateAge(currentYear)).to.equal(52);

 

megalomaniac.birthYear = 2000;
expect(megalomaniac.calculateAge(currentYear)).to.equal(22);

 

megalomaniac.changeBirthYear(2010);
expect(megalomaniac.calculateAge(currentYear)).to.equal(12);

 

/**
* !!Advanced [this.mastermind]? this.birthYear? this가 무엇일까요?
*
* method는 '어떤 객체의 속성으로 정의된 함수'를 말합니다. 위의 megalomaniac 객체를 예로 든다면,
* getMembers는 megalomaniac 객체의 속성으로 정의된 함수인 '메소드'라고 할 수 있습니다. megalomaniac.getMembers()와 같은 형태로 사용(호출)할 수 있죠.
* 사실은, 전역 변수에 선언한 함수도 웹페이지에서 window 객체의 속성으로 정의된 함수라고 할 수 있습니다.
* window. 접두사 없이도 참조가 가능하기 때문에(window.foo()라고 사용해도 됩니다.), 생략하고 쓰는 것뿐입니다. 이렇듯, method는 항상 '어떤 객체'의 method입니다.
* 따라서 호출될 때마다 어떠한 객체의 method일 텐데, 그 '어떠한 객체'를 묻는 것이 this입니다.
* 예시로, obj이라는 객체 안에 foo라는 메서드를 선언하고, this를 반환한다고 했을 때 ( 예: let obj = {foo: function() {return this}}; )
* obj.foo() === obj 이라는 코드에 true라고 반환할 것입니다.
* this는 함수의 호출에 따라서 값이 달라지기도 합니다. (apply나 call, bind에 대해서는 하단의 학습자료를 통해 더 공부해 보세요.)
*
* 그러나 화살표 함수는 다릅니다. 자신의 this가 없습니다.
* 화살표 함수에서의 this는 자신을 감싼 정적 범위(lexical context)입니다. (전역에서는 전역 객체를 가리킵니다.)
* 일반 변수 조회 규칙(normal variable lookup rules)을 따르기 때문에, 현재 범위에서 존재하지 않는 this를 찾을 때, 화살표 함수 바로 바깥 범위에서 this를 찾습니다.
* 그렇기에 화살표 함수를 사용할 때, 이러한 특이점을 생각하고 사용해야 합니다.
*
* 이와 관련하여, this에 대해서 더 깊이 학습하셔도 좋습니다.
* 가이드가 될 만한 학습자료를 첨부합니다.
*/
});

 

 

 

 

 

 

 

 

value와 reference / shallow copy와 deep copy (추가하기)


shallow 카피는 레퍼런스가 아니다.

 

 

원시자료형은 밸류를 복사하고 참조자료형은 reference로 원본 변수, 함수를 참조한다.

얕은 복사와 깊은 복사는 참조자료형의 복사에 

얕은 복사와 깊은 복사

 

얕은복사: 객체 안의 객체는 원본과 연동. assingn, spread syntax.

얕은 복사 - '객체 안의 객체' 기억하기

중첩된 객체를 복사 시, 가장 바깥 객체만 복사되어 내부 객체는 참조 관계가 유지.

깊은 복사~ 레퍼런스 중간 형식이다.

참조는 완전히 연동, 얕은 복사는 내부 객체가 연동됨.

https://fromnowwon.tistory.com/entry/object-reference-copy

 

  it('Object를 함수의 전달인자로 전달할 경우, reference가 전달됩니다.', function () {
    const obj = {
      mastermind: 'Joker',
      henchwoman: 'Adam West',
      relations: [1, 2, 3],
      twins: {
        'Jared Leto': 'Suicide Squad',
        'Joaquin Phoenix': 'Joker',
        'Heath Ledger': 'The Dark Knight',
        'Jack Nicholson': 'Tim Burton Batman',
      },
    };

    function passedByReference(refObj) {
      refObj.henchwoman = 'Adam West';
    }
    passedByReference(obj);
    expect(obj.henchwoman).to.equal('Adam West');

    const assignedObj = obj; 
    assignedObj['relations'] = [1, 2, 3];
    expect(obj['relations']).to.deep.equal([1, 2, 3]);

    const copiedObj = Object.assign({}, obj);
    copiedObj.mastermind = 'James Wood';
    expect(obj.mastermind).to.equal('Joker');

    obj.henchwoman = 'Harley';
    expect(copiedObj.henchwoman).to.equal('Adam West');

    delete obj.twins['Jared Leto'];
    expect('Jared Leto' in copiedObj.twins).to.equal(false);

    /*
    마지막 테스트 코드의 결과가 예상과는 달랐을 수도 있습니다.
    'Object.assign'을 통한 복사는 reference variable은 주소만 복사하기 때문입니다. 
    이와 관련하여 얕은 복사(shallow copy)와 깊은 복사(deep copy)에 대해서 학습하시기 바랍니다.
    가이드가 될 만한 학습자료를 첨부합니다.
      https://scotch.io/bar-talk/copying-objects-in-javascript
      https://medium.com/watcha/깊은-복사와-얕은-복사에-대한-심도있는-이야기-2f7d797e008a
    */
  });
it('여러 개의 객체를 병합할 수 있습니다.', function () {
    const fullPre = {
      cohort: 7,
      duration: 4,
      mentor: 'hongsik',
    };

    const me = {
      time: '0156',
      status: 'sleepy',
      todos: ['coplit', 'koans'],
    };

    const merged = { ...fullPre, ...me };
    // 변수 'merged'에 할당된 것은 'obj1'과 'obj2'의 value일까요, reference일까요? //밸류.
    // 만약 값(value, 데이터)이 복사된 것이라면, shallow copy일까요, deep copy일까요? // 얕은 복사. 내부에 객체가 있다면 원본을 참조함

    expect(merged).to.deep.equal({
      cohort: 7,
      duration: 4,
      mentor: 'hongsik',
      time: '0156',
      status: 'sleepy',
      todos: ['coplit', 'koans'],
    });

 

 

 

rest parameter

function getAllParamsByRestParameter(...args) { /
      return args;
    }
    
getAllParamsByArgumentsObj('first', 'second', 'third');
//['first', 'second', 'third'] 배열로 나옴

 

    function getAllParamsByArgumentsObj() {
      return arguments;
    }
    const argumentsObj = getAllParamsByArgumentsObj('first', 'second', 'third');
    //['first', 'second', 'third', callee: ƒ, Symbol(Symbol.iterator): ƒ]
    // object로 나옴

 

arguments를 통해 '비슷하게' 함수의 전달인자들을 다룰 수 있습니다. (spread syntax 도입 이전)
// arguments는 모든 함수의 실행 시 자동으로 생성되는 '객체'입니다.
 
 
 

Array.from

유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운Array 객체를 만듭니다.

console.log(Array.from('foo'));
// expected output: Array ["f", "o", "o"]

console.log(Array.from([1, 2, 3], x => x + x));
// expected output: Array [2, 4, 6]

 

 

rest parameter는 항상 배열
 function getAllParams(required1, required2, ...args) {
      return [required1, required2, args];
    }
    expect(getAllParams(123)).to.deep.equal([ 123, undefined, [] ]);

rest parameter 자리가 비어 있으면 빈 배열이 나옴

 

 

 

 

구조분해할당 - 객체의 단축 문법

const name = '김코딩'
    const age = 28

    const person = {
      name,
      age,
      level: 'Junior',
    }
    expect(person).to.eql({
      name,
      age,
      level: 'Junior',
    })
  })
  
  person // {name: '김코딩', age: 28, level: 'Junior'}

'p r o g r a m m i n g' 카테고리의 다른 글

고차함수  (0) 2022.05.24
section 1 기술면접 준비  (0) 2022.05.23
DOM  (0) 2022.05.17
closure  (0) 2022.05.13
scope  (1) 2022.05.12