JS 반복문과 함수(2024-11-05 수업)

짝은별·2024년 11월 5일

JS

목록 보기
5/23

for...in문 추가 정리

hasOwn() 과 hasOwnProperty()의 차이

우선 hasOwn()hasOwnProperty()가장 큰 차이static methodinstance method라는 점이 있다.
그렇다면 static methodinstance method는 무엇일까?

static method

객체 생성 없이 바로 사용할 수 있는 util method이다.
쉽게 말해 모든 변수들이 사용가능하고 모든 객체가 사용이 가능한 method라는 소리이다

instance method

생성자 함수(constructor function)을 통해서 생성된 객체만 사용할 수 있는 함수이다.

const obj = new Object();

의 형식으로 선언이 되어야 hasOwnProperty()를 사용할 수 있게된다

이는 vscode의 snippet으로 확인해보았다

static전역이라는 뜻을 가진 것을 알고 있다. 그렇다면 instance는 어디서 봤을까?
바로 FIGMA에서 component에 대한 복사본instance라고 불렀던 기억이 있다
여기서의 instance도 역시 비슷한 의미이다(생성자 함수에 의해 복사된 객체)

그렇다면 이제 for...in문에 대해 살펴보겠다

for (const key in ${objectName}) {
	console.log(key);
}

이는 입력한 objectName에 해당하는 모든 key값을 출력하겠다는 반복문이다
만약 value를 얻고 싶다면 objectName[key]와 같이 사용이 가능하다
여기서 주의할 점은 key는 변수이므로 .(점)표기법으로는 해당 value를 얻을 수 없다
반드시 [](대괄호)표기법을 사용해야한다

for...in문치명적인 약점은 해당 object만 순회하는 것이 아닌 JS내장객체의 Object까지 순회한다는 점이다

따라서 이를 방지하고자 hasOwnProperty() 혹은 hasOwn() 함수를 사용하는 것이다
하지만 hasOwn()static methodObject.hasOwn('obj' , 'keyName') 처럼 사용해도 되지만 hasOwnProperty()instance methodObject.prototype.hasOwnProperty().call('obj' , 'keyName') 처럼 사용하는 것이다.

해당 내용은 객체 뿐만 아니라 배열에도 똑같이 적용할 수 있다.

const tens = [10, 100, 1000, 10000];

for (const value in tens) {
  if (Object.hasOwn(tens, value)) console.log(tens[value]);
}

하지만 배열은 객체와는 다르게 순서가 존재한다
따라서 순서를 보장할 수 없는 for...in문을 쓰기엔 부적절할 수 있다

또한 for...in문은 상대적으로 성능적으로 안좋을 수 있다

enumerable

enumerable(열거가능한)

이는 해석하면 '열거 가능한' 이라는 의미이다
프로그래밍적 관점에서 바라보면 이는 인식할 수 있는지 없는지에 대한 여부이다

for...in문에서 한 가지 의문이 생겼을 수 있다.
hasOwnProperty()없이 그냥 사용하게 되면 JS내장객체인 Object까지 침범한다고 했는데 그냥 console.log(obj)를 해보면 별 탈 없이 obj내부의 key값들만 나왔다
이는 Objectkey값들이 바로 'enumerable 하지 않기 때문이다.'

그렇다면 일반 객체를 만들때도 enumerable하게 만들 수 있을까?
바로 살펴보자

Object.defineProperty(${objectName} , 'age', {
	value:30
	enumerable:true
	writable:false
	configurable:false
});

코드를 실행하면 입력한 objectName이라는 객체 안에 age라는 key가 생성되고 value가 30이 할당된다.
enumerable:true는 열거가능한 즉, 읽을 수 있게 만든다는 의미이다 이때 enumerable:false로 바꾸게 되면 이는 읽을 수 없는 key값이 된다

const test = {};

Object.defineProperty(test , 'age', {
  value: 40,
  enumerable:false,
});

console.log(test); // {}출력

그렇다면 writable:false는 무엇일까? 바로 value의 수정이 불가능하도록 만드는 것이다
configurable:false도 비슷하다 이는 삭제가 불가능하도록 만드는 것이다

만약 이러한 방식으로 여러 개의 property를 만들고 싶다면 Object.defineProperties()를 이용할 수 있다

이런 번거로운 방식말고 간단한 방법으로 객체가 수정이 불가능하도록 만드려면
Object.freeze('obj')를 사용할 수 있다

const test = {};

test.age = 40;
console.log(test);  // { age : 40 } 출력

Object.freeze(test);

test.age=20;
console.log(test) // 여전히 { age : 40 } 출력

하지만 한 번 freeze를 사용하게 되면 다시 수정가능하도록 되돌리는 방법이 없으니 주의해야 한다.

for...of문

iterable(반복 가능한)

