ホームページ  >  記事  >  ウェブフロントエンド  >  jsでのメモリの使用法は何ですか? jsメモリの役割を理解する

jsでのメモリの使用法は何ですか? jsメモリの役割を理解する

不言
不言オリジナル
2018-08-16 15:09:251460ブラウズ

この記事の内容は、js でのメモリの使用とは何ですか? js メモリの役割を理解する上で、参考にしていただければ幸いです。

JS 開発のプロセスにおいて、JS メモリのメカニズムを理解することは、開発者が自分が書いたコードの実行中に何が起こったのかを明確に理解するのに役立ち、また、プロジェクトのコード品質を向上させることもできます。 JSでの保存は元の値と参照値に分かれています:

元の値:元のデータ型:unknownnullnumberstringboolean、および es6 に新しく追加された symbol

  • 引用符の値: object code>、arrayfunction などの種類の値は参考値です

undefinednullnumberstringboolean以及es6新加入的symbol.
  • 引用值: objectarrayfunction等类型的值便是引用值.

  • JS中的内存也分为栈内存和堆内存. 堆与栈 详细了解查看这里.
    eg:

    const str = '我是说明内存的文档'; // 这里 str 以及 '我的说明内存的文档' 都存储在栈内存当中
    const obj = { a: 1 }; // 这里 obj(指向存储在堆中的数据的指针) 是存储在栈内存 而 { a: 1 } 则存储在堆当中

    内存中的存储对象声明周期是怎么样的呢?

    MDN中的介绍:

    1. 当对象将被需要的时候为其分配内存.

    2. 使用已分配的内存(读、写操作)

    3. 当对象不再被需要的时候, 释放存储这个对象的内存

    1 2在所有语言中都是一样的, 3在JS当中不是那么明显

    看看内存中发生了什么?

    let str1 = 1; // 为str1分配栈内存 str1: 1
    let str2 = str1; // 原始类型直接访问值, 即为str2新分配栈内存: str2: 1
    
    str2 = 2; // 栈内存: str2: 2. str2的值为2, 而str1的值仍然是1
    
    /************************ 我是分割线: 上面为原始类型 下面为复杂类型 *******************************/
    
    let obj1 = { a: 1 }; // 为obj1分为栈内存访问地址的指针: obj1. 堆内存中存储对象值: { a: 1 }
    let obj2 = obj1; // 为obj2分配栈内存访问地址的指针: obj2. 引用了堆内存中的值{ a: 1 }
    
    obj2.a = 2; // 通过obj1修改堆内存的数据, 由于obj2与obj2都是指向堆内存中的同一个数据的指针(也叫引用). 所以堆内存中的值{a: 1}修改为{a: 2} 即 obj1.a 为 2; obj2.a 也为 2; (这里它们是指向了堆内存中的同一个数据的不同指针)
    
    obj2 = { a: 3 }; // 因为改的是整个对象, 这里会在堆内存中创建一个新的对象值: {a:3}, 而obj2引用的是这个新对象, 所以obj1.a 依旧为 2; 而obj2.a 则为 3了. (这里它们是指向了堆内存中的不同数据的不同的指针)

    然后看看这个问题:

    let a = { n: 1 };
    let b = a;
    a.x = a = { n: 2 };

    具体查看详细解释, 对理解基础知识点还是很有帮助的. 例如: js的赋值运算顺序永远都是从右往左的,但是.是优先级最高的运算符.

    从内存角度看函数传值的变化

    关于传值/址的解说. 用原始类型和引用类型来区分. 原始类型传的是值, 引用类型传的则为址.

    let str = '我是初始字符串';
    let fn = (arg) => {
        console.log(arg); // #1 我是初始字符串
    
        arg = '我是修改后的字符串';
        console.log(arg); // #2 我是修改后的字符串
        console.log(str); // #3 我是初始字符串
    };
    fn(str);

    上面例子#1可以看到传入fn的是str的值, 在栈内存中分配了新的空间来保存函数参数和其值(函数运行后自动释放这部分内存, _垃圾回收机制_). 所以在#2出输出的值为我是修改后的字符串. 在调用函数fn时给参数arg传了值(在栈内存中新分配的数据), 而str又为原始类型. 在#3处输出与初始化定义保持一致.

    let obj = { a: 1 };
    let fn = (arg) => {
        arg = { a: 2 };
    };
    
    fn(obj);
    // 这个时候obj还是{a: 1}
    
    let fn1 = (arg) => {
        arg.a = 2;
    };
    fn1(obj);
    // 这个时候obj则为{a: 2}

    上面这个例子中的两个函数都是传址, 起初传入的参数arg都是引用(指向堆内存中的同一个数据的指针), 在fn中重新为变量arg赋值新的对象(引用类型). 而在fn1中的arg依旧是引用(指向堆内存中数据的指针), 所以fn1中是修改成功的.

    垃圾回收机制

    JS具有垃圾回收机制, 这给开发人员带来了极大的方便, 至少不用太考虑内存释放的问题(有部分还是要考虑的).

    • 函数的变量只在函数执行过程中存在. 在函数执行过程中, 函数内部的变量将会在内存中分配一定的空间, 当函数执行完毕后, 自动将这些变量从内存中释放, 以留出空间作其它用处.

    • 当内存中某个变量不再被引用, JS将清理掉这部分内存的分配. eg:

    let obj = { a: 1 }; // 内存中存在{a: 1}对象, 以及obj这个引用地址
    obj = { a: 2 }; // 垃圾回收机制自动清理{a: 1}, 并为新的有用的{a: 2}分配空间

    内存优化

    就全局变量而言, JS不能确定它在后面是否用到, 所有它从声明之后就一直存在于内存中, 直至手动释放或者关闭页面/浏览器, 这就导致了某些不必要的内存消耗. 我们可以进行以下优化.
    使用立即执行函数

    (() => {
        // do something...
    })();

    手动接触变量的引用

    let obj = { a: 1, b: 2, c: 3 };
    obj = null;

    在JS中, 闭包是最容易产生内存问题的, 我们可以使用回调函数代替闭包来访问内部变量. 使用回调的好处就是(针对访问的内部变量时原始类型的值, 因为在函数传参的时候传的是值), 在执行后会自动释放其中的变量, 不会像闭包一样一直将内部变量存在于内存中(但如果是引用类型, 那么这个被引用的对象依旧在内存中).

    function fn() {
        var val = '你好';
        return function() {
                return val
            };
    };
    var getVal = fn();
    var v = getVal(); // 你好

    上面例子中, 虽然函数fn已经执行完毕, 但是对于函数中变量val的引用还在, 所以垃圾回收机制不会将函数中的valJS のメモリも分割されています。スタック メモリとヒープ メモリ。ヒープとスタックの詳細については、こちらを参照してください。

    例:

    function fn1(cb) {
        var val = '你好';
        return cb(val);
    };
    function fn2(arg) {
        return arg;
    };
    var v = fn1(fn2);
    メモリ内のストレージ オブジェクトの宣言サイクルとは何ですか?MDN での紹介:

      必要になったときにオブジェクトにメモリを割り当てます。

      割り当てられたメモリを使用します(読み取り、書き込み操作)

      オブジェクトが不要になったとき、このオブジェクトを保存しているメモリを解放します

    1 2 はすべての言語で同じですが、3 ではありませんJS ではとても明白です
    メモリ内で何が起こっているか見てみましょう?rrreee

    次にこの質問を見てください:rrreee詳細な説明を見てください。基本的な知識のポイントを理解するのに非常に役立ちます。例: js の割り当て。演算の順序は常に右から左ですが、. が最も優先順位の高い演算子です。

    🎜関数の値の転送の変化をメモリの観点から見てみましょう🎜🎜値/アドレスの転送について説明します。プリミティブ型と参照型を区別します。プリミティブ型は値を渡し、参照型はアドレスを渡します。上の例 #1 では、fn は <code>str の値として渡され、関数パラメータとその値を保存するためにスタック メモリに新しい領域が割り当てられます (メモリのこの部分は関数の実行後に自動的に解放されます)実行、_ガーベジ コレクション メカニズム_)。したがって、#2 の出力値は、関数を呼び出すときにパラメータ arg を渡します。 fn 値 (スタック メモリに新しく割り当てられたデータ)、str はプリミティブ型です。#3 の出力は初期化と一致します。 🎜rrreee🎜上記の例では、両方の関数がアドレスによって渡されます。最初に渡されるパラメータ arg は、fn 内の同じデータを指す参照です。 新しいオブジェクト (参照型) を変数 arg に再割り当てします。 fn1arg は参照のままです (参照型を指します)。ヒープ メモリ ポインター内のデータ) なので、fn1 の変更は成功します。 🎜🎜ガベージ コレクション メカニズム🎜🎜JS にはガベージ コレクション メカニズムがあり、開発者には非常に便利です。メモリ解放について心配しすぎる必要がある 問題 (いくつかはまだ考慮する必要があります) 🎜🎜🎜🎜 関数の変数は関数の実行中にのみ存在します。関数内の変数は割り当てられます。関数が実行されると、これらの変数がメモリから自動的に解放され、他の用途に使用できる領域が残ります。🎜🎜🎜メモリ内の変数が参照されなくなると、JS はクリーンアップします。メモリのこの部分の割り当て 例:🎜rrreee🎜メモリ最適化🎜🎜 グローバル変数に関する限り、JS はそれが後で使用されるかどうかを判断できないため、常に存在します。宣言された瞬間から手動で解放されるか、ページ/ブラウザが閉じられるまでのメモリ。これにより、次のような最適化を行うことができます。🎜🎜即時実行関数を使用します🎜🎜rrreee🎜🎜。変数の参照🎜🎜rrreee🎜 JS では、クロージャがメモリを生成するのが最も簡単です。問題は、内部変数にアクセスするためにクロージャの代わりにコールバック関数を使用できることです。コールバックを使用する利点は、(アクセスされる内部変数は値です)関数がパラメータを渡すときに値が渡されるため、プリミティブ型の場合は、関数がパラメータを渡すときに自動的に変数が解放され、クロージャのように内部変数がメモリに保持されません(ただし、参照型の場合は、参照されたオブジェクトはまだメモリ内にあります) 🎜rrreee🎜 上の例では、関数 fn が実行されていますが、関数内の変数 val への参照は行われていません。はまだ存在するため、ガベージ コレクション メカニズムは関数内の val をリサイクルしません🎜🎜 🎜コールバックを使用する🎜🎜rrreee🎜同時に、これはそうすることが意味するものではないことも述べられています。クロージャにもクロージャの利点はありますが、最適なタイミングでクロージャを使用する必要があるだけです。🎜🎜関連する推奨事項: 🎜🎜🎜JS メモリ管理の例の説明🎜🎜🎜🎜🎜 JSクラスライブラリBindows1.3_javascriptスキル🎜🎜🎜🎜🎜JSメモリ管理例解説🎜🎜

    以上がjsでのメモリの使用法は何ですか? jsメモリの役割を理解するの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。