blog

コンパクトな基礎で、jsのコア技術を徹底的にマスターする(3):スタック・メモリとクロージャの詳細

1. データ値操作メカニズム 2. 変数リフティングメカニズム 1. "var "は未定義宣言のみ 2. "function "で宣言と代入が完了 3. ES6 letは存在しない 変数リフティング ...

Mar 7, 2020 · 9 min. read
シェア

データレンダリングの仕組みとスタックメモリー

データ値操作メカニズム

/*
* 1. 値を代入せずに変数aを宣言する。
* 2. の値を保存する場所を現在のスコープにオープンする。
* 3. 変数aと12を関連付ける
*/
var a = 12
var b = a;
b = 13;
console.log(a);
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1)
function sum() {
	var total = null;
 for(var i = 0; i< arguments.length; i++;) {
 	var item = arguments[i];
 item = parseFloat(item);
 !isNaN(item) ? total += item : null;
 }
 return total;
}
console.log(sum(12, 23, '34', 'AA'))

解析結果は以下の通りです:



スタックメモリー:スコープ
  1. jsコードのトップダウン実行環境の提供
  2. 基本的なデータ型の値は比較的単純なので、これらはすべてスタック・メモリの場所をオープンし、そこに直接値を格納します。
  3. スタック・メモリが破壊されると、それらの基本値もすべて一緒に破壊されます。

ヒープメモリ:参照値に対応する空間

  1. 参照型の値の格納
  2. 現在のヒープ・メモリは解放され破棄されるので、参照はなくなります。
  3. ヒープメモリの解放:ヒープメモリは、任意の変数または他のものによって占有されていない場合、ブラウザは、アイドル、自律的なメモリの回復、すべての占有されていないヒープメモリは、元の占有ヒープメモリが何かによって占有されていない、ブラウザはそれを破壊する)を指していない人を破壊されます。

変数のリフティング・メカニズム

  • 変数のリフティングとは?

変数リフティング:スタック・メモリが形成され、jsコードがトップダウンで実行されるとき、ブラウザはまず、すべてのキーワードを事前にva r/functionで「宣言」または「定義」します。このような前処理メカニズムは「変数リフティング」と呼ばれます。

宣言: :var a/ function sum

定義: a= 12

変数上げの段階で

1. "var "は宣言されていますが、定義されていません。

2. "function "の宣言と代入が行われます。

  1. 変数のリフティングは現在のスコープでのみ発生します。
  2. グローバル・スコープで宣言された関数や変数は「グローバル変数」、プライベート・スコープで宣言された変数は「プライベート変数」です。
  3. ブラウザは非常に怠け者で、2度目に行ったことを繰り返しません。つまり、コード実行が関数を作成するコードのこの部分に遭遇したとき、直接スキップすることができます。
var a = 12
var b = a;
b = 13;
console.log(a);
var ary1 = [12, 23];
var ary2 = ary1;
ary2.push(100);
console.log(ary1)
function sum() {
	var total = null;
 for(var i = 0; i< arguments.length; i++;) {
 	var item = arguments[i];
 item = parseFloat(item);
 !isNaN(item) ? total += item : null;
 }
 return total;
}
console.log(sum(12, 23, '34', 'AA'))

可変リフティング機構の解析:

  • var がある場合とない場合の違い

グローバルスコープで変数を宣言することは、ウィンドウ上でプロパティをグローバルに設定することでもあり、変数の値はプロパティの値となります。

console.log(a) // undefined
console.log(window.a) // undefined
console.log('a' in window) // true 変数aの宣言のグローバル・スコープでは、この時点ではaはウィンドウに割り当てられたプロパティだが、この時点ではaに与えられていない。
//代入、デフォルト値は:プロパティがオブジェクトに属しているかどうかを検出する。
var a = 12; // グローバル変数の値が変更されると、ウィンドウの属性の値も変更される。
console.log(window.a) // windowのプロパティ名
a = 13
console.log(window.a) // 13
window.a = 14;
console.log(a) // 14