기본적으로 for...of문iterable한 요소에만 적용이 가능하다
그렇다면 iterable한 요소란 무엇일까?
바로 순회할 수 있는지 없는지에 대한 여부이다

배열순서와 길이가 있으니 순회할 수 있다 따라서 iterable하다고 얘기할 수 있다
String을 살펴보자 iterable할까? 그렇지 않아 보일 수 있지만 iterable하다

맨 마지막 쪽을 보면 Symbol(Symbol.iterator)라는 속성이 있다
이러한 근거를 바탕으로 iterable한지 판단할 수 있다

그렇다면 생성자 함수로 생성된 변수만 그럴까? 그렇지 않다

iterable한 요소에만 사용할 수 있는 for...of문literable하게 정의된 string에도 적용이 되는 것을 확인할 수 있다.

하지만 객체iterable이 아니기 때문에 for...of문을 사용하면 오류가 발생한다

친절하게 obj는 iterable하지 않다고 말해준다

이처럼 배열과 비슷하게 생겼지만 배열은 아닌 것을 유사배열(arrayLike)라 부른다

for...of문장점은 이후 함수를 작성할때 편리함을 가져올 수 있다는 것이다
그리고 for...in문다르게 바로 value에 접근한다는 점도 있다

그렇다면 객체는 영원히 for...of문을 사용하지 못하는 것일까?
그렇지 않다 우선 객체에 Symbol(Symbol.iterator)라는 속성을 심어주면 사용 가능해진다
하지만 이는 상당히 귀찮다고 한다...
따라서 우리가 사용할 수 있는 두번째 방법으로는 key값 혹은 property값들을 모두 배열로 치환해주는 함수를 사용할 수 있다

console.log(Object.keys(obj)); //['age', 'height', 'width', 'color']
console.log(Object.entries(obj));//사진으로 대체

Object.entries(obj)에 대한 결과값이다.

이번엔 좀 더 복잡하게 객체 안에 객체가 있는 변수에 대해 살펴보자

const randomUser = {
  gender: 'female',
  name: { title: 'Ms', first: 'Carol', last: 'May' },
  location: {
    street: { number: 9162, name: 'Church Road' },
    city: 'Birmingham',
    state: 'Cumbria',
    country: 'United Kingdom',
    postcode: 'FO5E 4TN',
    coordinates: { latitude: '-4.3301', longitude: '155.0223' },
    timezone: {
      offset: '-4:00',
      description: 'Atlantic Time (Canada), Caracas, La Paz',
    },
  },
  email: 'carol.may@example.com',
  login: {
    uuid: '39e4e214-7f66-44a6-a3ba-3b5ce46b8e25',
    username: 'redduck745',
    password: 'picks',
    salt: '8xzqOzAn',
    md5: '7250e4042c2367cc82487f798c7c5253',
    sha1: '6c0e2fac669d6d7f11fb0bab52493f441cf5834b',
    sha256: '9e49256b8917113750533c24c015336af43d5d7130cf8faa19054c1ba36e7de8',
  },
  dob: { date: '1962-12-07T21:51:26.781Z', age: 59 },
  registered: { date: '2018-06-08T04:07:17.788Z', age: 4 },
  phone: '022 1280 9236',
  cell: '07653 428700',
  id: { name: 'NINO', value: 'SH 44 98 72 L' },
  picture: {
    large: 'https://randomuser.me/api/portraits/women/21.jpg',
    medium: 'https://randomuser.me/api/portraits/med/women/21.jpg',
    thumbnail: 'https://randomuser.me/api/portraits/thumb/women/21.jpg',
  },
  nat: 'GB',
};

이를 for...of문과 for...in문으로 순회하려면 다음과 같다

// for...of
for (const keyValue of Object.entries(randomUser)) {
  const key = keyValue[0];
  const value = keyValue[1];

  // 구조분해 할당
  // for(const [key,value] of Object.entries(randomUser))
  if (typeof value === 'object') {
    for (const keyValue of Object.entries(value)) {
      const key = keyValue[0];
      const value = keyValue[1];

      if (typeof value === 'object') {
        for (const keyValue of Object.entries(value)) {
          const key = keyValue[0];
          const value = keyValue[1];
        }
      }
    }
  }
}

---
// for...in
for (const value in randomUser) {
  if (Object.hasOwn(randomUser, value)) {
    const L1 = randomUser[value];

    if (typeof L1 === 'object') {
      for (const value in L1) {
        if (Object.hasOwn(L1, value)) {
          const L2 = L1[value];

          if (typeof L2 === 'object') {
            for (const value in L2) {
              if (Object.hasOwn(L2, value)) {
                const L3 = L2[value];
              }
            }
          }
        }
      }
    }
  }
}

