본문 바로가기
programming/Topic Stack

javascript

by zsgg 2018. 8. 11.

Bubbling, Capturing

이벤트 버블링은 특정 화면 요소에서 이벤트가 발생했을 때 해당 이벤트가 더 상위의 화면 요소들로 전달되어 가는 특성을 의미한다.

<div>부모 
    <div>자식
        <div>자식자식
            <div>자식자식자식

버블링은 자식자식자식을 건드려서 거품처럼 위로 이벤트 감지를 하는 것으로 상상하면 된다. 기본값이고 반대로 이벤트건게 캡쳐링이다.

Document ready load

ready 는 DOM이 완성된 이후에 호출되는 callback 함수이고 load 는 img와 같은 다른 요소가 모두 load된 이후에 호출되는 callback 함수이다.

변수범위와 호이스팅

블록범위

아래의 firtName은 둘다 전역 범위다. 두번째, firstName은 {} 블럭으로 쌓여있지만, 자바 스크립트는 블럭단위 범위를 지원하지 않는다.

var firstName = "Richard";
{
     var firstName = "Bob";
}
console.log(firstName); // Bob

변수 호이스팅(Variable Hoisting)

모든 변수선언은 호이스트 됩니다. 호이스트란, 변수의 정의가 그 범위에 따라 선언과 할당으로 분리되는 것을 의미합니다. 즉, 변수가 함수내에서 정의되었을 경우 선언이 함수의 최상위로, 함수 바깥에서 정의되었을 경우는 전역 컨텍스트의 최상위로 변경됩니다.

변수의 선언이 초기화나 할당시에 발생하는것이 아니라, 최상위로 호이스트 된다는 것을 명심해야 합니다. 다음 코드를 주목하십시오.

function showName() {
     console.log("First Name : " + name);
     var name = "Ford";
     console.log("Last Name : " + name);
}
showName();
// First Name : undefined
// Last Name : Ford
// First Name이 undefined인 이유는 지역변수 name이 호이스트 되었기 때문입니다.

이 코드는 자바스크립트 엔진에 의해 다음과 같이 해석됩니다.

function showName() {
     var name; // name 변수는 호이스트 되었습니다. 할당은 이후에 발생하기 때문에, 이 시점에 name의 값은 undefined 입니다.
     console.log("First name : " + name); // First Name : undefined
     name = "Ford"; // name에 값이 할당 되었습니다.
     console.log("Last Name : " + name); // Last Name : Ford
}

호이스트 되었을때, 함수 선언은 변수선언을 덮어 씁니다.

// 다음 두 변수와 함수는 myName으로 이름이 같습니다.
var myName; // string
function myName() {
     console.log("Rich");
}
// 함수 선언은 변수명을 덮어 씁니다.
console.log(typeof myName); // function

하지만, 변수에 값이 할당될 경우에는 반대로 변수가 함수선언을 덮어 씁니다.

var myName = "Richard";
function myName() {
     console.log("Rich");
}
console.log(typeof myName); //string

자바스크립트 클로저 쉽게 이해하기

클로저(Closure)는 프로그래머가 창조적이고 인상적이며 간결한 프로그래밍을 할 수 있게 해줍니다.

클로저는 외부함수(포함하고 있는)의 변수에 접근할 수 있는 내부 함수를 일컫습니다. 스코프 체인(scope chain)으로 표현되기도 합니다. 클로저는 세가지 스코프 체인을 가집니다.
기본적인 클로저 예제

function showName(firstName, lastName) {
    var nameIntro = "Your name is ";
    // 이 내부 함수는 외부함수의 변수뿐만 아니라 파라미터 까지 사용할 수 있습니다.
    function makeFullName() {
        return nameIntro + firstName + " " + lastName;
    }
    return makeFullName();
}
showName("Michael", "Jackson"); // Your name is Michael Jackson

jQuery의 전형적인 클로저 사용예

$(function() {
    var selections = [];
    $(".niners").click(function() { // 이 클로저는 selections 변수에 접근합니다.
        selections.push(this.prop("name")); // 외부 함수의 selections 변수를 갱신함
    });
});

아래의 내부(private) 변수예제는 더글라스 크락포드(Douglas Crockford)에 의해 처음 시연되었습니다.

