2024.09.26-30 (Day 15,16,17) - JavaScript OOP / 다양한 OOP 기법

반응형

typeof, instanceof

• typeof 연산자는 타입을 확인하기 위한 연산자이다.

 

• instanceof 는 객체의 타입이 특정 타입인지를 판단하기 위한 연산자이며

instanceof 왼쪽에 객체를, 오른쪽에 타입을 명시해서 왼쪽 객체의 타입이

오른쪽에 명시한 타입인지를 판단하는 연산자이고 연산의 최종 결과는 true/false 이다.

instanceof 는 생성자를 가지고 판단하는 연산자이기도 한다.

 

▶ 다른 함수의 프로토타입을 그대로 자신의 프로토타입으로 지정하는 경우의 instanceof

 

▶ 상위 객체를 생성해서 하위 프로토타입으로 지정하는 경우의 instanceof

"use strict"

function User(){}
let user1 = new User()

//typeof
console.log(typeof 10)//number
console.log(typeof 'hello')//string
console.log(typeof true)//boolean
console.log(typeof User)//function
console.log(typeof [10, 20])//object
console.log(typeof user1)//object

//instanceof
//instanceof 는 객체의 타입을 판단하기 위한 연산자이다.
//내부적으로 오른쪽의 생성자 함수의 prototype 을 판단한다.
console.log(10 instanceof Number)//false
console.log(new Number(10) instanceof Number)//true

function Car(){}
console.log(user1 instanceof User)//true
console.log(user1 instanceof Car)//false

function Shape(){}
function Rectangle(){}
//두 생성자 함수의 prototype 이 동일
Rectangle.prototype = Shape.prototype

let shape = new Shape()
let rect = new Rectangle()
console.log(shape instanceof Shape)//true
console.log(shape instanceof Rectangle)//true
console.log(rect instanceof Shape)//true
console.log(rect instanceof Rectangle)//true

Rectangle.prototype = new Shape()
let shape1 = new Shape()
let rect1 = new Rectangle()
console.log(shape1 instanceof Shape)//true
console.log(shape1 instanceof Rectangle)//false
console.log(rect1 instanceof Shape)//true
console.log(rect1 instanceof Rectangle)//true

 

 

 


프로퍼티 Descriptor

• 객체에 프로퍼티에는 설명자(Descriptor)라는 정보가 있다.

설명자를 이용해 객체의 프로퍼티 값이 변경되지 않게 하거나 열거로 사용되지 않게 하는 등

원하는데로 사용되게 할 수 있다. 객체를 선언하면서 프로퍼티의 설명자를 따로 지정하지 않는다고 하더라도

기본으로 프로퍼티에 설명자가 추가 된다.

프로퍼티의 설명자를 확인하려면 Object.getOwnPropertyDescriptor() 를 이용하면 된다.

 

△ 설명자의 각 속성의 의미

• value : 프로퍼티에 대입된 값

• writable: 프로퍼티 값을 수정할 수 있는지에 대한 여부

• enumerable : 프로퍼티가 열거형으로 이용이 가능한지에 대한 여부

• configurable : 프로퍼티의 설명자를 변경할 수 있는지에 대한 여부

 

▼ writable

• writable 은 프로퍼티의 값을 변경하지 못하게 할 때 이용한다.

객체를 선언할 때 지정한 값으로만 사용되어야 하거나 특정 업무가 진행되는 동안

값 변경이 불가능하게 하고자 할 때 이용한다.

만약 프로퍼티의 설명자 값을 변경하고 싶다면 Object.defineProperty() 를 이용한다.

 

▼ enumerable  

• enumerable 은 프로퍼티가 열거로 이용이 가능한지를 설정하기 위해서 사용한다.

열거라는 것은 프로퍼티 명을 명시하지 않고 어떤 객체에 등록된 프로퍼티들을 순서대로 나열해서 사용하는 것을 의미하며 특정 프로퍼티가 이 열거로 이용되지 않게 하고자 할 때 enumerable 을 false 로 지정한다.

 

▼ configurable

• configurable 은 프로퍼티의 설명자를 재설정 할 수 있는지에 대한 정보이며

false 로 지정하면 재설정이 불가능하다.

 

"use strict"

let obj = {
  name: '홍길동',
  age: 10,
  address: 'seoul'
}

//특정 객체의 property 의 descriptor 확인 
console.log(Object.getOwnPropertyDescriptor(obj, 'name'))
//{value: '홍길동', writable: true, enumerable: true, configurable: true}

