>웹 프론트엔드 >JS 튜토리얼 >JavaScript는 메모리 최적화_javascript 기술에 대해서도 이야기합니다.

JavaScript는 메모리 최적화_javascript 기술에 대해서도 이야기합니다.

WBOY
WBOY원래의
2016-05-16 16:45:591056검색

C/C에 비해 우리가 사용하는 JavaScript의 메모리 처리 덕분에 개발 과정에서 비즈니스 로직 작성에 더 집중할 수 있었습니다. 그러나 비즈니스의 지속적인 복잡성과 단일 페이지 애플리케이션, 모바일 HTML5 애플리케이션, Node.js 프로그램 등의 개발로 인해 JavaScript의 메모리 문제로 인한 지연 및 메모리 오버플로와 같은 현상은 더 이상 낯설지 않습니다.

이 기사에서는 JavaScript 언어 수준의 메모리 사용 및 최적화에 대해 설명합니다. 누구나 익숙하거나 조금이라도 들어본 부분부터 대부분의 사람들이 인지하지 못하는 부분까지 하나씩 분석해보겠습니다.

1. 언어별 메모리 관리

1.1 범위

범위는 JavaScript 프로그래밍에서 매우 중요한 작동 메커니즘입니다. 동기식 JavaScript 프로그래밍에서는 초보자의 관심을 완전히 끌지는 못하지만, 비동기식 프로그래밍에서는 좋은 범위 제어 기술이 JavaScript 개발에 필요한 기술이 됩니다. 또한 범위는 JavaScript 메모리 관리에서 중요한 역할을 합니다.

JavaScript에서는 명령문과 전역 범위를 사용하여 함수 호출을 통해 범위를 구성할 수 있습니다.

다음 코드를 예로 들어 보겠습니다.

코드 복사 코드는 다음과 같습니다.

var foo = function() {
var local = {};
};
foo();
console.log(local) //=>
var bar = function() {
local = {};
};
bar();
console.log(local) //=>

여기서 foo() 함수와 bar() 함수를 정의합니다. 그들의 의도는 local이라는 변수를 정의하는 것입니다. 그러나 최종 결과는 완전히 달랐습니다.

foo() 함수에서는 var 문을 사용하여 지역 변수를 선언하고 정의합니다. 범위는 함수 본문 내부에 형성되므로 이 변수는 범위에 정의됩니다. 게다가 foo() 함수의 본문에서는 범위 확장 처리를 수행하지 않으므로 함수가 실행된 후 지역 변수도 소멸됩니다. 외부 범위에서는 변수에 액세스할 수 없습니다.

bar() 함수에서는 var 문을 사용하여 지역 변수를 선언하지 않고 전역 변수로 직접 정의합니다. 따라서 외부 범위에서 이 변수에 액세스할 수 있습니다.


local = {};
/ / 여기서 정의는
global.local = {};



1.2 Scope Chain

과 동일합니다. JavaScript 프로그래밍에서는 확실히 여러 수준의 함수 중첩이 있는 시나리오를 접할 때 이는 일반적인 범위 체인을 나타냅니다.
다음 코드와 같습니다.


코드를 복사합니다 코드는 다음과 같습니다.
function foo( ) {
var val = 'hello';

function bar() {
function baz() {
global.val = 'world;'
}
baz ();
console.log(val); //=> 안녕하세요
}
bar();
}
foo();


범위에 대한 이전 설명을 토대로 여기 코드에서 표시하는 결과는 world라고 생각할 수 있지만 실제 결과는 hello입니다. 많은 초보자들이 여기서 혼란스러워하기 시작할 것이므로 이 코드가 어떻게 작동하는지 살펴보겠습니다.

자바스크립트에서는 변수 식별자 검색이 현재 범위에서 시작하여 전역 범위까지 검색되기 때문입니다. 따라서 JavaScript 코드의 변수에 대한 액세스는 외부에서만 수행할 수 있으며 반대 방향으로는 수행할 수 없습니다.

JavaScript는 메모리 최적화_javascript 기술에 대해서도 이야기합니다.baz() 함수의 실행은 전역 범위에서 전역 변수 val을 정의합니다. bar() 함수에서 식별자 val에 접근할 때 검색 원칙은 내부에서 외부로입니다. bar 함수의 범위에서 발견되지 않으면 상위 수준, 즉 foo의 범위로 이동합니다. () 기능을 검색합니다.

