⚠️ Javascript 언어 이해하기

미니·2022년 8월 10일
0
post-thumbnail

👾JavaScript의 자료형JavaScript만의 특성은 무엇일까 ?


01. 동적언어(Dynamically Typed Language)와 정적언어(Statically Typed Language)

정적언어

  • 컴파일 시간에 따라서 변수의 타입이 결정되는 언어
  • 자료형을 컴파일 시에 결정(=타입)
  • C, C++, Java 등은 대표적인 정적 언어
  • 정적 언어는 변수에 들어갈 값의 형태에 따라 자료형을 지정
  • 컴파일 시에 자료형에 맞지 않는 값이 들어있을 경우 컴파일 에러가 발생
  • 컴파일 시간에 변수의 타입을 체크하므로 사소한 버그들을 쉽게 체크할 수 있는 장점
  • 타입 에러로 인한 문제점을 초기에 발견할 수 있어 타입의 안정성이 올라감

동적언어

  • 런타임에 타입이 결정되는 언어
  • 소스가 빌드될 때 자료형을 결정하는 것이 아니라 실행 시 결정!
  • 매번 타입을 써줄 필요가 없기 때문에 프로그래머가 빠르게 코드를 작성할 수 있음
  • JavaScript, Ruby, Python 등은 대표적인 동적 언어이다.
  • 런타임까지 타입에 대한 결정을 가지고 갈 수 있기 때문에 타입을 선택할 수 있음
  • 실행 도중에 변수에 예상치 못한 타입이 들어와 Type Error가 발생하는 경우가 생길 수 있음

느슨한 타입(loosely typed)의 동적(dynamic) 언어

  • 느슨한 타입(Loosely Typed)의 동적언어

    장점

    • 느슨한 타입의 동적 언어이기 때문에 (변수 생성 시)원시 변수의 타입을 미리 선언하지 않아도 된다

    단점

    • 많은 기능 명세서와 API가 오고 가는 대형프로젝트(혹은 협업 시)에서 타입이 올바른지 체크하는 것이 굉장히 까다롭기 때문에 배포 시 예상치 못한 문제가 생길수 있음
    • 실행 도중에 변수에 예상치 못한 타입이 들어와 타입에러가 발생할 수 있음
    • 타임 시 확인할 수 밖에 없기 때문에, 코드가 길고 복잡해질 경우 타입 에러를 찾기가 어려워짐

⇒ 정적 타입 체크와 강력한 문법을 추가한 TypeScipt나 Flow 등을 사용


02. JavaScript 형변환(Type Conversion)

// 형변환 (type Conversion)

// 문자로 : String | 숫자로 : Number | 불린으로 : Boolean
console.log("10" + "5"); //문자열 105
console.log(10 + 5); //숫자형 15

console.log(Number("10") + Number("5")); // 숫자열 15
console.log(String(10) + String(5)); // 문자열 105
// 숫자 -> 문자
let x = 123;
console.log(x); //123
console.log(String(x)); //123
console.log(typeof x); //number
console.log(typeof String(x)); //string

// 불린 -> 숫자 (0과 1이 바뀜)
let y = true; 
console.log(y); //true
console.log(String(y)); //true
console.log(typeof y); //boolenan
console.log(typeof String(y)); //string

// 문자 -> 숫자
let x = "문자";
console.log(x); // 문자
console.log(Number(x)); // NaN (: 문자열이라도 숫자형태의 문자열이라면 자연스럽게 숫자형으로 보여짐<if x = "123";)
console.log(typeof x); // string 
console.log(typeof Number(x)); // number

// 불린 -> 숫자
let y = true;
console.log(y); // true
console.log(Number(y)); // 1 (불린값은 숫자형태로 형변환될떄 0,1로 숫자가 바뀐다)
console.log(typeof y); // boolean
console.log(typeof Number(y));

// 문자 -> 불린
let x = "문자";
console.log(x); // 문자
console.log(Boolean(x)); // true
console.log(typeof x); // string
console.log(typeof Boolean(x)); // Boolean

// 숫자 -> 불린
let y = 123;
console.log(y); //123
console.log(Boolean(y)); // true
console.log(typeof y); // number
console.log(typeof Boolean(y)); // boolean

// false로 되는경우 (falsy:없거나 비어있는경우) / 대부분은 true
// "" | 0 | NaN

일정한 법칙에 따라 형변환하는게 있답니댱...

