본문 바로가기

프로그래밍/JavaScript

Java Script (2)

Java Script


설정 가능한 이벤트

- 왼쪽 클릭에 대한 이벤트 : click

- 오른쪽 클릭에 대한 이벤트 : contextmenu

- 엔터에 대한 이벤트 : submit

- 커서가 목표 내부에 들어왔을 때 : focus

- 커서가 목표 바깥으로 나갔을 때 : blur

- 무엇인가 키를 눌렀을 때 : keypress

특정 문자는 입력 불가하게 하거나 숫자만 입력하게 하기 위해서 등의 목적으로 사용된다.


target과 currentTarget

- 간단하게 봤을 때는 둘 다 현재의 이벤트가 일어난 지점이 어디인지 알 수 있다.

- target은 이벤트의 가장 마지막에 위치한 최하위 요소를 반환한다. 즉, target은 클릭된 요소를 기준으로 사용할 때 주로 사용한다.

- currentTarget은 이벤트 리스너를 달아놓은 해당 요소를 반환한다. 

ex)

html

<div onclick="checkTarget();">

<span>click</span>

</div>

javascript

function checkTarget (event) {

var listener = e.currenttarget;

console.log(listener);

}

->e.currentTarget은 이벤트가 바인딩된 div요소를 반환하게 된다.

->e.target은 클릭된 span 태그를 반환하게 된다.


스코프 (scope)

- 생명 주기와 유사한 느낌이라고 생각하자

- 전역 스코프와 지역 스코프가 존재한다.

- 지역 스코프는 또한 function(함수) 스코프라고도 불린다.

- 전역 스코프를 선언한 후 임의의 함수 내에 같은 이름의 지역 스코프를 선언하지 않는다면 해당 이름으로 된 변수의 데이터값이 바뀌게 되면 전역 스코프의 값이 바뀌게 된다.


스코프 체인

- 스코프 간의 상하관계를 의미하는 용어다.

- 여러 개의 함수를 다중으로 겹쳐 생성한 상태가 있다고 하자. 가장 내부에서 특정 변수값을 호출하게 되었을 때 해당 값이 없다면 계속 위의 함수를  찾아 올라간다. 만일 전역 범위까지 올라가도 해당 변수가 존재하지 않는다면 에러를 반환, 그렇지 않다면 해당 변수값을 반환한다.

ex)

var name = "zero";

function outer () {

console.log("외부", name);

function inner () {

var enemy = "one";

console.log("내부", name);

}

inner();

}

outer();

console.log(enemy);

-> 외부와 내부 모두 zero가 출력되며 enemy는 함수 inner에서 생명이 끝나기 때문에 오류가 발생한다.


렉시컬 스코프

- 다른 말로는 스테틱 스코프(정적 스코프)라고도 부른다.

- 코드가 적힌 순간 스코프가 전역인지 지역인지 정해진다는 뜻을 가지고 있다.

- 전반적으로 자바스크립트는 다이나믹한 경향을 보이나 스코프는 정적인 경향을 보여준다.


클로저

- 함수와 함수가 선언된 어휘적 환경의 조합이다. 이는 곧 함수와 함수가 접근할 수 있는 스코프가 클로저 관계를 맺는다고 할 수 있다.

- 이 때의 어휘적 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.

- 반복문과 비동기 함수가 만나게 되면 클로저 문제가 발생한다.

이는 함수 안의 변수가 '실행'되는 시점에 값이 결정되기 때문에 발생하는 문제이다.


시간 측정

- 자바스크립트에서 시간을 측정하는 방식은 3가지가 있다.

1) Date클래스 이용. => x = new date();

2) console.time부터 console.timeEnd를 넣어 두 코드 사이의 시간 구하기

이때는 두 코드의 구분자가 정확하게 동일해야 한다. => console.time("hi"); ~ console.timeEnd("hi");

console클래스를 이용하기 때문에 주로 디버깅시 디버그 시간을 체크할 때 사용된다.

3) performance.now()메소드 이용

이 방법은 소수점을 더 자세하게 측정해주기 때문에 더 정확한 시간을 측정하고자 할 때 사용된다.

- 세 방식 모두 밀리초 단위이기 때문에 1000당 1초가 된다.


호출 스택

- 모든 명령들은 실행 스택에 들어간 후 호출된다. 이 스택은 LIFO형식으로 실행이 이루어진다.

ex)

function d() {}

function e() {}

function a() {

function b() {

function c() {}

c();

}

b();

}

d();

e();

a();

-> 위 코드는 d e c b a순으로 실행된다.

- 호출 스택은 재귀함수를 사용할 때 등의 상황에서 Maximum call stack exceed에러가 발생할 수 있다.ㅌ