그러나 모두를 혼란스럽게 만드는 핵심은 여기에 있습니다. 이 식별자 액세스는 foo() 함수 범위에서 일치하는 변수를 찾고 외부 검색을 계속하지 않으므로 baz() 함수에서 정의된 전역 변수 val은 이 변수 ​​액세스에 영향을 미치지 않습니다.

1.3 폐쇄

우리는 JavaScript의 식별자 조회가 내부 원칙을 따른다는 것을 알고 있습니다. 그러나 비즈니스 로직이 복잡하기 때문에 단일 전달 순서로는 증가하는 새로운 요구 사항을 충족하기가 어렵습니다.

먼저 다음 코드를 살펴보겠습니다.


코드 복사 코드는 다음과 같습니다.

function foo() {
var local = 'Hello';
return function() {
return local;
};
}
var bar = foo();
console.log(bar()) //=> 안녕하세요

여기서 외부 스코프가 내부 스코프에 접근할 수 있도록 하는 기술이 클로저(Closure)입니다. 고차 함수의 적용 덕분에 foo() 함수의 범위가 "확장"되었습니다.

foo() 함수는 foo() 함수 범위에 존재하는 익명 함수를 반환하므로 foo() 함수 범위에 있는 지역 변수에 액세스하고 해당 참조를 저장할 수 있습니다. 이 함수는 지역 변수를 직접 반환하므로 bar() 함수를 외부 범위에서 직접 실행하여 지역 변수를 얻을 수 있습니다.

클로저는 JavaScript의 고급 기능으로 다양한 요구 사항을 충족하기 위해 더 복잡한 효과를 얻을 수 있습니다. 그러나 내부 변수 참조가 있는 함수는 함수에서 제거되기 때문에 내부 변수에 대한 모든 참조가 해제될 때까지 함수가 실행된 후 범위 내의 변수가 반드시 소멸되지는 않는다는 점에 유의해야 합니다. 따라서 클로저를 적용하면 쉽게 메모리를 해제할 수 없게 될 수 있습니다.

2. 자바스크립트 메모리 재활용 메커니즘

여기서는 JavaScript의 메모리 재활용 메커니즘을 간략하게 소개하기 위해 Chrome과 Node.js에서 사용되는 Google에서 출시한 V8 엔진을 예로 들어 보겠습니다. 더 자세한 내용을 보려면 내 좋은 친구 Pu Ling의 책 "Speaking in"을 구입하세요. 간단하고 심층적인 언어 "Node.js"를 학습하세요. "메모리 제어" 장에 꽤 자세한 소개가 있습니다.

V8에서는 모든 JavaScript 객체에 "힙"을 통해 메모리가 할당됩니다.

JavaScript는 메모리 최적화_javascript 기술에 대해서도 이야기합니다.

코드에서 변수를 선언하고 값을 할당하면 V8은 힙 메모리의 일부를 변수에 할당합니다. 할당된 메모리가 이 변수를 저장할 만큼 충분하지 않으면 V8은 힙 크기가 V8 메모리 제한에 도달할 때까지 계속해서 메모리를 적용합니다. 기본적으로 V8의 힙 메모리 크기 상한은 64비트 시스템에서 1464MB, 32비트 시스템에서 732MB로 약 1.4GB와 0.7GB입니다.

또한 V8은 힙 메모리의 JavaScript 개체를 세대별로(신세대와 구세대) 관리합니다. 새로운 세대는 임시 변수, 문자열 등과 같이 수명 주기가 짧은 JavaScript 객체를 의미하는 반면, 이전 세대는 메인 컨트롤러, 서버 객체와 같이 여러 번의 가비지 수집에서 살아남고 수명 주기가 긴 객체를 나타냅니다. , 등.

가비지 컬렉션 알고리즘은 항상 프로그래밍 언어 개발에 중요한 부분을 차지해 왔으며, V8에서 사용되는 가비지 컬렉션 알고리즘은 주로 다음과 같습니다.

1. Scavange 알고리즘: 복사를 통한 메모리 공간 관리, 주로 신세대의 메모리 공간에 사용됨
2. Mark-Sweep 알고리즘 및 Mark-Compact 알고리즘: 마킹을 통한 힙 메모리 정리 및 정리; 주로 구세대 물체의 검사 및 재활용에 사용됩니다.


PS: 관련 책, 문서 및 소스 코드를 읽으면 보다 자세한 V8 가비지 수집 구현을 배울 수 있습니다.