function celebrityID() {
    var celebrityID = 999;
    // 우리는 몇개의 내부 함수를 가진 객체를 리턴할것입니다.
    // 모든 내부함수는 외부변수에 접근할 수 있습니다.
    return {
        getID: function() {
            // 이 내부함수는 갱신된 celebrityID변수를 리턴합니다.
            // 이것은 changeThdID함수가 값을 변경한 이후에도 celebrityID의 현재값을 리턴합니다.
            return celebrityID;
        },
        setID: function(theNewID) {
            // 이 내부함수는 외부함수의 값을 언제든지 변경할 것입니다.
            celebrityID = theNewID;
        }
    }
}
var mjID = celebrityID(); // 이 시점에, celebrityID외부 함수가 리턴됩니다.
mjID.getID(); // 999
mjID.setID(567); // 외부함수의 변수를 변경합니다.
mjID.getID(); // 567; 변경된 celebrityID변수를 리턴합니다.

자바스크립트 Promise 쉽게 이해하기 • Captain Pangyo

Can I use Promise, IE는 안된다.

그럼 프로미스가 어떻게 동작하는지 이해하기 위해 예제 코드를 살펴보겠습니다. 먼저 아래 코드는 간단한 ajax 통신 코드입니다.

function getData(callbackFunc) {
  $.get('url 주소/products/1', function (response) {
    callbackFunc(response); // 서버에서 받은 데이터 response를 callbackFunc() 함수에 넘겨줌
  });
}

getData(function (tableData) {
  console.log(tableData); // $.get()의 response 값이 tableData에 전달됨
});

위 코드에 프로미스를 적용하면 아래와 같은 코드가 됩니다.

function getData(callback) {
  // new Promise() 추가
  return new Promise(function (resolve, reject) {
    $.get('url 주소/products/1', function (response) {
      // 데이터를 받으면 resolve() 호출
      resolve(response);
    });
  });
}

// getData()의 실행이 끝나면 호출되는 then()
getData().then(function (tableData) {
  // resolve()의 결과 값이 여기로 전달됨
  console.log(tableData); // $.get()의 reponse 값이 tableData에 전달됨
});

프로미스 에러 처리는 가급적 catch()로 앞에서 프로미스 에러 처리 방법 2가지를 살펴봤습니다. 개개인의 코딩 스타일에 따라서 then()의 두 번째 인자로 처리할 수도 있고 catch()로 처리할 수도 있겠지만 가급적 catch()로 에러를 처리하는 게 더 효율적입니다.

그 이유는 아래의 코드를 보시면 알 수 있습니다.

// then()의 두 번째 인자로는 감지하지 못하는 오류
function getData() {
  return new Promise(function (resolve, reject) {
    resolve('hi');
  });
}

getData().then(function (result) {
  console.log(result);
  throw new Error("Error in then()"); // Uncaught (in promise) Error: Error in then()
}, function (err) {
  console.log('then error : ', err);
});

getData() 함수의 프로미스에서 resolve() 메서드를 호출하여 정상적으로 로직을 처리했지만, then()의 첫 번째 콜백 함수 내부에서 오류가 나는 경우 오류를 제대로 잡아내지 못합니다.

자바스크립트 async와 await • Captain Pangyo

async & await 기본 문법

async function 함수명() {
  await 비동기_처리_메서드_명();
}

먼저 함수의 앞에 async 라는 예약어를 붙입니다. 그러고 나서 함수의 내부 로직 중 HTTP 통신을 하는 비동기 처리 코드 앞에 await를 붙입니다. 여기서 주의하셔야 할 점은 비동기 처리 메서드가 꼭 프로미스 객체를 반환해야 await가 의도한 대로 동작합니다.

async & await 예외 처리

async & await에서 예외를 처리하는 방법은 바로 try catch입니다. 프로미스에서 에러 처리를 위해 .catch()를 사용했던 것처럼 async에서는 catch {} 를 사용하시면 됩니다.

async function logTodoTitle() {
  try {
    var user = await fetchUser();
    if (user.id === 1) {
      var todo = await fetchTodo();
      console.log(todo.title); // delectus aut autem
    }
  } catch (error) {
    console.log(error);
  }
}

Promise - JavaScript | MDN

Promise는 다음 중 하나의 상태를 가집니다.

대기(pending): 이행하거나 거부되지 않은 초기 상태.
이행(fulfilled): 연산이 성공적으로 완료됨.
거부(rejected): 연산이 실패함.
alt promise

