Upload
young-beom-rhee
View
10.016
Download
4
Embed Size (px)
Citation preview
프론트엔드 스터디CH.05. Closure, OOP
1. Closure 와 가까워지기
2. 객체지향과 프로토타입
Closure
함수를 선언할때 만들어지는 유효범위
함수는 클로저를 통해서 자신이 선언될 때 속해 있던 유효 범위 내의 변수와 함수를 사용할 수 있고 , 변수의 경우 그 값을 변경할 수도 있다 .
흥미로운건 함수를 선언한 후에 언제든지 , 심지어 함수가 속해 있던 유효 범위가 사라진 후에도 그 함수를 호출할 수 있는 경우가 생기게 된다
Closure – 간단한 클로저
var outerValue = 'outerValue';
function outerFunction() { console.log(outerValue == 'outerValue');}
outerFunction();
외부에 있는 변수와 함수가 모두 전역 유효 범위에 선언되어있고 , 실제로는 클로저에 해당하는 전역 유효 범위는 페이지가
로드되어 있는 한 사라지지 않기 때문에 그다지 흥미로운 코드는 아니다 .
Closure – 간단하지 않은 클로저
var outerValue = 'outerValue';
var later;
function outerFunction() {
var innerValue = 'innerValue'
function innerFunction() { console.log(outerValue); // innerValue 는 있을까 ? console.log(innerValue); }
later = innerFunction;}
outerFunction();
later();
클로저는 함수가 선언된 시점의 유효 범위에 있는 모든 함수와 변수를 가지고
있으며 , 필요할 때 그것들을 사용할 수있다 .
Closure – 심화
var outerValue = 'outerValue';
var later;
function outerFunction() { // 함수 내에 변수를 하나 선언한다 . 이 변수의 유효 범위는 함수 내부로 제한이 되고 , // 함수 외부에서는 접근할 수 없다 . var innerValue = 'innerValue'
// inner 함수에 매개변수를 추가한다 . function innerFunction(paramValue) { console.log(outerValue); console.log(innerValue); console.log(paramValue); // 매개변수를 볼 수 있는지 테스트 console.log(tooLate); // 클로저가 함수가 선언된 이후에 정의된 변수를 볼 수 있는지 테스트 }
later = innerFunction;}
console.log(tooLate); // outer closure 에서는 tooLate 를 쓸 수 없다 .
// innerFunction 을 정의한 후에 변수를 선언var tooLate = 'tooLate';outerFunction();
later('paramValue');
Closure – Closure 를 사용하지 않고 값을 가지고 있을 때
var uniqueId = function(){ if(!arguments.callee.id){ arguments.callee.id = 0; } return arguments.callee.id++;}
uniqueId(); // 0uniqueId(); // 1uniqueId(); // 2
// id 를 0 으로 초기화 할 수 있을까 ?
var uniqueId = function(){ if(!arguments.callee.id){ arguments.callee.id = 0; } return arguments.callee.id++;}
uniqueId(); // 0uniqueId(); // 1uniqueId(); // 2
// id 를 0 으로 초기화 할 수 있을까 ?uniqueId.id = 0;
uniqueId(); // 0
Closure – Closure 를 사용하지 않고 값을 가지고 있을 때
Closure – Closure 를 사용해서 값을 가지고 있을 때
var uniqueId = (function(){ var id = 0; return function(){ return id++; }})();
uniqueId(); // 0uniqueId(); // 1uniqueId(); // 2uniqueId(); // 3
// id 를 0 으로 초기화 할 수 있을까 ?
var uniqueId = (function(){ var id = 0; return function(){ return id++; }})();
uniqueId(); // 0uniqueId(); // 1uniqueId(); // 2uniqueId(); // 3
// 0 으로 초기화uniqueId.id = 0 // ?uniqueId(); // 4
Closure – Closure 를 사용했을 때
Closure 의id 에는 접근할
수 없다x
=> Closure 를 통해 모듈화를 할 수 있다 .
Closure – 응용 : Private 변수
function DtoObj(initVal) { var val = initVal;
this.getVal = function () { // this 는 새로 만들어진 객체를 가리킴 return val; };
this.setVal = function(pVal) { val = pVal; }}
var dto = new DtoObj(3);
// dto 객체의 내부 변수는 getter 를 통해서만 접근하여 값을 받을 수 있다 .console.log(dto.getVal()); // 3// dto 객체의 내부 변수는 setter 를 통해서만 접근하여 값을 바꿀 수 있다 .dto.setVal(4);console.log(dto.getVal()); // 4dto.val = 5;console.log(dto.getVal()); // 4
Closure – 응용 : success callback
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Closure 예제 </title></head><body> <button type="button" id="testButton">Go!</button> <div id="testSubject"></div>
<script src="https://code.jquery.com/jquery-2.2.3.js"></script> <script> $('#testButton').click(function () { var elem$ = $('#testSubject');
elem$.html(' 로딩 중 ...'); $.ajax({ url: '/test' , success: function (data) { console.dir(this.success); elem$.html(JSON.stringify(data)); } }); }); </script></body></html>
Closure 핵심 정리
1. 내부 함수는 외부 스코프에 선언된 변수를 참조할 수 있다 .
2. 클로저는 자신을 생성한 함수보다 더 오래 지속된다 .
3. 클로저는 내부적으로 외부 변수에 대한 참조를 저장하고 , 저장된 변수를 읽고 갱신할 수 있다 .
Javascript 에는 자바 , C++, C# 등의 class 가없다 .
Prototype 기반 언어들에 영감을 받음 . (https://ko.wikipedia.org/wiki/ _ _프로토타입 기반 프로그래밍)
JavaScript 는 prototype 객체 , 생성자 등을 통해 구현 가능
클래스 기반 vs 프로토타입기반
function Func(x, y) { this.x = x; this.y = y;}
var func = Func(1, 2);
this === ?
생성자 함수와 this
function Func(x, y) { this.x = x; this.y = y;}
var func = new Func(1, 2);
this === ?
this === func=> 인스턴스를 바라본다
When Number is called as a function rather than as a constructor, it performs a type conversion.
var num = 1;var num2 = Number(1);var num3 = new Number(1);
console.log(num === num2);console.log(num === num3);
console.dir(num);console.dir(num3);
new 키워드필수Number, String 등등에는 안써도 되던데 ?
Type 변환을 위한 메서드를 추가로 가지고 있을뿐 .new 로 생성하지 않으면 instance 가 없다 .
var func = Func(1, 2); // func === undefined
Spec.
function GoodWaffle() { if(!(this instanceof GoodWaffle)) { return new GoodWaffle(); } this.tastes = 'yummy';}
var waffle = new GoodWaffle();var waffle2 = new GoodWaffle();
console.log(waffle instanceof GoodWaffle);console.log(waffle2 instanceof GoodWaffle);
New 를 강제하는 방법
GoodWaffle.call(new GoodWaffle);
var GoodWaffle = (function(){ var RealWaffle = function(){}; RealWaffle.prototype.xxx = xxx; return function(){ return new RealWaffle(); }})();
var waffle = new GoodWaffle();var waffle2 = new GoodWaffle();
New 를 강제하는 방법 - 더블쉐도우
function Person(name) { this.name = name;
this.getName = function () { return this.name; }
this.setName = function (name) { this.name = name; }}
var me = new Person("yb"); console.log(me.getName());
me.setName("new yb");console.log(me.getName());
공통속성을 추가하는 경우
// Quiz : getName, setName 메서드는 Person 에 추가 되었을까 ? 아니면 me 에만
추가 되었을까 ?
중복되는 영역을 메모리에 올려놓고 사용-> 메모리 낭비
var personA = new Person("personA");var personB = new Person("personB");var personC = new Person("personC");
불필요한 중복
personA.getName = function () { return 'Hello, ' + this.name;}
console.log(personA.getName());console.log(personB.getName());console.log(personC.getName());
변경이 발생했을 때
Hello, personApersonBpersonC
변경된 사항이 적용되지 않는다 .
function Person(name) { this.name = name;}
Person.prototype.getName = function () { return this.name;}
Person.prototype.setName = function (name) { this.name = name;}
var personA = new Person("personA");var personB = new Person("personB");var personC = new Person("personC");
console.log(personA.getName());console.log(personB.getName());console.log(personC.getName());
공통속성을 추가하는 경우– Prototype 을 이용
Instance 마다 개별적으로 가져야 할 프로퍼티
공통으로 가져야 할프로퍼티
cf. Prototype 을 사용하지 않은 경우
Prototype 참조
변경이 발생했을 때
Person.prototype.getName = function () { return 'Hello, ' + this.name;}
console.log(personA.getName());console.log(personB.getName());console.log(personC.getName());
Hello, personAHello, personBHello, personC
Prototype 을 참조하고 있는 다른 모든 Instance 에도 모두 적용
Prototype 의 몇 가지 속성
name : name / hasOwnProperty : truename : getName / hasOwnProperty : falsename : setName / hasOwnProperty : false
for (var obj in personA) { console.log('name : ', obj, ' / hasOwnProperty : ' + personA.hasOwnProperty(obj));}
- 마치 자신의 속성처럼 열거할 수 있다 .(for in 사용 )- hasOwnProperty() 메서드를 사용해 소유여부 ( 자신이
소유하고 있는지 , prototype 으로 참조하는지 ) 를 확인할 수 있다 .
function Circle(r) { this.r = r;}
Circle.prototype.PI = 3.14;
Circle.prototype.getArea = function () { return 2 * this.r * this.PI;}
var r2 = new Circle(2);console.log(r2.getArea());
var r3 = new Circle(3);console.log(r3.getArea());
var r3Alien = new Circle(3);r3Alien.PI = 4.74; // PI 재정의console.log(r3Alien.getArea());
내부의 property 가 있는 경우에 prototype 을 참고하지 않는다 . 이런 경우를 다른 property 가 가렸다 (shadows) 혹은 숨겼다
(hides) 라고 한다
- Prototype property 가 참조되지 않을 수 있다 .
Function.prototype.addMethod = function (name, func) { if(!this.prototype[name]) { this.prototype[name] = func; }}
function Person(name) { this.name = name;}
Person.addMethod('setName', function(name) { this.name = name;});
Person.addMethod('getName', function () { return this.name;});
var personA = new Person("personA");var personB = new Person("personB");var personC = new Person("personC");
크락포드옹 스탈
Overriding 을 방지할 수 있다
Function 의 용도가 2 가지: 실행을 위한 함수 , 생성자 함수
혼란을 피하기 위해 생성자 함수는 대문자로 시작하도록 권고
크락포드옹님 주의사항
function createObject(parentObj) { function Func() {} Func.prototype = parentObj; return new Func();}
Object.create() 와동일
var personPrototype = { getName : function() { return this.name; } , setName : function (name) { this.name = name; }};
var student = createObject(personPrototype);
console.log(student.getName());student.setName('student');console.log(student.getName());
이런 방식으로 prototype 을 구현한 것과 같이 생성 가능
Class 기반의 생성자처럼 생성하는 시점에 초깃값을 주고 싶다면 ?
상속을 구현
// 초기값을 넣고 싶은 경우var personPrototypeFunc = function(name){ return { name : name , getName : function() { return this.name; } , setName : function (name) { this.name = name; } }};
var student2 = createObject(personPrototypeFunc('student2'));
console.log(student2.getName());student2.setName('student2_changed');console.log(student2.getName());
- 초기값을 주고 싶은 경우
재정의 , 기능확장function extend(obj, prop) { if(!prop) { prop = obj; obj = this; }
for (var i in prop) { obj[i] = prop[i]; }
return obj;}
var added = { setAge : function (age) { this.age = age; } , getAge : function () { return this.age; }};
extend(student, added);
student.setAge(25);console.log(student.getAge());
extend 메서드를 구현하거나jQuery 의 extend 메서드를 사용
슈퍼 클래스와 서브 클래스
function Rectangle(w, h) { this.width = w; this.height = h;}
Rectangle.prototype.area = function () { return ' 넓이 : ' + this.width * this.height;}
// 아래 코드는 Rectangle 클래스를 어떻게 서브 클래스화 하는지 보여준다function PositionedRectangle(x, y, w, h) { // 생성자 체이닝 Rectangle.call(this, w, h);
this.x = x; // 사각형의 좌표를 저장한다 . this.y = y;}
// Rectangle 를 서브 클래스화 시키려면 명시적으로 프로토타입 객체를 생성해야 한다 .PositionedRectangle.prototype = new Rectangle();
// PositionedRectangle 객체의 constructor 를 가지도록 기본값을 다시 할당한다 .PositionedRectangle.prototype.constructor = PositionedRectangle;
PositionedRectangle.prototype.getPosition = function () { return 'x : ' + this.x + ' / y : ' + this.y;}
// 3, 4 에 위치한 2x2 사각형var rect = new PositionedRectangle(3, 4, 2, 2);console.log(rect.getPosition()); // x : 3 / y : 4console.log(rect.area()); // 넓이 : 4
// rect 객체는 세 개 클래스의 인스턴스가 된다 .console.log(rect instanceof PositionedRectangle && rect instanceof Rectangle && rect instanceof Object); // true
클래스 방식의 상속패턴 완성// 라이브러리로var inherit = (function () { var F = function () {}; return function (C, P) { F.prototype = P.prototype; C.prototype = new F(); C.uber = P.prototype; C.prototype.constructor = C; }});
inherit(PositionedRectangle, Rectangle);var positioned_rect = new PositionedRectangle(1, 2, 3, 4);console.log(positioned_rect);
ES6 Class 사용class Rectangle { constructor(w, h) { this.width = w; this.height = h; } area() { return ' 넓이 : ' + this.width * this.height; }}
class PositionedRectangle extends Rectangle { constructor(x, y, w, h) { super(w, h); this.x = x; // 사각형의 좌표를 저장한다 . this.y = y; } getPosition() { return 'x : ' + this.x + ' / y : ' + this.y; }}
console.log( positioned_rect.getPosition());console.log( positioned_rect.area());
console.log(positioned_rect instanceof PositionedRectangle && positioned_rect instanceof Rectangle && positioned_rect instanceof Object);
생성자 체이닝
function PositionedRectangle(x, y, w, h) { this.superclass(w, h); this.x = x; this.y = y;}
// 상위 클래스 생성자에 대한 참조를 저장한다 .PositionedRectangle.prototype.superclass = Rectangle;
처음의 예제처럼 call 이나 apply 를 사용하지 않아도된다 .
생성자 함수에서 상위 클래스의 생성자 함수를 명시적으로 호출할때 아래와 같은 방법도 가능
메서드 재정의
서브 클래스에서 재정의하는 메서드는 기존 메서드에 있던 기능을 완전히 교체하기보다 확장시키는 것이 좋다 .
// 메서드 재정의Rectangle.prototype.toString = function () { return '[' + this.width + ',' + this.height + ']';}
PositionedRectangle.prototype.toString = function () { return '(' + this.x + ',' + this.y + ')' + // PositionedRectangle 필드들 Rectangle.prototype.toString.apply(this); // 상위 클래스에 체이닝 . 어떤 객체를
참조할지 지정하기 위해 apply() 와 함께 호출 .};
console.log(rect.toString());
참고자료
- http://www.ecma-international.org/ecma-262/5.1/
- 자바스크립트 완벽 가이드 데이비드 플래너건
- 자바스크립트 핵심 가이드 더글라스 크락포드
- 인사이드 자바스크립트 송형주 고현준
- JavaScript Patterns 스토얀 스토파노프