console.log('4' - true); // 3
  • 산술연산(+, - , *, /, %, **) :연산되어지는 두값을 모두 숫자로 변형한담 계산
      • : 문자열을 연결하는 기능도 있음(헷갈릴수있음)
      // 산술연산
      console.log(4 + "2"); // "42"
      // (+): 순서에 상관없이 어느 한쪽에 문자열이 있다면 → 모두 문자열로 바꾼담 문자열로 동작
      console.log(4 + 2); // 6
      console.log(4 - true); // 3
      console.log(4 * false); // 0
      console.log(4 / "2"); // 2 (숫자로)
      console.log("4" ** true); // 4
      console.log(4 % "two"); // NaN
  • 관계비교연산(<, ≤. ≥, >)
    • 특별한 경우가 아니면 두값을 모두 숫자로 바꿔 비교

      console.log(2 < "3"); //true
      console.log(2 > true); //true
      console.log("2" <= false); //false
      console.log("two" >= 1); //false
  • 같음 비교 연산( ==, ! ==, ===, ! ===)
    • 일치비교는 형변환이 일어나지 않지만 동등비교는 숫자형태로 형 변환이 일어나기 때문

      console.log(1 === "1"); // 일치, 불일치(!==)
      console.log(1 === true);
      console.log(1 == "1"); // 동등, 부등(!=)
      console.log(1 == true);
      
      /*
      일치비교는 형변환이 일어나지 않지만
      동등비교는 숫자형태로 형 변환이 일어나기 때문
      
      ===가 안전하게 비교하는데 도움이 된다!
      */

03. ==, === (비교연산자)

== : 동등비교일 때는 둘다 비슷한 의미를 가지고 있어서 true가 출력

=== : 일치비교를 하게 되면 두값이 서로 다른 자료형이기 때문에 false가 출력된다

// null과 undefined (자료형)
// 둘다 값이없다는 뜻
// null(의도적으로 표현할떄 사용하는값) | undefined(값이 없다는것을 확인하는 값)
console.log(null == undefined); // true
console.log(null === undefined); // false

의도적으로 '값이 없는 상태'를 표현하려면 반드시 null을 사용할것을 권장드림니다.

undefined & null

undefined : 선언한다음 값을 정해주지않음 (지정된 값이 없다)
null : 의도적으로 값이 비어있다는걸 표현할때!


👾JavaScript 객체와 불변성이란?

불변성(Immutability) : 객체가 생성된 이후 그 상태를 변경할 수 없는 상태

01. 원시타입(primitive type)의 불변 (원시타입 혹은 기본형)

원시 타입(primitive type)은 불변한다! 변수에 할당할 때 완전히 새로운 값이 만들어져 재 할당

  • 원시타입의 종류
    • Boolean
    • null
    • undefined
    • Number
    • String
    • Symbol (New in ECMAScript 6)

원시 타입 이외의 모든 값은 객체(Object)타입(=참조형)이며 객체타입 은 변경 가능한 값(mutable value) 입니다...
객체는 새로운 값을 다시 만들 필요없이 직접 변경이 가능

02. 객체(Object type)

→ 변수에 객체값을 할당한경우에는 특별하게 동작
→ 객체값이 어딘가에서 만들어지고 => 변수에는 그 객체값으로 가는 주소가 저장

💡 객체값이 수정되는게 아니라 주소가 수정!! 같은주소를 갖고있는 애들은 다 같이 수정!

let string = 'HI'; // string(주소)에 HI가 저장
console.log(string) // HI 
string = '바보'; // 주소가 변경
console.log(string) // 바보
let arr = [];
console.log(arr.length); // 0

let potato = arr.push(2); 
console.log(arr.length); // 1

🔼 복사본을 리턴하는 문자열의slice()와는 달리 배열(객체)의 메소드 push()는 배열 원본 자체를 변경해줌다...

03. 어떻게 하면 불변 객체를 만들 수 있을까요?

객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze() 메소드를 호출!

function deepFreeze(target){
  //  객체가 아니거나 동결된 객체는 무시하고 객체이고 동결되지 않은 객체만 동결
  if(target && typeof target === 'object' && !Object.isFrozen(target)){
    Object.freeze(target)
    // 모든 프로퍼티를 순회하며 재귀적으로 동결, Object.keys메소드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환.
    Object.keys(target).forEach(key => deepFreeze(target[key]))
  }
  return target;
}