자바스크립트의 Async/Await 가 Promises를 사라지게 만들 수 있는 6가지 이유

  1. 간결함과 깔끔함
  2. 에러 핸들링
  3. 분기
  4. 중간값(Intermediate values)
  5. error stack
  6. 디버깅

결론

async/await는 최근 몇년간 JavaScript에 추가된 기능 중에 가장 혁명적인 기능 중에 하나이다. 이를 사용하다보면 promise가 가진 문법적인 번잡함을 대신할 직관적인 대체재라는 것을 깨닫게 될 것이다.

[JS] ECMAScript 버전별 명칭 정리 - sunkyu kim - Medium

ECMAScript는 ES라는 약어로도 사용되어지며 정보 통신 시스템의 표준화하는 조직인 ECMA International에서 ECMA-262를 표준화한 범용 스크립팅 언어 상표이다.

ECMA스크립트 - 위키백과, 우리 모두의 백과사전
Browsers, JavaScript Versions

ES1, ES2, ES3, ES4, ES5

ES1: 1997년 6월 (초판)
ES2: 1998년 6월
ES3: 1999년 12월
ES4: 언어에 얽힌 정치적 차이로 인해 버려짐
ES5: 2009년 12월

ES6 / ES2015

2015년 6월 여기서부터 모든 혼란의 원인이 시작되는데 ECMAScript 6 (ES6)는 출시 이전에 대중화된 이름으로 알려졌으나 나중에 ECMAScript 2015 (ES2015)로 공개연도를 반영한 이름으로 변경되었고 이후부터는 공개 될 연도에 따라 이름이 지정되게 된다.

요약하자면…

ECMAScript의 초기 버전(ES1, ES2, ES3, ES4, ES5)은 숫자로 이름이 지정되었으며 1씩 증가
2015년부터 시작하는 새로운 개정판(ES2015, ES2016, ES2017, ES2018)들은 공개연도를 삽입

Error

Error - JavaScript | MDN

[Javascript] 에러 처리 방법 - Dongmin Jang - Medium

new Error <- 어느 것도 하지 않을 것입니다.

javascript - throw Error('msg') vs throw new Error('msg') - Stack Overflow

Both are fine; this is explicitly stated in the specification, ECMAScript 2015 Language Specification – ECMA-262 6th Edition

둘다 같다 Error() 를 하면 new Error()가 호출된다. 가독성 있게 new Error() 하는게 좋겠다.
throw '이건 문자열만 보낸다.' 이렇게 할경우 catch 에서 log 찍을때 stacktrace는 안나온다.

javascript : call, apply, bind 차이 - this의 사용 - Web Standard

update.call(madeline, 1942, 'actress');
update.apply(madeline, [1918, 'writer']);

bind

this의 값을 바꿀 수 있는 마지막 함수는 bind이다. bind를 사용하면 함수의 this 값을 영구히 바꿀 수 있다. update 메서드를 이리저리 옮기면서 호출할 때 this 값은 항상 bruce가 되게끔, call이나 apply, 다른 bind와 함께 호출하더라도 this 값이 bruce가 되도록 하려면 bind를 사용한다.

const updateBruce = update.bind(bruce);

updateBruce(1904, "actor");
// bruce 는 이제 { name: "Bruce", birthYear: 1904, occupation: "actor" }

updateBruce.call(madeline, 1274, "king");
// bruce 는 이제 { name: "Bruce", birthYear: 1274, occupation: "king" };
// madeline은 변하지 않음

bind는 함수의 동작을 영구적으로 바꾸므로 찾기 어려운 버그의 원인이 될 수 있다. bind를 사용한 함수는 call이나 apply, 다른 bind와 함께 사용할 수 없는 거나 마찬가지다. 함수를 여기저기서 call이나 apply로 호출해야 하는데, this 값이 그에 맞춰 바뀌어야 하는 경우를 상상해보라. 이럴 때 bind를 사용하면 문제가 생긴다. bind를 쓰지 말라고 권하는 것은 아니다. bind는 매우 유용하지만, 함수의 this가 어디에 묶이는지 정확히 파악하고 사용해야 한다.