//writable - 값 변경 못하게 하고자 할때
Object.defineProperty(obj, 'age', {writable: false})
obj.name = '김길동'
// obj.age = 20     //error

//enumerable
console.log(Object.keys(obj))//['name', 'age', 'address']
console.log(Object.values(obj))//['김길동', 10, 'seoul']
console.log(Object.entries(obj))
//0:(2) ['name', '김길동']
//1: (2) ['age', 10]
//2: (2) ['address', 'seoul']

//in.. 열거 예약어..obj 의 entry 갯수만큼 for 반복.
for(let property in obj){
  console.log(property)
}
//name
//age
//address

Object.defineProperty(obj, 'age', {enumerable: false})
console.log(Object.entries(obj))
//0: (2) ['name', '김길동']
//1: (2) ['address', 'seoul']

for(let property in obj){
  console.log(property)
}
//name
//address
console.log(obj.age)//10

//configurable
//wriable 을 false 로 지정했다고 하더라도 누군가가 true 로 변경해서
//값 변경을 할수도있다.
Object.defineProperty(obj, 'age', {writable:true})
obj.age = 20
//descriptor 를 조정한 후에 다시 descriptor 가 조정되지 못하게함
Object.defineProperty(obj, 'age', {writable:false, configurable:false})
Object.defineProperty(obj, 'age', {writable:true})


new Object()

• 함수나 클래스 같은 객체의 모형을 선언하고 이 모형을 이용해 객체를 생성하지 않고

간단하게 객체를 선언하는 방법은 객체 리터럴이다.

또한 객체 리터럴 방법을 이용하지 않고 new Object() 로 객체를 선언할 수도 있다.

 

 

 

 

 

 

Object.create()

• 자바스크립트의 모든 객체는 그 객체를 만드는 프로토타입이 있어야 한다.

• 객체 리터럴로 객체를 생성하는 것은 new Object() 방법으로 객체를 생성하는 것과 동일함으로

객체 리터럴로 만든 객체의 프로토타입은 Object 의 프로토타입이다.

그런데 경우에 따라 객체를 생성하면서 Object의 프로토타입이 아닌

다른 프로토타입을 지정하고 싶은 경우가 있는데 이렇게 하고자 하는 주된 이유는

어떤 프로토타입을 지정해 객체를 생성해서 그 프로토타입에 등록된 멤버를 객체에서 이용하고자 한다.

일종의 상속개념으로 프로토타입을 지정해 그 프로토타입의 코드를 재사용하는 개념이며

이때 Object.create() 로 객체를 생성한다.

 

• Object.create() 로 객체를 생성할 때 첫번째 매개변수로 생성되는 객체의 프로토타입을 지정해 주어야 한다.

그리고 두번째 매개변수는 옵셔널로 생략가능한데 등록한다면 생성되는 객체의 프로퍼티이다.

객체의 프로퍼티를 지정하고 있는데 name: { } 형태로 프로퍼티를 등록해야 한다

name 은 프로퍼티 명이며 { } 은 프로퍼티의 설명자이다.

즉 value, writable, enumerable, configurable 등의 정보로 프로퍼티의 설명자를 등록해야 한다.

 

• Object.create() 를 이용하는 것은 등록하는 프로퍼티에 설명자를 객체를 생성하면서 지정하고자 하는 경우이다.

또다른 경우는 특정 프로토타입을 지정해서 코드 재사용을 한다.

 

"use strict"

//간단하게 객체를 생성해서 사용하는 방법 - object literal
let user1 = {
  name: '홍길동',
  age: 20
}
console.log(user1)//{name: '홍길동', age: 20}
console.dir(user1)
//this - name, age
//prototype - Object
//==>모든 객체는 prototype이 지정된다.

//==>위 object literal 기법으로 만든 객체는 아래처럼 만든것과 동일하다.
//prototype - 객체 정보
let user2 = Object.create(Object.prototype, {
  name: {value: '홍길동'},
  age: {value: 20}
})
console.log(user2)//{name: '홍길동', age: 20}
console.dir(user2)

//==>
//생성자 함수를 이용해 객체를 만들면 그 함수에 prototype 이 자동으로 만들어진다.
//그런데 object literal 기법으로 만들면 prototype 지정?
//Object.create() 로 객체를 생성하면서 원하는 prototype 을 지정해서 사용한다.
function Shape(name){
  this.name = name
}
Shape.prototype.draw = function(){
  console.log(`${this.name}을 그립니다.`)
}
let rect1 = Object.create(Shape.prototype, {
  name: {value: 'rect1'},
  width: {value:100},
  height: {value:200}
})
rect1.draw()
console.dir(rect1)

 

 

 


 

