[JS] [번역] 40가지의 유용한 JavaScript팁, 트릭과 모범사례

[JS] [번역] 40가지의 유용한 JavaScript팁, 트릭과 모범사례

이 글에서는 브라우저 / 엔진 또는 SSJS (Server Side JavaScript) 인터프리터에 관계없이 모든 JavaScript 개발자가 알아야 할 JavaScript 팁, 트릭 및 모범 사례를 제공합니다.

이 글의 코드 스니펫은 V8 JavaScript 엔진 (V8 3.20.17.15)을 사용하는 최신 Chrome 버전 30에서 테스트되었습니다.


1. 변수를 처음으로 선언 할 때마다 var를 붙인다

선언되지 않은 변수는 자동으로 전역 변수로 정의된다. 전역변수를 피하자!


2. == 대신에 === 사용

== 및 != 연산자는 필요한 경우 자동 유형 변환을 수행한다

=== 및 !== 연산자는 변환을 수행하지 않고, 값과 유형을 비교한다. (그래서 ==보다 빠르다, == 및 != 연산자는 되도록 사용하지 않도록하자! )

1
2
3
4
5
6
7
8
[10] === 10    // is false
[10] == 10 // is true
'10' == 10 // is true
'10' === 10 // is false
[] == 0 // is true
[] === 0 // is false
'' == false // is true but true == "a" is false
'' === false // is false

3. undefined, null, 0, false, NaN, ''(empty string) 는 논리 값에 False


4. 세미콜론을 사용하여 행 구분

세미콜론을 사용하여 라인종료하는 것이 좋다. 대부분의 경우 JavaScript 파서에 의해 삽입되기 때문에 잊어 버리면 경고하지 않는다. 세미콜론을 왜 사용해야하는지에 대한 자세한 내용은… 참조


5. 객체 생성자 (object constructor) 생성

1
2
3
4
5
6
function Person(firstName, lastName) {
this.firstName = firstName;
this.lastName = lastName;
}

var Saad = new Person("Saad", "Mousliki");

6. typeof, instanceof, constructor를 사용할 때 주의

  • typeof : JavaScript 단항 연산자는 변수의 기본 유형을 나타내는 문자열을 반환하는 데 사용되며 typeof null “객체”를 반환 한다는 사실을 잊지 말고 대부분의 객체 유형 (Array, Date 등)에서도 “객체”를 반환한다.
  • constructor : 내부 프로토 타입 속성의 속성으로 코드에 의해 재정의 될 수 있습니다.
  • instanceof : 모든 프로토 타입 체인을 검사하는 또 다른 JavaScript 연산자입니다. 생성자가 발견되면 true를 반환하고 그렇지 않으면 false를 반환합니다.
1
2
3
4
var arr = ["a", "b", "c"];
typeof arr; // return "object"
arr instanceof Array; // true
arr.constructor(); //[]

7. 자신을 실행하는 Function (자체 호출) 만들기

이를 종종 자체 호출 익명 함수, 즉시 실행 함수 또는 IIFE (Immediately Invoked Function Expression)라고합니다. 이 함수는 함수를 작성할 때 자동으로 실행되며 다음과 같은 형식을 갖습니다.

1
2
3
4
5
6
7
(function () {
// some private code that will be executed automatically
})();
(function (a, b) {
var result = a + b;
return result;
})(10, 20);

8. 배열에서 임의의 항목 가져오기

1
2
3
var items = [12, 548, "a", 2, 5478, "foo", 8852, , "Doe", 2145, 119];

var randomItem = items[Math.floor(Math.random() * items.length)];

9. 특정 범위의 난수 가져오기

이 코드 조각은 테스트를 위해 더미 데이터를 생성하는 예를 들어 월급의 하한과 상한을 지정하고 그 범위에서 임의의 값을 얻고 싶은 경우에 유용합니다.

1
var x = Math.floor(Math.random() * (max - min + 1)) + min;

10. 0부터 최대값을 갖는 배열 생성

1
2
3
4
var numbersArray = [],
max = 100;

for (var i = 1; numbersArray.push(i++) < max; ); // numbers = [1,2,3 ... 100]

