hasOwnProperty

for-in 구문을 쓸 때 상속된 프로퍼티나 this 구문을 방지하기 위하여 보통 hasOwnProperty 로 체크한다.

1
2
3
4
5
for (prop in obj) {
if (obj.hasOwnProperty(prop)) {
// code...
}
}

문제점

위와 같은 방식은 아래와 같이 hasOwnProperty 가 재정의 된 객체에서 오류를 발생할 수 있다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
var obj = {
hasOwnProperty: function () {
return false;
},
data1: 1,
};

for (prop in obj) {
// obj.hasOwnproperty(prop) 의 결과는 항상 false
if (obj.hasOwnproperty(prop)) {
// 이 구문은 실행되지 않는다.
}
}

prototype.hasOwnProperty

따라서 prototype 을 사용해 코딩해야한다.

소스

1
2
3
4
5
6
7
8
9
for (prop in obj) {
// 첫번째 방법
if ({}.hasOwnProperty.call(obj, prop)) {
}

// 두번째 방법
if (Object.prototype.hasOwnProperty.call(obj, prop)) {
}
}

설명

첫번째 방법은 익명 Object 를 생성한 후 obj 와 prop 을 밀어넣는 방식이고
두번째 방법은 기본 Object 의 hasOwnProperty 를 가져와 obj 와 prop 을 밀어넣는 방식이다.

두번째 방법이 새(익명) Object 를 생성하지 않기에 이 방법을 사용하면 된다.

더 자세하게 알고 싶다면 mdn을 참조한다.

javascript throttle 패턴과 debounce 패턴에 대해 알아보자.

Throttle

매 ms 마다 한 번만 호출된다.
mousemove, scroll 같은 이벤트로 호출되는 함수는 이벤트 발생시 무한정 호출되어 성능 저하를 가지고 오는데, 이를 방지할 수 있다.

모바일에서 스크롤링 더보기에 사용할 수 있다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var throttleFunction = (function () {
'use strict';

var timeWindow = 500; // 여기에 시간(ms)을 지정한다
var lastExecution = new Date(new Date().getTime() - timeWindow);
// ES6 이하일 경우 ...args에 호출할 parameter 만큼 준다 function(arg1, arg2...)
var throttleFunction = function (...args) {
// 여기에 로직을 구현한다
};

return function () {
if (lastExecution.getTime() + timeWindow <= new Date().getTime()) {
lastExecution = new Date();
return throttleFunction.apply(this, arguments);
}
};
})();

// 사용법
throttleFunction(param1, param2);

Debounce

마지막 호출로부터 ms 후에 함수를 한번 호출한다.
지연된 호출을 할 수 있게 해주는데, 호출이 반복되는 동안은 실행을 방지하고, 호출이 멈춘 뒤 지정한 ms 후에 함수를 실행해 성능 저하를 막을 수 있다.

검색 자동완성 기능에 적합하다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var debounceFunction = (function () {
'use strict';

var timeWindow = 500; // 여기에 시간(ms)을 지정한다
var timeout;
// ES6 이하일 경우 ...args에 호출할 parameter 만큼 준다 function(arg1, arg2...)
var debounceFunction = function (...args) {
// 여기에 로직을 구현한다
};

return function () {
var context = this;
var args = arguments;

clearTimeout(timeout);
timeout = setTimeout(function () {
debounceFunction.apply(context, args);
}, timeWindow);
};
})();

// 사용법
debounceFunction(param1, param2);

여담

즐겨 사용하는 sublime text package 인 javascript patterns에서 참조했다.

lodash를 사용한다면 debounce, throttle로 더 쉽게 사용할 수 있다.

UMD (Universal Module Definition) Pattern 이란
AMD (requireJS), CommonJS (node), 일반 browser 환경에서 통합해서 쓸 수 있는 javascript module pattern 이다.

jQuery, moment, D3 등의 라이브러리에서도 해당 패턴을 사용한다.

예제 - jQeury 종속형

새로 만들 예제의 모듈명은 module1 이고 jQuery 에 종속적이라고 가정한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
// AMD 환경
define(['exports', 'jquery'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
// CommonJS 환경
factory(exports, require('jquery'));
} else {
// 일반 브라우저
factory((global.module1 = global.module1 || {}), global.$);
}
})(this, function (exports, $) {
// module1의 private 기능 구현

// exports에 public function을 붙혀주면 된다.
exports.action = function () {};
});