자바스크립트 엔진이 어떤 상황에서 어떤 객체를 재활용하는지 살펴보겠습니다.

2.1 범위 및 참고

초보자들은 함수 실행이 완료되면 함수 내부에 선언된 객체가 소멸될 것이라고 잘못 생각하는 경우가 많습니다. 그러나 실제로 이러한 이해는 엄격하고 포괄적이지 않으며 쉽게 혼란을 초래할 수 있습니다.

참조는 JavaScript 프로그래밍에서 매우 중요한 메커니즘이지만 이상한 점은 대부분의 개발자가 참조에 관심을 두지 않거나 심지어 이해하지도 않는다는 것입니다. 참조는 "객체에 대한 코드 액세스"의 추상 관계를 나타냅니다. 이는 C/C 포인터와 다소 유사하지만 동일한 것은 아닙니다. 참조는 JavaScript 엔진의 가비지 수집을 위한 가장 중요한 메커니즘이기도 합니다.

다음 코드를 예로 들어 보겠습니다.

코드 복사 코드는 다음과 같습니다.

// ......
var val = 'hello world';
function foo() {
return function() {
return val;
};
}
global.bar = foo();
// ......

이 코드를 읽은 후, 코드의 이 부분이 실행된 후에도 어떤 객체가 아직 살아 있는지 알 수 있나요?

관련 원칙에 따르면 이 코드에서 재활용되지 않은 객체에는 val 및 bar()가 포함되어 있으며 재활용할 수 없는 이유는 무엇입니까?

JavaScript 엔진은 어떻게 가비지 수집을 수행하나요? 앞서 언급한 가비지 수집 알고리즘은 재활용 중에만 사용되는데, 어떤 객체가 재활용될 수 있는지, 어떤 객체가 계속해서 살아남아야 하는지 어떻게 알 수 있을까요? 대답은 JavaScript 개체에 대한 참조입니다.

자바스크립트 코드에서는 아무런 연산을 하지 않고 단순히 변수 이름을 별도의 줄로 작성하더라도 자바스크립트 엔진은 이를 객체에 대한 접근 동작으로 간주하고 객체에 대한 참조가 있습니다. 가비지 수집 동작이 프로그램 로직의 작동에 영향을 미치지 않도록 하기 위해 JavaScript 엔진은 사용 중인 객체를 재활용해서는 안 됩니다. 그렇지 않으면 혼란스러울 것입니다. 따라서 객체의 사용 여부를 판단하는 기준은 해당 객체에 대한 참조가 아직 존재하는지 여부입니다. 그러나 실제로 이는 JavaScript 참조가 전송될 수 있고 일부 참조가 전역 범위로 가져올 수 있기 때문에 절충안입니다. 그러나 실제로는 비즈니스 논리에서 이를 수정할 필요가 없으며 일단 액세스하면 재활용되어야 합니다. 그러나 JavaScript 엔진은 프로그램에 여전히 그것이 필요하다고 굳게 믿습니다.

변수와 참조를 올바르게 사용하는 방법은 언어 수준에서 JavaScript를 최적화하는 열쇠입니다.

3. 자바스크립트를 최적화하세요

마지막으로 이 글을 읽어주셔서 진심으로 감사드립니다. 위의 모든 소개를 마치셨다면, 여러분은 이미 JavaScript의 메모리 관리 메커니즘을 잘 이해하고 계실 것입니다. .

3.1 기능을 잘 활용하세요

훌륭한 JavaScript 프로젝트를 읽는 습관이 있다면 많은 전문가가 프런트엔드 JavaScript 코드를 개발할 때 익명 함수를 사용하여 코드의 가장 바깥쪽 레이어를 래핑하는 경우가 많다는 것을 알게 될 것입니다.

코드 복사 코드는 다음과 같습니다.

(function() {
/ / 주요 사업 코드
})();

일부는 더욱 고급입니다:
코드 복사 코드는 다음과 같습니다.

;(function(win, doc, $, undefine) {
// 주요 업무 코드
})(window, document, jQuery);

RequireJS, SeaJS, OzJS 등과 같은 프런트엔드 모듈러 로딩 솔루션도 비슷한 형식을 채택합니다.
코드 복사 코드는 다음과 같습니다.

// RequireJS
define(['jquery'], function($) {
// 주요 비즈니스 코드
});

// SeaJS
define('module', ['dep', 'underscore'], function($, _) {
// 주요 비즈니스 코드
});

