JavaScript基礎(chǔ)知識點(diǎn)解讀—變量(聲明/賦值/使用/類型檢測/銷毀···)

變量是每一門編程語言中非常重要的一個(gè)概念,不同的編程語言中變量的作用也基本一致。變量可以通過變量名訪問,是計(jì)算機(jī)語言中能儲存計(jì)算結(jié)果或能表示值的抽象概念。

JavaScript變量

JavaScript屬于弱類型定義語言,某一個(gè)變量被定義類型,該變量可以根據(jù)環(huán)境變化自動進(jìn)行轉(zhuǎn)換,不需要經(jīng)過顯性強(qiáng)制轉(zhuǎn)換。

//比如
var a = 1;
a = {x: 1};
console.log(a); //{x:1}

但是像Java、.net、Python、c++等這樣的強(qiáng)類型定義語言,上述的操作就會引起程序報(bào)錯。

JavaScript變量聲明(創(chuàng)建)

JavaScript變量命名需要遵循的規(guī)則

  • 變量必須以字母開頭
  • 變量也能以 $ 和 _ 符號開頭(不過我們不推薦這么做)
  • 變量名稱對大小寫敏感(y 和 Y 是不同的變量)

JavaScript變量聲明的幾種方式

  • es6之前使用var和function進(jìn)行變量聲明
  • es6中新增let和const

function也是變量的聲明方式嗎?

我們來驗(yàn)證一下function是否可以聲明變量。

let add = 1;
function add(num1, num2) {return num1 + num2};
//Uncaught SyntaxError: Identifier 'add' has already been declared

思考一下使用var,function,let,const聲明變量有什么區(qū)別?

在這之前我們要知道在JavaScript中變量聲明和賦值(初始化)對于Js引擎來說是分為了兩個(gè)階段。一個(gè)Js的變量初始化語句也將被解釋成兩個(gè)語句來執(zhí)行,比如

var a = 1;
//被Js引擎解釋為
var a; //變量聲明語句
a = 1; //變量賦值語句

我們知道了這個(gè)過程后繼續(xù)探索JavaScript變量操作相關(guān)的特性。

  1. 變量聲明提升

    • var 和 function有變量聲明提升的功能
    • let 和 const 則沒有變量聲明提升的功能,必須要先聲明才能使用
//驗(yàn)證變量提升
//1. 如果不進(jìn)行聲明直接使用會報(bào)錯
console.log(a); //Uncaught ReferenceError: a is not defined
//2. 先使用后聲明
console.log(a); //undefined
var a = 1;
//對于js引擎來說和下面的情況是一樣的
console.log(a); //undefined
var a;
//或者
var b;
console.log(b); //undefined
//3. 先聲明并賦值后使用
var a = 1;
console.log(a); // 正常輸出 1
//4. 使用function聲明函數(shù)變量
add(1, 2); // 正常輸出 3
function add(num1, num2) {
    console.log(num1 + num2);
}
//5. 使用let
console.log(a); //Uncaught ReferenceError: a is not defined
let a = 1;
//6. 使用const
console.log(a); //Uncaught ReferenceError: a is not defined
const a = 1;

從上面幾個(gè)例子中可以看出,如果在程序中未使用var和function對一個(gè)變量進(jìn)行聲明直接使用則會報(bào)錯;如果聲明了變量但未賦值或者初始化,則該變量的默認(rèn)值為undefined;如果變量賦值或者初始化在使用之后,則在使用變量時(shí)其值也為undefined。使用var和function聲明的變量在當(dāng)前執(zhí)行環(huán)境下,即使調(diào)用該變量的地方其值為undefined,也能說明該變量是存在的(也就是說該變量被聲明了)。而使用let和const聲明的變量并不能達(dá)到這樣的效果。

思考一個(gè)問題,變量聲明提升到底是一個(gè)什么過程?