종속되는 모듈을 추가하려면, AMD 는 배열안에, commonJS 와 일반은 파라미터로 추가한 뒤 12 번째 줄의 콜백함수 안에 원하는 변수로 받으면 된다.

예제 - 기본형

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function (global, factory) {
if (typeof define === 'function' && define.amd) {
define(['exports'], factory);
} else if (
typeof exports === 'object' &&
typeof exports.nodeName !== 'string'
) {
factory(exports);
} else {
factory((global.module1 = global.module1 || {}));
}
})(this, function (exports) {
exports.action = function () {};
});

즉시실행 함수에 window 객체와 함수를 파라미터로 보내서 전역 객체를 생성하는 방식이다.

예제 - 3 항연산자 사용

1
2
3
4
5
6
7
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.module1 = global.module1 || {})));
}(this, (function (exports) {
exports.action = function () {};
}));

설명

window 객체 안에 모듈이 선언되므로, module1.action(); 으로 바로 호출해 사용하면 된다.

더 자세한 설명을 보고싶다면 umd를 참조하면 된다.

jsonp (json with padding)은 다른 서버의 데이터를 내 브라우저에서 호출하기 위해 사용한다.
client 에서 callback 함수명과 전송할 값을 같이 보내어 server 에서 callback 함수 안에 data 를 담아 반환하는 방식이다.

전송할 값을 보낼 때, script 태그를 사용한다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
function jsonp(url, callback) {
var callbackName = 'jsonp_' + Math.round(100000 * Math.random());
window[callbackName] = function (data) {
delete window[callbackName];
document.body.removeChild(script);
callback(data);
};

var script = document.createElement('script');
script.src =
url + (url.indexOf('?') >= 0 ? '&' : '?') + 'callback=' + callbackName;
document.body.appendChild(script);
}

stackoverflow 에서 가져온 소스인데 현재 출처를 찾을 수 없다.

설명

임의의 함수명으로 callback 함수를 생성한 뒤 script 태그를 생성해 server 와 통신한다.

예제

1
2
3
jsonp('http://aaa.com?data=' + data, function (result) {
console.log(result);
});

여담

jQuery 가 있다면 jQuery ajax의 dataType 을 jsonp 로 설정해주면 되고,
요새는 CORS 를 적용해 그냥 호출하는 방식으로 가는 추세다.

es5 부터 객체의 수정을 제어할 수 있다.
ie9 부터 지원하고, ie8 은 es5-sham을 추가하면 된다.

freeze method

객체를 불변으로 만든다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var frozenObject = {
value1: 1,
value2: 2,
};

Object.freeze(frozenObject);
console.log(Object.isFrozen(frozenObject)); // true

// 삭제 불가
delete frozenObject.value1;
console.log(frozenObject.value1); // 1

// 변경 불가
frozenObject.value2 = 3;
console.log(frozenObject.value2); // 2

// 객체 추가 불가
frozenObject.value3 = 3;
console.log(frozenObject.value3); // undefined

seal method

확장, 축소는 불가능하지만 writable 쓰기 가능하다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var sealedObject = {
value1: 1,
value2: 2,
};

Object.seal(sealedObject);
console.log(Object.isSealed(sealedObject)); // true

// 삭제 불가
delete sealedObject.value1;
console.log(sealedObject.value1); // 1

// 변경 가능
sealedObject.value2 = 3;
console.log(sealedObject.value2); // 3

// 객체 추가 불가
sealedObject.value3 = 3;
console.log(sealedObject.value3); // undefined

preventExtensions method

확장만 불가능하다.

예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var preventExtensionObject = {
value1: 1,
value2: 2,
};

Object.preventExtensions(preventExtensionObject);
console.log(Object.isExtensible(preventExtensionObject)); // false

// 삭제 가능
delete preventExtensionObject.value1;
console.log(preventExtensionObject.value1); // undefined

// 변경 가능
preventExtensionObject.value2 = 3;
console.log(preventExtensionObject.value2); // 3

// 객체 추가 불가
preventExtensionObject.value3 = 3;
console.log(preventExtensionObject.value3); // undefined