- 이 에러를 방지하는 방법에는 여러 가지가 있으며 그 중 하나가 setTimeout을 이용하는 것이다.

function f() {

setTimeout(function () {

f();

}, 0);

}

f();

-> setTimeout을 이용하게 되면 내부 동작을 이벤트 루프에 저장한 후 호출 스택에서 pop된다. 이 결과 setTimeout이 실행 완료되어 더 이상 동작시킬 코드가 없어지게 된 함수 f또한 pop되며 호출 스택이 비워진다. 이 후 setTimeout이 이벤트 루프에 저장했던 익명 함수가 다시 push되어 내부 함수 f를 실행하고 이 함수가 setTimeout을 불러온 후 처음부터 동작이 반복되며 호출 스택이 넘치는 현상을 막을 수 있게 된다.


참조와 복사

- 참조와 복사는 각각 얕은 복사와 깊은 복사로 불린다.

참조 : 얕은 복사

복사 : 깊은 복사

- 문자, 숫자, 논리형 등의 원시값은 대입을 통해 값을 복사해올 수 있으며, 원본 변수의 값 또한 함께 바뀌지 않는다.

- 객체를 대입할 경우 서로 참조관계가 되며, 참조한 객체의 값을 변경했을 때 원본 객체의 값 또한 같이 바뀌어버린다.

- 원본 객체와 새로 생성할 객체가 서로 참조관계를 이루지 않게 하는 방법에는 여러 가지가 있다.

1) 빈 객체를 선언한 후 원본 객체의 값을 하나씩 대입하는 방법

이 방식의 경우 객체를 참조하는 것이 아니라 객체 내부의 원시값들을 일일히 대입하는 방식이기 때문에 원본 객체는 데이터가 바뀌지 않게 된다.

ex)  var obj1 = {a: 1, b: 2};

var obj2 = {};

obj2.a = obj1.a;

obj2.b = obj1.b;

2) Object.keys 메소드를 통해 객체의 키들을 받아온 후 forEach로 각 키의 값을 받아오는 방법

forEach를 이용해 대입을 하기 때문에 대규모 데이터도 수월하게 대입할 수 있어 유용한 방식이다.

원본 객체의 키들 중 하나 이상에 또다시 객체가 값으로 들어있을 경우 이 방식을 사용해도 여전히 그 키에 대한 값은 참초관계가 유지되어 완벽하게 효율적이지는 않게 된다.

ex)  var obj1 = {a: 1, b: 2, c: };

var obj2 = {};

Object.keys(obj1).forEach(function (key) {

obj2[key] = obj1[key];

});

3) 2번에서 완전한 깊은 복사를 완성하기 위해서는 재귀를 통해 다중 객체를 다시 깊은 복사해줄 수 있게 만드는 방법을 사용해야 한다.

ex)  function deepCopy(obj) {

var comp = {};

if (typeof obj === "object" && obj !== null) {

for (var i in obj) {

if (obj.hasOwnProperty(i)) {

comp[i] = copyObj(obj[i]);

}


}

} else {

comp = obj;

}

return comp;

}

-> 이 방법 또한 에외사항이 많이 발생하게 된다.

4) JSON.parse를 이용하는 방법

JSON의 parse와 stringify 메소드를 통한다면 깊은 복사가 편하게 이루어지게 되어 가장 유용한 방법이라 볼 수 있다.

를 통한다면 깊은 복사가 편하게 이루어지게 되어 가장 유용한 방법이라 볼 수 있다.

이 방식도 완벽한 단계의 깊은 복사를 이뤄내지는 못하지만 참조관계를 없애는 방식 중 가장 간단한 방법이라 할 수 있다.

하지만 성능이 좋지 못하며 프로토타입을 복사할 수 없다는 문제가 있다.

ex)  obj2 = JSON.parse(JSON.stringify(obj1));

4) 배열의 경우 slice메소드를 이용하는 방법

2번 방법과 같이 배열 내의 배열에 대해서는 깊은 복사가 이루어질 수 없다는 단점이 있다.

ex)  obj2 = obj1.slice();

5) Object.assign() 메소드를 사용하는 방법

1단계 깊은 복사가 가능한 2번 방식의 길이를 Object.assign(obj2, obj1);을 통해 한 줄로 간단히 줄이면서도 기능은 거의 같도록 해줄 수 있다. 또한 4번의 기능을 확장하여 배열과 객체 둘 다 깊은 복사를 가능하게 한다.

- 원본 객체 === 복사한 객체로 비교했을 시 true가 반환된다면 참조되었음을 쉽게 알 수 있게 된다.


팩토리 패턴과 프로토타입