我們知道JavaScript是解釋執(zhí)行語言,和Java那種編譯執(zhí)行語言不同。說到這里我們需要知道JavaScript代碼的執(zhí)行其實(shí)分為兩個(gè)階段—解釋階段(也可以理解為編譯階段,但是和Java那種編譯成二進(jìn)制的過程不一樣)和執(zhí)行階段。在解釋階段,Js引擎會對當(dāng)前環(huán)境下所有的變量聲明(注意,此處是聲明而不是賦值)放到了程序的最前端。下面看一看幾段JavaScript代碼經(jīng)過Js引擎解釋后執(zhí)行時(shí)的真正順序。

console.log(a);
var a = 1;
console.log(a);
//被Js引擎解釋后的執(zhí)行順序
var a; //a的值為undefined
console.log(a); // undefined
a = 1; // a的值為1
console.log(a); // 1

add( 1, 2);
function add(num1, num2) {
  console.log(num1 + num2);
}
//被Js引擎解釋后的執(zhí)行順序
function add(num1, num2) {
  console.log(num1 + num2);
}
add(1, 2);

到這里我們應(yīng)該對使用var聲明的變量提升和function聲明的函數(shù)變量提升有了一定的理解。那么,思考一下下面幾段代碼的輸出結(jié)果是什么呢?

//第一段代碼
var a = 1;
function a {
  return 2;
}
console.log(a);
//第二段代碼
var b;
console.log(b);
function b(){
  return 3;
}
b = 4;
console.log(b);
//第三段代碼
var c;
console.log(c);
c = 5;
console.log(c);
function c() {
  return 6;
};
c();
console.log(c);
//第四段代碼
var d;
console.log(d);
d = 7;
console.log(d);
var d = function() {
  console.log(8);
}
d();
console.log(d);

感覺怎么樣?大腦有沒有點(diǎn)懵?
上面的幾段代碼中普通變量和函數(shù)變量使用了相同的變量名,在我們平時(shí)寫程序是不推薦這樣的,但是你作為一名JavaScript程序員你應(yīng)該能分析其執(zhí)行順序,這也是我們前端開發(fā)面試過程中經(jīng)??疾斓膯栴}。下面我們看看上面三段代碼經(jīng)過Js引擎解釋之后真正的執(zhí)行順序。

//第一段代碼被Js引擎解釋之后的執(zhí)行順序

var a;
function a() {
  return 2;
}
a = 1;
console.log(a); // 1

//第二段代碼被Js引擎解釋之后的執(zhí)行順序

var b;
function b() {
  return 3;
}
console.log(b); // ? b(){ return 3;}
b = 4;
console.log(b); // 4

//第三段代碼被Js引擎解釋之后的執(zhí)行順序

var c;
function c() {
  return 6;
}
console.log(c); // ? c(){ return 6;}
c = 5;
console.log(c); // 5
c(); // 報(bào)錯 Uncaught TypeError: c is not a function
console.log(c); // 不會被執(zhí)行

//第四段代碼被Js引擎解釋之后的執(zhí)行順序

var d; //此處是第一個(gè)變量聲明
var d; //此處是函數(shù)表達(dá)式的變量聲明提升
console.log(d); //此處d的值為undefined
d = 7;
console.log(d); // 7
d = function() {
  console.log(8);
}
d(); // 8
console.log(d); // ? (){ console.log(8);}
  1. 重復(fù)聲明
    • var 和 function能重復(fù)聲明,后者覆蓋前者
    • let 和 const 則不能重復(fù)聲明
var a = 1;
var a;
console.log(a); // 1 如果不明白為什么是1,仔細(xì)看上一部分

let a = 1;
let a;
console.log(a); //報(bào)錯 Uncaught SyntaxError: Identifier 'a' has already been declared
  1. 作用域的范圍
    • var 定義的變量的作用域是以函數(shù)為界限
    • let 和 const 是塊作用域(常見為 for 和 if 語句的大括號為界限)
    • var 可以定義全局變量和局部變量,let 和 const 只能定義局部變量

如何理解var定義的變量沒有塊級作用域,let 和 const 聲明的變量有塊級作用域?

看下面的程序

