
스코프·생성자·프로토타입·strict mode·배열
var x = 'global x';
var y = 'global y';
function outer() {
var z = "outer's local z";
console.log(x); // global x
console.log(y); // global y
console.log(z); // outer's local z
function inner() {
var x = "inner's local x";
console.log(x); // inner's local x
console.log(y); // global y
console.log(z); // outer's local z
}
inner();
}
outer();
console.log(x); // global x
// console.log(z); // ReferenceError
// 함수 밖에서 var → 전역 변수
var i = 0;
for (var i = 0; i < 10; i++) {}
console.log(i); // 10
var는 함수 몸체만 지역 스코프로 인정let, const는 모든 코드 블록을 스코프로 인정중복 선언 허용
var msg = '안녕하세요';
var msg = '안녕히가세요'; // 덮어씀
함수 레벨 스코프
var i = 0;
for (var i = 0; i < 10; i++) {}
console.log(i); // 10
변수 호이스팅
console.log(test); // undefined
test = '반갑습니다';
console.log(test); // 반갑습니다
var test;
선언이 스코프의 선두로 끌어올려진 것처럼 동작해 흐름을 헷갈리게 만든다.
let
const
정리하면, ES6 기준으로는
constletvar는 사용하지 않는 방향이 가장 안전하다.const student = new Object(); // 빈 객체
student.name = '유관순';
student.age = 16;
new Object()로도 객체를 만들 수 있지만, 대부분의 경우 더 간단한 객체 리터럴 {}을 사용한다.
function Student(name, age) {
this.name = name;
this.age = age;
this.getInfo = function () {
return `${this.name}(은)는 ${this.age}세이다.`;
};
}
const student = new Student('장보고', 30);
console.log(student.getInfo());
function Student(name, age) {
// 1. 빈 객체 생성, this에 바인딩
console.log(this);
// 2. this에 프로퍼티 추가
this.name = name;
this.age = age;
// 3. this가 자동으로 반환
}
const student = new Student('홍길동', 20);
new 없이 호출하면 그냥 일반 함수처럼 동작new.target으로 생성자 호출 여부를 방지할 수 있다. 모든 객체는 숨겨진 프로퍼티 [[Prototype]]을 가진다.
이 값이 다른 객체를 참조하면, 그 객체를 프로토타입(prototype) 이라고 부른다.
const user = {
activate: true,
login: function () {
console.log('로그인 되었다.');
}
};
const student = {
passion: true
};
student.__proto__ = user;
console.log(student.activate); // true
student.login(); // 로그인 되었다.
student에는 activate, login이 없지만user를 통해 상속받는다.페이지 4의 다이어그램을 보면, student → user → Object.prototype → null 순으로 화살표가 이어지는 프로토타입 체인 구조가 그려져 있다. 프로퍼티 검색 시 이 체인을 위로 따라 올라가며 찾는 방식으로 동작한다.
const greedyStudent = {
class: 11,
__proto__: student
};
console.log(greedyStudent.activate); // user에서 상속
console.log(greedyStudent.passion); // student에서 상속
__proto__에는 객체나 null만 들어갈 수 있다.프로토타입은 프로퍼티 읽기에만 사용된다.
const student2 = {
__proto__: user
};
student2.id = 'user01';
student2.login(); // this는 항상 . 앞의 객체, 여기서는 student2
this는 프로토타입이 아니라 호출한 객체를 가리킨다. for (let prop in student) {
let isOwn = student.hasOwnProperty(prop);
if (isOwn) {
console.log(`자신의 프로퍼티: ${prop}`);
} else {
console.log(`상속 프로퍼티: ${prop}`);
}
}
for...in은 상속 프로퍼티도 함께 순회한다.hasOwnProperty로 직접 가진 프로퍼티인지 구분할 수 있다. function Student() {}
// 기본 prototype 객체에는 constructor 프로퍼티만 있다.
// Student.prototype = { constructor: Student }
console.log(Student.prototype.constructor === Student); // true
const student = new Student();
console.log(student.constructor === Student); // true
prototype 프로퍼티를 가진다.prototype 객체 안에는 기본적으로 constructor가 들어 있고,const obj = {};
console.log(obj.__proto__ === Object.prototype); // true
const num = new Number(100);
console.log(num.__proto__ === Number.prototype); // true
console.log(num.__proto__.__proto__ === Object.prototype); // true
console.log(num.__proto__.__proto__.__proto__); // null
Object.prototype 위에는 아무 것도 없고, null에서 체인이 끝난다.Object.prototype이 올라가는 구조를 가진다. 페이지 8의 그림은 String.prototype, Function.prototype, Number.prototype이 모두 Object.prototype을 통해 null로 이어지는 구조를 시각적으로 보여준다.
const user = { activate: true };
// [[Prototype]]이 user인 객체 생성
const student3 = Object.create(user);
console.log(student3.activate); // true
console.log(Object.getPrototypeOf(student3) === user); // true
Object.setPrototypeOf(student3, {});
console.log(Object.getPrototypeOf(student3) === user); // false
Object.create(proto)Object.getPrototypeOf(obj)Object.setPrototypeOf(obj, proto)__proto__ 직접 사용은 여러 이유로 권장되지 않고, 위 메서드를 사용하는 것이 좋다.
function test() {
// 암묵적 전역
x = 10;
}
test();
console.log(x); // 10
키워드를 쓰지 않고 값을 대입하면, 의도와 무관하게 암묵적 전역 변수가 만들어질 수 있다.
이런 잠재적인 버그를 줄이기 위해 ES5에서 strict mode가 도입되었다.
// 전역 전체에 적용
'use strict';
function test() {
x = 10; // ReferenceError
}
function test() {
'use strict';
x = 10; // ReferenceError
}
'use strict';(function () {
'use strict';
// 1) 암묵적 전역
x = 1; // ReferenceError
// 2) 변수/함수/매개변수 삭제
var y = 1;
// delete y; // SyntaxError
// 3) 중복 매개변수
// function test(a, a) {} // SyntaxError
// 4) with 사용
// with ({ x: 1 }) { console.log(x); } // SyntaxError
})();
strict mode에서는 애매한 문법이나 권장되지 않는 기능들을 아예 에러로 막는다.
(function () {
'use strict';
function test() {
console.log(this);
}
test(); // undefined
new test(); // test {}
})();
this는 암묵적으로 전역 객체가 아니라 undefined가 된다.(function (x) {
'use strict';
x = 2;
console.log(arguments); // [1] 유지
})(1);
arguments가 더 이상 서로 연동되지 않는다. 배열은 여러 개의 값을 순서대로 나열한 자료구조이다. 요소는 어떤 타입이든 올 수 있다.
// 배열 리터럴
const arr = ['바나나', '복숭아', '키위'];
// 배열 생성자 함수
const arr2 = new Array(); // []
const arr3 = new Array(10); // 길이만 10
const arr4 = new Array(1, 2, 3); // [1, 2, 3]
// Array.of
const arr5 = Array.of(10); // [10]
const arr6 = Array.of(1, 2, 3); // [1, 2, 3]
const arr7 = Array.of('hello', 'js'); // ['hello', 'js']
const arr = ['바나나', '복숭아', '키위'];
console.log(arr[0]); // 바나나
console.log(arr.length); // 3
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
length 프로퍼티에 요소 개수가 들어간다.typeof 배열 → object (별도의 타입이 아니라 객체의 한 종류) 자바스크립트 배열은 일반적인 저수준 배열과 다르게 동작하는 특수한 객체이다.
console.log(Object.getOwnPropertyDescriptors([1, 2, 3]));
/*
'0': { value: 1, ... },
'1': { value: 2, ... },
'2': { value: 3, ... },
length: { value: 3, ... }
*/
length 프로퍼티를 가진 객체일 뿐이다.const arr = [1, 2, 3, 4, 5];
arr.length = 3;
console.log(arr); // [1, 2, 3]
arr.length = 10;
console.log(arr); // [1, 2, 3, <7 empty items>]
const sparse = [, 2, , 4];
console.log(sparse.length); // 4
length를 줄이면 뒤 요소가 잘린다.length와 실제 요소 개수가 일치하지 않을 수 있어 주의가 필요하다. 배열 인스턴스는 모두 Array.prototype을 상속받고, 여기에 다양한 메서드가 정의된다.
const foodList = ['물회', '삼계탕', '냉면', '수박', '물회'];
foodList.indexOf('물회'); // 0
foodList.indexOf('물회', 1); // 4
foodList.lastIndexOf('물회'); // 4
foodList.includes('물회'); // true
foodList.includes('삼겹살'); // false
indexOf(value, fromIndex?)lastIndexOf(value, fromIndex?)includes(value)const chinese = ['짜장면', '짬뽕', '우동'];
chinese.push('탕수육'); // 뒤에 추가
chinese.pop(); // 뒤에서 제거
const chicken = ['양념', '후라이드', '파닭'];
chicken.unshift('간장'); // 앞에 추가
chicken.shift(); // 앞에서 제거
push / pop → 뒷부분에서 스택처럼 사용unshift / shift → 앞부분에서 요소 조작 // concat
const idol1 = ['아이브', '오마이걸'];
const idol2 = ['트와이스', '에스파'];
const mix = idol1.concat(idol2);
// ['아이브', '오마이걸', '트와이스', '에스파']
// slice(시작, 끝)
const front = ['HTML', 'CSS', 'JavaScript', 'jQuery'];
front.slice(1, 3); // ['CSS', 'JavaScript'], 원본 유지
// splice(index, deleteCount, ...items)
front.splice(3, 1, 'React'); // jQuery 삭제, React 추가
// join
const snacks = ['사탕', '초콜릿', '껌'];
snacks.join('/'); // '사탕/초콜릿/껌'
// reverse
[1, 2, 3].reverse(); // [3, 2, 1]
concat은 원본을 건드리지 않고 새 배열을 리턴slice는 잘라낸 부분으로 새 배열을 만들어 리턴splice는 원본 배열을 직접 수정한다. let numbers = [10, 2, 33, 4];
numbers.sort(); // ['10','2','33','4'] 기준의 문자열 정렬
numbers.sort((a, b) => a - b); // 숫자 오름차순
numbers.sort((a, b) => b - a); // 숫자 내림차순
기본 정렬은 문자열 기준이라 숫자 정렬 시 비교 함수를 넘기는 편이 안전하다.
numbers = [1, 2, 3, 4, 5];
numbers.forEach(item => console.log(item * 10));
const types = [true, 1, 'text'].map(item => typeof item);
// ['boolean', 'number', 'string']
const odds = [1, 2, 3, 4, 5].filter(item => item % 2);
// [1, 3, 5]
const nums = [1, 2, 3, 4, 5];
const sum = nums.reduce((prev, cur) => prev + cur);
// 15
const max = nums.reduce((prev, cur) => (prev > cur ? prev : cur));
// 5
[1, 5, 3, 2, 4].some(item => item > 3); // true
[1, 5, 3, 2, 4].every(item => item > 0); // true
some: 조건을 만족하는 요소가 하나라도 있는지every: 모든 요소가 조건을 만족하는지const students = [
{ name: '유관순', score: 90 },
{ name: '홍길동', score: 80 },
{ name: '장보고', score: 70 }
];
students.find(s => s.name === '유관순');
// { name: '유관순', score: 90 }
students.findIndex(s => s.name === '유관순');
// 0
students.filter(s => s.score >= 60);
// 조건을 만족하는 모든 요소 배열
find / findIndex → 조건을 만족하는 첫 번째 요소/인덱스만 반환filter를 사용한다. 이번 묶음에서 다루는 흐름을 다시 정리하면 다음과 같다.