11. 알파벳중에서 무작위 문자열 생성

1
2
3
4
5
6
7
8
9
function generateRandomAlphaNum(len) {
var rdmString = "";
for (
;
rdmString.length < len;
rdmString += Math.random().toString(36).substr(2)
);
return rdmString.substr(0, len);
}

12. 숫자 배열 섞기

1
2
3
4
5
var numbers = [5, 458, 120, -215, 228, 400, 122205, -85411];
numbers = numbers.sort(function () {
return Math.random() - 0.5;
});
/* the array numbers will be equal for example to [120, 5, 228, -215, 400, 458, -85411, 122205] */

더 나은 옵션은 기본 정렬 JavaScript 함수를 사용하는 것보다 코드에 의해 임의의 정렬순서를 구현하는 것일 수 있다. 참조


13. 문자열 trim 함수

문자열에서 공백을 제외하는 trim 함수는 Java, C#, PHP 등 다양한 언어로 구현되어 있지만, JavaScript에는 존재하지 않는다. 그래서 String 개체에 추가할 수 있다.

1
2
3
String.prototype.trim = function () {
return this.replace(/^s+|s+$/g, "");
};

14. 배열을 다른 배열에 추가

1
2
3
4
5
var array1 = [12 , "foo" , {name "Joe"} , -2458];

var array2 = ["Doe" , 555 , 100];
Array.prototype.push.apply(array1, array2);
/* array1 will be equal to [12 , "foo" , {name "Joe"} , -2458 , "Doe" , 555 , 100] */

15. arguments 객체를 배열로 반환

1
var argArray = Array.prototype.slice.call(arguments);

16. 주어진 argument가 숫자인지 확인

1
2
3
function isNumber(n) {
return !isNaN(parseFloat(n)) && isFinite(n);
}

17. 주어진 argument가 배열인지 확인

1
2
3
function isArray(obj) {
return Object.prototype.toString.call(obj) === "[object Array]";
}

위 코드에서 toString() 메소드가 재정의되면, 예상한 결과를 얻지 못할 수 있다.
그래서…

1
Array.isArray(obj); // its a new Array method

18. 배열 안의 숫자의 최대 값과 최소값 얻기

1
2
3
var numbers = [5, 458, 120, -215, 228, 400, 122205, -85411];
var maxInNumbers = Math.max.apply(Math, numbers);
var minInNumbers = Math.min.apply(Math, numbers);

19. 배열 비우기

1
2
var myArray = [12, 222, 1000];
myArray.length = 0; // myArray will be equal to [].

20. 배열에서 item을 제거할 때 delete 사용하지 말것

배열의 item를 제거 할 때는 delete 대신 split을 사용하자.
delete 는 배열에서 item을 제외하는 것이 아니라, undefined으로 바꾼다.

1
2
3
4
5
var items = [12, 548, "a", 2, 5478, "foo", 8852, , "Doe", 2154, 119];
items.length; // return 11
delete items[3]; // return true
items.length; // return 11
/* items will be equal to [12, 548, "a", undefined × 1, 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */

위 대신에 다음과 같이 사용

1
2
3
4
5
var items = [12, 548, "a", 2, 5478, "foo", 8852, , "Doe", 2154, 119];
items.length; // return 11
items.splice(3, 1);
items.length; // return 10
/* items will be equal to [12, 548, "a", 5478, "foo", 8852, undefined × 1, "Doe", 2154, 119] */

21. 배열의 length 속성을 사용하여 자르기

위의 배열을 비우기 예와 같이 length 속성을 사용하여 자를 수 있다.

1
2
var myArray = [12, 222, 1000, 124, 98, 10];
myArray.length = 4; // myArray will be equal to [12 , 222 , 1000 , 124].

또한 배열의 length보다 큰 값을 넣으면 배열의 길이가 변경되고, 새로운 item이 undefined 값으로 추가 된다.
배열 길이는 읽기 전용 속성이 아니다.

1
2
myArray.length = 10; // the new array length is 10
myArray[myArray.length - 1]; // undefined

22. 조건 판정에 논리적 AND / OR 사용

