首頁  >  文章  >  web前端  >  手把手帶你了解Javascript中變數和詞法環境

手把手帶你了解Javascript中變數和詞法環境

青灯夜游
青灯夜游轉載
2020-07-07 15:54:273398瀏覽

手把手帶你了解Javascript中變數和詞法環境

其實,我覺得Javascript核心中重要的東西並非是從舊版本擴展來的高大上的語法,例如解構賦值啊、展開語法和剩餘參數(嘛…雖然的確是很666),但是用好這些,其實都建立在你對變數的認識上(常有人不知道什麼是左值或右值的區別)正因如此,我覺得了解一個Javascript還是從最基本做起,就是了解何為變數吧。

本文其實也並非完全基礎,還是建立在對Javascript有一定了解之上的,至少對物件要有一定的認知。開始吧。

變數與資料

什麼是變數?

越簡單的問題答案往往越是讓人感到意外,多數人的答案都與有關;事實上變數就是程式可操作的儲存區(術語記憶體空間),在Javascript程式執行時,儲存區(術語就是記憶體空間)可以保存我們所需的任何東西,程式碼、資料…等等。然後可以將變數保存的資料大致上分割為兩個類別:原始類型(同基本類型)和引用類型;從變數中取出來的資料就是值,把一個值放到變數中時,該值就又變成資料了。

Javascript和其他語言相似,變數也需要被宣告才能實際存在,宣告後的變數稱之為實例化,為變數賦予一個值(預設為undefined)時,稱之為變數的初始化,只是實例化而未初始化的變數都處於uninitialized狀態。

例如:

let a = a ;         // (*)
console.log(a);
// ReferenceError: can't access lexical declaration `a' before initialization

很完美的報錯了(在(*)標誌的位置),提示我們沒有初始化這個變數時就無法使用。這是不同於C 這類底層的變數的地方。其實這種現像在Javascript有一個極為高大上的名字:暫存死區,等過幾章節我就說明產生的原因。

(忘了說了,在變數宣告也需要一個名字,術語稱作標識符,我覺得不補充也不影響什麼…)

#不過Javascript另外特殊的地方在於,它對var宣告的變數是可以自動初始化的,Javascript會自動會為var宣告的變數賦予一個undefined值。
例如:

var a = a;console.log(a);    // undefined.

看吧,明明都差不多,結果卻全然不同。
但是其實卻沒什麼卵用,請看下面的程式碼:

var a = a;console.log(a+2);   // NaN

結果是NaN, 得到了一個我們完全不想要的結果。在如果無法順利數學計算時,Javascript便會給出一個非數字的結果,用NaN表示。但比較有趣的是,如果你用typeof去驗證NaN類型:

typeof NaN ;      // number

卻告訴我們,這TMD是一個數值number
Javascript莫名其妙的地方還有許多許多,不過我們還是不要繼續調戲javascript了,開始認真學習了。

類型與儲存

Javascript一共有7 種原始類型與1 種參考類型,如下:

  • #原始型別

    1、number

    2、string

    3、boolean

    4、symbol

    5、bigint

    #6、undefined

    7、null

  • 引用型別:

    object

(這裡面我就用小寫了,因為typeof返回的是小寫的)
我就是介紹一下這些必須要了解的東西,具體用法其他資料都有我就不贅述了。不過關於typeof還有要補充的一點是,它對於nullfunction結果:

function sayHello(){
     console.log('hello the world');
 }
 console.log(typeof sayHello);  // function
 console.log(typeof null);      // object

…對於一個函數,它真的回傳的是一個“函數”,某種意義上用處很大,不過對null值回傳一個object,這只能說有得就有缺吧。

我覺得對變數加深了解的方法就是明白它的底層運作方式。其實沒有什麼了不起的,原始值是直接放在記憶體堆疊區, 引用型別值則是放在記憶體堆疊區(這是它的實際儲存區位置); (如果是常數,那麼就會放在中,好像也是堆疊區的一部分)。正常情況下,變數取值都是直接都是從記憶體堆疊區中取得的,但是引用型別的值是放在記憶體堆中,那怎麼辦?

引用型別值的存取:

1、一個引用型別的變量,會在記憶體堆疊中儲存一個指標

2、這個指標是用於引用記憶體堆中的儲存區的記憶體位址

3、在存取一個型別值時

4、会通过指针找到内存堆中的存储区,然后从中获取值。

例如:

  var first  = {
      name:'hahei...'
  }
  var gggiii=111222;

映射图如下:

手把手帶你了解Javascript中變數和詞法環境

