JavaScriptで準備する

高洛峰
リリース: 2017-02-04 16:46:22
オリジナル
1096 人が閲覧しました

ES6 より前は、変数は var を使用して宣言されており、変数の事前解析が行われていました (関数にも事前解析がありました)。let と const を初めて学習したとき、多くの学生が事前解析に戸惑ったと思います。 ES6 で導入されましたが、現段階では ES6 は完全には普及しておらず、古いコードの多くは依然として ES5 標準、さらには ES3 標準に従って書かれています。

01 メモリ内の変数と関数の表示

JavaScript の変数の型は、基本データ型と参照データ型を含め、他の言語と同じです。基本的なデータ型には、未定義、null、ブール、文字列、数値が含まれます。参照データ型は主にオブジェクトです ({}、[]、/^$/、日付、関数など)。

var num = 24;
var obj = {name:'iceman' , age:24};
function func() {
    console.log('hello world');
}
ログイン後にコピー

ブラウザが HTML ページをロードすると、まずグローバル スコープと呼ばれるグローバル JavaScript コード実行のための環境が提供されます。

基本データ型は値を操作し、参照データ型はアドレスを操作します。

上記の原則によれば、上記のコードのメモリ モデルは次のとおりです:

JavaScript 中的预解析

Memory model.png

基本型はスタック メモリに直接格納されますが、オブジェクトはヒープ メモリに格納され、変数は単に保持されますこのオブジェクトのアドレス。したがって、obj はオブジェクト oxff44 のアドレスを保持し、関数 func はアドレス oxff66 を保持します。

上記のコードを元に実行:

console.log(func);
console.log(func());
ログイン後にコピー

出力の1行目は関数全体(関数自体)の定義部分です:

JavaScript 中的预解析

コードの1行目出力結果.png

となっています上で説明したように、 func はヒープ メモリの一部を指すアドレスを格納し、ヒープ メモリは関数の定義を保持します。

出力の 2 行目は func 関数の戻り結果です:

JavaScript 中的预解析

コードの 2 行目は result.png を出力します。func 関数には戻り値がないため、出力は未定義です。

注: return の後に記述された関数の戻り結果が戻り値になります。return がない場合、デフォルトの戻り値は未定義です。

02 事前解析

上記のメモリモデルを理解すると、事前解析のメカニズムをよりよく理解できるようになります。いわゆる事前解析とは、現在のスコープ内で、JavaScript コードが実行される前に、デフォルトでブラウザはまず var および function 宣言を使用してすべての変数を事前に宣言または定義します。

2.1. 宣言と定義

var num = 24;
ログイン後にコピー

この単純なコード行は、実際には宣言と定義の 2 つのステップです。

宣言: var num; は、変数が宣言されているだけで値が割り当てられていない場合、デフォルト値は未定義であることをブラウザーに伝えます。

定義: num = 12; 定義は変数に値を代入することです。

2.2. var で宣言された変数と事前解析時の function で宣言された関数の違い

var で宣言された変数と事前解析時の function で宣言された関数には違いがあります。事前解析中に、 function で宣言された関数が事前に宣言され、事前解析中に同時に定義されます。つまり、var で宣言された変数と function で宣言された関数の違いは、宣言と同時に定義されるかどうかです。

2.3. 事前解析は現在のスコープ内でのみ行われます

プログラムの開始時は、関数が実行されるときのみ、関数内の変数と関数が事前解析されます。 -解析されました。

console.log(num);
var num = 24;
console.log(num);

func(100 , 200); 
function func(num1 , num2) {
    var total = num1 + num2;
    console.log(total);
}
ログイン後にコピー

JavaScript 中的预解析Output result.png

num が初めて出力されるとき、事前解析の理由により、それはまだ定義されていないと宣言されるだけなので、num が 2 回目に出力されるときは、unknown が出力されます。と定義されているため、出力は 24 になります。

関数の宣言と定義が同時に行われるため、func関数定義文の前にfunc()が呼ばれていますが、正常に呼び出すことができ、正常に300が出力されます。

JavaScript 中的预解析Memory model.png

03 スコープチェーン

まず、次の 3 つの概念を理解します。

関数内のスコープはプライベート スコープとなり、ウィンドウが配置されているスコープはグローバル スコープと呼ばれます。

グローバル スコープで宣言された変数はグローバル変数です。

「プライベート スコープで宣言された変数」と「関数の仮パラメーター」は両方ともプライベート変数です。

在私有作用域中,代码执行的时候,遇到了一个变量,首先需要确定它是否为私有变量,如果是私有变量,那么和外面的任何东西都没有关系,如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直查找到window为止,这就是作用域链。

当函数执行的时候,首先会形成一个新的私有作用域,然后按照如下的步骤执行:

如果有形参,先给形参赋值;

进行私有作用域中的预解析;

私有作用域中的代码从上到下执行

