其實,我覺得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
還有要補充的一點是,它對於null
和function
結果:
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;
映射图如下:
注意:此处我用 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
这个标识符,这是为什么?
因为执行过程是这样的:
hihihi
函数执行 , 然后为 val
赋值……hahaha
函数执行hahaha
找不到val
标识符,便去外部词法环境
hahaha
外部词法环境就是** hahaha函数声明时代码的外部**,即全局代码(下称全局词法环境)3-5
步, 找val找的是函数声明代码的外部,而不是函数调用时的位置。)现在应该提一下概念了,词法环境(Lexical Environment)就是根据代码结构时决定的作用域,也可以称作词法作用域(Lexical Scoping)它是静态作用域。可以这么说,在源代码写好时,所有标识符的作用域就已经被决定。当然也有动态作用域,你可以去试试bash脚本,它就是动态的。嘿嘿。详细也可以参考静态作用域、词法作用域。
此处只要发现了个中区别就极好掌握,所以我就略了。
词法环境的抽象
在Javascript常用三种词法环境: 一、块级作用域 二、全局作用域 三、函数作用域。
有时,我们会将一个词法环境(即作用域,下面我会正式使用词法环境替代作用域这个术语)抽象成伪代码,如下:
LexicalEnvironment = { OuterEnv: , This : , EnvironmentRecord:{ // ... identifiername:variable } }
很简单:
this
的值,但它是运行时决定的。例如:
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中文網其他相關文章!