this

• this 예약어는 코드를 실행시키는 객체를 의미하고 주로 함수 내에서 이용되며

함수를 호출한객체를 지칭하기 위해서 this 가 사용된다.

그런데 자바스크립트에서는 함수를 호출한 객체가 정적이지 않고 

자바스크립트에서는 실행단계에서 동적으로 this 가 결정된다.

동일한 함수의 this 라고 하더라도 함수를 어떻게 호출했는지에 따라 다른 객체를 지칭하게 된다.

 

▼ 전역위치에 선언된 함수에서의 this

• 전역위치 함수 호출의 경우,

자바스크립트 코드를 엄격모드에서 작성하고 있는지에 따라 this 가 다르게 나올 수 있다.

• 일반모드에서는 전역위치를 실행시키는 객체는 별도로 명시하지 않아도 window 가 지정된다.

 

• 또한 엄격모드에서 전역위치 함수를 객체를 지정하지 않고 호출하게 되면

this 는 window 가 아니라 undefined상태이다.

 

함수 내에 선언된 함수에서의 this

• 함수 내에 선언된 함수를 호출하는 경우도 전역 위치의 함수 호출과 동일하다.

 

▼ 객체 메서드 호출시의 this

• 객체를 생성하고 그 객체의 메서드를 호출했을 때 메서드를 실행시키는 this는 메서드를 호출한 객체가 된다.

//객체 메서드의 this
function User2(arg1, arg2){
  this.name = arg1
  this.age = arg2

  this.sayHello = function() {
    console.log(`Hello ${this.name}, ${this.age}`)
  }
}
let user2 = new User2('홍길동', 20)
let user3 = new User2('김길동', 30)
//현 순간의 sayHello 의 this 는 user2 로 호출하는 것임으로 user2
user2.sayHello()//Hello 홍길동, 20
//현 순간의 sayHello 의 this 는 user3 로 호출하는 것임으로 user3
user3.sayHello()//Hello 김길동, 30

▼ 생성자 함수에서의 this

• new 연산자에 의해 호출되는 시점의 this 는 새로 만들어지는 객체를 의미한다.

//생성자 함수내에서의 this
function User1(arg1){
  //new 로 호출이 됨으로 호출하자 마자 빈 상태의 객체가 만들어지고.
  //생성자 함수가 호출이 되는 동안의 this 는 그 객체
  console.log(this)//User1 {}
  this.data = arg1
  console.log(this)//User1 {data: '홍길동'}
}
let user1 = new User1('홍길동')
console.log(user1)//User1 {data: '홍길동'}

 

▼ 화살표 함수  

• 자바스크립트에서 함수가 실행될때의 this는

호출하는 시점에 결정되는 동적 바인딩이지만 화살표 함수에한해서만 정적 바인딩이 된다.

• 화살표 함수내의 this 는 호출하는 시점에 결정되는 것이 아니라 코드를 작성하는 시점,

즉 선언 시점에 결정된다.

이를 전문 용어로 렉시컬(Lexical) this 혹은 렉시컬 바인딩이라 부르며

화살표 함수의 this 는 선언되는 시점에 지정되며 화살표 함수의 상위 스코프에 있는 this가 지정된다.

 

▷ 화살표 함수 – 전역 위치에 선언

• 화살표 함수는 선언된 위치의 상위 스코프의 this 에 바인딩이 됨으로

전역 위치에 선언된 화살표 함수의 상위 스코프는 window 이다.

 

▷ 화살표 함수 – 생성자 함수내에 선언

• 화살표 함수는 선언한 순간 this 가 결정되며 상위 스코프의 this 에 등록되며

결국 this.fun3 = () => { } 로 등록했음으로 생성자 함수로 생성되는 객체에 대입된다.

 

화살표 함수 – 객체 리터럴에 선언

• 객체 리터럴 내의 함수를 화살표 함수로 선언하면 화살표 함수내에서의 this 는 생성되는 객체를 지칭하지 못한다.

 

//화살표 함수, 생성자 함수내에 선언될 때 this
function User3() {
  this.data = 20
  this.fun1 = function() {
    console.log(this.data)
  }
  //화살표 함수, 선언 시점에 상위 스코프의 this 가 지정
  this.fun2 = () => {
    console.log(this.data)
  }
}
let user4 = new User3()
user4.fun1()//20
user4.fun2()//20