函数形成一个新的私有的作用域,保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的),这也就是闭包的概念。

console.log(total); 
var total = 0;
function func(num1, num2) {
    console.log(total); 
    var total = num1 + num2;
    console.log(total);
}
func(100 , 200);
console.log(total);
ログイン後にコピー

以上代码执行的时候,第一次输出total的时候会输出undefined(因为预解析),当执行func(100,200)的时候,会执行函数体里的内容,此时func函数会形成一个新的私有作用域,按照之前描述的步骤:

先给形参num1、num2赋值,分别为100、200;

func中的代码进行预解析;

执行func中的代码

因为在func函数内进行了预解析,所以func函数里面的total变量会被预解析,在函数内第一次输出total的时候,会输出undefined,接着为total赋值了,第二次输出total的时候就输出300。 因为函数体内有var声明的变量total,函数体内的输出total并不是全局作用域中的total。

最后一次输出total的时候,输出0,这里输出的是全局作用域中的total。

console.log(total); 
var total = 0;
function func(num1, num2) {
    console.log(total); 
    total = num1 + num2;
    console.log(total);
}
func(100 , 200);
console.log(total);
ログイン後にコピー

将代码作小小的变形之后,func函数体内的total并没有使用var声明,所以total不是私有的,会到全局作用域中寻找total,也就说说这里出现的所有total其实都是全局作用域下的。


04全局作用域下带var和不带var的区别

在全局作用域中声明变量带var可以进行预解析,所以在赋值的前面执行不会报错;声明变量的时候不带var的时候,不能进行预解析,所以在赋值的前面执行会报错。

console.log(num1);
var num1 = 12;

console.log(num2);
num2 = 12;
ログイン後にコピー

JavaScript 中的预解析

输出结果.png


num2 = 12; 相当于给window增加了一个num2的属性名,属性值是12;

var num1 = 12; 相当于给全局作用域增加了一个全局变量num1,但是不仅如此,它也相当于给window增加了一个属性名num,属性值是12;


问题:在私有作用域中出现一个变量,不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window也没有呢?

获取值:console.log(total); --> 报错 Uncaught ReferenceError: total is not defined

设置值:total= 100; --> 相当于给window增加了一个属性名total,属性值是100

function fn() {
    // console.log(total); // Uncaught ReferenceError: total is not defined
    total = 100;
}
fn();
console.log(total);
ログイン後にコピー

注意:JS中,如果在不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不再执行了

05预解析中的一些变态机制

5.1 不管条件是否成立,都要把带var的进行提前的声明

if (!('num' in window)) { 
    var num = 12;
}
console.log(num); // undefined
ログイン後にコピー

JavaScript进行预解析的时候,会忽略所有if条件,因为在ES6之前并没有块级作用域的概念。本例中会先将num预解析,而预解析会将该变量添加到window中,作为window的一个属性。那么 'num' in window 就返回true,取反之后为false,这时代码执行不会进入if块里面,num也就没有被赋值,最后console.log(num)输出为undefined。


5.2 只预解析“=”左边的,右边的是指,不参与预解析

fn(); // -> undefined();  // Uncaught TypeError: fn is not a function
var fn = function () {
    console.log('ok');
}

fn(); -> 'ok'
function fn() {
    console.log('ok');
}
fn(); -> 'ok'
ログイン後にコピー

建议:声明变量的时候尽量使用var fn = ...的方式。


5.3 自执行函数:定义和执行一起完成

(function (num) {
  console.log(num);
})(100);
ログイン後にコピー

自治性函数定义的那个function在全局作用域下不进行预解析,当代码执行到这个位置的时候,定义和执行一起完成了。


补充:其他定义自执行函数的方式

~ function (num) {}(100)  
+ function (num) {}(100)  
- function (num) {}(100)  
! function (num) {}(100)
ログイン後にコピー

5.4 return下的代码依然会进行预解析

function fn() {                             
    console.log(num); // -> undefined
    return function () {             

    };                               
    var num = 100;                   
}                                  
fn();
ログイン後にコピー

函数体中return下面的代码,虽然不再执行了,但是需要进行预解析,return中的代码,都是我们的返回值,所以不进行预解析。

5.5 名字已经声明过了,不需要重新的声明,但是需要重新的赋值

var fn = 13;                                       
function fn() {                                    
    console.log('ok');                               
}                                                  
fn(); // Uncaught TypeError: fn is not a function
ログイン後にコピー

经典题目

fn(); // -> 2                                             
function fn() {console.log(1);}                           
fn(); // -> 2                                             
var fn = 10; // -> fn = 10                                
fn(); // -> 10()  Uncaught TypeError: fn is not a function                          
function fn() {console.log(2);}                           
fn();
ログイン後にコピー

更多JavaScript 中的预解析相关文章请关注PHP中文网!

関連ラベル:
ソース:php.cn
このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート