C/C ++와 비교할 때, 우리가 사용하는 JavaScript에서 메모리에서 JavaScript를 처리하면 개발에서 비즈니스 논리 작성에 더 많은 관심을 기울였습니다. 그러나 비즈니스의 지속적인 복잡성, 단일 페이지 응용 프로그램 개발, 모바일 HTML5 애플리케이션, Node.js 프로그램 등의 개발, JavaScript의 메모리 문제로 인한 지연 및 메모리 오버플로는 더 이상 익숙하지 않습니다.
이 기사는 JavaScript의 언어 수준에서 메모리 사용 및 최적화에 대해 논의합니다. 모든 사람이 익숙하거나 약간 듣는 측면에서 모든 사람이 대부분의 시간을 눈치 채지 못하는 것까지, 우리는 그것들을 하나씩 분석 할 것입니다.
1. 언어 수준의 메모리 관리
1.1 범위
범위는 JavaScript 프로그래밍에서 매우 중요한 작동 메커니즘입니다. 동기식 JavaScript 프로그래밍에서 초보자의 관심을 끌지는 않지만 비동기 프로그래밍에서 우수한 범위 제어 기술은 JavaScript 개발자에게 필요한 기술이되었습니다. 또한 SCOPE는 JavaScript 메모리 관리에서 중요한 역할을합니다.
JavaScript에서는 성명서와 전역 스코프가 포함 된 기능을 호출 할 수 있습니다.
예를 들어 다음 코드에 표시된대로.
코드 사본은 다음과 같습니다.
var foo = function () {
var local = {};
};
foo ();
Console.log (로컬); // => 정의되지 않았습니다
var bar = function () {
로컬 = {};
};
술집();
Console.log (로컬); // => {}
여기서 우리는 foo () 함수와 bar () 함수를 정의합니다. 그들의 의도는 로컬이라는 변수를 정의하는 것입니다. 그러나 최종 결과는 완전히 다릅니다.
foo () 함수에서 var 문을 사용하여 로컬 변수가 정의되었다고 선언합니다. 범위가 함수 본문 내부에서 형성 되므로이 변수는 범위에 정의됩니다. 또한 foo () 함수에는 범위 확장 처리가 없으므로 함수가 실행 된 후 로컬 변수도 파괴됩니다. 그러나이 변수는 외부 범위에서 액세스 할 수 없습니다.
BAR () 함수에서 로컬 변수는 VAR 문을 사용하여 선언되지 않고 대신 로컬을 글로벌 변수로 직접 정의합니다. 따라서이 변수는 외부 범위로 액세스 할 수 있습니다.
코드 사본은 다음과 같습니다.
로컬 = {};
// 여기의 정의는 동일합니다
Global.local = {};
1.2 스코프 체인
JavaScript 프로그래밍에서는 스코프 체인의 일반적인 표현 인 다층 기능 중첩 시나리오를 확실히 만나게됩니다.
다음 코드에 표시된대로 :
코드 사본은 다음과 같습니다.
함수 foo () {
var val = 'Hello';
함수 바 () {
함수 baz () {
global.val = 'world;'
}
baz ();
Console.log (Val); // => 안녕하세요
}
술집();
}
foo ();
범위에 대한 이전 설명을 바탕으로 여기에있는 코드에 표시된 결과는 세계라고 생각할 수 있지만 실제 결과는 hello라고 생각할 수 있습니다. 많은 초보자들이 여기서 혼란스러워지기 시작할 것이므로이 코드가 어떻게 작동하는지 살펴 보겠습니다.
JavaScript 이후 변수 식별자 검색은 현재 범위에서 시작하여 전역 범위까지 바깥쪽으로 보입니다. 따라서 JavaScript 코드의 변수에 대한 액세스는 바깥쪽으로 만 수행 할 수 있지만 반대로 수행 할 수는 없습니다.
BAZ () 함수의 실행은 글로벌 범위에서 글로벌 변수 VAL을 정의합니다. BAR () 함수에서 식별자 VAL에 액세스 할 때 내부에서 외부로 검색하는 원리는 다음과 같습니다. 막대 함수의 범위에서 찾을 수없는 경우 이전 레이어, 즉 foo () 함수의 범위로 이동합니다.
그러나 모든 사람을 혼동하기위한 핵심은 다음과 같습니다.이 식별자 액세스는 foo () 함수의 범위에서 일치하는 변수를 찾아서 바깥쪽으로 계속 보이지 않으므로 BAZ () 함수에 정의 된 글로벌 변수 VAL 은이 변수 액세스에 영향을 미치지 않습니다.
1.3 폐쇄
JavaScript의 식별자 조회는 내부 원리를 따른다는 것을 알고 있습니다. 그러나 비즈니스 논리의 복잡성으로 인해 단일 배송 주문은 새로운 요구가 증가하는 것과는 거리가 멀다.
다음 코드를 살펴 보겠습니다.
코드 사본은 다음과 같습니다.
함수 foo () {
var local = 'Hello';
return function () {
로컬 리턴;
};
}
var bar = foo ();
Console.log (bar ()); // => 안녕하세요
외부 범위가 여기에 표시된대로 내부 범위에 액세스 할 수있는 기술은 폐쇄입니다. 고차 함수의 적용 덕분에 foo () 함수의 범위는 "확장"됩니다.
foo () 함수는 foo () 함수의 범위 내에 존재하는 익명 함수를 반환하므로 foo () 함수의 범위 내에서 로컬 변수에 액세스하고 참조를 저장할 수 있습니다. 이 함수는 로컬 변수를 직접 반환하기 때문에 Bar () 함수는 외부 범위에서 직접 실행하여 로컬 변수를 얻을 수 있습니다.
폐쇄는 JavaScript의 높은 수준의 기능이며,이를 사용하여 다양한 요구를 충족시키기 위해 점점 더 복잡한 효과를 달성 할 수 있습니다. 그러나 내부 변수 참조가있는 함수가 함수에서 나오기 때문에 내부 변수에 대한 모든 참조가 취소 될 때까지 함수가 실행 된 후에이 범위의 변수는 파괴되지 않습니다. 따라서 폐쇄를 적용하면 메모리가 쉽지 않게 만들 수 있습니다.
2. JavaScript의 메모리 재활용 메커니즘
여기에서는 Chrome 및 Node.js에서 사용하는 V8 엔진을 가져 와서 Google에서 JavaScript의 메모리 재활용 메커니즘을 간단히 소개하기 위해 Google에서 시작합니다. 보다 자세한 내용을 보려면 Good Friend Park Ling의 책 "학습을위한 심층적이고 이해하기 쉬운 node.js"를 구입할 수 있습니다.
V8에서는 모든 JavaScript 객체가 "힙"을 통해 할당됩니다.
코드에서 값을 선언하고 할당하면 V8은이 변수의 일부를 힙 메모리에 할당합니다. 요청 된 메모리 가이 변수를 저장하기에 불충분 한 경우 힙 크기가 V8의 메모리 제한에 도달 할 때까지 V8은 메모리를 계속 신청합니다. 기본적으로 V8의 힙 메모리의 상한은 64 비트 시스템에서 1464MB이고 32 비트 시스템에서는 약 1.4GB 및 0.7GB입니다.
또한 V8은 세대의 힙 메모리에서 JavaScript 객체를 새로운 세대와 구식 세대를 관리합니다. 새로운 세대는 임시 변수, 문자열 등과 같은 짧은 생존주기를 가진 JavaScript 객체입니다. 구식은 주 컨트롤러, 서버 객체 등과 같은 여러 쓰레기 수집 후 긴 생존주기를 가진 개체이지만.
쓰레기 재활용 알고리즘은 항상 프로그래밍 언어 개발의 중요한 부분이었으며 V8에 사용되는 쓰레기 재활용 알고리즘은 주로 다음과 같습니다.
1. Scavange 알고리즘 : 메모리 공간 관리는 주로 새로운 세대의 메모리 공간에서 사용되는 복사를 통해 수행됩니다.
2. 마크 스위프 알고리즘 및 마크 컴팩트 알고리즘 : 힙 메모리는 주로 구식 객체의 검사 및 재활용에 사용되는 마킹을 통해 정렬되고 재활용됩니다.
추신 : 관련 서적, 문서 및 소스 코드를 읽음으로써보다 자세한 V8 쓰레기 수집 구현을 배울 수 있습니다.
JavaScript 엔진이 어떤 물체를 재활용 할 것인지 살펴 보겠습니다.
2.1 범위 및 참조
초보자는 종종 함수가 실행되면 기능 내부에서 선언 된 객체가 파괴 될 것이라고 잘못 생각합니다. 그러나 실제로이 이해는 엄격하고 포괄적이지 않으며 혼동하기 쉽습니다.
참조는 JavaScript 프로그래밍에서 매우 중요한 메커니즘이지만 이상하게도 대부분의 개발자는 이에주의를 기울이지 않거나 이해하지 못할 것입니다. 참조는 초록 관계 "객체에 대한 코드 액세스"를 나타냅니다. C/C ++ 포인터와 다소 유사하지만 같은 것은 아닙니다. 참조는 또한 쓰레기 수집에서 JavaScript 엔진의 가장 중요한 메커니즘입니다.
다음 코드는 예입니다.
코드 사본은 다음과 같습니다.
// ......
var val = 'Hello World';
함수 foo () {
return function () {
반환 val;
};
}
Global.bar = foo ();
// ......
이 코드를 읽은 후 코드 의이 부분이 실행 된 후에도 어떤 객체가 여전히 생존하는지 알 수 있습니까?
관련 원칙에 따라이 코드에서 재활용 및 릴리스되지 않은 객체에는 Val 및 Bar ()가 포함됩니다. 정확히 재활용 할 수없는 이유는 무엇입니까?
JavaScript 엔진은 어떻게 쓰레기 수집을 수행합니까? 위에서 언급 한 쓰레기 수집 알고리즘은 재활용 중에 만 사용됩니다. 그렇다면 어떤 객체를 재활용 할 수 있고 어떤 개체가 계속 생존 해야하는지 어떻게 알 수 있습니까? 대답은 JavaScript 객체에 대한 참조입니다.
JavaScript 코드에서는 변수 이름을 아무것도하지 않고 단일 줄로 기록하더라도 JavaScript 엔진은 이것이 객체에 대한 액세스 동작이며 객체에 대한 참조가 있다고 생각합니다. 쓰레기 수집 동작이 프로그램 논리의 작동에 영향을 미치지 않도록하기 위해 JavaScript 엔진은 사용중인 물체를 재활용해서는 안됩니다. 그렇지 않으면 지저분합니다. 따라서 객체가 사용 중인지 판단하기위한 표준은 여전히 객체에 대한 참조가 있는지 여부입니다. 그러나 실제로 JavaScript 참조를 전송할 수 있기 때문에 이것은 타협입니다. 따라서 글로벌 범위로 일부 참조가 가져올 수 있지만 실제로는 더 이상 비즈니스 논리에서 액세스 할 필요는 없으며 재활용해야하지만 JavaScript 엔진은 여전히 프로그램에 여전히 필요하다고 믿습니다.
올바른 자세에서 변수와 참조를 사용하는 방법은 언어 수준에서 JavaScript를 최적화하는 데 핵심입니다.
3. JavaScript를 최적화하십시오
마침내 요점에 도달했습니다. 인내심으로 이것을 봐 주셔서 대단히 감사합니다. 위의 많은 소개 후에, 나는 당신이 JavaScript의 메모리 관리 메커니즘을 잘 이해하고 있다고 생각합니다. 그러면 다음 기술은 기분이 나아질 것입니다.
3.1 기능을 잘 활용하십시오
우수한 JavaScript 프로젝트를 읽는 습관이 있다면 프론트 엔드 JavaScript 코드를 개발할 때 많은 큰 사람들이 익명의 기능을 사용하여 코드의 가장 바깥층에 래핑한다는 것을 알게 될 것입니다.
코드 사본은 다음과 같습니다.
(기능() {
// 기본 비즈니스 코드
}) ();
일부는 더욱 진보되어 있습니다.
코드 사본은 다음과 같습니다.
; (함수 (WIN, DOC, $, UNDEFINED) {
// 기본 비즈니스 코드
}) (창, 문서, jQuery);
requirejs, seajs, ozjs 등과 같은 프론트 엔드 모듈 식 하중 솔루션조차도 모두 비슷한 형태를 채택합니다.
코드 사본은 다음과 같습니다.
// 요구 사항
정의 ([ 'jQuery'], function ($) {
// 기본 비즈니스 코드
});
// seajs
define ( 'module', [ 'dep', 'suddscore'], function ($, _) {
// 기본 비즈니스 코드
});
Node.js 오픈 소스 프로젝트의 많은 코드가 이런 식으로 처리되지 않는다고 말하면 잘못된 것입니다. 실제로 코드를 실행하기 전에 Node.js는 각 .js 파일을 다음 형식으로 랩핑합니다.
코드 사본은 다음과 같습니다.
(함수 (내보내기, 요구, 모듈, __Dirname, __filename) {
// 기본 비즈니스 코드
});
이 작업의 이점은 무엇입니까? 우리는 기사의 시작 부분에서 JavaScript가 진술과 글로벌 범위와 함께 범위를 가진 기능을 가질 수 있다고 말했습니다. 또한 글로벌 범위에 정의 된 객체는 프로세스가 종료 될 때까지 살아남을 수 있습니다. 큰 대상이라면 번거 롭습니다. 예를 들어, 일부 사람들은 JavaScript에서 템플릿을 렌더링하는 것을 좋아합니다.
코드 사본은 다음과 같습니다.
<? php
$ db = mysqli_connect (서버, 사용자, 암호, 'myapp');
$ topics = mysqli_query ($ db, "선택 *에서 주제에서 선택;");
?>
<! doctype html>
<html lang = "en">
<헤드>
<meta charset = "utf-8">
<title> 당신은 원숭이에 의해 초대 된 재미있는 사람입니까? </제목>
</head>
<body>
<ul id = "주제"> </ul>
<script type = "text/tmpl"id = "topic-tmpl">
<li>
<h1> <%= 제목%> </h1>
<p> <%= content%> </p>
</li>
</스크립트>
<script type = "text/javaScript">
var data = <? php echo json_encode ($ topics); ?>;
var topictmpl = docum
var render = function (tmlp, view) {
var 컴파일 = tmlp
.replace (// n/g, '// n')
.replace (/<%= ([/s/s]+?))%>/g, 함수 (일치, 코드) {
return ' " + Escape (' + code + ') +"';
});
컴파일 = [
'var res = "";',
'with (view || {}) {',
'res = "' + 컴파일 + '";',
'}',
'반환 res;'
] .join ( '/n');
var fn = new 함수 ( '보기', 컴파일);
반환 fn (보기);
};
var topics = document.querySelector ( '#주제');
함수 init ()
data.foreach (function (topic) {
topics.innerhtml += render (topictmpl, topic);
});
}
init ();
</스크립트>
</body>
</html>
이런 종류의 코드는 종종 초보자 작품에서 볼 수 있습니다. 여기서 문제는 무엇입니까? 데이터베이스에서 얻은 데이터 양이 매우 크면 프론트 엔드가 템플릿 렌더링을 완료 한 후에는 데이터 변수가 유휴 상태입니다. 그러나이 변수는 전역 범위에 정의되어 있으므로 JavaScript 엔진은 재활용 및 파괴되지 않습니다. 페이지가 닫힐 때까지 구식 힙 메모리에 계속 존재합니다.
그러나 우리가 매우 간단한 수정을하고 논리 코드 외부에서 함수 계층을 랩핑하면 효과가 매우 다릅니다. UI 렌더링이 완료되면 코드의 데이터 참조도 취소됩니다. 가장 바깥 함수가 실행되면 JavaScript 엔진이 객체를 확인하기 시작하고 데이터를 재활용 할 수 있습니다.
3.2 전역 변수를 정의하지 마십시오
변수가 글로벌 범위에서 정의 될 때 JavaScript 엔진은 기본적으로 재활용 및 파괴되지 않습니다. 페이지가 닫힐 때까지 구식 힙 메모리에 계속 존재합니다.
그런 다음 우리는 항상 원칙을 따랐습니다. 전역 변수를 사용하지 마십시오. 글로벌 변수는 실제로 개발하기가 매우 쉽지만 글로벌 변수로 인한 문제는 편의보다 훨씬 심각합니다.
변수를 재활용 할 가능성이 적게 만듭니다.
1. 여러 사람이 협력 할 때 혼란이 쉽게 발생합니다.
2. 스코프 체인에 간섭하기가 쉽습니다.
3. 위의 래핑 함수와 함께 래핑 함수를 통해 "글로벌 변수"를 처리 할 수도 있습니다.
3.3 수동으로 회의 변수
비즈니스 코드에서 변수가 필요하지 않은 경우 변수를 재활용하도록 변수를 수동으로 해석 할 수 있습니다.
코드 사본은 다음과 같습니다.
var data = { / * 일부 빅 데이터 * /};
// blah blah blah
데이터 = null;
3.4 콜백을 잘 활용하십시오
내부 가변 액세스에 클로저를 사용하는 것 외에도 비즈니스 처리에 현재 인기있는 콜백 기능을 사용할 수도 있습니다.
코드 사본은 다음과 같습니다.
함수 getData (콜백) {
var data = '일부 빅 데이터';
콜백 (NULL, 데이터);
}
getData (function (err, data) {
Console.log (데이터);
콜백 기능은 CPS (Continuation Passing Style)의 기술입니다. 이 스타일의 프로그래밍은 기능의 비즈니스 초점을 반환 값에서 콜백 함수로 전송합니다. 그리고 폐쇄에 대한 많은 이점이 있습니다.
1. 전달 된 매개 변수가 기본 유형 (예 : 문자열, 숫자 값) 인 경우 콜백 함수에서 전달 된 공식 매개 변수는 복사 값이되며 비즈니스 코드를 사용한 후에는 재활용하기가 더 쉬워집니다.
2. 콜백을 통해 동기식 요청을 완료하는 것 외에도 비동기 프로그래밍에서도 사용할 수 있습니다. 이는 현재 매우 인기있는 작문 스타일입니다.
3. 콜백 함수 자체는 일반적으로 임시 익명 함수입니다. 요청 함수가 실행되면 콜백 함수 자체에 대한 참조가 취소되고 재활용됩니다.
3.5 좋은 폐쇄 관리
우리의 비즈니스 요구 (예 : 원형 이벤트 바인딩, 개인 속성, 인수가있는 콜백 등)가 클로저를 사용해야하는 경우 세부 사항에주의하십시오.
루프 바인딩 이벤트는 JavaScript 폐쇄를 시작하기위한 강제 과정이라고 할 수 있습니다. 시나리오를 가정 해 봅시다. 6 개의 이벤트에 해당하는 6 개의 버튼이 있습니다. 사용자가 버튼을 클릭하면 해당 이벤트가 지정된 장소에서 출력됩니다.
코드 사본은 다음과 같습니다.
var btns = document.querySelectorall ( '. btn'); // 6 요소
var output = document.querySelector ( '#output');
var 사건 = [1, 2, 3, 4, 5, 6];
// 사례 1
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = function (evt) {
output.innertext + = '클릭' + 이벤트 [i];
};
}
// 사례 2
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (function (index) {
반환 기능 (EVT) {
output.innertext + = 'clicked' + events [index];
};
})(나);
}
// 사례 3
for (var i = 0; i <btns.length; i ++) {
btns [i] .onclick = (function (event) {
반환 기능 (EVT) {
output.innerText + = '클릭' + 이벤트;
};
}) (이벤트 [i]);
}
여기서 첫 번째 솔루션은 분명히 일반적인 루프 바인딩 이벤트 오류입니다. 여기서 자세히 설명하지 않겠습니다. 네티즌에 대한 나의 대답을 자세히 언급 할 수 있습니다. 두 번째와 세 번째 솔루션의 차이는 폐쇄에 전달 된 매개 변수에 있습니다.
두 번째 구성표에서 전달되는 매개 변수는 전류 루프 첨자이며, 후자는 해당 이벤트 객체로 직접 전달됩니다. 실제로, 후자는 많은 양의 데이터 애플리케이션에 더 적합합니다. JavaScript 기능 프로그래밍에서 함수 호출에 전달 된 매개 변수는 기본 유형 객체이므로 기능 본체에서 얻은 공식 매개 변수는 복사 값 이므로이 값은 함수 본체의 스코프 내에서 로컬 변수로 정의됩니다. 이벤트 바인딩이 완료된 후, 이벤트 변수는 외부 범위의 메모리 사용량을 줄이기 위해 수동으로 해석 될 수 있습니다. 또한 요소가 삭제되면 해당 이벤트 청취 함수, 이벤트 객체 및 클로저 기능도 파괴 및 재활용됩니다.
3.6 메모리는 캐시가 아닙니다
비즈니스 개발에서 캐싱의 역할은 중요한 역할을하며 시공간 자원의 부담을 줄일 수 있습니다. 그러나 메모리를 캐시로 쉽게 사용해서는 안됩니다. 메모리는 모든 프로그램 개발을위한 모든 토지의 일입니다. 그것이 매우 중요한 자원이 아닌 경우, 직접 메모리에 넣거나 만료 캐시를 자동으로 파괴하기 위해 만료 메커니즘을 공식화하지 마십시오.
4. JavaScript의 메모리 사용량을 확인하십시오
매일 개발에서는 일부 도구를 사용하여 JavaScript에서 메모리 사용량을 분석하고 문제를 해결할 수 있습니다.
4.1 BLINK/WEBKIT 브라우저
Blink/WebKit 브라우저 (Chrome, Safari, Opera 등)에서 개발자 도구의 프로파일 도구를 사용하여 프로그램에서 메모리 검사를 수행 할 수 있습니다.
4.2 Node.js에서 메모리 검사
Node.js에서는 메모리 검사에 Node-HeapDump 및 Node-Memwatch 모듈을 사용할 수 있습니다.
코드 사본은 다음과 같습니다.
var heapdump = require ( 'heapdump');
var fs = 요구 ( 'fs');
var path = 요구 ( '경로');
fs.writefilesync (path.join (__ dirname, 'app.pid'), process.pid);
// ...
코드 사본은 다음과 같습니다. <span style = "Font-Family : Georgia, 'Times New Roman', 'Bitstream Charter', Times, Serif; Font-Size : 14px; Line-Height : 1.5EM;"> Business Code에 Node-HeapDump를 도입 한 후 Node.js 프로세스에 sigusr2를 보내야합니다. 힙 메모리. </span>
다음과 같이 코드를 복사하십시오. $ kill -usr2 (cat app.pid)
이런 식으로 파일 디렉토리에 heapdump- <sec>. <usec> .HeapSnapShot 형식으로 이름이 지정된 스냅 샷 파일이 있습니다. 브라우저의 개발자 도구에서 프로파일 도구를 사용하여 열고 확인할 수 있습니다.
5. 요약
이 기사는 곧 다시 올 것입니다. 이 공유는 주로 다음 내용을 보여줍니다.
1. JavaScript는 언어 수준에서 메모리 사용과 밀접한 관련이 있습니다.
2. JavaScript의 메모리 관리 및 재활용 메커니즘;
3. 생성 된 JavaScript가 더 확장되고 활력이 될 수 있도록 메모리를보다 효율적으로 사용하는 방법;
4. 메모리 문제가 발생할 때 메모리 검사를 수행하는 방법.
이 기사를 배우면 어머니가 편안하게 느끼고 상사가 편안하게 느끼도록 더 나은 JavaScript 코드를 만들 수 있기를 바랍니다.