많은 Node.js 오픈소스 프로젝트의 코드가 이런 방식으로 처리되지 않는다고 한다면 틀린 말입니다. 실제로 코드를 실행하기 전에 Node.js는 각 .js 파일을 다음 형식으로 패키징합니다.
코드 복사 코드는 다음과 같습니다. 다음과 같습니다:

(function(exports, require, module, __dirname, __filename) {
// 주요 비즈니스 코드
});

이렇게 하면 어떤 이점이 있나요? 기사 시작 부분에서 언급했듯이 JavaScript의 범위에는 명령문 및 전역 범위가 있는 함수 호출이 포함된다는 것을 모두 알고 있습니다. 그리고 전역 범위에 정의된 객체는 프로세스가 종료될 때까지 살아남을 가능성이 크다는 것도 알고 있습니다. 예를 들어, 일부 사람들은 JavaScript로 템플릿 렌더링을 수행하는 것을 좋아합니다.

코드 복사 코드는 다음과 같습니다.
 
  $db = mysqli_connect(서버, 사용자, 비밀번호, 'myapp');
  $topics = mysqli_query($db, "SELECT * FROM topic;");
?>



 
  你是猴子请来的逗比么?


 
    < ;/ul>
     
     

这种代码는 새로운 수작업을 하는 회사 중 하나입니다.完成模板渲染以后, data变weight便被闲置에서一边.可因为这个变weight是被定义는현재완전히작동하고있으며, 所以JavaScript引擎不会将其回收销毁。堆内存中,直到页면被关闭。

可是如果我们改, 这样效果就大不同了.当UI渲染完成之后, 代码对데이터 활용 사례는 외부에서 볼 수 있습니다.层函数执行完毕时,JavaScript引擎就开始对其中的对象进行检查,data也就可以随之被回收。

3.2 绝对不要定义全局变weight

我们刚才也谈到了,当一个变weight被 정정义에서 전체局작용域中,默认情况下JavaScript引擎就不会将其回收销毁。如此该变weight就会一直存在于老生代堆内存中,直到页face被关闭。

저희는 那么我们就一直遵循一个原则:绝对不要使사용 전체局变weight입니다.题远比其所带来的方便更严寥。

使变weight不易被回收;
1.多人协作时容易产生淆;
2.현재 작업용域链中容易被干扰。
3.配合上 上 包装函数,저는们也可以通过包装函数来处理 『전체局变weight』。

3.3 핸디캡 사용

如果在业务代码中,一个变weight已经确切是不再需要了,那么就可以使其被回收.

复主代码 代码如下:

var data = { /* 일부 빅 데이터 */ } ;
// 어쩌구 저쩌구
data = null;

3.4 善用回调

除了使用闭包进行内part变weight访问,我们还可以使用现十分流行的回调函数来进行业务处理。

复代码 代码如下:

function getData(callback) {
var data = '일부 빅 데이터';

callback(null, data);
}

getData(function (오류, 데이터) {
console.log(data);

콜백 함수는 CPS(Continuation Passing Style) 기술로, 이 스타일의 프로그래밍은 함수의 비즈니스 초점을 반환 값에서 콜백 함수로 전환합니다. 그리고 클로저에 비해 많은 장점이 있습니다:

1. 전달된 매개변수가 기본 유형(예: 문자열, 숫자 값)인 경우 콜백 함수에 전달된 형식 매개변수는 비즈니스 코드가 사용된 후 복사되기 쉽습니다. >2. 콜백을 통해 동기 요청을 완료하는 것 외에도 현재 매우 인기 있는 작성 스타일인 비동기 프로그래밍에서도 사용할 수 있습니다.
3. 콜백 함수 자체는 일반적으로 요청된 후에는 임시 익명 함수입니다. 함수가 실행되면 콜백 함수 자체의 참조가 해제되고 자체가 재활용됩니다.

3.5 폐쇄관리 우수

비즈니스 요구 사항(예: 루프 이벤트 바인딩, 개인 속성, 매개변수 포함 콜백 등)에서 클로저를 사용해야 하는 경우 세부 사항에 주의하세요.

루프 바인딩 이벤트는 JavaScript 클로저를 시작하기 위한 필수 과정이라고 할 수 있습니다. 사용자가 버튼을 클릭하면 6가지 유형의 이벤트에 해당하는 6개의 버튼이 있다고 가정합니다. 지정된 장소에서.


코드 복사 코드는 다음과 같습니다.
var btns = document.querySelectorAll ('.btn'); // 6개 요소
var output = document.querySelector('#output');
var events = [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) {
return function(evt) {
output.innerText = 'Clicked' events[index];
};
})(i);
}

