본문 바로가기

Server Programming/Spring Boot 2 Full-Stack Programming

[작업 관리 애플리케이션 개발] 1. 자바 개발자 관점의 자바스크립트

반응형

목차

  1. 함수와 메서드
  2. 객체와 클래스
  3. 객체, 프로퍼티, 프로포티 속성
  4. 프로토타입과 상속
  5. 스코프와 클로저
  6. this 키워드
  7. 호이스팅

 

함수와 메서드

자바스크립트에서 함수 Function

-자바스크립트 내장객체 Function 생성자로 생성된 객체

 

자바스크립트에서 메서드 Method

-함수Function이 객체의 프로퍼티일 때

 

즉, 모든 메소드는 함수지만, 모든 함수가 메소드는 아니다.


함수와 메서드의 차이

프로퍼티와 메소드를 가질 수 있는 함수

-instanceof를 이용해 함수여부 확인

 

var workout=function() {};
console.log(workout instanceof Function);

 

-함수는 호출 가능, 다른 객체는 호출 불가

-함수는 프로토타입 프로퍼티를 가지지만, 다른 객체는 가지지 않는다.

 

객체 생성 키워드 new

-생성자 역할을 하는 함수

-생성자 역할을 할 때, 대문자로 시작

function User(){
}
var user = new User();

 


객체와 클래스

자바스크립트에서 객체 생성 방법

  • Object() 생성자
  • 객체 리터럴
  • 생성자 함수
  • Object.create()
  • 생성 함수
  • ES6 클래스

 

// Call the Object constructor with new
var user = new Object();
user.name = 'Sunny';
user.interests = ['Traveling', 'Swimming'];
user.greeting = function () {
  console.log('Hi, I\'m ' + this.name + '.');
};
user.greeting(); // Hi, I'm Sunny.



// Create a user with object literal
var user = {
  name: 'Sunny',
  interests: ['Traveling', 'Swimming'],
  greeting: function () {
    console.log('Hi, I\'m ' + this.name + '.');
  }
}
user.greeting();  // Hi, I'm Sunny.



// Create a constructor function
function User (name, interests) {
  this.name = name;
  this.interests = interests;
  this.greeting = function () {
    console.log('Hi, I\'m ' + this.name + '.');
  }
}
// Call the constructor with new to create a user object
var user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.



// Use Object.create() method with the prototype of
// User constructor function created above
var user = Object.create(User.prototype, {
  name: { value: 'Sunny' },
  interests: { value: ['Traveling', 'Swimming']}
}); 
user.greeting(); // Uncaught TypeError: user.greeting() is not a function


// Add greeting to prototype object
User.prototype.greeting = function () {
  console.log('Hi, I\'m ' + this.name + '.');
}
user.greeting(); // Hi, I'm Sunny.