//object literal 로 만든 객체의 함수, this
let obj = {
  data: 30,
  fun1: function(){
    console.log(this.data)
  },
  fun2: () => {
    console.log(this.data)
  }
}
//function 예약어의 함수에서 this 는 동적 결정임으로 객체가 만들어 진후 함수가 
//호출.
obj.fun1()//30
//화살표 함수는 lexical this, 작성 시점에 상위 스코프가 실행되어야 객체가 만들어진다.
//선언 시점에는 {} 는 객체를 만들기 위한 정보에 지나지 않는다. 스코프 아님
//위 예에서의 상위 scope 는 window
obj.fun2()//undefined

//==>화살표 함수는 함수를 간단하게 선언하고 싶을때 자주 이용.
//==>this 를 함수내에서 사용하지 않는 경우에 쓸 것을 권장하고 있다.

 

 

this 동적 바인딩

• 함수에서의 this 는 그 함수를 호출한 객체를 의미한다.

자바스크립트에서는 함수의 this 를 동적 바인딩이 가능하며 동적 바인딩이란

실행시점에 함수의 this 를 지정할 수 있다는 의미이다.

또한 이 동적 바인딩 기법을 이용해 함수의 this 역할을 하는 객체를 바꿔서 이용할 수 있다

 

▼ bind()

• bind() 함수는 함수에 this 역할을 하는 객체를 바인딩하여 새로운 함수를 반환하는 역할이다.

• bind() 는 함수를 호출하는 것이 아니라 새로운 함수를 만드는 역할이다..

 

• bind 되는 함수에 매개변수를 지정하고 새로 만들어지는 함수를 호출하면서 매개변수 값을 대입할 수 있다.

 

• bind() 로 함수에 객체를 바인딩 시키면서 매개변수 값을 지정할 수 있으며

또한 함수를 호출하면서 지정한 매개변수 값이 모두 같이 이용되게 할 수 있다.

 

let obj1 = {
  name: '홍길동'
}
//생성자 함수가 아닌이상 혹은 어떤 객체에 동작 바인딩 되어 실행될
//함수가 아닌 이상 함수내에 this 는 안쓰는게 좋다.
let sayHello = function(){
  console.log(`Hello, ${this.name}`)
}
// sayHello()   //error

//==>위 객체와 함수는 별개이다
//의도하에 함수를 obj1 객체 안에 선언된 것처럼 돌리고자 할때
let newFun = sayHello.bind(obj1)
newFun()//Hello, 홍길동

 

call(), apply()

bind()는 새로운 함수를 만드는 역할이지 함수를 호출하는 역할은 아니다.

• call(), apply() 를 이용하면 함수에 객체를 바인딩 시키고 그 함수를 호출까지 해주며

결국 call(), apply() 의 반환 값은 새로운 함수가 아니라 함수를 호출한 결과 값이다.

 

• call() 로 객체를 바인딩 시켜 함수를 호출할 수 있는데 이때 원한다면 매개변수 값을 전달 할 수 있다.

 

• apply() 함수도 call() 와 마찮가지로 함수에 객체를 바인딩 하면서 함수를 호출하는 역할을 한다.

apply() 함수가 call() 과 차이가 있는 것은 함수를 호출하면서 전달하는 매개변수 값을 지정하는 방법이다.

또한 apply() 는 전달하는 매개변수를 배열로 지정해야 한다.

 

"use strict"

let obj1 = {
  name: '홍길동'
}
//생성자 함수가 아닌이상 혹은 어떤 객체에 동작 바인딩 되어 실행될
//함수가 아닌 이상 함수내에 this 는 안쓰는게 좋다.
let sayHello = function(){
  console.log(`Hello, ${this.name}`)
}
// sayHello()   //error

//==>위 객체와 함수는 별개이다
//의도하에 함수를 obj1 객체 안에 선언된 것처럼 돌리고자 할때
let newFun = sayHello.bind(obj1)
newFun()//Hello, 홍길동

//apply, call
//함수를 만들자 마자 호출까지.. 
let sayHello1 = function(arg1, arg2){
  console.log(`Hello, ${this.name}, ${arg1}, ${arg2}`)
}
sayHello1.call(obj1, 10, 20)//Hello, 홍길동, 10, 20
sayHello1.apply(obj1, [10, 20])//Hello, 홍길동, 10, 20

 

※참고용 - 유사배열 객체
//사례
//어떤 함수가 있고, 객체가 있고. 
//함수가 객체의 멤버로 준비되지 않은 별개의 함수이지만 
//마치 자신의 멤버로 등록된 함수처럼 사용하고 싶을때 