const person = {
  name : 'miinii',
  address : {city : 'seoul'}
}

// 깊은 객체 동결
deepFreeze(person)

console.log(Object.isFrozen(person)) // true

// 중첩객체까지 동결
console.log(Object.isFrozen(person.address)) // true

person.address.city = 'anyang';
console.log(person) // { name: 'miinii', address: { city: 'seoul' } }

04. 얕은복사(Sahllow Copy) & 깊은복사(Deep Copy)

객체를 프로퍼티 값으로 갖는 객체의 경우 얕은복사한 단계까지만 복사하는것 깊은복사객체에 중첩되어 있는 객체까지 모두 복사하는것!

얕은복사와 깊은복사로 생성된 객체는 원본과는 다른 객체!(= 원본과 복사본은 참조값이 다른 별개의 객체!) 하.지.만 얕은복사는 객체에 중첩되어 있는 경우 참조값을 복사 / 깊은복사는 객체에 중첩되어 있는 객체까지 모두 복사해서 원시값 처럼 완전한 복사본을 만듦!!

const v = 1;

// 깊은복사
const c1 = v;
console.log(c1 === v); //true

const o = { x : 1 };

// 얕은복사
const c2 = o;
console.log(c2 === o); //true

이해하고 더 알아볼것~~


👾호이스팅TDZ는...?


01. 스코프, 호이스팅, TDZ

스코프(유효범위)

스코프 : 모든 식별자(변수이름, 함수이름, 클래스이름 등) 자신이 선언된 위치에 의해 다른 코드가 식별자 자신을 참조할 수 있는 유효범위가 결정! (= 스코프는 식별자가 유효한 범위)

함수의 매개변수는 함수 내에서만 참조할 수 있고 함수 외부에서는 참조할수 없다
=> 매개변수의 스코프가 함수 내부로 한정 되기 때문!

letconst 키워드로 선언한 변수는 if, for, function 등등 어떤 키워드와 관계없이 코드 블록, 즉 {} 중괄호로 감싸진 부분을 기준으로 scope를 갖게 되지만, var 키워드로 선언한 변수는 scope가 function에서만 구분

스코프가 필요한 이유@

  • 의도치 않은 변수값의 변경을 방지합니다 : 외부 API라이브러리 연동하여 사용할떄 같은 변수명끼리 충돌 하는 경우가 종종 있는데 그것을 방지합니다
  • 효율적인 메모리 관리 : 코드 블럭 안의 참조값들은 코드가 실행될때만 메모리에 할당되는것이 효율적이기 때문입니다~

호이스팅

변수가 끌어올려 지는 현상을 '호이스팅(hoisting)'이라고 부른다! 고나마 다행인건 호이스팅은 선언과 동시에 값을 할당하더라도, 선언문만 올려짐...

console.log(myVariable); // undefined
var myVariable;

함수를 한 번 선언하고 나면 어디서든 유연하게 사용할 수 있다는 장점이 있지만, 코드의 흐름에는 부정적인 영향을 끼친다.. 그래서 함수를 선언할 떄는 가급적 코드 윗부분에 선언하거나, 호출을 항상 아래쪽에서 한다거나 나름대로 규칙을 정해놓고 만들기!

!ES6에서 도입된 let, const, class를 사용한 선언문은 호이스팅 현상 발생하지 않는 것처럼 동작!

TDZ(Temporal Dead Zone) : 일시적 사각지대

스코프의 시작 지점부터 초기화 시작지점까지 변수를 참조할 수 없는 구간을 TDZ라고 부름

console.log(foo); // ReferenceError : foo is not undefined

let foo; // 변수선언문에서 초기화 단계가 실행
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행
console.log(foo); // 1

02. 함수 선언문과 함수 표현식에서 호이스팅 방식의 차이

함수표현식

변수에 할당되는 값이 함수 리터럴인 문!-> 함수표현식은 변수선언문과 변수 할당문을 한번에 기술한 축약표현!

// 함수 참조
console.dir(add); // f add(x,y)
console.dir(sub); // undefined

// 함수 호출
console.log(add(2,5)); // 7
console.log(sub(2,5)); // TypeError: sub is not a function

// 함수 선언문
function add(x, y) {
  return x + y;
}