// Use a creator function with an object as its return value
function createUser (name, interests) {
  var user = {};
  user.name = name;
  user.interests = interests;
  user.greeting = function () {
    console.log('Hi, I\'m ' + this.name + '.');
  };
  return user;
}
// Call the creator function with parameters
var user = createUser('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.



// Create User class
class User {
  // Equivalent to User constructor function
  constructor (name, interests) {
    this.name = name;
    this.interests = interests;
  }
  // Equivalent to User.prototype.greeting
  greeting () {
    console.log('Hi, I\'m ' + this.name + '.')
  }
}
let user = new User('Sunny', ['Traveling', 'Swimming']);
user.greeting(); // Hi, I'm Sunny.



// Use class expression
let User = class {
  constructor (name, interests) {
    this.name = name;
    this.interests = interests;
  }
  greeting () {
    console.log('Hi, I\'m ' + this.name + '.')
  } 
}

참고

 

(2) 객체 리터럴에서의 게터와 세터 접근

var user={
	get role(){
    	return 'Engineer';
    }
}
user.role;

 

 

(3) User 생성자 함수의 프로토타입과 Object.create() 메소드 활용시

-greeting()이 user 객체의 함수가 아니다

-Object.create() 메소드가 생성자의 프로토타입 객체로 새로운 객체를 생성

 

 

프로토타입 객체에 추가하면 해당 생성자로 생성된 모든 객체는 greeting 함수를 가진다.

-상위 클래스가 하위 클래스에 상속할 메소드 제공하는 방법

 

(4) 생성함수는 자바에서 객체를 인스턴스화할 때 사용하는 정적 팩토리 방법과 유사한 팩토리 메소드

-생성 함수 내부에서 객체 생성의 세부사항을 감싸기 위한 패턴

 

(5) ES6 클래스 표현식 이용하는 경우, 자바의 클래스와 완전히 다르다

-static이나 private 클래스가 존재하지 않는다.

 


객체, 프로퍼티, 프로퍼티 속성

-객체 생성시 런타임 수행시 메소드 수정 방법이 거의 없는 자바와 다른 자바스크립트

-동적언어로 런타임 중에 객체 생성과 수정이 가능

-객체를 자료형처럼 인식하기 때문에, 자료형의 값을 변경하는 것과 같다.

 

자바의 객체

-필드와 메소드

 

자바스크립트의 객체

-프로퍼티의 집합

-프로퍼티는 String 타입의 이름과 속성 리스트를 갖는다.

 

자바스크립트의 속성

-프로퍼티의 상태를 정의하고 설명

-데이터 프로퍼티와 접근 프로퍼티의 형태가 존재

 

데이터 프로퍼티의 속성

  1. value
  2. writable
  3. enumerable
  4. configurable

접근 프로퍼티의 속성

  1. get 접근자
  2. set 접근자
  3. enumerable
  4. configurable

객체의 프로퍼티 접근 표기법

-점 표기법과 대괄호 표기법

-대괄호 표기법에서 프로퍼티 이름으로 객체 사용시 toString() 메소드로 문자열로 변환해야 한다.

var obj = {};
obj['100'] = 'one hundred';
// Number 100 will be casted to '100'
console.log(obj[100]);  // 'one hundred'
// Both foo and bar will be casted to string '[object Object]'
var foo = {prop: 'f'}, bar = {prop: 'b'};
obj[foo] = 'Foo'
console.log(obj[bar])  // 'Foo'

객체의 프로퍼티 수정

1. Object.defineProperty 사용

2. Obejct.defineProperties 사용

 

// Define properties using Object.defineProperty()

function User (name, department) {
  var _department = department;
  var _name = name;
  Object.defineProperty(this, 'name', {
    value: _name,
    writable: true,
    enumerable: true,
    configurable: false
  }); 
  Object.defineProperty(this, 'department', {
    get: function () {
      console.log('Retrieving department');
      return _department;
    },
    set: function (newValue) {
      console.log('Updating department value to "' + newValue + '"');
      _department = newValue;
    },
    enumerable: true,
    configurable: true
  });
  Object.defineProperty(this, 'greeting', {
    value: function () {
      console.log('Hi, I\'m ' + _name + '.');
    },
    enumerable: false,
    configurable: false
  }); 
}

 

런타임 시점에서 user 객체의 이름과 부서 프로퍼티 수정

var User = new User('Sunny', 'Engineering');
console.log(user.department);
user.department = 'Marketing';
user.greeting();
Object.defineProperty(user, 'name', {
	enumerable:false
});

delete user.name;
delete user.department;
for (var prop in user){
	console.log(prop);
}

-name 프로퍼티의 configurable이 false이므로 name을 파라미터로 갖는 defineProperty와 delete user.name은 수행되지 않는다.

-department는 삭제, greeting 프로퍼티는 열거형이 아니기 때문에 name만 콘솔에 표시


프로토타입과 상속

-상속을 위해 생성자 함수의 프로토타입을 활용하는 자바스크립트

 

프로토타입

-다른 객체에 공유 프로퍼티를 제공하는 객체

-호출이 가능하고 다른 객체를 생성할 수 있는 함수 객체만 프로토타입을 가진다. (화살표 함수 제외)

 

함수는 공장, 프로토타입은 공장에서 생산된 제품

-new 키워드로 함수를 호출시마다 해당 제품의 주문이 들어가며 공장은 프로토타입에 지정된 방식으로 생산

 

상속의 동작 원리 이해

1. TeamMembe라는 생성자 함수 생성

2. User로부터 프로퍼티 상속

3. greeting() 메소드 재정의, 새로운 메소드 work() 추가

4. eat() 메소드를 User에 추가, move()를 Object에 추가

 

//(1) User 생성자 함수 생성
//-Function 생성자로 function 객체 생성
function User (name, interests) {
  this.name = name;
  this.interests = interests;
}

//(2) User 프로토타입에 greeting 프로퍼티 생성
//-User의 하위 클래스가 상속할 수 있는 메소드 생성
User.prototype.greeting = function () {
   console.log('Hi, I\'m ' + this.name + '.');
}

//(3) TeamMember 생성자 함수 생성하고, task 프로퍼티 추가
//-생성자 함수 내 Function 객체에서 생성자 체인을 위한 call() 메소드 호출
//-call() 메소드의 첫번째 인자는 실행 컨텍스트 역할하는 객체로 해당 객체의 모든 프로퍼티는 초기화
function TeamMember (name, interests, tasks) {
   User.call(this, name, interests);
   this.tasks = tasks;
}

//(4) Object.create()와 User 프로토타입 객체로 프로토타입 객체 생성
TeamMember.prototype = Object.create(User.prototype);

//(5) 생성된 객체의 greeting() 메소드 재정의해 다른 동작하도록 변경
//- 두 프로토타입 객체는 같은 생성자지만, 다른 객체로 프로토타입에 영향을 끼치지 않는다.
TeamMember.prototype.greeting = function () {
  console.log('I\'m ' + this.name + '. Welcome to the team!');
};

//(6) 새로운 메서드 work() 추가
TeamMember.prototype.work = function () {
  console.log('I\'m working on ' + this.tasks.length + ' tasks');
};


// TeamMember 생성자 함수에 의해 생성된 객체가 추가적인 동작을 수행
var member = new TeamMember('Sunny', ['Traveling'],
                            ['Buy three tickets','Book a hotel']);
member.greeting();  // I'm Sunny. Welcome to the team!
member.work();      // I'm working on 2 tasks

console.log(member instanceof TeamMember); // true
console.log(member instanceof User);       // true
console.log(member instanceof Object);     // true

User.prototype.eat = function () {
  console.log('What will I have for lunch?');
};
member.eat();     // What will I have for lunch?         

// Add a method to the top
Object.prototype.move = function () {
  console.log('Every object can move now');
};
member.move();    // Every object can move now
var alien = {};
alien.move();     // Every object can move now
User.move();      // Even the constructor function


스코프와 클로저

스코프

-변수의 접근성

 

자바의 스코프

  1. 클래스-레벨 스코프
  2. 메소드-레벨 스코프
  3. 블록-레벨 스코프
//스코프와 클로저 자바 예제
public class User{
	//클래스-레벨 스코프
    private String name;
    private List<String> interests;
    
    public User(String name, List<String> interests){
    	this.name=name;
        this.interests=interests;
    }
    
    //user의 interests에 something이 있는지 체크
    public boolean isInterestedIn(String something){
    	//메소드-레벨 스코프
    	boolean interested = false;
        
        //블록-레벨 스코프
        for(int i=0; i<interests.size(); i++){
        	if(interests.get(i).equals(something){
            	interested=true;
                break;
            }
        }
        return interested;
    }
}

 

 


자바스크립트의 스코프

  1. 전역 스코프
  2. 함수 스코프
  3. 블록 스코프
// Scope and Closure JavaScript Example
function bookHotel (city) {
  //함수 스코프
  var availableHotel = 'None';
  for (var i=0; i<hotels.length; i++) {
    var hotel = hotels[i];
    if (hotel.city === city && hotel.hasRoom) {
      availableHotel = hotel.name;
      break;
    }
  }
 
  //i와 hotel은 함수 스코프로 접근 가능
  console.log('Checked ' + (i+1) + ' record(s)'); // Checked 2 record(s)
  console.log('Last checked ' + hotel.name);      // Last checked Hotel B
  {
  	//클로저 : 중첩된 함수
    //-부모 함수 스코프와 전역 스코프에 접근 가능
    function placeOrder() {
      var totalAmount = 200;
      console.log('Order placed to ' + availableHotel);
    }
  }  
  placeOrder();
  // 중첩된 함수인 클로저의 스코프 밖이므로 접근 불가능
  // console.log(totalAmount);
  return availableHotel;
}

//전역 스코프로, 변수가 함수보다 나중에 정의되어도 어디서든 접근 가능
var hotels = [{name: 'Hotel A', hasRoom: false, city: 'Sanya'},
              {name: 'Hotel B', hasRoom: true, city: 'Sanya'}];
console.log(bookHotel('Sanya')); // Hotel B

// 함수 스코프 변수에는 접근이 불가능하다.
// console.log(availableHotel);

-ES6의 let키워드를 이용하면 블록 스코프로 만들 수도 있다.


this 키워드

자바에서의 this 키워드

-현재 객체 참조

 

자바스크립트에서의 this키워드

-하나의 객체인 현재 실행 컨텍스트를 참조

-동작 중인 실행 컨텍스트로 구성된 실행 컨텍스트 스택이 존재

-실행 가능한 코드의 이동시 담당할 새로운 실행 컨텍스트로 진입

-이 컨텍스트가 현재 실행 컨텍스트가 되거나, 동작 중인 실행 컨텍스트를 참조하는 형태

 

자바스크립트의 실행 가능한 코드 유형

  1. 전역 코드
  2. Eval 코드
  3. 함수 코드
function User (name) {
  //User() 함수 코드
  console.log('I\'m in "' + this.constructor.name + '" context.');
  this.name = name;
  this.speak = function () {
    console.log(this.name + ' is speaking from "' +
      this.constructor.name + '" context.');
    var drink = function () {
      //drink() 함수의 함수 스코프
      console.log('Drinking in "' + this.constructor.name + '"');
    } 
    drink(); 
  }; 
  
  //ask() 함수 코드
  function ask() {
    console.log('Asking from "' + 
      this.constructor.name + '"   context.');
    console.log('Who am I? "'  + this.name + '"');
  }
  ask();
}

//전역 코드
var name = 'Unknown';
var user = new User('Ted');
user.speak();

// I'm in "User" context.
// Asking from "Window"   context.
// Who am I? "Unknown"
// Ted is speaking from "User" context.
// Drinking in "Window"

 

 

함수 생성 방식

1. 함수 선언

-함수 선언 발견시, function객체 생성해 함수 선언 스코프내 접근 가능

2. 함수 표현식

 var drink = function () {
      //drink() 함수의 함수 스코프
      console.log('Drinking in "' + this.constructor.name + '"');
    } 
    drink();

 

 

스코프와 실행 컨텍스트

-스코프 : 접근성

-실행 컨텍스트 : 소유권

(실행 컨텍스트의 경우 함수 생성 방식뿐만 아니라 함수 호출 방식에도 영향을 받는다.)

 

Function 객체에 의해 생성된 메소드

  1. call() : 리스트 형태의 인자 전달, 첫 번째 인자를 함수 코드의 실행 컨텍스트로 사용
  2. apply() : 배열 형태의 인자 전달, 첫 번째 인자를 함수 코드의 실행 컨텍스트로 사용
  3. bind() : 새로운 함수의 실행 컨텍스트로 전달받은 첫 번째 인자와 바인딩될 새로운 함수 생성하므로 즉시 실행

 

함수 호출 방법

  1. 생성자 함수 호출 : new User()
    • 함수 본문 내의 this가 다른 유형으로 인한 인스턴스 제외하고 생성자로 생성된 객체 참조
  2. 직접 함수 호출 : ask()
    • 함수 본문 내의 this가 다른 유형으로 인한 인스턴스 제외하고 전역 컨텍스트를 참조
  3. 메소드 호출 : user.speak()
    • 함수 본문 내의 this가 다른 유형으로 인한 인스턴스 제외하고 메소드가 속한 객체를 참조
  4. 컨텍스트 변경 호출 : ask.call(this) or ask.apply(this)
    • 함수 본문 내의 this가 다른 유형으로 인한 인스턴스 제외하고 call()메소드의 첫번째 인자 객체를 참조

호이스팅

:자바스크립트의 인터프리터가 함수 선언과 변수 선언을 선언들이 속해 있는 스코프의 최상단으로 끌어올리는 방법

 

 

travel = 'No plan';
var travel;
console.log(travel); //"No plan'

function travel(){
	console.log('Traveling');
}
travel(); //"Uncaught TypeError"

 

 

자바스크립트 인터프리터 처리 방식

//함수 선언을 최상위로 위치
function travel(){
	console.log('Traveling');
}
//변수 선언을 그 다음에 위치
travel = 'No plan';
var travel;

console.log(travel); //"No plan'
travel(); //"Uncaught TypeError"

 

 

function workout() { 
  goToGym();    // What will the output be?
  var goToGym = function () {
    console.log('Workout in Gym A');
  }
  return;    
  function goToGym() {
    console.log('Workout in Gym B');
  }
}
workout();


// Interpreter's view
function workout() {
  function goToGym() {
    console.log('Workout in Gym B');
  }
  var goToGym;
  goToGym();
  goToGym = function () {
    console.log('Workout in Gym A');
  }
  return;
}
workout();

 

인터프리터는 함수 선언을 스코프의 최상위로 이동 후 대입은 하지 않고 변수 선언

 


ES6 기본

 

  1. 블록 스코프, let, const
  2. 클래스
  3. 강화된 객체 리터럴
  4. 화살표 함수
    1. 자신의 this를 가지지 않는다.
    2. 프로토타입 객체를 가지지 않는다.
  5. 매개변수 기본값
  6. 나머지 매개변수
  7. 전개 구문
  8. 비구조화 할당
    1. 객체 비구조화
    2. 배열 비구조화
    3. 중첩 비구조화
    4. 나머지 요소
    5. 함수 매개변수 비구조화
  9. 탬플릿 리터럴
  10. 모듈
    1. 내보내기
    2. 가져오기
  11. 프로미스
    1. 프로미스의 상태
      1. 대기
      2. 이행
      3. 실패

블록 스코프, let, const

: 변수를 정의하는데 사용하는 let과 상수 정의하는데 사용하는 const

-블록 레벨 스코프를 가지며, 변수 호이스팅이 적용되지 않는다.

function workout() {
  let gym = 'Gym A';

  const gymStatuses = {'Gym A': 'open', 'Gym B': 'open', 'Gym C': 'closed'};
  for (let gym in gymStatuses) {
    console.log(gym + ' is ' + gymStatuses[gym]);
  }

  {
    const gym = 'Gym B';
    console.log('Workout in ' + gym);
    // The following will throw TypeError
    // gym = 'Gym C'; 
  }

  console.log('Workout in ' + gym);

  {
    function gym () {
      console.log('Workout in a separate gym');
    }
    gym();
  }

  if (gymStatuses[gym] == 'open') {
    let exercises = ['Treadmill', 'Pushup', 'Spinning'];
  }
  // exercises are no longer accessible here
  // console.log(exercises);

  try {
    let gym = 'Gym C'; 
    console.log('Workout in ' + gym);   
    throw new Error('Gym is closed');
  } catch (err) {
    console.log(err);
    let gym = 'Gym D';
    console.log('Workout in ' + gym);   
  }
}
workout();

-let과 const를 이용해 가능한 스코프

  1. switch 블록 
  2. for 블록
  3. if 블록
  4. try-catch 블록
  5. 블록 구문

 


클래스

: 프로토타입 기반의 상속이 아닌 이해하기 쉽고 명확한 문법인 설탕 클래스 도입

-클래스 구문으로 생성자를 생성하고 상위 클래스로부터 확장하고 정적 메서드 생성 가능

-게터와 세터도 생성 가능

 

클래스 구문을 활용해 User와 TeamMember 구현

class User {
  constructor(name, interests) {
    this.name = name;
    this.interests = interests;
  }
  greeting () {
    console.log('Hi, I\'m ' + this.name + '.');
  }
  get interestsCount () {
    return this.interests ? this.interests.length : 0;
  }
}

class TeamMember extends User {
  constructor(name, interests) {
  	//생성자에서 name과 interests 프로퍼티 초기화
    super(name, interests);
    //새로운 두 개의 추가 프로퍼티 정의
    //'_'를 이용해 private라는 것을 암시한다.
    this._tasks = [];
    this._welcomeText = 'Welcome to the team!';
  }
  //재정의
  greeting () {
    console.log('I\' m ' + this.name + '. ' + this._welcomeText);
  }
  //새로운 work() 메서드 추가
  work () {
    console.log('I\' m working on ' + this._tasks.length + ' tasks.')
  }
  //정적 메소드에 접근하는 tasks 세터 정의
  set tasks (tasks) {
    let acceptedTasks = [];
    if (tasks.length > TeamMember.maxTasksCapacity()) {
      acceptedTasks = tasks.slice(0, TeamMember.maxTasksCapacity());
      console.log('It\'s over max capacity. Can only take two.');
    } else {
      acceptedTasks = tasks;
    }    
    this._tasks = this._tasks.concat(acceptedTasks);
  }
  static maxTasksCapacity () {
    return 2;
  }
}

 

let member = new TeamMember('Sunny', ['Traveling']);
member.greeting();   // I' m Sunny. Welcome to the team!
member.tasks = ['Buy three tickets', 'Book a hotel', 'Rent a car'];
                     // It's over max capacity. Can only take two.
member.work();       // I' m working on 2 tasks.
console.log(member.interestsCount); // 1

//세터가 없어서 동작하지 않는다.
member.interestsCount = 2;          // This won’t save the change
console.log(member.interestsCount); // 1
//게터가 없어서 undefined 출력
console.log(member.tasks);

 

 

//User 클래스에 새로운 메소드를 추가하기 위해서는 prototype을 이용해야 한다.
User.prototype.eat = function () {
  console.log('What will I have for lunch?');
};
member.eat();  // What will I have for lunch?

//User 객체에 직접 추가하면 TypeError 발생
User.sleep = function () {
  console.log('Go to sleep');
};

//User 생성자 함수 자체에 프로퍼티로 추가하면 생성자함수에 정적메소드를 추가한다.
member.sleep();  // Uncaught TypeError: member.sleep is not a function
//class 구문 사용시에 메소드 정의시 프로토타입 객체에 메소드를 추가한다.
User.sleep();    // Go to sleep

console.log(User.prototype.hasOwnProperty('eat'));  // true
console.log(User.hasOwnProperty('eat'));            // true

 


강화된 객체 리터럴

: 프로토타입 설정, 프로퍼티 축약 표현, 메소드 축약 표현, super 호출, 표현식을 이용한 프로퍼티 계산 기능 지원

 

프로퍼티 축약 표현을 이용

-> 프로퍼티의 값으로 변수 사용해 프로퍼티 이름 생략 가능하고, 프로퍼티 이름은 변수 이름으로 자동 생성

메소드 축약 표현을 이용

-> function 키워드를 생략해 메서드 선언 가능

표현식을 이용한 프로퍼티 계산 기능 지원

->계산된 프로퍼티 이름이라고 불리며, 객체 프로퍼티 이름을 표현식으로 지정할 수 있고 대괄호 안에 표현식을 쓴다.

 

const advice = 'Stay hungry. Stay foolish.';

let advisor = {
  __proto__: new TeamMember('Adam', ['Consulting']), // Setting prototype
  advice,                                            // Shorthand assignmetn
  greeting () {
    super.greeting();                                // Call super method
    console.log(this.advice); 
  },
  [advice.split('.')[0]]: 'Always learn more'        // Compute property name
};


console.log(TeamMember.prototype.isPrototypeOf(advisor));  // true
console.log(advisor instanceof TeamMember);                // true
advisor.greeting();   // I' m Adam. Welcome to the team!
                      // Stay hungry. Stay foolish.

 


화살표 함수

'=>' 구문을 사용해 함수 축약을 표현하며 표현식과 명령문 블록을 지원하며 표현식으로 구성된 본문 이용시 표현식 결과를 반환한다.

 

문법

[함수 인자] => [함수 본문]

 

const fruits = [{name: 'Apple', price: 100}, {name: 'Orange', price: 80}, {name: 'Banana', price: 120}];

// Variation 1
//인자가 없을 때 빈 괄호 세트()가 필요하다
var countFruits = () => fruits.length;
// equivalent to ES5
var countFruits = function () {
  return fruits.length;
}; 

// Variation 2
//하나의 인자가 있을 때 하나의 괄호는 생략할 수 있다.
//표현식의 값은 함수의 반환 값이다.
fruits.filter(fruit => fruit.price > 100);
// equivalent to ES5
fruits.filter(function(fruit) {
  return fruit.price > 100;
});

// Variation 3
//함수가 객체 리터럴을 반환할 때 괄호로 감싸야 한다.
var inventory = fruits.map(fruit => ({name: fruit.name, storage: 1}));
//ES5 코드
var inventory = fruits.map(function (fruit) {
  return {
    name: fruit.name,
    storage: 1
  };
});

// Variatoin 4
//화살표 함수가 구문들로 이뤄진 본문을 가지고 있는 결과를 반환해야 할 때 return 구문이 필요하다.
var inventory = fruits.map(fruit => {
  console.log('Checking ' + fruit.name + ' storage');
  return {name: fruit.name, storage: 1};
});

//ES5 코드
var inventory = fruits.map(function (fruit) {
  console.log('Checking ' + fruit.name + ' storage');
  return {name: fruit.name, storage: 1};
});

 

 

//중괄호 사용해 변형3 이용해 객체 리터럴 반환시 주의사항

//함수 본문은 단일 또는 여러 명령문으로 구성되어야 한다.
var sum = (a, b) => { return a + b };
sum(1, 2);    // 3

//동작 하지 않는 코드
var sum = (a, b) => { a + b };
sum(1, 2);   // undefined

//표현식을 이용해 수정한 코드
var sum = (a, b) => a + b;
sum(1, 2);    // 3

 


화살표 함수는 자신의 this를 가지지 않는다.

: 분리된 실행 컨텍스트를 생성하는 ES5 함수와는 달리 화살표 함수는 상위 스코프의 실행 컨텍스트를 사용

 

var shoppingCart = {
  items: ['Apple', 'Orange'],
  inventory: {Apple: 1, Orange: 0},
  checkout () {
    //Arrays.prototype.forEach() 메소드의 콜백 내부에서도 객체 자신 참조
    this.items.forEach(item => {
      //객체 자신 참조
      if (!this.inventory[item]) {
        console.log('Item ' + item + ' has sold out.');
      }      
    })    
  }
}
shoppingCart.checkout();

// ES5 코드
var shoppingCart = {
  items: ['Apple', 'Orange'],
  inventory: {Apple: 1, Orange: 0},
  checkout () {
    //컨텍스트를 재할당하고 forEach에 전달한 콜백에서
    //참조할 수 있도록 클로저를 활용한다.
    var that = this;
    this.items.forEach(function(item) {
      if (!that.inventory[item]) {
        console.log('Item ' + item + ' has sold out.');
      }      
    })    
  }
}
shoppingCart.checkout();

 

화살표 함수는 분리된 실행 컨텍스트를 가지지 않기 때문에
call(),apply(),bind() 메소드를 활용해 호출시 첫 번째 인자로 전달된 실행 컨텍스트는 무시된다.

var name = 'Unknown';
var greeting = () => {
  console.log('Hi, I\'m ' + this.name); 
};
greeting.call({name: 'Sunny'});    // I'm Unknown
greeting.apply({name: 'Tod'});     // I'm Unknown
var newGreeting = greeting.bind({name: 'James'});
newGreeting();                     // I'm Unknown

# 화살표 함수에서 this는 항상 상위 스코프의 실행 컨텍스트로 해석되지만, call(), apply(),bind() 메소드는 자체 실행 컨텍스트에 아무런 영향을 미치지 않는다.

 

 

상위 스코프의 실행 컨텍스트를 이용하는 화살표 함수는 객체의 메소드 정의에 사용하는 것은 적합하지 않는다.

: checkout을 화살표 함수로 변경하면, this가 shoppingCart 객체를 참조하지 않아 에러를 발생한다.

var shoppingCart = {
  items: ['Apple', 'Orange'],
  inventory: {Apple: 1, Orange: 0},
  checkout: () => {
    // Uncaught TypeError: Cannot read property 'forEach' of undefined
    this.items.forEach(item => {
      if (!this.inventory[item]) {
        console.log('Item ' + item + ' has sold out.');
      }      
    })    
  }
}
shoppingCart.checkout();

 

객체 리터럴로 작성된 장바구니

: 프로토타입 객체를 이용해 객체 메소드 정의시 화살표 함수가 제대로 동작하지 않는다.

class User {
  constructor(name) {
    this.name = name;
  }
}
User.prototype.swim = () => {
  console.log(this.name + ' is swimming');
};
var user = new User();
console.log(user.swim());   //  is swimming

-> this가 User객체를 참조하지 않고, 전역 컨텍스트를 참조


화살표 함수는 프로토타입 객체를 가지지 않는다.

-화살표 함수는 프로토타입 객체를 가지지 않고 생성자 함수도 가지지 않는다.

-new 연산자로 호출 불가

const WorkoutPlan = () => {};

// Uncaught TypeError: WorkoutPlan is not a constructor cnffur
let workoutPlan = new WorkoutPlan(); 
console.log(WorkoutPlan.prototype);  // undefined

 


ES6 특징

  1. 매개변수 기본값
  2. 나머지 매개변수
  3. 전개 구문
  4. 비구조화 할당
    1. 객체 비구조화
    2. 배열 비구조화
    3. 중첩 비구조화
    4. 나머지 요소
    5. 함수 매개변수 비구조화
  5. 탬플릿 리터럴
  6. 모듈
    1. 내보내기
    2. 가져오기
  7. 프로미스
    1. 프로미스의 상태
      1. 대기
      2. 이행
      3. 실패

매개변수 기본값

-함수 매개변수의 기본값 정의 방법

const shoppingCart = [];
function addToCart(item, size = 1) {
  shoppingCart.push({item: item, count: size});
}
addToCart('Apple');     // size is 1
addToCart('Orange', 2); // size is 2


// ES5 코드
function addToCart(item, size) {
  size = (typeof size !== 'undefined') ? size : 1;
  shoppingCart.push({item: item, count: size});
}

나머지 매개변수

ES5 : 함수 본문 내에서 함수의 매개변수들을 반복하는 데 arguments 객체를 이용 가능

ES6 : 나머지 매개변수 구문으로 무한 개의 매개변수를 정의하는데 사용 가능

//ES5 arguments 이용한 코드
function workout(exercise1) {
  var todos = Array.prototype.slice.call(arguments, workout.length);
  console.log('Start from ' + exercise1);
  console.log(todos.length + ' more to do');
}

//ES6 나머지 매개변수 이용한 코드
//나머지 매개변수로 매개변수들을 배열로 정의

//나머지 매개변수인 todos를 정의하는데, 세 개의 점으로 시작해 매개변수명으로 끝난다.
function workout(exercise1, ...todos) {
  console.log('Start from ' + exercise1);    // Start from Treadmill
  console.log(todos.length + ' more to do'); // 2 more to do
  console.log('Args length: ' + workout.length); // Args length: 1
  //나머지 매개변수는 함수 내 매개변수 개수에 영향을 끼치지 않는다.
}
workout('Treadmill', 'Pushup', 'Spinning');

전개 구문

-ES6에서 3점 표기법을 함수 선언 내에 사용해 정의한다

-이 표기법을 배열에서 사용해 배열의 요소들을 전개시켜 배열의 각 요소를 함수에 전달해 배열 리터럴에서도 사용 가능

let urgentTasks = ['Buy three tickets'];
let normalTasks = ['Book a hotel', 'Rent a car'];
//전개 구문으로 urgentTask 배열과 nomalTasks 배열을 확장
let allTasks = [...urgentTasks, ...normalTasks];

//전개 구문으로 allTasks 배열을 확장하고 함수의 인자로 각 요소를 전달
((first, second) => {
  console.log('Working on ' + first + ' and ' + second)
})(...allTasks);

 

let hotel = {name: 'Hotel A', city: {name: 'Sanya'}};
let price = {price: 100};

// 얕은 복사를 이용한 객체 병합
let booking = {...hotel, ...price};
console.log(booking); // {name: "Hotel A", price: 100, city : {name: 'Sanya'}}

hotel.city.name = 'Xiamen';
console.log(booking.city.name); // Xiamen

비구조화 할당

: 배열 내의 요소, 문자열 내의 문자, 객체 내의 프로퍼티를 분리하고 배열 리터럴, 객체 리터럴과 비슷한 구문으로 구분된 변수들을 할당

-> 변수 선언이나 변수 할당, 함수 매개변수 할당에 활용가능

  1. 객체 비구조화
  2. 배열 비구조화
  3. 중첩 비구조화
  4. 나머지 요소
  5. 함수 매개변수 비구조화

객체 비구조화

// Object destructuring

let user = {name: 'Sunny', interests: ['Traveling', 'Swimming']};
let {name, interests, tasks = []} = user;
console.log(name);       // Sunny
console.log(interests);  // ["Traveling", "Swimming"]
console.log(tasks);      // undefined

let {name: firstName} = user;
console.log(firstName)  // Sunny

배열 비구조화

// Array destructuring

let [first, second] = ['Traveling', 'Swimming', 'Shopping'];
console.log(first);   // Traveling
console.log(second);  // Swimming


let [,,third, fourth = ''] = ['Traveling', 'Swimming', 'Shopping'];
console.log(third);   // Shopping
console.log(fourth);  // undefined

중첩 비구조화

// Nested destructuring

let user = {name: 'Sunny', interests: ['Traveling', 'Swimming']};
let {interests: [,second]} = user;
console.log(second);    // Swimming
console.log(interests); // ReferenceError


const fruits = [{name:'Apple', price:100},{name:'Orange', price:80}];
let [,{name:secondFruitName}] = fruits;
console.log(secondFruitName); // Orange

나머지 요소

: 비구조화 할당에서 배열의 나머지 요소를 다른 배열에 넣는 데 나머지 요소와 동일한 구문 사용

 

// Rest elements

let [first, ...others] = ['Traveling', 'Swimming', 'Shopping'];
console.log(others);   // ["Swimming", "Shopping"]


const fruits = [{name:'Apple', price:100},{name:'Orange', price:80}];
let [...myFruits] = fruits;
console.log(myFruits[0].name);            // Apple
myFruits.push({name:'Banana', price:90});
console.log(myFruits.length);             // 3
console.log(fruits.length);               // 2
myFruits[0].price = 110;
console.log(fruits[0].price);            // 110

함수 매개변수 비구조화

//객체 비구조화 구문으로 workout() 함수의 첫 번째 인자에서 gym 변수를 추출
//만약 전달된 인자가 null 또는 undefined면 TypeError 발생
//workout()함수에 숫자나 문자열, 배열, 함수 전달시 gym 변수값이 undefined가 된다.

function workout({gym, times}) {
  console.log('Workout in ' + gym + ' for ' +times+ ' tiems'));
}

let thisWeek = {gym: 'Gym A'};
workout(thisWeek, 2); // Workout in Gym A for 2 times 출력

 

//첫 번째 인자의 매개변수 비구조화 수행
function workout({gym, todos}) {
//workout()함수에 todos 프로퍼티를 가지는 배열을 인자로 전달해 todos 변수의 추가 비구조화 수행
  let [first] = todos;
  console.log('Start ' + first + ' in ' + gym);
}

//todos에 기본값 설정해 배열이 아닌 인자 전달해도 에러 방지
let today = {gym: 'Gym A', todos: ['Treadmill']};
workout(today); // Start Treadmill in Gym A
//배열이 아닌 인자를 전달시 TypeError 발생
workout({gym: 'Gym B'})       // TypeError 출력


function workout({gym, todos=['Treadmill']}) {
  let [first] = todos;
  console.log('Start ' + first + ' in ' + gym);
}
workout({gym: 'Gym A'})   // Start Treadmill in Gym A
workout(); // TypeError 출력


//어떤 매개변수도 없이 호출해도 기본값 할당하기 위해 전체 매개변수 비구조화에 기본값 할당
function workout({gym = 'Gym A', todos=['Treadmill']} = {}) {
  let [first] = todos;
  console.log('Start ' + first + ' in ' + gym);
}
workout();  // Start Treadmill in Gym A

탬플릿 리터럴

-문자열 리터럴에 표현식을 포함하고 여러 라인을 지원하는 기능 제공

-문자열을 묶는 데 작은따옴표나 큰따옴표가 아닌 역 따옴표를 사용한다.

let user = {
  name: 'Ted',
  greeting () {
    //${...}구문을 사용해 this로 해당 실행 컨텍스트에 접근
    console.log(`Hello, I'm ${this.name}.`);
  }
};
user.greeting();  // Hello, I'm Ted.

 

 

//여러 줄 사용한 예제
let greeting = `Hello, I'm ${user.name}.
Welcome to the team!`;
console.log(greeting);// Hello, I'm Ted.
                       //Welcome to the team!

 

let greeting = `Hello, I'm ${user.name}.
                Welcome to the team!`;
console.log(greeting); // Hello, I'm Ted.
                       //                Welcome to the team!

 


모듈

: 자바스크립트는 언어 차원에서 모듈 지원하는데, 모듈 구성과 정적 모듈 구조를 만드는데 컴파일 시점에서 내보내거나 가져오기 가능

-단, 반드시 내보내기나 가져오기는 최상위에 위치해야 하며 if와 try/catch 같은 블록 내에 넣을 수 없다.

 

모듈 생성 방법

-바벨로 ES6 -> ES5로 컴파일

-웹팩을 이용해 코드를 묶는다.

-<script type="module">로 모듈 파일을 브라우저로 불러온다.


내보내기

-모듈 내부에서 아무것도 내보내지 않을 것을 선택

내보내기 유형

  1. 명명된 내보내기
    : 동일 모듈에 여러 개의 명명된 내보내기가 가능
  2. 기본 내보내기
    : 하나의 모듈에는 하나의 기본 내보내기만 존재 가능

 

모듈 생성

  1. User 클래스를 내보내는 user.js 모듈
  2. 역할 상수를 내보내는 role.js 모듈
  3. 모듈과 완료된 업무의 총 건수를 파악하는 task.js 모듈

 

1. User 클래스를 내보내는 user.js 모듈

//User 클래스 앞에 export와 default 키워드를 붙여 기본 내보내기로 User 클래스 내보내기

//인라인 방식으로 export선언하는 대신 User 클래스 먼저 선언 후
//다음 모듈의 맨 아래 또는 모듈 최상위, 심지어 User 클래스 이전에 내보내기 가능

export default class User {
  constructor (name, role) {
    this.name = name;
    this.role = role;
  }
};

 

 

2. 역할 상수를 내보내는 role.js 모듈

//두 개의 상수를 생성해 중괄호로 감싸 명명된 내보내기

const DEFAULT_ROLE = 'User';
const ADMIN = 'Admin';

//객체를 내보낸 것이 아니라 상수를 내보낸 것
export {DEFAULT_ROLE as USER, ADMIN};

 

3.모듈과 완료된 업무의 총 건수를 파악하는 task.js 모듈

console.log('Inside tasks module');

//기본 내보내기
export default function completeTask(user) {
  console.log(`${user.name} completed a task`);
  completedCount++;
}

// 완료된 작업의 개수를 추적해 명명된 내보내기
export let completedCount = 0;

가져오기

-생성된 모듈을 가져오는 모듈

//기본 가져오기로 User 클래스를 가져온다. [User가 아닌 다른 이름 사용가능]
import User from './user.js';    

//네임스페이스 가져오기
import * as Roles from './roles.js';

//기본 가져오기로 completeTask 함수를 가져온다
import completeTask from './tasks.js';

//명명된 가져오기로 다시 같은 모듈에서 completedCount를 가져온다.
//: 싱글톤 모듈이므로 taks.js 모듈 코드를 한 번만 평가
import {completedCount} from './tasks.js';

//위의 두 코드와 같은 의미로 사용가능한 기본 가져오기와 명명된 가져오기 함께 작성도 가능
//import completeTask, {completedCount} from './tasks.js';
//모듈에서 다른 로컬 이름과 충돌한다면 명명된 가져오기의 이름을 변경가능
//import {completedCount as totalCompletedTasks} from './tasks.js';

//함수 선언과 마찬가지로 가져오기 호이스팅 가능하지만, 모듈 상단에 모든 가져오기 코드 작성 권장
let user = new User('Ted', Roles.USER);

 

completeTask(user);    
console.log(`Total completed ${completedCount}`);
// completedCount++;  
// Only to show that you can change imported object.
// NOT a good practice to do it though.
User.prototype.walk = function () {
  console.log(`${this.name} walks`);
};
user.walk();

 

index.html 파일 작성

<!DOCTYPE html>
<html>
<body>
  //app.js를 하나의 모듈로 브라우저에 적재하며, 구문 분석 마친 후 모듈 실행하는 defer 속성을 가짐
  <script type="module" src="./app.js"></script>
  //이 코드가 먼저 실행된다.
  <script>console.log('A embedded script');</script>
</body>
</html>

#브라우저 직접 여는 경우 CORS 정책으로 동작하지 않는다. 

 

자바스크립트에 스크립트와 모듈의 두 가지 형태가 존재

-스크립트 코드의 경우 strict 모드로 코드 렌더링하기 위해서는 'use strict'를 넣어야한다.

-모듈 코드의 경우 자동 strict 모드

 

모듈 최상위 특징

-모듈 최상위 변수는 외부에서 활용하기 위해 내보내기 하지 않는 한 해당 모듈의 로컬변수다.

-모듈 최상위에서 this는 undefined이다

-브라우저에서는 모듈 내부의 window 객체에 접근 가능

 


 프로미스 

: 자바스크립트에서 비동기 프로그래밍에 사용하는 기능으로 비동기 작업의 최종 결과 표현하며 then(), catch()와 같은 메소드 제공해 코드 흐름을 구성하는데 도움을 준다.

 

-setTimeout을 이용해 비동기동작 처리 가능

 

콜백 활용

function getProjects(callback) {
  // Use setTimeout to stimulate calling server API
  setTimeout(() => {
    callback([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
  }, 100);
}

function getTasks(projects, callback) {  
  // Use setTimeout to stimulate calling server API
  setTimeout(() => {
    // Return tasks of specified projects
    callback([{id: 1, projectId: 1, title: 'Task A'}, 
              {id: 2, projectId: 2, title: 'Task B'}]);
  }, 100);    
}

function render({projects, tasks}) {
  console.log(`Render ${projects.length} projects and ${tasks.length} tasks`);
}

//비동기 호출 구성하는데 콜백 활용하는데 메서드가 중첩되어 파멸의 피라미드, 콜백 지옥 생성
getProjects((projects) => {
  getTasks(projects, (tasks) => {
    render({projects, tasks});
  });
});

 

프로미스 활용

: 생성자 함수가 가지는 실행함수가 resolve와 reject함수로 인수로 전달되어 실행된다.

//then() 메소드는 매개변수로 두 개의 함수를 가진다.
//첫 번째 함수는 resolve()로 이행시
//두 번째 함수는 reject()로 오류시 호출

function getProjects() {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve([{id:1, name:'Project A'},{id:2, name:'Project B'}]);
    }, 100);
  });  
}

function getTasks(projects) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({projects, tasks:['Buy three tickets', 'Book a hotel']});
    }, 100);
  });
}

function render({projects, tasks}) {  
  console.log(`Render ${projects.length} projects and ${tasks.length} tasks`);
}
//-> getProjects()와 getTasks() 메소드에서 비동기 연산을 래핑한 Promise 객체를 반환
//Promise 생성자 함수는 매개변수로 하나의 함수를 가지며 실행 함수라고 부른다.

//비동기 작업 완료시 resolve 호출 후, 에러가 발생하거나 작업 실패시 reject함수 이용해 프로미스 거부 가능

getProjects()
//이행시 호출되는 메소드로 then() 메소드는 promise 객체를 반환한다.
.then(getTasks)
.then(render)
//오류 발생시 호출되는 메소드로 .then(undefined, onRejected)의 설탕 구문
.catch((error) => {
  console.log('Hanlding error', error);
});

 

 

프로미스의 상태

  1. 대기
  2. 이행
  3. 실패

 

반응형