1
2
3
var foo = 10;
foo == 10 && doSomething(); // is the same thing as if (foo == 10) doSomething();
foo == 5 || doSomething(); // is the same thing as if (foo != 5) doSomething();

논리적 OR은 함수 argument에 기본 값을 설정하는데 사용할 수 있다.

1
2
3
function doSomething(arg1) {
arg1 = arg1 || 10; // arg1 will have 10 as a default value if it’s not already set
}

23. Use the map() function method to loop through an array’s items

1
2
3
4
var squares = [1, 2, 3, 4].map(function (val) {
return val * val;
});
// squares will be equal to [1, 4, 9, 16]

24. 소수점 이하 N자리수 반올림

1
2
var num = 2.443242342;
num = num.toFixed(4); // num will be equal to 2.4432

25. 부동 소수점 문제

1
2
3
0.1 + 0.2 === 0.3; // is false
9007199254740992 + 1; // is equal to 9007199254740992
9007199254740992 + 2; // is equal to 9007199254740994

0.1 + 0.2 는 0.30000000000000004과 같다. IEEE 754 표준에 따라 JavaScript의 숫자는 모두 내부적으로 64bit 부동 소수점 형으로 다루어지는 것을 알 필요가 있다. 자세한 설명은 블로그 게시물 참조

toFixed()및 버튼 toPrecision()을 사용하면이 문제를 해결할 수 있습니다.


26. for-in 루프를 사용할 때 객체의 속성확인

이 코드 조각은 객체의 프로토타입의 속성을 열거하고 싶지 않은 경우에 편리하다.

1
2
3
4
5
for (var name in object) {
if (object.hasOwnProperty(name)) {
// do something with name
}
}

27. 쉼표 연산자

1
2
3
4
var a =  0 ;
var b = (a + , 99 );
console . log (a); // a will be equal to 1
console . log (b); // b is equal to 99

28. 요소의 쿼리와 계산을 필요로 하는 Cache 변수

jQuery 셀렉터의 경우는 DOM 요소를 캐시 할 수 있다.

1
2
3
4
var navright = document.querySelector("#right");
var navleft = document.querySelector("#left");
var navup = document.querySelector("#up");
var navdown = document.querySelector("#down");

29. isFinite() 를 지나기 전에 인수 확인하기

1
2
3
4
5
6
7
isFinite(0 / 0); // false
isFinite("foo"); // false
isFinite("10"); // true
isFinite(10); // true
isFinite(undefined); // false
isFinite(); // false
isFinite(null); // true !!!

30. 배열에서 음수 인덱스 사용하지 않기

1
2
3
var numbersArray = [1, 2, 3, 4, 5];
var from = numbersArray.indexOf("foo"); // from is equal to -1
numbersArray.splice(from, 2); // will return [5]

splice에 전달 된 인수가 음수가 아닌지 확인해야 함


31. 직렬화와 역직렬화 (JSON 작업)

1
2
3
4
5
var person = { name: "Saad", age: 26, department: { ID: 15, name: "R&D" } };
var stringFromPerson = JSON.stringify(person);
/* stringFromPerson is equal to "{"name":"Saad","age":26,"department":{"ID":15,"name":"R&D"}}" */
var personFromString = JSON.parse(stringFromPerson);
/* personFromString is equal to person object */

32. eval(), Function 생성자 사용하지 말기

eval이나 Function 생성자의 사용은 JavaScript 엔진이 실행가능한 소스코드로 변환해야하기 때문에 cost가 높다.

1
2
var func1 = new Function(functionCode);
var func2 = eval(functionCode);

33. with() 사용하지 말기 (The Good Part)

with()를 사용하면 전역 범위에 변수가 삽입된다. 따라서 다른 변수와 이름이 같으면 혼동을 일으켜 값을 덮어 쓸 수 있다.


34. 배열에 for-in 루프 사용하지 말기

1
2
3
4
var sum = 0;
for (var i in arrayNumbers) {
sum += arrayNumbers[i];
}

위 대신에 다음과 같이 사용

1
2
3
4
var sum = 0;
for (var i = 0, len = arrayNumbers.length; i < len; i++) {
sum += arrayNumbers[i];
}