function fn() {
  var count1 = 0;
  var count2 = 0;
  for(var i=0;i<10;i++) {
    count1++
  }
  for(let j=0;j<10;j++) {
    count2++
  }
  /*if(true) {
    let k = 'hello';
  }*/
  console.log(i); // 10
  console.log(j); // 報(bào)錯 Uncaught ReferenceError: j is not defined at fn
  //console.log(k); // 報(bào)錯 Uncaught ReferenceError: k is not defined at fn
}
fn();
  1. const 的特殊之處
    • 使用const聲明的基本數(shù)據(jù)類型一旦聲明其值不可更改,引用類型比較特殊,其內(nèi)部屬性的值可更改。
//使用const定義變量,如果其值為基本數(shù)據(jù)類型,則值不可更改
const a = 1;
a = 2; // 報(bào)錯 Uncaught TypeError: Assignment to constant variable.

//使用const定義的變量,如果其值為引用數(shù)據(jù)類型,則該變量內(nèi)部屬性的值可更改
const obj = {x: 1};
obj.x = 2;
console.log(obj.x); // 2

const arr = [1];
arr.push(1);
console.log(arr); // [1, 1]
JavaScript變量使用(賦值)

聲明一個(gè)變量,就是為了在這個(gè)變量中存儲一些值。

還記得JavaScript變量的值都有哪些數(shù)據(jù)類型嗎?

簡單數(shù)據(jù)類型:number,string,boolean,null(特殊類型),undefined(特殊類型)
引用數(shù)據(jù)類型:object
es6新增數(shù)據(jù)類型:symbol

存儲簡單數(shù)據(jù)類型的變量和存儲引用類型的變量在使用過程中需要注意什么?

看一下下面的程序

var age= 22;
var job = 'programer';
var obj = {name: "zhang san"};
var arr = [1, 2];
var fn = function(age, obj, arr) {
  var newAge = age;
  var newObj = obj;
  var newArr = arr;
  newAge = 23;
  newObj.name = "Jack";
  newArr.push(3);
};
fn(age, obj, arr);
console.log(age); // 22
console.log(obj); // {name: "Jack"}
console.log(arr); // [1, 2, 3]

上面的程序相信大部分前端程序員都能知道是什么原因?qū)е碌摹?br> 簡單數(shù)據(jù)類型,其在內(nèi)存中分別占有固定大小的空間,他們的值保存在??臻g,我們通過按值來訪問。
引用類型,由于其值的大小不固定,因此不能把它們保存到棧內(nèi)存中。但內(nèi)存地址大小的固定的,因此可以將內(nèi)存地址保存在棧內(nèi)存中。 這樣,當(dāng)查詢引用類型的變量時(shí), 先從棧中讀取內(nèi)存地址, 然后再通過地址找到堆中的值。我們按其引用訪問。
如果理解的不是很好,可以詳細(xì)讀前端高質(zhì)量知識(一)-JS內(nèi)存空間詳細(xì)圖解這篇文章

思考!函數(shù)傳參中包括引用類型變量,在函數(shù)執(zhí)行后如何保證外部的引用類型變量不被修改?

對!深拷貝。后續(xù)文章中會詳細(xì)介紹。

JavaScript變量類型檢測

說起變量類型檢測,感覺和變量聲明提升一樣,又有好多說不完的話了。先思考兩個(gè)問題吧。

使用typeof variable 得到的值都有哪些?這些值和上文中提到的JavaScript數(shù)據(jù)類型有什么關(guān)系?

來,看下面的程序。

var a = 1;
var b = "hello Jack";
var c = true;
var d = null;
var e = undefined;
var obj = {name: "Jack"};
var arr = [1, 2];
var date = new Date();
var pattern1 = /hello/g;
var pattern2 = new RegExp("[bc]at", "i");
var fn = function(msg) { alert(msg);};