注意:此处我用 ref. first表示  存储区的引用 , 因为虽然保存的尽管是指针,但是在访问这个值时,会进行二次解析(即通过这个指针找到存储区), 而不是直接返回这个指针的具体数据。详细可以参考 C++引用。

初识词法环境

想必各位都已经对什么是作用域了若指掌,但是我还是必须重新提一下作用域标识符的可访问范围,在Javascript中的任何操作,几乎都有作用域的参与。Javascript中使用词法环境决定作用域,在下面我会简单介绍一下。(请注意,这里我没有用变量这个术语,因为解析标识符范围时,应该还没有真正生成代码,感兴趣的可以去了解一下手把手帶你了解Javascript中變數和詞法環境ST语法树

看,以下代码:

 var val=111;
 function hahaha(){
     console.log(val);
 }
 function hihihi(){
    hahaha();
 }
 hihihi();  /// 111

的确是正确输出了,111

但是我更喜欢把 val放在一个函数中,如:

   function hahaha(){
       console.log(val);      /// (**)
   }
   function hihihi(){
      var val=111;            /// (*)
      hahaha();
   }
   hihihi();

结果就是Uncaught ReferenceError: val is not defined, 根本没找到val这个标识符,这是为什么?

因为执行过程是这样的:

  1. hihihi函数执行  , 然后为 val赋值……
  2. hahaha函数执行
  3. hahaha找不到val标识符,便去外部词法环境
  4. hahaha外部词法环境就是** hahaha函数声明时代码的外部**,即全局代码(下称全局词法环境)
  5. 全局词法环境没找到val,终了。
    (请注意3-5步, 找val找的是函数声明代码的外部,而不是函数调用时的位置。)

现在应该提一下概念了,词法环境(Lexical Environment)就是根据代码结构时决定的作用域,也可以称作词法作用域(Lexical Scoping)它是静态作用域。可以这么说,在源代码写好时,所有标识符的作用域就已经被决定。当然也有动态作用域,你可以去试试bash脚本,它就是动态的。嘿嘿。详细也可以参考静态作用域词法作用域

此处只要发现了个中区别就极好掌握,所以我就略了。

词法环境的抽象

在Javascript常用三种词法环境: 一、块级作用域 二、全局作用域 三、函数作用域。

有时,我们会将一个词法环境(即作用域,下面我会正式使用词法环境替代作用域这个术语)抽象成伪代码,如下:

	LexicalEnvironment = {
		OuterEnv:  ,
		This :    ,
		EnvironmentRecord:{
			// ... identifiername:variable
		}
	}

很简单:

  • OuterEnv:当前词法环境的外部词法环境
  • This: 当前词法环境的 this的值,但它是运行时决定的。
  • EnvironmentRecord(环境记录): 标识符-变量的映射,注意,这里的标识符只是单纯的字符串,变量指的是存储区的数据。而且标识符必须是当前词法环境,而不是当前代码的。

例如:

  function first(){
      var a  =100;
      let d = 220;
      {     // Block, 
          var b = a+100;
          let c = b*10;
          console.log(a,b,c,d);
      }
  }
  first();  // 100 200 2000 220

一定不要忽略first函数中的块级作用域,这很重要。

然后写成抽象就是:
函数内部的块级作用域

	BlockEnv = {
		OuterEnv:  ,
		This :    ,
		EnvironmentRecord:{
			c:              // 这里没有b
		}
	}

函数作用域

	FuncEnv = {
		OuterEnv:  ,
		This :    ,
		EnvRec:{
			a:,
			d:,
			b:
		}
	}

OKay,先到这里吧。

一些问题:

1、为什么用词法环境代替作用域
–词法环境涵盖了作用域,但反之则不能。
–但注意,词法作用域和词法作用域链与作用域以及作用域链都可通用。

2、环境记录是什么?
–当前环境下的标识符-变量的映射
–但是标识符只是“合法标识符”的字符串形式。
–变量是是指存储区的内容,但是确切说法是存储区

最后

我把我的笔记,重新整理后发到博客上后发现——我笔记干净了好多,艹。

這種只深入核心的內容很有用,而且在寫程式碼時也變得靈活很多了。我覺得這就是最有用的地方
最後:
個人理解,常有失誤;細細看不知何處,望君做到心中有數。

本文轉載自:https://blog.csdn.net/krfwill/article/details/106155266

相關教學建議:JavaScript影片教學

以上是手把手帶你了解Javascript中變數和詞法環境的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:csdn.net。如有侵權,請聯絡admin@php.cn刪除
上一篇:WEB程式設計師必備之 關於