또한, ilen의 인스턴스화는 for 루프의 첫번 째 명령문에 있기 때문에 한번 실행된다. 이렇게 하는 것이 아래처럼 하는 것보다 빠르다

1
for (var i = 0; i < arrayNumbers.length; i++)

그 이유는 arrayNumbers의 length를 루프마다 다시 계산하기 때문이다.


35. setTimeout()setInterval()는 문자열이 아닌 함수를 전달

setTimeOut() 또는 setInterval()로 문자열을 전달하면 실행속도가 느린 eval과 같은 방식으로 평가된다.

1
2
setInterval("doSomethingPeriodically()", 1000);
setTimeout("doSomethingAfterFiveSeconds()", 5000);

위 대신에…

1
2
setInterval(doSomethingPeriodically, 1000);
setTimeout(doSomethingAfterFiveSeconds, 5000);

를 사용


36. 일련의 if / else 대신 switch / case 문 사용

2개 이상의 case가 있을 경우, switch / case를 사용하는 편이 빠르고 우아하다. 10개 이상의 case가 있는 경우는 특히 피해야한다.


37. 숫자 범위 판정에 switch / case 사용

숫자 범위의 판정은 아래와 같이 실형 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function getCategory(age) {
var category = "";
switch (true) {
case isNaN(age):
category = "not an age";
break;
case age >= 50:
category = "Old";
break;
case age <= 20:
category = "Baby";
break;
default:
category = "Young";
break;
}
return category;
}
getCategory(5); // will return "Baby"

38. 주어진 객체를 프로토 타입 객체로 생성하기

줘진 객체를 프로토 타입 객체로 생성하는 함수는 다음과 같이 설명 할 수 있다.

1
2
3
4
5
6
function clone(object) {
function OneShotConstructor() {}
OneShotConstructor.prototype = object;
return new OneShotConstructor();
}
clone(Array).prototype; // []

39. HTML 이스케이프 함수