上記の例は、グローバル変数とウィンドウのプロパティとの間に「マッピングメカニズム」があることを示しています。

**

var がなければ、それは本質的にウィンドウのプロパティです。

次の例のように:

console.log(a) // Uncaught ReferenceError: a is not defined
console.log(window.a) // undefined
console.log('a' in window) // false
a = 12 // window.a = 12
console.log(a) // 12
console.log(window.a) // 12
var a = 12,
 b = 13; // これは、varを使ったbの書き方だ。
var a = b = 12; // これは、varを使わないbの書き方である。

また、プライベート・スコープにはvar付きとvarなしの違いがあります:

  1. varを持つ変数は、private-scope変数の昇格フェーズでprivate宣言され、外部とは何の関係もありません。
  2. varを持たない非プライベート変数は、親変数かどうかを親スコープで調べ、親変数でない場合はwindowに達するまで調べ続けます。つまり、プライベートスコープで操作される非プライベート変数は、常に他の誰かに操作されていることになります
console.log(a, b) // undefined , undefined
var a = 12,
 b = 12;
function fn() {
	console.log(a, b) // undefined, 12
 var a = b = 13
 console.log(a, b) // 13 13
}
fn();
console.log(a, b) // 12, 13

解析結果は以下の通りです:



スコープチェーンの延長:
function fn() {
 
	 b= 13;
 console.log('b' in window) // true,スコープ・チェーンを検索する過程で、ウィンドウがこの変数を持っていないことがわかったら、その変数に
 // ウィンドウにプロパティを設定する b(window.b = 13)
 console.log(b)
}
console.log(b)
  • 等号の左側にある変数は、以下のようになります。
/*
	* 変数のリフティング:
 * var fn; 等号の左側だけ変数を上げる
 
**/
 
sum();
fn(); // Uncaught TypeError: fn is not a function
// 無名関数の関数式
var fn = function () {
	console.log(1)
}
// 普通の関数
function sum() {
	console.log(2)
}
  • 条件付き判定の下での変数解除

現在のスコープでは、条件が成立しているかどうかにかかわらず、変数の引き上げが実行されます。

  1. varを持つものはまだ単なる宣言です。
  2. 古いブラウザのレンダリングメカニズムの関数では、宣言+定義が、ES6のブロックレベルのスコープに対応するために、関数の新しいブラウザが確立されているかどうかは、単に最初に宣言され、定義されていない、varに似ています。
  3. グローバルスコープで宣言されたグローバル変数も、ウィンドウにプロパティを設定することと同じです。
/**
	* 現在のスコープでは、条件が有効かどうかに関係なく、変数を上げる必要がある。
 * varを持つものはまだ単なる宣言である。
 * 関数では、古いブラウザのレンダリング機構では+しかし、ES6のブロック・レベル・スコープに対応するため、新しいブラウザでは関数
 確立されるかどうかは別として、最初に宣言されるだけで、定義されることはない。
*/
console.log(a) // undefined
if(1 === 2) {
	var a = 12
}
console.log(a) //undefined
//グローバル・スコープで宣言されたグローバル変数も、ウィンドウのプロパティを設定することと同じである。
console.log(a) //undefined
if('a' in window) {
	var a = 100
}
console.log(a) // 100
f= function () {
	 return true;
}; // window.f = ....
g = function () {
	return false
}; // window.g = ...
~function() {
 /*
 * 変数のリフティング:
 * function g; gはプライベート変数である。
 */
	if(g() && ([] == ![])) { // Uncaught TypeError: g is not a function [] == ![] => 0 == 0
 	
 // fをグローバルに変更する
 f= function () {
 	return false
 };
 function g() {
 	return true;
 }
 }
}()
console.log(f());
console.log(g());
/*
 * グローバルの下での変数のリフティング
 * function fn
*/
console.log(fn) // undefined
if(1 === 1) {
	console.log(fn)
 function fn() {
 	console.log('ok')
 }
}
console.log(fn) //関数自体
  • リネーム問題の処理
  1. 同じ名前をvarキーワードとfunctionキーワードで宣言すると、リネームとみなされます。
  2. リネームの処理について:名前が重複した場合、再宣言はされませんが、変数のリフティングとコード実行の両方の段階で再定義されます。
