자바스크립트는 브라우저 이외에도 아주 다양한 종류의 런타임(혹은 엔진) 위에서 돌아갈 수 있다. 그리고 각각은 스스로의 환경에 최적화 되어있기 때문에, 모두 제 각각이다. 자유분방한 언어의 특성으로 인해서 빌트인된 객체들의 편집이나 주입도 가능하기때문에, 이렇게 예측이 어려운 환경 위에서 돌아가는 코드를 작성해야 할때는 사이드 이펙트로부터 벗어나기 위해 고려해봄직한 요소들이 있다.
JavaScript 로 작성된 SDK 는 다양한 환경에서 실행될 수 있기 때문에, 자세한 정보를 수집하거나 올바른 코드만을 실행하도록 예외 처리를 해주기 위해서 환경을 알아내는 작업들이 종종 필요하다. 만약 document 가 없는 환경에서 document 의 함수를 호출하게 되면 에러가 발생한다.
하나의 예시로 글로벌 스코프를 생각해볼 수 있는데, 브라우저에서만 사용되는 document
가 그 중 하나이다.
브라우저 환경인지 파악하기 위해서는 typeof document !== 'undefined'
같은 방식으로, 특정 환경에만 존재하는 객체가 존재하는지 체크를 할 수 있다.
하지만 우리가 염두해야 할 것은, 이 글로벌 스코프는 언제든지 오염이 될 수 있다는 점이다.
// Node.js
const isBrowser = () => (typeof document !== 'undefined')
isBrowser(); // false
document = {};
isBrowser(); // true
때문에 단순히 객체가 존재하는지 체크를 하는것에서 더 나아가서, 구체적으로 체크할 수 있는 방법이 있다면 사용하는것이 좋다.
예를 들면, React-Native 와 환경에서는 별도로 환경을 체크할 수 있는 플래그를 navigator.product
에 주입해주고 있다.
const isReactNative = () => (typeof navigator !== 'undefined' && navigator.product == 'ReactNative');
다시 한번 말하지만, SDK 가 사용되는 환경은 우리가 특정할 수 없기 때문에, 가장 최악의 상황을 가정하여 구체적인것만 사용하여 체크하는것이 좋다. 예를 들면 React-Native 환경에서 document 객체를 글로벌에 선언해놓고 사용하는 케이스도 있기때문에, document 등에 대한 체크는 하지 않고 React-Native 에서 제공해주는 플래그만을 체크하는것이 유리하고, 문제가 생기더라도 책임으로부터 어느정도 자유로워 질 수 있다(...)
위에서 언급한 글로벌 스코프의 오염 이외에도, 빌트인 객체 또한 오염이 될 수 있다.
빌트인 객체란 언어에서 자체적으로 제공을 해줘야 하는 특별한 객체들을 의미한다. 예를 들면 Array 같은 객체가 대표적이다.
일반적으로 추천되지는 않지만, 편의를 위해서 다음과 같은 방식으로 확장을 하고 사용하는 경우들이 있다.
Array.prototype.sum = function() {
return this.filter(it => typeof it === 'number').reduce((a,b) => (a+b), 0);
}
[1,2,3,'4'].sum(); // 6
일반적으로는 이미 존재하는걸 임의로 편집하는게 아니라 추가하는 것은 큰 문제가 되지 않겠다고 생각을 하지만, for ... in
문법을 사용할 때 문제가 생길 수 있다.
![](https://camo.githubusercontent.com/f939e0919dde9576e33919508e3190eeaaafaf78be000074eff6f2eac543ef56/68747470733a2f2f76656c6f672e76656c63646e2e636f6d2f696d616765732f62616e67396465762f706f73742f33346235643937362d393138362d343939382d613632352d6330343530636438396135392f696d6167652e706e67)
for...in
은 다른 반복문들과 다르게 스스로의 요소와 상속받은 요소들의 키까지 포함해서 순회를 하기 때문에, 빌트인 객체로부터 상속받은 prototype enumerable 프로퍼티가 있다면 뜬금없이 등장을 하게 된다.
Array.prototype.jsIsAmazing = function jsIsAmazing() {
return this;
}
const arr = [1,2,3,4];
for (const i in arr) {
console.log(arr[i]);
}
// 1
// 2
// 3
// 4
// f jsIsAmazing() { return this; } 누구세요??
![](https://camo.githubusercontent.com/c6b0a5585570afb35381d7a1a4e46f34dc553528d5f9df016d3d4118abc47d2c/68747470733a2f2f76656c6f672e76656c63646e2e636f6d2f696d616765732f62616e67396465762f706f73742f66326637663931612d613035392d343837392d623334312d3632366635373961613363362f696d6167652e706e67)
이를 회피하기 위해서는 수행하기 전에 hasOwn/hasOwnProperty 등으로 객체 자신의 것인지 ownership 을 확인해주면 된다. (아니면 for...in
이 아닌 다른 반복문을 쓰거나)
Array.prototype.jsIsAmazing = function jsIsAmazing() {
return this;
}
const arr = [1,2,3,4];
for (const i in arr) {
if (Object.hasOwn(arr, i)) console.log(arr[i]);
}
// 1
// 2
// 3
// 4
참고 링크: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Enumerability_and_ownership_of_properties
*만약 빌트인 객체의 프로토 타입을 확장시키고 싶다면 Object.defineProperty
를 사용하여 non-enumerable 로 만들어주자.
Object.defineProperty(Array.prototype, 'jsIsAmazing', {
value: function() {
return this;
}
})
const arr = [1,2,3,4];
for (const i in arr) {
console.log(arr[i]);
}
// 1
// 2
// 3
// 4
일반적으로 마주할일이 없기는 한데, 만약 SDK 가 글로벌 스코프에 객체들을 할당을 하고 사용하는 방식이라면 네이밍에 주의하여야 한다.
하나의 예시로 paper.js 에서 사용하던 Symbol 객체가 글로벌 스코프의 Symbol 과 이름이 같아서 변경한 케이스가 있다.
paperjs/paper.js#1964
https://github.com/paperjs/paper.js/commit/bc2729683c2c9de992000f3a76f8882b13e00af0
객체 이름을 지을때는 빌트인 혹은 예약어에 쓰일법한 단어를 사용하기 보다는, 길고(?) 구체적인 이름을 짓자.
아니면 프로젝트의 약자를 prefix 로 붙이는것도 하나의 방법이다.
👍 👍 👍