// 함수 표현식
var sub = function (x,y) {
  return x - y;
};
  • 함수선언문 : 함수선언문이전에 호출 가능
  • 함수표현식 : 함수표현식 이전에 호출 불가능
    ➡️ 함수선언문으로 정의한 함수와 함수 표현식으로 정의한 함수의 생성시점이 다르기 때문
  • 함수선언문으로 함수를 정의하면 런타임 이전에 함수객체가 먼저생성 -> 자바스크립트 엔진은 함수이름과 도일한 이름의 식별자를 암묵적으로 생성 후 생성 함수객체를 할당 => 함수 선언문 이전에 함수를 참조 가능하며 호출 가능(함수 호이스팅)

∴ 변수 선언은 런타임 이전에 실행되어 undefined로 초기화 되지만 변수 할당문의 값은 런타임에 평가됨! -> 함수표현식의 함수 리터럴도 할당문이 실행되는 시점에 평가되어 함수 객체가 됨...
함수표현식으로 함수를 정의하면 함수 호이스팅이 발생하는 것이 아니라 변수 호이스팅이 발생

함수 호이스팅은 함수를 호출하기 전에 반드시 함수를 선언해야한다는 규칙을 무시한다! 그래서 함수선언문대신 함수 표현식 사용할것을 권장!

03. var의 문제점?

  • 중복 선언 허용 : 길고 복잡한 코드를 작성할 때 실수를 할 가능성이 커지고, 상황에 따라서는 치명적인 오류가 발생
var myVariable = '보라돌이';
console.log(myVariable); // 보라돌이
var myVariable = '뚜비!';
console.log(myVariable); // 뚜비!
  • 함수 스코프 : var 키워드로 선언한 변수는 scope가 function에서만 구분
{ let x = 3; }

function myFunction() {
  let y = 4;
}

console.log(x); // 3
console.log(y); // Uncaught ReferenceError: y is not defined

⭐️ 함수를 기준으로만 적용되는 스코프를 함수 스코프, 코드 블록을 기준으로 적용되는 스코프를 블록 스코프

  • 호이스팅
console.log(myVariable); // undefined
var myVariable = 2;
console.log(myVariable); // 2

04. 실행 컨텍스트콜 스택...?

실행 컨텍스트요...?

코드가 실행되려면 스코프, 식별자, 코드실행 순서등의 관리가 필요한데 실행 컨텍스트가 이 일을 한대요
∴ 소스코드를 실행하는데 필요한 환경을 제공하고 코드의 실행결과를 실제로 관리

구체적으로 말하면...
식별자(변수,함수, 클래스 등의 이름)를 등록하고 관리하는 스코프와 코드 실행 순서를 관리를 구현한 내부 매커니즘! 모든 코드는 실행컨텍스트를 통해 실행되고 관리

일단 이렇게만 알고.... 담에 더 자세히 다뤄보자..

콜스택은 또 뭐져...?

실행 컨텍스트 스택을 콜스택이라고 부르기도 한대용!

자바스크립트 엔진은 먼저 전역코드를 평가하여 전역 실행 컨텍스트를 생성 하는디 함수가 호출되면 함수 코드를 평가하여 함수 실행 컨텍스트를 생성하고 얘네들을 스택 구조로 관리 한다고 합니다~~

05. 👻스코프 체인, 변수 은닉화

스코프 체인

함수내 함수가 정의된것을 함수 중첩이라고 한답니다. 중첩된 함수의 스코프의 레퍼런스를 차례대로 저장하고, 각각의 스코프가 어떻게 연결되어 있는지를 보여준대용. 또한 해당 코드레벨에 참조값이 없으면 상위 레벨의 스코프로 참조값을 찾으러 여행을 떠나는 현상...

은닉화!

직접적으로 변경되면 안되는 변수에 대한 접근을 막는것을 은닉화라고 한다.

(function hi() {
  let a = 'hello;
  })() // hi is not defined 
function a(){
  let temp = 'a' 
  
  return temp;
} 

// console.log(temp)  error: temp is not defined
const result = a()
console.log(result); //a

위 함수 내부적으로 선언된 temp에는 직접적으로 접근을 할 수 없습니다. 함수 a를 실행시켜 그 값을 result라는 변수에 담아 클로저를 생성함으로써 temp의 값에 접근이 가능합니다.

이렇게 함수 안에 값을 숨기고 싶은 경우 클로저를 활용해볼 수 있습니다.

출처:mincho log


profile
ㅂr람ㄸrㄹr, 水ㄸrㄹr...🌊

0개의 댓글