//배열
let array = ['orange','yellow', 'green']
array.push('black')
array.push('white')
console.log(array.shift())//orange
console.log(array.pop())//white
console.log(array)//['yellow', 'green', 'black']

// 유사 배열 객체 생성 - 배열처럼 보임
let myArray = {
  0: 'orange',
  1: 'yellow',
  2: 'green',
  length:3,
  push: function(e){
    //마지막 index 에 e 값을 추가
    this[this.length] = e
    this.length++
  },
  pop: function(){
    //마지막 index 데이터 제거, 반환 
    let last = this[this.length - 1]
    this.length--
    delete this[this.length]
    return last
  },
  shift: function(){
    //맨 앞에꺼 제거, 반환
    let first = this[0]
    for(let i=0; i<this.length-1; i++){
      this[i] = this[i+1]
    }
    this.length-- 
    delete this[this.length]
    return first
  }
}
myArray.push('black')
myArray.push('white')
console.log(myArray.shift())//orange
console.log(myArray.pop())//white
console.log(myArray)//{0: 'yellow', 1: 'green', 2: 'black'


//유사 배열 객체를 만들면서 배열에 있는 필요한 함수를 직접 알고리즘으로 구현했다
//Array 에 push, pop, shift 함수가 있지 않나? Array 에 선언된 함수이지만.
//마치 나의 객체에 있는 함수처럼 연결해서 사용하면 된다.
let myArray2 = {
  0: 'orange',
  1: 'yellow',
  2: 'green',
  length:3,
  push: function(e){
    Array.prototype.push.call(this, e)
  },
  pop: function(){
    return Array.prototype.pop.call(this)
  },
  shift: function(){
    return Array.prototype.shift.call(this)
  }
}
myArray2.push('black')
myArray2.push('white')
console.log(myArray2.shift())//orange
console.log(myArray2.pop())//white
console.log(myArray2)//{0: 'yellow', 1: 'green', 2: 'black'​

 

getter/setter

• 소프트웨어 언어에서 흔히 사용하는 용어로 어떤 변수가 있고

그 변수의 값을 획득하기 위한 함수를 getter함수라고 부르고,

그 변수의 값을 변경하기 위한 함수를 setter 라고 부르며이 둘을 합쳐서 흔히 getter/setter 함수라고 한다 

소프트웨어 언어별로 getter/setter 함수를 선언하는 문법의 차이는 있으며

자바스크립트에서는 get, set 예약어를 통해 변수의 getter/setter 를 추가한다.

 

  getter/setter은 함수 스타일로 프로퍼티를 정의할 수도 있으며 흔히 getter/setter 함수라고 부른다.

함수 스타일의 프로퍼티란 객체내에 선언된 함수인데 외부에서는 객체의 변수처럼 사용되는 프로퍼티를 의미한다.

일반적으로 어떤 객체가 가지고 있는 값을 위한 프로퍼티인데 이 프로퍼티에 값이 대입될 때 로직이 실행되어야 하거나 아니면 이 값이 참조될 때 로직이 실행되어야 하는 경우에 이용한다.

로직이 실행되어야 함으로 함수로 작성이 되고 그 함수가 호출이 되어야 하는데 외부에서는 이 함수를 변수처럼 이용한다는 개념이다. 

 

• 자바스크립트에서 프로퍼티로 이용되는 getter/setter 함수를 만들기 위해서는

get, set 이라는 예약어로 함수가 선언되어 있어야 한다.

get 예약어로 선언된 함수를 getter 라고 부르며 프로퍼티 값을 참조할 때 호출 되고

set 예약어로 선언된 함수는 매개변수를 가지고 있어야 하며 이 프로퍼티 값을 변경할 때 호출된다.

 

• get 함수만 선언하면 함수를 프로퍼티로 활용할 수 있지만 get 만 있는 프로퍼티가 됨으로 값 참조만 되고변경은 불가능한 프로퍼티가 된다.

또한 set 만 선언된 함수를 만들 수도 있습니다. 이렇게 되면 값 대입만 되는 프로퍼티가 된다.

 

"use strict"

// let obj = {
//   //이 값이 변경되는 순간 운용 로그를 남겨야 한다는 유지보수 사항이 발생했다면??
//   setNum: (value) => {
//     console.log(`어디선가 값 변경을 시도합니다.`)
//     //~~~~
//   }
// }
// obj.setNum(20)
// console.log(obj.num)

let obj = {
  _num: 0,
  get num(){
    return this._num
  },
  set num(value){
    console.log('운용로그를 남깁니다.')
    this._num = value
  }
}
obj.num = 20
console.log(obj.num)//20

반응형