fn(); // 4
function fn() {
	console.log(1);
}
fn(); // 4
function fn() {
	console.log(2);
}
fn(); // 4
var fn = 100 // 昇格フェーズのvarでは、宣言処理のみで、代入操作は処理されない。
fn(); // Uncaught TypeError: fn is not a function
function fn() {
	console.log(3);
}
fn();
function fn() {
	console.log(4);
}
fn();

ES6のletは可変リフティングを持ちません。

  • 重複定義を許さない
  • 変数のリフティングなし
  1. ES6では、変数や関数はlet/constなどに基づいて作成され、変数のリフティング機構はありません。
  2. グローバル変数とウィンドウプロパティ間のマッピング機構を遮断
  3. 同じ名前の変数を同じスコープで宣言することはできません。
  4. 変数の昇格メカニズムはありませんが、上から下へ実行するコードの現在のスコープでは、ブラウザは重複検出を行います、上から下へ現在のスコープ下のすべての変数を見つけるために、一度重複していることが判明し、直接例外をスローし、コードは再び実行されません!
console.log(a) //Uncaught ReferenceError: a is not defined
let a = 12;
console.log(window.a) // undefined
console.log(a) // 12
//-------------
let a = 10,
 b = 10
let fn =function () {
 // console.log(a, b) //Uncaught ReferenceError: a is not defined
	let a = b = 20
 console.log(a, b) // 20, 20
}
fn();
console.log(a, b) // 10, 20
//-------------
let a = 12
console.log(a) 
let a = 13 // Uncaught SyntaxError: Identifier 'a' has already been declared
console.log(a)
  • 一時的なデッドゾーン
  1. letに基づいて変数を作成すると、ほとんどの{}がプライベートなブロックレベルのスコープとして扱われ、変数が新しい構文に基づいて作成されたかどうか、新しい構文仕様に従って解析されたかどうかを確認するために、構文仕様の再テストも行われます
  2. es6がブラウザの一時的なデッドゾーン問題を解決
var a = 12;
if(true) {
	console.log(a)
 let a = 13 
}
//--------
console.log(typeof a) // "undefined" オリジナルのブラウザのレンダリング機構では、typeofやその他の論理演算子に基づいて、宣言されていない
 // 変数はエラーを報告せず、未定義を返す。
//--------
console.log(typeof a) // Uncaught ReferenceError: a is not defined
let a //現在の変数がes6構文処理に基づいている場合、この変数の宣言がない場合、typeof検出の使用は直接エラーとして報告され、元のjsのデッドゾーンの問題を解決するために、未定義にはならない。

閉鎖範囲

プライベート変数とグローバル変数の区別

プライベート・スコープでは、以下の2つのケースのみがプライベート変数となります:

  1. 宣言された変数
  2. 行参照もプライベート変数です。

残りはプライベート変数ではないので、スコープチェインメカニズムに基づいて検索する必要があります。

var a = 12,
 b = 13,
 c = 14;
function fn(a) {
 /*
 * 行パラメーターの割り当て a = 12
 * 変数のリフティング: var b
 * プライベート・スコープでは、以下の2つのケースだけがプライベート変数となる。
 * 1.宣言された変数
 * 2.行参照もプライベート変数である
 * 残りの変数はプライベートではなく、スコープチェイニングのメカニズムに基づいて検索する必要がある。
 **/
	console.log(a, b, c) // 12, undefined, 14
 var b = c = a = 20; // var b = 20; c=20; a = 20
 console.log(a, b, c) //20, 20, 20
}
fn(a)
console.log(a, b, c) // 12, 13, 20
//---------
var ary = [12, 23]
function fn(ary) {
	console.log(ary) //[12, 23]
 ary[0] = 100; //[100, 23]
 ary = // 
 ary[0] = 0 // [0]
 console.log(ary) //[0]
}
fn(ary)
console.log(ary) //[100, 23]