// 사례 3
for (var i = 0; i < btns.length; i ) {
btns[i].onclick = (function(event) {
반환 함수(evt) {
       output.innerText = '클릭됨' event;
    };
   })(events[i]);
}

여기서 첫 번째 해결 방법은 분명히 일반적인 루프 바인딩 이벤트 오류입니다. 자세한 내용은 제가 네티즌에게 제공한 답변을 참조하세요. 두 번째 해결 방법과 세 번째 해결 방법의 차이점은 다음과 같습니다. 들어오는 클로저 매개변수에서.

두 번째 솔루션에 전달된 매개변수는 현재 루프 첨자이고, 후자는 해당 이벤트 개체를 직접 전달합니다. 실제로 후자가 대규모 데이터 애플리케이션에 더 적합합니다. 왜냐하면 JavaScript 함수형 프로그래밍에서는 함수 호출 시 전달되는 매개변수가 기본 유형 객체이므로 함수 본문에서 얻은 형식 매개변수는 복사된 값이 되기 때문입니다. 따라서 이 값은 함수 본문 범위에서 지역 변수로 정의됩니다. 이벤트 바인딩을 완료한 후 이벤트 변수를 수동으로 역참조하여 외부 범위에서 메모리 사용량을 줄일 수 있습니다. 그리고 요소가 삭제되면 해당 이벤트 청취 함수, 이벤트 객체, 클로저 함수도 소멸되어 재활용됩니다.

3.6 메모리는 캐시가 아니다

캐싱은 비즈니스 개발에 중요한 역할을 하며 시간과 공간 자원의 부담을 줄일 수 있습니다. 그러나 메모리를 캐시로 쉽게 사용하지 않는다는 점에 유의해야 합니다. 메모리는 모든 프로그램 개발에 귀중한 리소스입니다. 매우 중요한 리소스가 아닌 경우 메모리에 직접 배치하지 않거나 만료된 캐시를 자동으로 삭제하는 만료 메커니즘을 개발하지 마십시오.

4. 자바스크립트 메모리 사용량 확인

일상 개발에서는 일부 도구를 사용하여 JavaScript의 메모리 사용량을 분석하고 문제를 해결할 수도 있습니다.

4.1 블링크 / 웹킷 브라우저

Blink/Webkit 브라우저(Chrome, Safari, Opera 등)에서는 개발자 도구의 프로필 도구를 사용하여 프로그램의 메모리를 확인할 수 있습니다.


4.2 Node.js에서 메모리 검사

Node.js에서는 메모리 검사를 위해 node-heapdump 및 node-memwatch 모듈을 사용할 수 있습니다.


코드 복사 코드는 다음과 같습니다.
var heapdump = require('heapdump') ;
var fs = require('fs');
var path = require('path');
fs.writeFileSync(path.join(__dirname, 'app.pid'), process.pid );
// ...

코드 복사 코드는 다음과 같습니다.
비즈니스 코드에 node-heapdump를 도입한 후 Node.js로 데이터를 보내야 합니다. 특정 런타임에 프로세스는 node-heapdump가 힙 메모리의 스냅샷을 찍을 수 있도록 SIGUSR2 신호를 보냅니다.

코드 복사 코드는 다음과 같습니다.
$ kill -USR2 (고양이 앱 .pid)

이렇게 하면 파일 디렉터리에 heapdump-..heapsnapshot 형식의 스냅샷 파일이 있게 됩니다. 브라우저 개발자 도구의 프로필 도구를 사용하여 열 수 있습니다. 그리고 확인해보세요.

5. 요약

글의 끝이 곧 다가왔습니다. 이번 공유는 주로 다음 사항을 보여줍니다.

1. JavaScript는 언어 수준에서 메모리 사용과 밀접한 관련이 있습니다.
2. JavaScript의 메모리 관리 및 재활용 메커니즘
3. 생성된 JavaScript가 더 활력을 가질 수 있도록 하는 방법.
4. 메모리 문제 발생 시 메모리 검사를 수행하는 방법.

이 기사를 연구하여 어머니와 상사를 안심시킬 수 있는 더 나은 JavaScript 코드를 생성할 수 있기를 바랍니다.

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.