번역가 노트 : 나는 외국어를 처음으로 번역했을 때, 내 말은 필연적으로 약간 모호하지만 저자의 원래 의도를 표현하기 위해 최선을 다했으며 광택이 많지 않았습니다. 비판과 교정을 환영합니다. 또한이 기사는 길고 많은 양의 정보가 있으며 소화하기 어려울 수 있습니다. 자세한 내용을 논의하려면 메시지를 남겨주세요. 이 기사는 주로 V8의 성능 최적화에 중점을두고 있으며 일부 컨텐츠는 모든 JS 엔진에 적용 할 수있는 것은 아닙니다. 마지막으로, 재판 할 때 소스를 표시하십시오 :)
===========================================================================================
Google의 V8 엔진 (Chrome 및 Node에서 사용)과 같은 많은 JavaScript 엔진은 빠른 실행이 필요한 대형 JavaScript 응용 프로그램을 위해 특별히 설계되었습니다. 개발자이고 메모리 사용 및 페이지 성능에 대해 우려하는 경우 브라우저의 JavaScript 엔진이 어떻게 작동하는지 이해해야합니다. V8, Spidermonkey (Firefox) Carakan (Opera), Chakra (IE) 또는 기타 엔진이든 응용 프로그램을 더 잘 최적화하는 데 도움이 될 수 있습니다. 그렇다고해서 특정 브라우저 나 엔진에 대해 구체적으로 최적화해야하며 결코 그렇게하지 않아야한다는 의미는 아닙니다.
그러나 몇 가지 질문을해야합니다.
빠른 로딩 웹 사이트는 특별히 맞춤화 된 부품이 필요한 빠른 스포츠카와 같습니다. 이미지 출처 : Dhybridcars.
고성능 코드를 작성할 때 일반적인 함정이 있으며이 기사에서는 코드를 작성하는 몇 가지 입증 된 방법을 보여줍니다.
JS 엔진에 대한 깊은 이해가 없다면 운전할 수있는 사람이 후드 내부의 엔진 만 보지 못했던 것처럼 대형 웹 애플리케이션을 개발하는 데 아무런 문제가 없습니다. Chrome이 내 브라우저의 첫 번째 선택이라는 점을 감안할 때 JavaScript 엔진에 대해 이야기 해 봅시다. V8은 다음 핵심 부분으로 구성됩니다.
쓰레기 수집은 메모리 관리의 한 형태 로, 실제로 수집기의 개념이며 더 이상 사용되지 않는 객체가 차지하는 메모리를 재활용하려고합니다. JavaScript와 같은 쓰레기 수집 언어에서는 응용 프로그램에서 여전히 참조되고있는 객체가 지워지지 않습니다.
대부분의 경우 객체 참조를 수동으로 제거 할 필요는 없습니다. 변수를 필요한 곳에두면 모든 것이 잘 작동합니다 (이상적으로는 가능한 한 로컬 스코프, 즉 함수의 외부 층 대신 사용되는 기능).
쓰레기 수집가는 메모리를 재활용하려고 시도합니다. 이미지 출처 : Valtteri Mäki.
JavaScript에서는 쓰레기 수집을 강요하는 것은 불가능합니다. 쓰레기 수집 프로세스는 런타임에 의해 제어되며 정리하기에 가장 좋은시기가 무엇인지 알고 있으므로이 작업을 수행해서는 안됩니다.
인터넷에서 키워드 삭제에 대한 JavaScript 메모리 재활용에 대한 많은 논의가 있습니다. 객체 (MAP)에서 속성 (키)을 삭제하는 데 사용될 수 있지만 일부 개발자는 "피해"를 강요하는 데 사용될 수 있다고 생각합니다. 가능할 때마다 삭제 사용을 피하는 것이 좋습니다. 아래의 예에서 delete ox 的弊大于利,因为它改变了o的隐藏类,并使它成为一个"慢对象"。
var o = {x : 1}; 황소 삭제; // True ox; // 한정되지 않은인기있는 JS 라이브러리에서 쉽게 참조 제거를 찾을 수 있습니다. 이것은 언어 적으로 목적이 있습니다. 여기서는 런타임에 "핫"객체의 구조를 수정하지 않는 것이 여기에 주목해야합니다. JavaScript 엔진은 이러한 "핫"객체를 감지하고 최적화하려고 시도 할 수 있습니다. 수명주기 동안 물체의 구조가 크게 변하지 않으면 엔진이 객체를 최적화하기가 더 쉬워지고 삭제 작업은 실제로이 더 큰 구조적 변화를 트리거하여 엔진의 최적화에 도움이되지 않습니다.
Null이 어떻게 작동하는지에 대한 오해도 있습니다. NULL에 대한 객체 참조를 설정하면 객체가 "비어있는"것이 아니라 비어있는 참조를 설정합니다. ox = null을 사용하는 것이 delete를 사용하는 것보다 낫지 만 필요하지 않을 수 있습니다.
var o = {x : 1}; o = null; o; // nullo.x // typeerror이 참조가 현재 객체에 대한 마지막 참조 인 경우, 물체는 수집됩니다. 이 참조가 현재 객체에 대한 마지막 참조가 아닌 경우 객체에 액세스 할 수 있으며 쓰레기가 수집되지 않습니다.
또한 페이지의 수명주기 동안 쓰레기 수집기에 의해 글로벌 변수가 정리되지 않는다는 점도 주목해야합니다. 페이지가 얼마나 오래 열려 있더라도 JavaScript가 실행될 때 전역 객체의 범위의 변수가 항상 존재합니다.
var myglobalnamespace = {};글로벌 객체는 페이지를 새로 고치거나 다른 페이지로 탐색하거나 탭을 닫거나 브라우저를 종료 할 때만 청소됩니다. 함수 스코프의 변수는 범위를 벗어 났을 때, 즉 함수가 종료되면 참조가 없으며 그러한 변수가 정리됩니다.
쓰레기 수집기가 가능한 한 많은 물체를 수집하려면 더 이상 사용되지 않는 물체를 유지하지 마십시오 . 기억해야 할 몇 가지 사항은 다음과 같습니다.
다음으로 기능에 대해 이야기합시다. 우리가 이미 말했듯이, 쓰레기 수집은 더 이상 접근 할 수없는 메모리 (객체)를 재활용하여 작동합니다. 이것을 더 잘 설명하기 위해 여기에 몇 가지 예가 있습니다.
funture foo () {var bar = new largeobject (); bar.somecall ();}FOO가 돌아 오면 기존의 참조가 없기 때문에 쓰레기 수집기가 자동으로 재활용됩니다.
비교하다:
funture foo () {var bar = new largeobject (); bar.somecall (); return bar;} // outhersvar b = foo ();이제 우리는 막대 객체를 가리키는 참조가 있으므로, 발신자가 다른 변수 b를 지정할 때까지 (또는 b가 범위를 벗어나지 않음), bar 객체의 수명주기가 foo 로의 호출에서 계속되도록합니다.
함수가 표시되면 내부 기능을 반환하면 외부 기능이 실행 된 후에도 범위 액세스가 발생하지 않습니다. 이것은 기본 폐쇄입니다. 특정 컨텍스트에서 설정할 수있는 변수의 표현입니다. 예를 들어:
함수 sum (x) {function sumit (y) {return x + y; }; return sumit;} // usagevar suma = sum (4); var sumb = suma (3); console.log (sumb); // 반환 7합 통화 컨텍스트에서 생성 된 함수 객체 (sumit)는 재활용 할 수 없습니다. 그것은 글로벌 변수 (SUMA)에 의해 참조되며 Suma (n)를 통해 호출 할 수 있습니다.
다른 예를 살펴 보겠습니다. 변수는 어디에 액세스 할 수 있습니까?
var a = function () {var largest = new Array (100000) .join ( 'x'); return function () {return largest; };} ();예, 우리는 ()을 통해 대규모 액세스 할 수 있으므로 재활용되지 않습니다. 다음은 어떻습니까?
var a = function () {var smallstr = 'x'; var largestreps = new Array (10000000) .join ( 'x'); 반환 함수 (n) {return smallstr; };} ();우리는 더 이상 대대적 인 접근을 할 수 없으며 이미 쓰레기 수거 후보입니다. [번역기 주 : 대단한 스트레스트가 더 이상 외부 참조가 없기 때문에]
메모리 누출에 최악의 장소 중 하나는 루프 또는 settimeout ()/setinterval ()에 있지만 이것은 매우 일반적입니다. 다음 예에 대해 생각해보십시오.
var myobj = {callmemaybe : function () {var myref = this; var val = settimeout (function () {console.log ( 'time is undown!'); myref.callmemaybe ();}, 1000); }}; myobj.callmemaybe ()를 실행하면; 타이머를 시작하려면 콘솔이 "시간이 다 떨어졌다"는 인쇄를 알 수 있습니다. 매 초마다. myObj = null,定时器依旧处于激活状态。为了能够持续执行,闭包将myObj传递给setTimeout,这样myObj是无法被回收的。相反,它引用到myObj的因为它捕获了myRef。这跟我们为了保持引用将闭包传给其他的函数是一样的。
또한 Settimeout/SetInterval 호출 (기능 등)의 참조는 쓰레기를 수집하기 전에 실행 및 완료해야한다는 것을 기억할 가치가 있습니다.
실제로 필요할 때까지 코드를 최적화하지 마십시오. 이제 N이 M보다 V8에서 더 최적화되어 있음을 보여주는 일부 벤치 마크를 볼 수 있지만 모듈 코드 또는 응용 프로그램에서 테스트하면 이러한 최적화가 예상보다 훨씬 작다는 것을 알 수 있습니다.
너무 많이하지 않는 것이 낫습니다. 이미지 출처 : Tim Sheerman-Chase.
예를 들어, 이러한 모듈을 만들려고합니다.
이 문제에는 몇 가지 다른 요소가 있지만 해결하기도 쉽습니다. 우리는 어떻게 데이터를 저장하고, 테이블을 효율적으로 그릴 수 있고 DOM에 추가하고, 테이블 이벤트를 어떻게 더 잘 처리합니까?
이러한 문제에 직면하기위한 초기 (순진한) 접근 방식은 객체를 사용하여 데이터를 저장하고 배열에 넣고 jQuery를 사용하여 데이터를 가로 지르고 테이블을 그리고 DOM에 추가하여 이벤트 바인딩을 사용하는 것입니다.
참고 : 이것은 당신이해야 할 일이 아닙니다
var modulea = function () {return {data : dataArrayObject, init : function () {this.addtable (); this.adevents (); }, addTable : function () {for (var i = 0; i <rows; i ++) {$ tr = $ ( '<tr> </tr>'); for (var j = 0; j <this.data.length; } $ tr.appendto ($ tbody); }}, addEvents : function () {$ ( 'table td'). on ( 'click', function () {$ (this) .toggleClass ( 'active');}); }};} ();이 코드는 간단하고 효과적으로 작업을 완료합니다.
그러나이 경우, 우리가 가로 지르는 데이터는 배열에 간단히 저장되어야하는 숫자 속성 ID 일뿐입니다. 흥미롭게도, jQuery (이러한 방식으로)를 사용하여 테이블을 생성하는 것보다 DocumentFragment 및 로컬 DOM 방법을 직접 사용하는 것이 좋습니다. 물론 이벤트 프록시는 각 TD 만 바인딩하는 것보다 성능이 향상됩니다.
jQuery는 내부적으로 DocumentFragment를 사용하지만,이 예에서는 코드 호출이 루프 내에서 Appeend를 사용하고 이러한 통화에는 다른 작은 지식이 포함되므로 여기서 최적화 효과는 그리 좋지 않습니다. 바라건대 이것은 고통스러운 지점이 아니지만 코드가 정상인지 확인하기 위해 벤치 마크를 수행해야합니다.
이 예를 들어, 위의 관행은 (원하는) 성능 향상을 가져옵니다. 이벤트 프록시는 간단한 바인딩의 개선이며 옵션 인 DocumentFragment도 도움이됩니다.
var moduled = function () {return {data : dataArray, init : function () {this.addtable (); this.adevents (); }, addTable : function () {var td, tr; var fragment = document.createdOcumentFragment (); var fragment2 = document.createdOcumentFragment (); for (var i = 0; i <rows; i ++) {tr = document.createElement ( 'tr'); for (var j = 0; td.appendChild (document.createtextnode (this.data [j])); frag2.appendChild (TD); } tr.appendChild (FRAG2); Frag.AppendChild (TR); } tbody.appendChild (조각); }, addEvents : function () {$ ( 'table'). on ( 'click', 'td', function () {$ (this) .toggleClass ( 'active');}); }};} ();성능을 향상시키는 다른 방법을 살펴 보겠습니다. 프로토 타입 모드를 사용하는 것이 모듈 모드보다 낫거나 JS 템플릿 프레임 워크를 사용하는 것이 더 잘 작동한다고 들었을 수도 있습니다. 때로는 이것이 사실이지만 코드를 더 읽기 쉽게 만드는 데 사용됩니다. 그건 그렇고, 사전 컴파일이 있습니다! 실제로 어떻게 작동하는지 봅시다?
moduleg = function () {{}; moduleg.prototype.data = dataArray; moduleg.prototype.init = function () {this.addtable (); this.adevents ();}; moduleg.prototype.addtable = function () {var emplate = _.template ($ ( '#emplate'). text ()); var html = 템플릿 ({ 'data': this.data}); $ tbody.append (html);}; moduleg.prototype.addevents = function () {$ ( 'table'). on ( 'click', 'td', function () {$ (this) .toggleEclass ( 'active');};}; var modg = new modleg ();이 상황에서 가져온 성능 개선은 무시할 수 있음이 밝혀졌습니다. 템플릿과 프로토 타입의 선택은 실제로 더 이상 제공하지 않습니다. 즉, 성능이 개발자가 사용하는 이유가 아니며, 가독성, 상속 모델 및 코드에 가져온 유지 관리가 실제 이유입니다.
보다 복잡한 문제에는 캔버스에 사진을 효율적으로 그리거나 유형 배열이 있거나없는 픽셀 데이터 조작이 포함됩니다.
자신의 응용 프로그램을위한 몇 가지 방법을 사용하기 전에 이러한 솔루션의 벤치마킹에 대해 자세히 알아보십시오. 어쩌면 누군가가 여전히 JS 템플릿의 촬영 및 후속 확장을 기억합니다. 볼 수없는 가상 응용 프로그램에는 벤치마킹이 존재하지 않지만 실제 코드로 가져온 최적화를 테스트해야합니다.
각 V8 엔진의 최적화 지점은이 기사의 범위 밖에서 자세히 소개됩니다. 물론 여기에는 언급할만한 팁이 많이 있습니다. 이 팁을 기억하면 성능이 저하되는 코드를 줄일 수 있습니다.
함수 추가 (x, y) {return x+y;} add (1, 2); 추가 ( 'a', 'b'); 추가 (my_custom_object, undefined);자세한 내용은 Google I/O에서 Daniel Clifford의 공유를 참조하십시오. V8로 JavaScript 속도 제한을 깨뜨립니다. V8에 대한 최적화 - 시리즈도 읽을 가치가 있습니다.
자바 스크립트의 객체와 배열 사이에는 하나의 주요 차이점, 즉 배열의 마법 길이 속성이 있습니다. 이 속성을 직접 유지하면 V8의 객체와 배열은 배열의 것만 큼 빠릅니다.
객체 클로닝은 응용 프로그램 개발자에게 일반적인 문제입니다. 다양한 벤치 마크는 V8 이이 문제를 잘 처리한다는 것을 증명할 수 있지만주의하십시오. 큰 물건을 복사하는 것은 일반적으로 느립니다. 그렇게하지 마십시오. JS의 for..in 루프는 악마 사양이 있고 엔진의 어떤 물체보다 빠르지 않을 수 있기 때문에 특히 나쁩니다.
Critical Performance Code Path에서 객체를 복사 할 때는 배열 또는 사용자 정의 "Copy Contructor"기능을 사용하여 각 속성을 명시 적으로 복사하십시오. 이것은 아마도 가장 빠른 방법 일 것입니다.
함수 클론 (원본) {this.foo = original.foo; this.bar = original.bar;} var copy = new Clone (Original);모듈 모드를 사용할 때 캐싱 함수는 성능 향상으로 이어질 수 있습니다. 아래 예제를 참조하십시오. 항상 멤버 함수의 새 사본을 생성하므로 변경 사항이 느려질 수 있습니다.
또한이 방법을 사용하는 것이 프로토 타입 모드에 의존하는 것이 아니라 (JSPERF 테스트로 확인) 분명히 더 좋습니다.
모듈 모드 또는 프로토 타입 모드를 사용할 때 성능 향상
프로토 타입 모드 및 모듈 모드의 성능 비교 테스트입니다.
// 프로토 타입 패턴 klass1 = function () {} klass1.prototype.foo = function () {log ( 'foo'); } klass1.prototype.bar = function () {log ( 'bar'); } // 모듈 패턴 klass2 = function () {var foo = function () {log ( 'foo'); }, bar = function () {log ( 'bar'); }; return {foo : foo, bar : bar}} // 캐시 된 함수가있는 모듈 패턴 var foofunction = function () {log ( 'foo'); }; var barfunction = function () {log ( 'bar'); }; klass3 = function () {return {foo : foofunction, bar : barfunction}} // 반복 테스트 // prototypal var i = 1000, objs = []; while (i-) {var o = new Klass1 () objs.push (new Klass1 ()); O.bar; O.foo; } // 모듈 패턴 var i = 1000, objs = []; while (i-) {var o = new Klass1 () objs.push (new Klass1 ()); O.bar; O.foo; } // 모듈 패턴 var i = 1000, objs = []; while (i-) {var o = klass2 () objs.push (klass2 ()); O.bar; O.foo; } // 캐시 된 함수가있는 모듈 패턴 var i = 1000, objs = []; while (i-) {var o = klass3 () objs.push (klass3 ()); O.bar; O.foo; } // 자세한 내용은 테스트를 참조하십시오다음으로 배열과 관련된 기술에 대해 이야기 해 봅시다. 일반적으로 배열 요소를 삭제하지 마십시오 . 이는 배열이 느린 내부 표현으로 전환됩니다. 인덱스가 드물게되면 V8은 요소를 더 느린 사전 패턴으로 전환합니다.
배열 리터럴은 매우 유용하며 VM 배열의 크기와 유형을 암시 할 수 있습니다. 일반적으로 작은 크기의 배열에 사용됩니다.
// v8은 숫자가 포함 된 4 요소 배열을 원하는 것을 볼 수 있습니다. var a = [1, 2, 3, 4]; // 이것을하지 마십시오 : a = []; // 여기 V8은 배열에 대해 아무것도 모릅니다 (var i = 1; i <= 4; i ++) {a.push (i);}혼합 유형 (숫자, 문자열, 정의되지 않은, 참/거짓)의 데이터를 배열에 저장하는 것은 결코 좋은 생각이 아닙니다. 예를 들어 var arr = [1, "1", 정의되지 않은, true, "true"]]]
유형 추론의 성능 테스트
우리가 보았 듯이, 정수의 배열이 가장 빠릅니다.
희소 배열을 사용하면 액세스 요소에주의를 기울이면 전체 배열보다 훨씬 느려집니다. V8은 공간의 일부만 사용하는 배열에 전체 공간을 할당하지 않기 때문입니다. 대신 사전으로 관리되어 두 공간을 절약하지만 시간이 걸리는 데 시간이 걸립니다.
희소 배열 및 전체 배열 테스트
대형 배열 (예 : 64K보다 큰 요소), 최대 크기를 사전 할당하지 않지만 동적으로 할당해야합니다. 이 기사에서 성능을 테스트하기 전에 일부 JavaScript 엔진에만 적용됩니다.
빈 리터럴 및 사전 배치 된 배열은 다른 브라우저에서 테스트됩니다.
니트로 (Safari)는 사전 배치 된 배열에 더 유리합니다. 다른 엔진 (V8, Spidermonkey)에서는 사전 할당이 효율적이지 않습니다.
Preallocated Array Testing
// 빈 배열 arr = []; for (var i = 0; i <10000000; i ++) {arr [i] = i;} // 사전에 할당 된 Arrayvar arr = new Array (10000000); for (var i = 0; i <10000; i ++) {arr [i] = i;}웹 애플리케이션의 세계에서는 속도가 전부입니다. 사용자는 총 열의 총 수를 계산하거나 정보를 요약하는 데 몇 초가 걸리는 테이블 응용 프로그램을 사용하려고합니다. 이것이 코드의 모든 성능을 짜내려는 중요한 이유입니다.
이미지 출처 : OLOF FORSBERG 당.
응용 프로그램의 성능을 이해하고 개선하는 것은 매우 유용하지만 어렵습니다. 성능 통증 지점을 해결하기위한 다음 단계를 권장합니다.
아래에 권장되는 도구와 기술 중 일부가 도움이 될 수 있습니다.
JavaScript 코드 스 니펫이 성능을 테스트하기 위해 벤치 마크를 실행하는 방법에는 여러 가지가 있습니다. 일반적인 가정은 벤치 마크가 단순히 두 개의 타임 스탬프를 비교한다는 것입니다. 이 패턴은 JSPERF 팀에 의해 지적되며 Sunspider와 Kraken의 벤치 마크 제품군에서 사용됩니다.
var totaltime, start = new 날짜, 반복 = 1000; while (반복-) {// 코드 스 니펫은 여기에 간다} // totaltime → 수백만 초의 코드 snippet 1000 timestotaltime = 새 날짜-시작;여기서 테스트 할 코드는 루프에 배치되어 설정된 횟수 (예 : 6 번)를 실행합니다. 그 후, 시작 날짜는 종료일로부터 빼고 루프에서 작동을 수행하는 데 걸리는 시간이 도출됩니다.
그러나이 벤치마킹은 특히 여러 브라우저 및 환경에서 벤치 마크를 실행하려는 경우 너무 간단한 작업을 수행합니다. 쓰레기 수집기 자체는 결과에 특정한 영향을 미칩니다. Window와 같은 솔루션을 사용하더라도 성과는 이러한 단점을 고려해야합니다.
코드의 벤치 마크 부분 만 실행하든 테스트 스위트를 작성하거나 벤치 마크 라이브러리를 작성하든 JavaScript 벤치 마크는 실제로 생각보다 많습니다. 보다 자세한 가이드 벤치 마크를 보려면 Mathias Bynens와 John-David Dalton이 제공하는 JavaScript 벤치 마크를 읽는 것이 좋습니다.
Chrome 개발자 도구는 JavaScript 분석을 잘 지원합니다. 이 기능을 사용하여 대부분의 시간을 차지하는 기능을 감지하여 최적화 할 수 있습니다. 이는 코드의 작은 변화조차도 전체 성능에 큰 영향을 줄 수 있습니다.
크롬 개발자 도구 분석 패널
분석 프로세스는 코드 성능 기준을 얻기 시작한 다음 타임 라인 형태로 나타납니다. 이것은 코드가 실행하는 데 걸리는 시간을 알려줍니다. 프로파일 탭은 응용 프로그램에서 무슨 일이 일어나고 있는지에 대한 더 나은 관점을 제공합니다. JavaScript CPU 분석 파일 코드에서 사용되는 CPU 시간의 양을 보여주고 CSS 선택기 분석 파일은 선택기 처리에 소요되는 시간의 시간을 보여주고 힙 스냅 샷은 객체에 얼마나 많은 메모리가 사용되는지를 보여줍니다.
이러한 도구를 사용하면 기능 또는 운영 성능 최적화가 실제로 효과적인지 여부를 측정하기 위해 분리, 조정 및 재분석 할 수 있습니다.
프로필 탭에는 코드 성능 정보가 표시됩니다.
분석에 대한 좋은 소개, Chrome Developer Tools에서 Zack Grossbart의 JavaScript 프로파일 링을 읽으십시오.
팁 : 이상적으로, 설치된 응용 프로그램이나 확장에 의해 분석이 영향을받지 않으려면 --user-data-dir <empty_directory> 플래그를 사용하여 크롬을 시작할 수 있습니다. 대부분의 경우,이 방법 최적화 테스트는 충분해야하지만 시간이 더 걸립니다. 이것이 V8 로고가 도움을 줄 수있는 것입니다.
Google 내에서 Chrome Developer 도구는 Gmail과 같은 팀에서 메모리 누출을 감지하고 문제를 해결하는 데 널리 사용됩니다.
크롬 개발자 도구의 메모리 통계
메모리는 개인 메모리 사용, JavaScript 힙의 크기, DOM 노드 수, 스토리지 청소, 이벤트 청취 카운터 및 우리 팀이 우려하는 쓰레기 수집기를 계산합니다. Loreena Lee의 "3 Snapshot"기술을 읽는 것이 좋습니다. 이 기술의 핵심 요점은 응용 프로그램에 일부 동작을 기록하고 쓰레기 수집을 강제하고 DOM 노드 수가 예상 기준으로 복원되었는지 확인한 다음 세 힙의 스냅 샷을 분석하여 메모리 누출이 있는지 확인하는 것입니다.
단일 페이지 응용 프로그램의 메모리 관리 (AngularJS, 백본, Ember)는 매우 중요하며 페이지를 거의 새로 고치지 않습니다. 이것은 메모리 누출이 상당히 분명 할 수 있음을 의미합니다. 모바일 터미널의 단일 페이지 응용 프로그램은 메모리가 제한되어 있고 이메일 클라이언트 또는 소셜 네트워크와 같은 응용 프로그램을 오랫동안 실행하기 때문에 함정이 가득합니다. 능력이 클수록 책임이 무겁습니다.
이 문제를 해결하는 방법에는 여러 가지가 있습니다. 백본에서는 Dispose ()를 사용하여 이전보기 및 참조 (현재 백본에서 사용 가능)를 처리해야합니다 (현재 백본 (Edge). Ember 요소가 제거 된 것을 감지 할 때 메모리 누출을 피하기 위해 리스너를 정리하십시오.
Derick Bailey의 현명한 조언 :
이벤트와 참조가 어떻게 작동하는지 이해하는 대신 표준 규칙을 따라 JavaScript에서 메모리를 관리하십시오. 사용자 객체로 가득 찬 백본 컬렉션에 데이터를로드하려면 더 이상 메모리를 사용하지 않도록 컬렉션을 지우려면 컬렉션에 대한 모든 참조 및 컬렉션의 객체에 대한 참조가 필요합니다. 사용 된 참조가 명확 해지면 리소스가 재활용됩니다. 이것은 표준 JavaScript 쓰레기 수집 규칙입니다.
이 기사에서 Derick은 Backbone.js를 사용할 때 많은 일반적인 메모리 결함과 이러한 문제를 해결하는 방법을 다룹니다.
Felix Geisendörfer의 노드에서 메모리 누출 디버깅에 대한 튜토리얼은 특히 더 넓은 스파 스택의 일부를 형성 할 때 읽을 가치가 있습니다.
브라우저가 문서의 요소를 다시 렌더링하면 다시 계산해야하며 위치 및 기하학을 반사적이라고합니다. Reflow는 브라우저에서 사용자의 운영을 차단하므로 리플 로우 시간 개선이 개선된다는 것을 이해하는 것이 매우 도움이됩니다.
반사 시간 차트
반사 길을 트리거하거나 배치로 트리거해야하지만 이러한 방법을 적당히 사용하십시오. Dom을 다루지 않으려는 것도 중요합니다. 가벼운 문서 개체 인 DocumentFragment를 사용할 수 있습니다. 문서 트리의 일부를 추출하거나 새 문서 "조각"을 만들 수있는 방법으로 사용할 수 있습니다. DOM 노드를 지속적으로 추가하는 대신 문서 조각을 사용한 후에 만 DOM 삽입 작업을 한 번만 수행하는 것이 좋습니다.
예를 들어, 요소에 20 개의 div를 추가하는 함수를 작성합니다. 매번 요소에 div를 추가하면 20 개의 리플 로우가 트리거됩니다.
함수 addDivs (요소) {var div; for (var i = 0; i <20; i ++) {div = document.createElement ( 'div'); div.innerhtml = 'heya!'; 요소. AppendChild (div); }}이 문제를 해결하기 위해 DocumentFragment를 대신 사용할 수 있으므로 한 번에 새 DIV를 추가 할 수 있습니다. 완료 후 DOM에 DocumentFragment를 추가하면 한 번만 반사율이 트리거됩니다.
함수 addDivs (요소) {var div; // 새로운 빈 DocumentFragment를 만듭니다. var fragment = document.createdOcumentFragment (); for (var i = 0; i <20; i ++) {div = document.createElement ( 'a'); div.innerhtml = 'heya!'; Fragment.AppendChild (div); } 요소 .appendChild (조각);}웹을 더 빠르게 만들고 JavaScript 메모리 최적화 및 메모리 누출을 찾으십시오.
JavaScript 메모리 누출을 발견하기 위해 Google Developers (Marja Hölttä 및 Jochen Eisinger)는 Chrome 개발자 도구와 함께 작동하는 도구를 개발하여 힙의 스냅 샷을 검색하고 메모리 누출의 원인을 감지하는 도구를 개발했습니다.
JavaScript 메모리 누출 감지 도구
이 도구를 사용하는 방법에 대한 완전한 기사가 있습니다. Memory Leak Detector Project 페이지로 이동하는 것이 좋습니다.
이러한 도구가 개발 도구에 통합되지 않은 이유를 알고 싶다면 두 가지 이유가 있습니다. 원래 Closure Library에서 특정 메모리 시나리오를 캡처하는 데 도움이되었으며, 이는 외부 도구로 더 적합합니다.
Chrome은 일부 플래그를 V8에 직접 전달하여 자세한 엔진 최적화 출력 결과를 지원합니다. 예를 들어 V8의 최적화를 추적 할 수 있습니다.
"/Applications/Google Chrome/Google Chrome"--js-flags = "-Trace-opt -Trace-Deopt"
Windows 사용자는 Chrome.exe JS-Flags = "Trace-OPT Trace-Deopt"를 실행할 수 있습니다.
응용 프로그램을 개발할 때 아래 V8 로고를 사용할 수 있습니다.
V8의 프로세싱 스크립트는 * (별표)를 사용하여 최적화 된 기능을 식별하고 최적화되지 않은 함수를 나타 내기 위해 ~ (Wavy)를 사용합니다.
V8의 로고와 V8의 인테리어 작동 방식에 대해 더 많이 배우고 싶다면 Vyacheslav Egorov의 V8 내부에 대한 우수한 게시물을 읽는 것이 좋습니다.
HRT (High Precision Time)는 시스템 시간 및 사용자 조정에 영향을 미치지 않는 하위 밀리 초 수준의 고정밀 시간 인터페이스입니다. 새로운 날짜 및 날짜보다 더 정확한 측정 방법으로 간주 될 수 있습니다 .now (). 이것은 우리가 벤치 마크를 쓰는 데 많은 도움이됩니다.
HRT (High-Precision Time)는 현재 밀리 초 시간 정확도를 제공합니다
현재 HRT는 Window.performance.webkitnow ()의 Chrome (안정 버전)에서 사용되지만 Prefix는 Chrome Canary에서 폐기되므로 Window.performance.now ()를 통해 호출 할 수 있습니다. Paul Irish는 HTML5rocks의 HRT에 대해 더 많이 게시했습니다.
현재 정확한 시간을 알았으므로 페이지 성능을 정확하게 측정 할 수있는 API가 있습니까? Well, now there is a Navigation Timing API that provides an easy way to get accurate and detailed time measurement records when web pages are loaded and presented to users. You can use window.performance.timing in console to get time information:
显示在控制台中的时间信息
我们可以从上面的数据获取很多有用的信息,例如网络延时为responseEnd fetchStart,页面加载时间为loadEventEnd responseEnd,处理导航和页面加载的时间为loadEventEnd navigationStart。
正如你所看到的,perfomance.memory的属性也能显示JavaScript的内存数据使用情况,如总的堆大小。
更多Navigation Timing API的细节,阅读Sam Dutton的Measuring Page Load Speed With Navigation Timing。
Chrome中的about:tracing提供了浏览器的性能视图,记录了Chrome的所有线程、tab页和进程。
About:Tracing提供了浏览器的性能视图
这个工具的真正用处是允许你捕获Chrome的运行数据,这样你就可以适当地调整JavaScript执行,或优化资源加载。
Lilli Thompson有一篇写给游戏开发者的使用about:tracing分析WebGL游戏的文章,同时也适合JavaScript的开发者。
在Chrome的导航栏里可以输入about:memory,同样十分实用,可以获得每个tab页的内存使用情况,对定位内存泄漏很有帮助。
我们看到, JavaScript的世界中有很多隐藏的陷阱,且并没有提升性能的银弹。只有把一些优化方案综合使用到(现实世界)测试环境,才能获得最大的性能收益。即便如此,了解引擎是如何解释和优化代码,可以帮助你调整应用程序。
测量,理解,修复。不断重复这个过程。
图片来源: Sally Hunter
谨记关注优化,但为了便利可以舍弃一些很小的优化。例如,有些开发者选择.forEach和Object.keys代替for和for..in循环,尽管这会更慢但使用更方便。要保证清醒的头脑,知道什么优化是需要的,什么优化是不需要的。
同时注意,虽然JavaScript引擎越来越快,但下一个真正的瓶颈是DOM。回流和重绘的减少也是重要的,所以必要时再去动DOM。还有就是要关注网络,HTTP请求是珍贵的,特别是移动终端上,因此要使用HTTP的缓存去减少资源的加载。
记住这几点可以保证你获取了本文的大部分信息,希望对你有所帮助!
原文:http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/
作者:Addy Osmani