bind에 매개변수를 넘기면 항상 그 매개변수를 받으면서 호출되는 새 함수를 만드는 효과가 있다. 예를 들어 bruce가 태어난 해를 항상 1949로 고정하지만, 직업은 자유롭게 바꿀 수 있는 업데이트 함수를 만들고 싶다면 다음과 같이 하면 된다.

const updateBruce1949 = update.bind(bruce, 1949); updateBruce1949("singer, songwriter");
/*
 bruce 는 이제 {
     name: "Bruce",
     birthYear: 1949,
     occupation: "singer, songwriter"
 }
*/

getter

getter - JavaScript | MDN

Getter는 객체에 프로퍼티를 정의할 수 있도록 하지만, 그 프로퍼티에 접근하기 전까지는 값을 계산하지 않습니다. getter는 값을 계산하는 비용을 실제 값이 필요할 때까지 미루며, 그 값이 필요없다면 계산 비용도 들지 않습니다.

또다른 최적화 기법으로는 계산 미루기와 함께 프로퍼티 값을 나중에 접근하기 위해 캐싱하는 것이 있습니다. 이를 똑똑한(smart), 혹은 메모화된(memoized) getter라고 부릅니다. 값은 getter가 호출될 때 처음 계산되며, 캐싱됩니다. 이후의 호출에는 다시 계산하지 않고 이 캐시값을 반환합니다. 이는 다음과 같은 상황에 유용합니다.

똑똑한 getter는 값을 다시 계산하지 않기 때문에, 값의 변경이 예상되는 경우에는 똑똑한 getter를 이용해서는 안 됩니다.

다음 예제에서, 객체는 원래 프로퍼티로 getter를 가집니다. 프로퍼티를 가져올 때, getter는 삭제되며 대신 명시적인 값이 저장됩니다. 최종적으로 값을 반환합니다.

Property - Accessor Descriptor

[Getter] 반환값의 캐싱이 가능합니다

// 코드는 복사해서 브라우저 콘솔창에서 실행 가능합니다
// Required only nodejs
// const { performance } = require('perf_hooks');

const obj = {
  get memoizedGetter() {
    delete this.memoizedGetter;
    return this.memoizedGetter = [...Array(5e+7).keys()].reduce((prev, num) => prev + num, 0);
  },
  get commonGetter() {
    return [...Array(5e+7).keys()].reduce((prev, num) => prev + num, 0);
  },
};

const measureExecutionTime = (functionName, fn) => {
  const start = performance.now();
  fn();
  return `[${functionName}] Elasped Time: ${performance.now() - start} ms`;
};

console.log(measureExecutionTime('Memoized Getter Test1', () => obj.memoizedGetter));
console.log(measureExecutionTime('Memoized Getter Test2', () => obj.memoizedGetter));
console.log(measureExecutionTime('Memoized Getter Test3', () => obj.memoizedGetter));

console.log(measureExecutionTime('Common Getter Test1', () => obj.commonGetter));
console.log(measureExecutionTime('Common Getter Test2', () => obj.commonGetter));
console.log(measureExecutionTime('Common Getter Test3', () => obj.commonGetter));

[Getter] 의 지연 평가의 장점을 살리면서
값은 상수처럼 변하지 않게 유지하려면 어떻게 할 수 있을까요?

const obj = {
  get hugeValue () {
    console.log(Object.getOwnPropertyDescriptor(this, 'hugeValue')); // {get: [Getter], set: undefined, enumerable: true, configurable: true}
    delete this.hugeValue;
    const cachedValue = [...Array(5e+7).keys()].reduce((prev, curr) => prev + curr, 0);
    return Object.defineProperty(this, 'hugeValue', {
      value: cachedValue,
      writable: false,
      configurable: false,
      enumerable: true,
    }).hugeValue 
  },
  name: 'HugeValueInside',
};

console.log(obj.hugeValue); // 1249999975000000
obj.hugeValue = '변해라 얍!';
console.log(obj.hugeValue); // 1249999975000000
console.log(Object.getOwnPropertyDescriptor(obj, 'hugeValue')); // {value: 1249999975000000, writable: false, enumerable: true, configurable: false}

캐싱해야할 큰 값을 변수에 담고, 반환 시점에 위와 같이 정의하면 됩니다!

'programming > Topic Stack' 카테고리의 다른 글

Java  (0) 2019.09.12
DDD  (0) 2018.11.07
Linux  (0) 2018.11.06
Design Pattern  (0) 2018.08.08

댓글