解析結果は以下の通りです:

より高いスコープを見つける

  1. 現在の関数が実行され、プライベートスコープAが形成されます。Aの親スコープが誰であるかは、関数が実行される場所とは関係ありませんが、関数がどこで作成されたか、どこで作成されたか、親スコープが誰であるかに関係します。
var a = 12
function fn() {
 // arguments:実際のパラメーターのコレクション,arguments.callee:関数自体 fn
	console.log(a)
}
function sum() {
 
	var a = 120
 fn(); // 12
}
sum();
//-------
var n = 10;
function fn() {
	var n = 20;
 function f() {
 	n++;
 console.log(n)
 }
 f()
 return f;
}
var x = fn(); //21
x(); //22
x(); //23
console.log(n); // 10




クロージャとスタックメモリの解放

jsのメモリは、ヒープ・メモリとスタック・メモリに分けられます。

ヒープ・メモリー:参照データ型の値を格納。

スタックメモリ:jsコードの実行環境を提供し、基本的なデータ型を格納します。

ヒープメモリの解放

nullに代入された変数のヒープメモリ空間のアドレスを参照させます。

スタックメモリの解放

一般的には、関数の実行が完了すると、プライベートスコープの形成は自動的に解放されますが、破壊しない特殊なケースもあります:

  1. 関数の実行が完了すると、スタックメモリの現在の形成は、スタックメモリの内容の一部がスタックメモリ外の変数によって占有され、この時点でスタックメモリを解放することはできません。
  2. グローバル・スタック・メモリは、ページが閉じられたときにのみ解放されます。

現在のスタック・メモリが解放されない場合、スタック・メモリに保存されている基本値も解放されず、常に保存されます。

var i = 1;
function fn(i) {
 return function (n) {
 console.log(n+ (++i));
 }
}
var f = fn(2); //fnを最初に実行し、fnの実行結果をfに代入する。
f(3) // returnの結果を実行する = 
fn(5)(6) //12 まずfnを実行し、次にfnの実行結果を実行するという点で、上の2つのステップと似ている。
fn(7)(8) // 16
f(4) //8
//---------
// 他の値で操作する場合、いくつかの違いがある。
// i++ ;自己蓄積1
// i++; 自己蓄積1
var k = 1;
console.log(5+(k++), k) // 6, 2
console.log(5+ (++k), k) //7, 2
//--------

解析結果は以下の通りです:



クロージャの保護メカニズム

クロージャ:

関数の実行は、内部のプライベート変数を外部からの干渉から保護するためにプライベートスコープを形成します。

市場の開発者は、クロージャを次のように考えています:破壊されないプライベートスコープを形成することがクロージャである。

// クロージャ:コリエライズされた関数
function fn() {
return function(){
}}
var f = fn()
// :不活性関数
var utils = (function(){
	return {
 }
})()

実際のプロジェクトでは、jsの性能を確保するために、クロージャーの使用を減らすことが可能であるべきです。

クロージャ保護機能:

  1. クロージャには "保護 "機能があり、プライベート変数を外界から保護します。
  • jQueryの方法:グローバルに公開する必要があるメソッドを投げます。
  • zeptoのアプローチ:リターンに基づいて外部で利用可能なメソッドを公開します。
  1. クロージャは "保護された "ものであり、スタック・メモリーを形成し、破壊されることはありません。
Read next

ズーキーパー

1.関連コマンド

Mar 7, 2020 · 2 min read