- 팩토리 패턴은 자바에서의 생성자와 같은 기능을 수행한다고 할 수 있다. 서로 다른 객체들이 공유할 수 있도록 중복된 정의들을 모아 쉽게 여러번 생성할 수 있도록 도와주는 기능을 한다.

- 팩토리 패턴을 다른 객체에 적용시키기 위해서는 객체.__proto__ = 팩토리 패턴; 과 같이 입력해야 한다.

- __proto__의 장점은 이를 생략하고 내부 값을 불러올 수 있다는 것이다. 만일 객체 내부에 불러오고자 하는 요소가 없다면 컴파일러가 알아서 proto로 들어가 요소를 찾는 과정을 반복하며 탐색을 진행한다.

- 프로토타입을 쓰는 이유로는 여러 객체들의 데이터 변경을 한 번에 관리할 수 있도록 해줘 유지 보수가 수월하도록 만들어준다는 점을 들수 있다.

- 하지만 실무에서는 __proto__를 사용해서는 안된다. 여러 이유가 있지만 가장 큰 이유로는 널리 쓰이는 표준이 아니라는 점이 있다.

- Object.create()메소드를 통해 __proto__대신 프로토타입을 정의할 수 있다.


call by value, call by reference, call by sharing

- 값에 의한 호출

자바스크립트의 동작은 기본적으로 값에 의한 참조다. 원본 값과 대입한 값은 개별의 변수로 취급하여 대입한 값이 변화해도 원본의 값은 변화하지 않는다.

- 참조에 의한 호출

일반적으로 객체를 대입하게 되면 참조한다고 할 수 있다. 이때는 대입한 변수의 값이 바뀌면 원본 값도 변화한다.

기본적으로 자바스크립트에 존재하지 않는 방식이다. 포인터같은 방식을 사용하지 않기 때문이다.

- 공유에 의한 호출

객체의 속성을 수정하는 것은 참조와 같은 방식으로 데이터가 변화하지만 객체 자체를 수정할 때에는 참조 관계가 무너지게 된다. 이렇게 제한적으로 참조 관계가 유지되는 것을 call by sharing이라 부르곤 하지만 정식으로 정의된 명칭은 아니기 때문에 자바스크립트는 전부 값에 의한 참조로 동작한다고 생각하면 된다.

- 종합해본다면 자바스크립트의 전체적인 동작은 값에 의한 호출로 이루어져있다고 할 수 있으나, 객체의 속성과 같은 제한적인 부분에는 참조 관계가 있다고 할 수 있다.

참조에 의한 호출이 성립하지 않는 이유

function f (value) {

value.a = 10;

value = 8;

console.log(value);

}

var value = {a: 5};

f (value);

console.log(value);

-> 8과 {a: 10}이 출력된다.

-> 객체의 속성을 수정할 때는 참조 관계가 성립하지만 변수 자체를 바꿀때는 참조관계가 끊긴다.

-> 일반적인 참조에 의한 호출이였다면 둘 다 8이 출력되는 것이 정상이지만 자바스크립트는 그렇지 않기 때문에 참조에 의한 호출이 성립하지 않는다는 것을 증명할 수 있는 예시가 된다.


생성자와 new

- 생성자 함수는 항상 코드의 형식이 일정하게 유지되기 때문에 패턴이라고도 볼 수 있다.

- 생성자 함수는 첫 글자를 대문자로 하는 경향이 있다. 이는 필수는 아니지만 프로그래머들 간의 약속이다.

- 생성자 함수를 만든 후 이를 이용해 객체를 만들기 위해서는 new를 사용해야 한다. 이는 자바와 입력 형식이 같다.

- 객체 지향 프로그램을 만들고자 할 때 쓰이게 되는 코딩 방식이다.

- 생성자를 사용하려 할 때 new를 붙여주지 않는다면 함수로 인식해 제대로 객체가 생성되지 않는다. 또한 this는 window 클래스에 속해있어 window를 통해 함수의 속성값을 불러올 수 있게 된다.

- 크롬 등의 콘솔에서 생성자를 사용할 때 window클래스에 객체의 속성값이 들어가지 못하도록 막기 위해서는 "use strict"를 생성자 맨 위에 작성하여 엄격모드를 적용시키면 된다. 이 때 "use strict"는 맨 위에 작성해야 모드가 정상적으로 작동된다.

- 생성자는 객체형 프로그램을 만들 때 자주 사용하며 팩토리 패턴과 프로토타입은 생성자를 쓰지 않는 함수형 프로그램을 만들 때 자주 사용한다.

'프로그래밍 > JavaScript' 카테고리의 다른 글

this  (0) 2019.08.21
클로저와 스코프 체인  (0) 2019.08.21
화살표 함수  (0) 2019.08.20
JavaScript (3)  (0) 2019.08.01
JavaScript (1)  (0) 2019.07.05