이는 같은 행위를 하는 본문여러 번 작성해야 한다.
또한 코드의 가독성이 매우 떨어지게 된다
따라서 이를 해소하기 위해 나중에 재귀함수를 사용한다

함수의 기본

함수 선언

function showMessage(${parameter}) {
  alert( '안녕하세요!' );
}

showMessage();

함수function으로 시작해서 작성할 수 있다
참고로 값을 받아오는 부분parameter(매개변수,인자)라고 부른다

호출부showMessage(); 이 부분을 실행하게 되면 함수가 작동한다
함수가 작동할때 함수 내부에 선언한 변수외부에서 접근이 불가능하다(지역 변수)
하지만 외부 변수내부에서 사용하는 것은 가능하다 이를 scope chaining이라고 부른다

그렇다면 함수의 변수는 어떤 순서로 탐색을 할까?
1. 우선 함수 내부에 선언이 되었는지 확인한다
2. 매개 변수에 선언이 되었는지 확인한다
3. 외부에 선언이 되었는지 확인한다
따라서 외부에서 같은 이름의 변수가 선언되었어도 내부에 같은 이름의 변수가 있으면 그것만 사용한다

함수가 선언되고 매개변수를 통해 값을 받아오는데 호출부에 작성된 값들을 arguments라고 부른다
그렇다면 함수 내부에서 arguments의 값이 변하면 입력한 arguments의 값이 변화하는 것일까?
그렇지 않다. 이유는 parameter로 불러온 값을 복사하여 함수 내부에서 사용하는 것이기 때문이다

그렇다면 parameter가 필요하다고 했지만 arguments를 작성하지 않았을때 일어나는 일에 대해선 어떻게 대처할까?
바로 parameter에 default값을 부여하여 해결이 가능하다

function sum(a,b=10) {
  return a+b;
}

console.log(sum(1)); // 11

함수를 종료하고 싶을땐 return을 사용한다
return은 함수 내부 어느 곳에 위치하든 상관은 없다
조건에 따른 return또한 가능하다
return문을 사용할때는 주의해야할 점이 있다.
바로 개행관대하지 않다는 점이다

//잘못된 return
return
	a+b;

//올바른 return
return(
  a+b
  );

만약 개행을 하고 싶다면 ()로 묶어야 한다

참고로 return을 이용해 다른 함수를 내보내는 것도 가능하다(currying function)

함수 작명 팁

좋은 함수를 만들기 위해선 작명도 매우 중요하다
단번에 그 함수가 무슨 역할을 하는지 명시하는 것도 중요하다

  • "get…" – 값을 반환함
  • "calc…" – 무언가를 계산함
  • "create…" – 무언가를 생성함
  • "check…" – 무언가를 확인하고 불린값을 반환함

해당 접두사들을 이용해 함수가 어떤 역할을 담당하는지 간단하게 나타내줄 수 있다

좋은 함수란?

함수의 작성개인의 선택이지만 이러한 규칙을 지켰을때 이후 코드 유지보수에 있어 이점이 있을 수 있다

좋은 함수의 조건으로 생각되는 것은 다음과 같다

  • 하나의 기능만을 담당한다
  • 재사용이 가능하다
  • 함수가 무슨 기능을 담당하는지 이름만으로 알 수 있다
  • 인자를 과도하게 설정하지 않았다(최대 4~5개가 적당한 수준)

ETC

TDD와 CDD

TDDTest Driven Development의 약자로 이는 함수의 작동을 확인하기 위해 주로 사용한다

CDDComponent Driven Development의 약자로 함수를 넘어 component단위의 테스트를 진행하기 위해 사용한다
이는 디자이너와 같이 테스트를 진행할 수 있다는 점에서 장점이 있다

parseInt , parseFloat

paseIntparseFloat숫자와 문자가 섞인 문자열에서 숫자만 추출하고 싶을때 사용한다

Scope Chaining

JS script파일웹 브라우저 환경에서 실행되면 global execution(전역 환경)이 생성된다

이후 변수들이 알맞은 곳으로 hoisting된다

만약 함수가 정의되고 실행된다면 내부에 함수 본인만의 환경을 구축하고 변수를 저장하는 행동을 실행한다
즉, function execution context가 따로 실행된다

이때 새로운 execution이 일어나면 그 안에 변수도 역시 hoisting이 일어난다

그리고 변수들은 앞서 언급했던 변수 찾는 규칙에 따라 변수를 찾게 된다

이후 함수 내부에 변수가 존재하지 않는다면 outer environment reference를 호출한다

outer environment reference의 역할은 자신의 외부 환경을 관찰하고 참조하는 역할이다

이러한 과정을 통해 함수 내부에서 외부에 작성된 변수에 접근이 가능해지는 것이다

이를 scope chaining이라고 부른다

profile
FE(철 아님) 개발자 꿈꾸다

0개의 댓글