자바스크립트는 매년 다양한 기능을 개발하고 이를 지원한다. 정보 통신에 대한 표준을 관리하는 비영리기구 Ecma International에서 새로운 규격을 제작하여 ES6를 기점으로 매년마다 발표하고 있다. 자바스크립트는 ECMAScript를 준수하는 언어이므로 ECMAScript의 규격 변화에 따라 Javascript의 성능도 변화한다고 할 수 있다.
브라우저 별로 지원사항을 확인할 수 있는 ECMAScript Table
키워드 별로 지원사항을 확인할 수 있는 CanIUse
오래된 브라우저에서는 최신 기술사항을 지원하지 않는다. 위의 페이지에서는 브라우저, 키워드에 따라 브라우저 별로 지원되는 기술사항을 확인할 수 있다. 예를 들어 2015년의 ES6 이상 버전의 let, const, 화살표 함수, promise 등은 internet explorer 등에서 더 이상 지원하지 않는다. 이를 위해 구형의 브라우저에서도 코드가 작동할 수 있도록 사용 가능한 코드 조각이나 플러그인으로 변경하는 것을 Polyfill이라 한다.
Babel은 ES6 이상의 코드를 구 브라우저에서도 호환 가능하도록 변경해주는 javascript 컴파일러다. Babel은 다음과 같은 순서로 동작한다.
Babel의 사용법에 대해 알아보자.
먼저, 프로젝트에 babel을 설치한다.
npm install --save-dev @babel/core @babel/cli @babel/preset-env
babel의 설정 파일 .babelrc
을 생성하고 적용할 preset과 플러그인을 정의한다.
{
"presets": [
"@babel/preset-env"
]
}
예를 들어, internet explorer 11을 지원해야 한다면 다음과 같이 작성한다.
module.exports = {
presets: [
['@babel/preset-env', { targets: { ie: 11 } }],
],
/* 그 외의 설정 */
};
babel은 명령어로 실행하거나 프로젝트 빌드 시에 자동으로 적용되도록 설정할 수 있다.
명령어로 실행하려면
npx babel [입력 파일 경로] -o [출력 파일 경로]
형태로 입력한다.
빌드 시 자동으로 적용되도록 하려면, 예를 들어 vite의 경우 vite.config.js에 다음과 같이 플러그인을 추가한다.
import { defineConfig } from 'vite';
export default defineConfig({
plugins: [
{
name: 'babel',
transform(code, id) {
if (/\.js$/.test(id)) {
const transformed = require('@babel/core').transform(code, {
presets: ['@babel/preset-env']
});
return {
code: transformed.code,
map: transformed.map
};
}
}
}
]
});
Array.from
은 유사 배열 객체나 반복 가능한 객체에 대하여 shallow copy를 통해 새로운 array
객체를 생성하는 메서드이다. ES6에서 처음 등장한 문법으로 이전의 브라우저에서도 작동할 수 있도록 polyfill을 만들어보자.
if (!Array.from) {
Array.from = function(object) {
'use strict';
if (object == null) {
throw new TypeError('Array.from requires an array-like object - not null or undefined');
}
var arrayLikeObject = Object(object);
if (arguments.length > 1) {
var mapFunction = arguments[1];
var thisArg = arguments[2];
}
var length = Number(arrayLikeObject.length);
var newArray = new Array(length);
for (var i = 0; i < length; i++) {
var value = arrayLikeObject[i];
if (mapFunction) {
newArray[i] = typeof thisArg === 'undefined' ? mapFunction(value, i) : mapFunction.call(thisArg, value, i);
} else {
newArray[i] = value;
}
}
return newArray;
};
}
참고로 Array.from
이 지원되지 않으면 let
, const
도 지원되지 않으므로 모든 변수 선언은 var
을 사용한다...
코드에 대해 설명을 덧붙이면,
if (!Array.from)
을 통해 브라우저에서 Array.from
을 지원하지 않을 때에만 Polyfill을 지원한다.Obejct(object)
를 사용해 입력으로 전달받은 object를 배열 형태로 변환하여 arrayLikeObject
에 저장한다.argumnets
객체의 길이가 1보다 큰 경우, 즉 배열의 각 값마다 적용할 메서드를 함께 전달받은 경우에는 적용할 메서드 mapFunction
과 사용할 객체 thisArg
를 설정한다.length
에는 앞서 생성한 배열 형태의 object
객체의 길이를 저장한다. newArray
의 길이는 length
를 갖는다.for
문을 통해 arrayLikeObject
의 각 요소를 newArray
에 복사한다. 이때, mapFunction
이 지정된 경우, 각 요소마다 mapFunction
을 호출하여 반환된 값을 저장한다.Array
객체 newArray
를 반환한다.아래는 해당 코드를 작성하는 데 참조한 mdn의 Array.from
의 polyfill이다.
/**
* Array.from() polyfill
*/
// From https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/from
// Production steps of ECMA-262, Edition 6, 22.1.2.1
if (!Array.from) {
Array.from = (function () {
var toStr = Object.prototype.toString;
var isCallable = function (fn) {
return typeof fn === 'function' || toStr.call(fn) === '[object Function]';
};
var toInteger = function (value) {
var number = Number(value);
if (isNaN(number)) { return 0; }
if (number === 0 || !isFinite(number)) { return number; }
return (number > 0 ? 1 : -1) * Math.floor(Math.abs(number));
};
var maxSafeInteger = Math.pow(2, 53) - 1;
var toLength = function (value) {
var len = toInteger(value);
return Math.min(Math.max(len, 0), maxSafeInteger);
};
// The length property of the from method is 1.
return function from(arrayLike/*, mapFn, thisArg */) {
// 1. Let C be the this value.
var C = this;
// 2. Let items be ToObject(arrayLike).
var items = Object(arrayLike);
// 3. ReturnIfAbrupt(items).
if (arrayLike == null) {
throw new TypeError('Array.from requires an array-like object - not null or undefined');
}
// 4. If mapfn is undefined, then let mapping be false.
var mapFn = arguments.length > 1 ? arguments[1] : void undefined;
var T;
if (typeof mapFn !== 'undefined') {
// 5. else
// 5. a If IsCallable(mapfn) is false, throw a TypeError exception.
if (!isCallable(mapFn)) {
throw new TypeError('Array.from: when provided, the second argument must be a function');
}
// 5. b. If thisArg was supplied, let T be thisArg; else let T be undefined.
if (arguments.length > 2) {
T = arguments[2];
}
}
// 10. Let lenValue be Get(items, "length").
// 11. Let len be ToLength(lenValue).
var len = toLength(items.length);
// 13. If IsConstructor(C) is true, then
// 13. a. Let A be the result of calling the [[Construct]] internal method
// of C with an argument list containing the single item len.
// 14. a. Else, Let A be ArrayCreate(len).
var A = isCallable(C) ? Object(new C(len)) : new Array(len);
// 16. Let k be 0.
var k = 0;
// 17. Repeat, while k < len… (also steps a - h)
var kValue;
while (k < len) {
kValue = items[k];
if (mapFn) {
A[k] = typeof T === 'undefined' ? mapFn(kValue, k) : mapFn.call(T, kValue, k);
} else {
A[k] = kValue;
}
k += 1;
}
// 18. Let putStatus be Put(A, "length", len, true).
A.length = len;
// 20. Return A.
return A;
};
}());
}
tosstech) 똑똑하게 브라우저 Polyfill 관리하기
요즘IT) ECMAScript 2022 살펴보기
sanha.log) JS ES6 - Polyfill
mdn web docs) Polyfill
2ssue's dev note) Array.from 이 모든 브라우저에서 동작하도록 polyfill 코드를 만들어보세요