여러분 안녕하세요,
새로운 정보를 가지고 왔고, 이 문제에 대한 해결책도 찾았습니다.
오늘 이 버그를 다시 파고들었고, 더 많은 정보를 찾은 것 같습니다.
Quantcast의 cmp2ui-fr.js 파일을 살펴보니 버그가 발생하는 위치를 찾을 수 있었습니다. 해당 함수는 다음과 같습니다(현재는 minified 버전만 존재함):
function(t){for(var n in t){t[n].status=e;}}
보시는 바와 같이, 이 함수는 for..in을 사용하며, t 변수는 배열입니다. 이전에 설명드린 바와 같이, ember.js는 JavaScript의 기본 Array를 확장합니다. 그런데 수정 사항 중 하나가 _super 항목을 추가한 것입니다.
_super 항목은 ROOT() 함수를 가리키는데, 이는 ember.js의 _utils.ROOT를 참조하는 것으로 보입니다. (여러분 중 일부에게는 익숙하게 들릴 수 있지만, 저에게는 그렇지 않네요 ^^). 이 ROOT() 함수는 확장할 수 없습니다.
명백히 이 _super 속성은 enumerable(열거 가능)로 취급되는 것 같습니다. 이는 배열에 대해 for..in 루프를 사용할 때, _super 항목이 “일반적인” 항목으로 취급된다는 뜻입니다. (예를 들어 values, bind, valueOf 등 배열 객체의 모든 함수들과는 달리).
저는 이것이 ember.js의 버그이며, 의도된 동작이 아니라고 생각합니다.
이로써 버그를 매우 재현하기 쉽게 만들었습니다. 방법은 다음과 같습니다. 먼저 객체 배열을 생성합니다:
var objs = [{'key':'val'},{'key':'val'}];
그 다음, strict 모드를 사용하고 배열을 반복하면서 각 항목에 새로운 속성을 설정하는 함수를 생성합니다:
var tst_func = function (objs){'use strict';for(var i in objs){objs[i].newproperty = true }};
마지막으로, 이 함수를 객체 배열에 대해 호출합니다:
tst_func(objs);
Uncaught TypeError: can't define property "newproperty": Function is not extensible라는 오류가 발생해야 합니다.
이것을 보니 버그가 이미 널리 퍼져 있을 가능성이 매우 높고, Quantcast에 국한된 문제가 아니라는 생각이 듭니다. 기본적으로 for..in을 사용하려는 누구든 일관되지 않은 동작이나 치명적인 버그에 노출됩니다.
저에게는 이것이 Discourse나 Quantcast의 문제가 아니라 명확히 ember.js의 문제입니다. 그러나 ember.js에서 수정될 때까지 우리는 해결책을 찾아야 한다는 사실은 변하지 않습니다. ^^.
좋은 소식은, 이 문제를 수정할 방법을 찾은 것 같다는 점입니다.
한 가지 방법은 모든 for..in 루프(아마도 each, for of 등도 포함)에 if (!objs.hasOwnProperty(i)) {continue};라는 줄을 사용하는 것입니다. 이는 Array의 _super라는 이상한 동작을 수정하지는 않지만, 로컬에서 해당 항목에 접근하는 것을 방지합니다. 이는 당연히 우리가 제어할 수 없는 외부 스크립트, 예를 들어 제 특정 Quantcast 사용 사례에서는 작동하지 않습니다.
또 다른 방법, 즉 제가 취해야 할 길이라고 생각하는 방법은 JavaScript의 Array 프로토타입을 수정하여(기본적으로 ember.js 자체의 오버라이드를 오버라이드하는 셈이죠 ^^) _super를 열거 불가능(not enumerable)하게 만드는 것입니다. 이를要做到 하려면, ember.js가 호출되고 평가된 후에 이 JS 줄을 실행해야 합니다:
//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, '_super', {'enumerable': false});
두 번째 방법은 _super의 직접적인 사용을 의도대로 허용하면서 루프에서 나타나는 것을 방지합니다. 다만, 이 이상한 동작이 ember.js 내부 함수나 외부 플러그인에서 사용되고 있는지 보장할 수는 없습니다.
_ember_jquery-189e46ebcb33594b835e782fd1ce916ec750bc0cf980ebc4fb7796649161a18d.js 파일을 살펴보니, Discourse에 특화된 몇몇 줄을 발견했습니다. 예를 들어:
var ALIASES = {
"ember-addons/ember-computed-decorators":
"discourse-common/utils/decorators",
"discourse/lib/raw-templates": "discourse-common/lib/raw-templates",
"preload-store": "discourse/lib/preload-store",
"fixtures/user_fixtures": "discourse/tests/fixtures/user-fixtures",
};
var ALIAS_PREPEND = {
fixtures: "discourse/tests/",
helpers: "discourse/tests/",
};
여기에 우리의 Object.defineProperty(Array.prototype, '_super', {'enumerable': false}); 줄을 추가할 수 있을까요?
저는 현재 Quantcast choice.js 스크립트를 호출하기 전에 다음 줄을 추가하여 버그를 수정했습니다:
//Make _super not enumerable to prevent bug between emberjs and for..in
Object.defineProperty(Array.prototype, ‘_super’, {‘enumerable’: false});
버그를 로컬에서 수정하려면, 이 두 줄을 가진 ‘fix_ember.js’ 파일을 생성하고, 리버스 프록시(예: Nginx)에서 정적으로 서빙하며, 커스터마이징을 사용하여 테마 푸터에 <script src="/fix_emmber.js"></script> 줄을 추가할 수 있다고 생각합니다. 링크를 생성하는 대신 스크립트를 직접 작성하는 것은 Discourse의 스크립트 추출 기능(Custom javascript in <head> disappear 참조) 때문에 작동하지 않습니다.
이 토픽이 다른 사람들에게 도움이 되기를 바랍니다. 내일 ember.js에 티켓을 열어 이것이 버그인지, 아니면 매우 이상하지만 의도된 동작인지 확인해 볼 것입니다.
Discourse에 수정 사항을 포함해야 하는지 확인하기 위해 정보를 계속 공유하겠습니다.
PS: Extending JavaScript Natives – JavaScript, JavaScript… 의 Angus Croll에게 많은 감사를 드립니다. 그의 포스트가 큰 도움이 되었습니다!