/*es6*/
var s = Symbol();
...
typeof a; // "number"
typeof NaN; // "number"
console.log(typeof b); // "string"
console.log(typeof c); // "boolean"
console.log(typeof d); // 注意 "object"
console.log(typeof e); // "undefined"
console.log(typeof obj); // "object"
console.log(typeof Object); // "function"
console.log(typeof arr); // "object"
console.log(typeof Array); // "function"
console.log(typeof date); // "object"
console.log(typeof pattern1 ); // "object"
console.log(typeof pattern2); // "object"
console.log(typeof RegExp); // "function"
console.log(typeof fn); // "function"
console.log(typeof Function); // "function"
console.log(typeof window); // "object"
console.log(typeof Math); // "object"
console.log(typeof s1); // "symbol"
...

使用typeof進(jìn)行類型檢測的情景差不多了吧。但是使用typeof對變量進(jìn)行類型檢測時(shí)有些情況下得到的值并不是我們預(yù)想的,比如 typeof null,可以說是JavaScript里面的一個(gè)bug,但是這個(gè)bug也不會被糾正了,所以記住就可以了。

使用typeof Object 為什么得到"function" ?

在JavaScript中,Object和Array等都是構(gòu)造函數(shù),構(gòu)造函數(shù)也是函數(shù),所以使用typeof得到的值是"function"是可以理解。

使用typeof檢測得到的"function" 是變量的數(shù)據(jù)類型嗎?

注意!typeof是JavaScript提供的一種檢測變量類型的方法,此處的變量類型是為了給Js引擎分析的,所以它把object和function給區(qū)分開了,和上文提到的JavaScript變量值的數(shù)據(jù)類型不完全同。上文描述的變量值的數(shù)據(jù)類型是相對于內(nèi)存方面有更重要的信息(比如存放于棧內(nèi)存或者堆內(nèi)存)。

為什么使用typeof檢測存放于堆內(nèi)存中的“對象”得到的值有的是“object”,有的是“function”?

JavaScript中的對象分為普通對象和函數(shù)對象。這兩種對象都屬于引用類型,并存放于堆內(nèi)存中。這兩種對象會在后續(xù)文章中深入討論。

怎么判斷一個(gè)對象是另外一個(gè)對象(在Java中稱為類)的實(shí)例?

是的,你應(yīng)該很快的就能想到instanceof。

JavaScript變量銷毀

變量中一旦存了值,它就會占用相應(yīng)的內(nèi)存空間。變量中存的是簡單數(shù)據(jù)類型則存儲在棧內(nèi)存中,如果是引用類型則存儲在堆內(nèi)存中。

銷毀,是指從內(nèi)存中回收該變量的存儲空間

那么變量什么時(shí)候會被銷毀(回收)?

如果是全局變量即在window環(huán)境下,當(dāng)瀏覽器窗口關(guān)閉后該變量被銷毀(回收)。
如果是局部變量(函數(shù)內(nèi)部變量),當(dāng)函數(shù)執(zhí)行完之后即被銷毀(回收)。
如果是使用let 和 const定義的應(yīng)用在塊級作用域中的變量,則在當(dāng)前塊執(zhí)行完之后被銷毀(回收)。

思考!什么樣的情況下函數(shù)內(nèi)部的變量會一直保存在內(nèi)存當(dāng)中?

是的,你沒有猜錯。使用閉包的情況下,函數(shù)內(nèi)部的變量不會在函數(shù)執(zhí)行完之后立即回收,它們會一直留在內(nèi)存中。關(guān)于JavaScript閉包會在后續(xù)文章中詳細(xì)討論。

小結(jié)

變量是編程語言的根基,我們前端程序員應(yīng)該熟練掌握J(rèn)avaScript變量的各個(gè)特性,尤其是變量聲明提升類型檢測。寫到這里,關(guān)于JavaScript變量相關(guān)的知識點(diǎn)聊的差不多了。其實(shí)每個(gè)知識點(diǎn)如果深入挖的越深則涉及的范圍越來越廣。在這篇文章中盡量做到點(diǎn)到為止。

這是我在簡書上寫的第一篇總結(jié)性文章,文章結(jié)構(gòu)以及文字表達(dá)方面經(jīng)驗(yàn)不足。如有遺漏或描述錯誤的地方請各位同學(xué)留言指正。

參考文章

JavaScript中的聲明變量方式的匯總

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容