1
2
3
4
5
6
function escapeHTML(text) {
var replacements= {"<": "&lt;", ">": "&gt;","&": "&amp;", """: "&quot;"};
return text.replace(/[<>&"]/g, function(character) {
return replacements[character];
});
}

40. 반복문 내에서 try-catch-finally 절은 사용하기 말기

try-catch-finally 절은 현재 범위에 매번 새로운 변수를 생성한다.
이것은 catch 절에서 포착되는 예외를 할당하기 때문이다.

1
2
3
4
5
6
7
8
9
var object = ["foo", "bar"],
i;
for (i = 0, len = object.length; i < len; i++) {
try {
// do something that throws an exception
} catch (e) {
// handle exception
}
}

위 대신에 다음과 깉이 사용

1
2
3
4
5
6
7
8
9
var object = ["foo", "bar"],
i;
try {
for (i = 0, len = object.length; i < len; i++) {
// do something that throws an exception
}
} catch (e) {
// handle exception
}

정리

이 밖에도 팁 및 우용한 모범사례가 많이 있습니다. 만약 다른 것을 추가하는 것을 바라고, 의견이나 지적이 있으면 댓글을주세요.


출처

40 Useful JavaScript Tips, Tricks and Best Practices

e">3
4
5
eval "$(rbenv init -)" 또는
eval "$(rbenv init - zsh)"

# 그리고 아래 줄을 작성한다.
export PATH=$HOME/bin:/usr/local/bin:$PATH
1
source ~/.zshrc
  • 루비 2.5.0 이상 설치

    1
    2
    3
    4
    5
    rbenv install 3.1.2

    rbenv global 3.1.2 rbenv rehash

    ruby -v
  • #3. fastlane을 설치하는 두가지 방법

    02. Init Fastlane

    fastlane을 위해 사용할 폴더 구조는 다양하다. 필자는 RN 프로젝트에서 다음과 같이 사용하고 있음.

    #1. 프로젝트 최상위 루트에서 init 후 아래와 같은 폴더 구조 생성

    1
    2
    3
    bundle exec fastlane init

    이후 4번 선택

    Untitled

    Untitled

    root project |_ fastlane |_ android |Fastfile | ios |Fastfile | android |_ ios

    #2. Fastfile설정

    1
    2
    3
    4
    5
    6
    7
    fastlane_require 'dotenv'

    before_all do
    end

    import("./ios/Fastfile")
    import("./android/Fastfile")

    #3. env 설정

    #4. env 로드

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    platform :android do
    before_all do
    Dotenv.overload '../.env.FLAndroid'
    ...
    end
    ...
    en

    ----------------------------------------------

    platform :ios do
    before_all do
    Dotenv.overload '../.env.FLIos'
    ...
    end
    ...
    en

    #5. Appfile 셋팅 (환경변수 파일)

    1
    2
    3
    4
    5
    # app_identifier('io.project') # The bundle identifier of your app
    apple_id('[email protected]') # Your Apple email address

    # itc_team_id('####') # App Store Connect Team ID
    # team_id("ABCDE") # Developer Portal Team ID

    03. How to Run Fastlane

    local에 설치 된 ruby 버전이 아닌 프로젝트에서 지정한 ruby버전을 사용하기 위해 bundle exec 명령어를 이용

    1
    2
    bundle update --bunder
    bundle exec fastane ....

    Install Plugin

    1
    2
    3
    4
    5
    bundle exec fastlane add_plugin appcenter
    bundle exec fastlane add_plugin load_json
    bundle exec fastlane add_plugin yarn

    # ... 등등 필요한 플러그인을 추가한다.
    [JS] 클로저 간단 정리

    [JS] 클로저 간단 정리

    이채현

    • 클로저는 먼저 자바스크립트 변수의 유효범위를 이해해야한다.
    • 클로저를 명확히 무엇이다라고 말하기는 어렵다.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    function returnX(){
    return 'x';
    }

    const x = returnX()
    console.log(typeof x) // string

    ----------------------------------------------

    function returnX() {
    let x = 'x';
    return function returnY() {
    return x + 'y';
    }
    }

    const x = returnX()
    console.log(typeof x) // function => return 값이 함수 덩어리이기 때문에

    const x = returnX()();
    console.log(typeof x) // string
    function sum(num1) {
    return function (num2) {
    return num1 + num2;
    };
    }

    const sum5 = sum(5); // 숫자5가 계속 바인딩되어 있는 상태
    console.log(sum5(10)); // 15

    은닉화

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    function privateData() {
    let temp = 'a';
    return temp;
    }

    const result = privateData(); // privateData를 실행시켜야만 temp값을 알 수 있다.
    console.log(result);

    -------------------------------------

    function privateData() {
    let temp = 'a';
    return {
    value: () => {
    return temp;
    },
    changeValue: (newVal) => {
    temp = newVal;
    }
    };
    }

    const private = privateData();
    console.log(private.value()); // a
    private.changeValue('b');
    console.log(private.value()); // b

    활용사례

    • 고민해봤을 때 debounce와 throttle에서 …사용

      • debounce: 어떤 이벤트를 실행할 때 과하게 실행되는 것을 지연시켜주는 역할 (클릭지연..무한스크롤 지연)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      buttonElement.addEventListener(
      'click',
      debounce(handleClick, 500),
      )

      function debounce(func, timeout = 300) {
      let timer;

      return (...args) => {
      clearTimeout(timer);

      timer = setTimeout(() => {
      func.apply(this, args);
      })
      }
      }
    [JS] Class 간단 정리

    [JS] Class 간단 정리

    이채현

    클래스

    • class 선언은 프로토타입 기반 상속을 사용
    • 정의: 함수 정의방법과 동일하게 가능, 함수 표현식과 함수 선언을 class표현식에서 사용 가능
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }

    class Person {
    constructor(name, age) {
    this.name = name;
    this.age = age;
    }
    }

    인스턴스

    • 싱글리터럴로 만든 객체도 각자의 인스턴스를 뜻함..객체이지만 인스턴스
    • 생성자 함수와 클래스를 활용하여…new 연산자와 더불어 만듬
    1
    2
    3
    4
    5
    6
    function Func() {} // 생성자함수의 이름은 Pascal case로..

    class Class {}

    const newInstance = new Func();
    const newInstance2 = new Class();

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    function Person(name, age) {
    this.name = name;
    this.age = age;
    this.location = location;
    }

    Person.prototype.getName = function () {
    return this.name + ' 입니다.';
    }

    =============================================

    class Person {
    constructor(name, age) {
    this.name = name;
    this.age = age;
    this.location = location;
    }

    getName() {
    return this.name + ' 입니다.';
    }
    }

    const one = new Person('one', 1, 'Korea');
    const two = new Person('two', 2, 'Korea');

    console.log(one.getName()); // one 입니다.
    • 클래스는 뿐만 아니라 Private키워드, 정적메서드 등 수 많은 기능을 제공

    클래스 확장 (extends, 상속)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    // Super Class
    class Animal {
    constructor(name, sound) {
    this.name = name
    this.sound = sound;
    }

    getInfo() {
    return this.name + '가(이)' + this.sound + '소리를 낸다.';
    }
    }
    // Sub Class
    class Friends extends Animal {
    constructor(name, sound) {
    super(name, sound); // 부모의 생성자함수를 호출가능
    }
    }

    const dog = new Friends('개', '멍멍');
    const cat = new Friends('고양이', '냐옹');

    console.log(dog.getInfo()); // 개가(이)멍멍소리를 낸다.
    console.log(cat.getInfo()); // 고양이가(이)냐옹소리를 낸다.

    -------------------------------------------------

    dog.contructor.name // Friends
    cat.contructor.name // Friends

    dog instanceof Friends // true
    dog instanceof Animal // true
    [JS] 프로토타입 간단 정리

    [JS] 프로토타입 간단 정리

    이채현

    자바스크립트는 프로토타입 기반의 언어다.

    constructor (생성자)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function Person(name, age) {
    this.name = name;
    this.age = age;
    }

    class Paerson {
    constructor() {

    }
    }

    생성자함수는 프로토타입이 모든 자바스크립트에 들어있듯이 생성자도 모든 자바스크립트에서 확인가능하다.

    1
    2
    3
    4
    5
    const one = new Person('one', 1);
    const two = new Person('two', 2);

    console.log(one.constructor); // [Function: Person]
    console.log(two.constructor.name); // Person

    어떠한 생성자로부터 생성되었는지 유추할 수 있다.

    instanceof로 식별

    object instanceof constructor

    proto

    브라우저에서 비표준으로 제공했던…

    1
    array.__proto__ ...로 array의 프로토타입에 접근

    자바스크립트의 프로토타입에 직접 접근이 아니라 접근제어자로 접근할 수 있도록 도와주는 것으로 생각하면 됨. ⇒ 하지만 사용하지 않는 것이 좋음

    ECMA Script2015부터는 표준화 된

    • getPrototypeOf()
    • setPrototypeOf()

    를 사용하는 것을 추천

    프로토타입 체인

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const animal = {
    sayName() {
    return 'ANIMAL';
    }
    }

    console.log(animal.sayName()); // ANIMAL
    const dog = Object.create(animal);
    console.log(dog.sayName()); // ANIMAL

    프로토타입 확장 (extends, 상속)

    부모 ⇒ 자식 … 상속보단 확장이란 개념이 더 이해하기 쉽다. 부모가 가진 기능보다 자식이 더 많이 가질 수 있기 때문

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    // Super Class
    function Animal(name, sound) {
    this.name = name
    this.sound = sound;
    }

    Animal.prototype.getInfo = function () {
    return this.name + '가(이)' + this.sound + '소리를 낸다.';
    }

    // Sub Class
    function Friends(name, sound) {
    Animal.call(this, name, sound); // 명시적 바인딩 -> Animal함수의 this를 Friends로 바인딩
    }

    Friends.prototype = Object.create(Animal.prototype);
    Friends.prototype.constructor = Friends;

    const dog = new Friends('개', '멍멍');
    const cat = new Friends('고양이', '냐옹');

    console.log(dog.getInfo()); // 개가(이)멍멍소리를 낸다.
    console.log(cat.getInfo()); // 고양이가(이)냐옹소리를 낸다.

    -------

    dog.contructor.name // Friends

    dog instanceof Friends // true
    dog instanceof Animal // true