설명

불변하게 하려면 얼리고,
수정이 가능하게 하려면 봉인하고
추가만 불가능하게 하려면 확장방지하자

client 에서 객체 변조를 못하게 전역변수를 얼려버리자

여담

freeze 는 중첩된 object 에 적용이 안 되는데,
이 때 deepFreeze 함수를 사용해 재귀적으로 객체를 얼려버리면 된다.

deepFreeze method

mdn을 참조했다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function deepFreeze(obj) {
if (!Object.isFrozen(obj)) {
Object.freeze(obj);
}

for (var key in obj) {
if (
{}.hasOwnProperty.call(obj, key) ||
!(
typeof obj[key] === 'function' ||
(typeof obj[key] === 'object' && !!obj[key])
)
) {
continue;
}

deepFreeze(obj[key]);
}
}

Javascript 기본값 연산자 (축약된 삼항연산자)

한번 쓰면 헤어나올 수 없는 기본값 연산자에 대해 알아보자.

소스

1
2
3
// JavaScript syntax to set a default value
var text = someString || 'default text';
var text2 = someString || someString2 || 'default text2';

설명

someString 이 있으면 someString 을 반환, 아니면 default text 를 반환한다.
계속 붙혀나가면서 쓸 수 있다.

php7 에서는 해당 기능이 추가되었다. || 대신에 ?? 를 사용하면 된다.

lodash 에선 defaultTo를 사용하면 된다.

링크를 클릭했는데, 페이지에 return false;가 찍혀나오는 경우가 있다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
<script>
function test() {
if (true) {
return false;
}
}
</script>

<!-- 페이지에 return false;가 출력됨 -->
<a href="javascript:test();">테스트</a>

<!-- 대체 -->
<a href="javascript://" onClick="test();">테스트</a>

설명

a href 속성 안에 javascript 를 사용시 발생한다.
javascript 호출문인 onClick 을 이용하자.

전체 레코드 수와 현재 페이지 번호, 페이지에 표시되는 레코드의 수를 안다면 쉽게 구할 수 있다.

소스

1
2
3
4
5
6
7
var totalCount;
var currentPageNum;
var shownRowNum;

for (var i = 0, len = data.length; i < len; i++) {
var index = Number(totalCount) - (i + (currentPageNum - 1) * shownRowNum);
}

여담

매번 공식을 알아내는게 귀찮아서 포스팅

aaa.com 에서 bbb.com 의 세션을 jsonp 방식을 활용해서 생성하고 싶을 때,
유독 IE 에서만 세션 생성이 안되는 경우가 있다.

이는 P3P (Platform for Personal Preferences) 규약이 적용되어 세션을 가져오지 않는 것인데, 아래와 같은 방법으로 해결할 수 있다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<!-- 세션을 만들어줄 서버에서 P3P 헤더를 설정 -->
<?php
header('P3P: CP="CAO PSA OUR"');
?>

<!-- 세션을 가지고 오는 클라이언트 요청-->
<script>
$.ajax({
url:'cross domain session create url',
type:'post',
data:{
token1 : 'token1',
token2 : 'token2'
},
dataType:'jsonp'
}).then(function (result) {

});
</script>

가장 빠른 배열 중복 제거 알고리즘

통계 사용시에 중복을 제거해야하는 경우가 있다.

소스

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
/*
@author http://www.shamasis.net/2009/09/fast-algorithm-to-find-unique-items-in-javascript-array/
*/

// prototype
Array.prototype.unique = function () {
var o = {},
i,
r = [];
var l = this.length;

for (i = 0; i < l; i++) {
o[this[i]] = this[i];
}

for (i in o) {
r.push(o[i]);
}

return r;
};

// function
var uniqueArray = function (arr) {
var o = {},
i,
r = [];
var l = arr.length;

for (i = 0; i < l; i++) {
o[arr[i]] = arr[i];
}

for (i in o) {
r.push(o[i]);
}

return r;
};

예제

1
2
3
4
5
6
var testArray = [1, 2, 1, 3];

testArray = testArray.unique();
testArray = uniqueArray(testArray);

console.log(testArray); // [ 1, 2, 3 ];

여담

언더스코어를 알게된 후로는 lodashuniquniqBy를 사용한다.