在ES5中,變量聲明只有var和function以及隱式聲明三種,在ES6中則增加了let,const,import和class四種,以下來(lái)介紹著七種變量的聲明。
var
ES5中最原始的變量聲明,用于聲明變量,其實(shí)JavaScript是弱類(lèi)型語(yǔ)言,對(duì)數(shù)據(jù)類(lèi)型變量要求不太嚴(yán)格,所以不必聲明每一個(gè)變量的類(lèi)型(這就是下面說(shuō)的隱式聲明,當(dāng)然這并不是一個(gè)好習(xí)慣),在使用變量之前先進(jìn)行聲明是一種好的習(xí)慣。
1.作用域
使用var聲明的變量的作用域是函數(shù)作用域(在ES5時(shí)代,只有函數(shù)作用域和全局作用域兩種作用域),在一個(gè)函數(shù)內(nèi)用var聲明的變量,則只在這個(gè)函數(shù)內(nèi)有效。
function test(){
var a;
console.log(a);//undefined
}
console.log(a);//ReferenceError: a is not defined
2.變量聲明提升
用var聲明變量時(shí),只要在一個(gè)函數(shù)作用域內(nèi),無(wú)論在什么地方聲明變量,都會(huì)把變量的聲明提升到函數(shù)作用域的最前頭,所以無(wú)論使用變量在變量聲明前還是聲明后,都不會(huì)報(bào)錯(cuò)(當(dāng)然只是聲明提前,賦值并沒(méi)有提前,所以如果使用在聲明之前,會(huì)輸出undefined,但不會(huì)報(bào)錯(cuò))。
function test(){
console.log(a);//undefined
var a=3;
}
隱式聲明
當(dāng)沒(méi)有聲明,直接給變量賦值時(shí),會(huì)隱式地給變量聲明,此時(shí)這個(gè)變量作為全局變量存在。
function test(){
a=3;
console.log(a);//3
}
test();
console.log(a);//3
當(dāng)然要注意,隱式聲明的話(huà)就沒(méi)有變量聲明提前的功能了,所以下面的使用是會(huì)報(bào)錯(cuò)的。
function test(){
console.log(a);//ReferenceError: a is not defined
a=3;
}
function
用function聲明的是函數(shù)對(duì)象,作用域與var一樣,是函數(shù)作用域。
function test(){
function a(){
console.log('d');
}
a();//'d'
}
a();//ReferenceError: a is not defined
同樣,function聲明也有變量聲明提升,下面是兩個(gè)特殊的例子:
function hello1(a){
console.log(a); //[Function: a]
function a(){}
console.log(a);//[Function: a]
}
hello1('test');
function hello2(a){
console.log(a); //test
var a=3;
console.log(a);//3
}
hello2('test');
這里有涉及到函數(shù)中形參的聲明,我們可以將以上兩個(gè)例子看成:
function hello1(a){
var a='test;
console.log(a); //[Function: a]
function a(){}
console.log(a);//[Function: a]
}
hello1('test');
function hello2(a){
var a='test;
console.log(a); //test
var a=3;
console.log(a);//3
}
hello2('test');
可以看到函數(shù)對(duì)象的聲明也提前了,但是在形參變量聲明之后(形參的變量聲明在所有聲明之前)。
當(dāng)函數(shù)對(duì)象和普通對(duì)象同時(shí)聲明時(shí),函數(shù)對(duì)象的聲明提前在普通對(duì)象之后。
function test(){
console.log(a);//[Function: a]
function a(){}
var a;
console.log(a);//[Function: a]
}
let
ES6新增的聲明變量的關(guān)鍵字,與var類(lèi)似。
當(dāng)然,與var也有很大區(qū)別:
1.作用域不同
let聲明的變量的作用域是塊級(jí)作用域(之前的js并沒(méi)有塊級(jí)作用域,只有函數(shù)作用域和全局作用域),var聲明的變量的作用域是函數(shù)作用域。
{
let a = 10;
var b = 1;
}
a // ReferenceError: a is not defined.
b // 1
2.不存在變量聲明提升
用var聲明變量時(shí),只要在一個(gè)函數(shù)作用域內(nèi),無(wú)論在什么地方聲明變量,都會(huì)把變量的聲明提升到函數(shù)作用域的最前頭,所以無(wú)論使用變量在變量聲明前還是聲明后,都不會(huì)報(bào)錯(cuò)。而let不一樣,與java以及其他語(yǔ)言一樣,let聲明的變量,在未聲明之前變量是不存在的。(js的語(yǔ)法越來(lái)越向java靠攏)
console.log(a); // undefined,但是不報(bào)錯(cuò)。
console.log(b); // ReferenceError: b is not defined.
var a = 2;
let b = 2;
注意:在使用babel時(shí)可能會(huì)遇到這樣的情況:
console.log(b); //undefined
let b = 2;
babel在翻譯es6時(shí),似乎直接將let變?yōu)榱藇ar,所以運(yùn)行時(shí)也有變量聲明提升了,但是在Chrome下運(yùn)行時(shí)是正確的。
3.暫時(shí)性死區(qū)
所謂暫時(shí)性死區(qū),意思是,在一個(gè)塊級(jí)作用域中,變量唯一存在,一旦在塊級(jí)作用域中用let聲明了一個(gè)變量,那么這個(gè)變量就唯一屬于這個(gè)塊級(jí)作用域,不受外部變量的影響,如下面所示。
無(wú)論在塊中的任何地方聲明了一個(gè)變量,那么在這個(gè)塊級(jí)作用域中,任何使用這個(gè)名字的變量都是指這個(gè)變量,無(wú)論外部是否有其他同名的全局變量。
暫時(shí)性死區(qū)的本質(zhì)就是,只要一進(jìn)入當(dāng)前作用域,所要使用的變量就已經(jīng)存在了,但是不可獲取,只有等到聲明變量的那一行代碼出現(xiàn),才可以獲取和使用該變量。
暫時(shí)性死區(qū)的意義也是讓我們標(biāo)準(zhǔn)化代碼,將所有變量的聲明放在作用域的最開(kāi)始。
var a = 123;
{
console.log(a);//ReferenceError
let a;
}
4.不允許重復(fù)聲明
在相同的作用域內(nèi),用let聲明變量時(shí),只允許聲明一遍。 (var是可以多次聲明的)
// 正確
function () {
var a = 10;
var a = 1;
}
// 報(bào)錯(cuò),Duplicate declaration "a"
function () {
let a = 10;
var a = 1;
}
// 報(bào)錯(cuò),Duplicate declaration "a"
function () {
let a = 10;
let a = 1;
}
const
const用來(lái)聲明常量,const聲明的常量是不允許改變的,只讀屬性,這意味常量聲明時(shí)必須同時(shí)賦值, 只聲明不賦值,就會(huì)報(bào)錯(cuò),通常常量以大寫(xiě)字母命名。
阮一峰大神的書(shū)里說(shuō),在嚴(yán)格模式下,重新給常量賦值會(huì)報(bào)錯(cuò),普通模式下不報(bào)錯(cuò),但是賦值無(wú)效。但是測(cè)試了一下,無(wú)論是嚴(yán)格還是非嚴(yán)格模式,都會(huì)報(bào)錯(cuò)。
const A = 1;
A = 3;// TypeError: "A" is read-only
const和let類(lèi)似,也是支持塊級(jí)作用域,不支持變量提升,有暫時(shí)性死區(qū).
注意:如果聲明的常量是一個(gè)對(duì)象,那么對(duì)于對(duì)象本身是不允許重新賦值的,但是對(duì)于對(duì)象的屬性是可以賦值的。
const foo = {};
foo.prop = 123;
foo.prop// 123
foo = {} // TypeError: "foo" is read-only
import
ES6采用import來(lái)代替node等的require來(lái)導(dǎo)入模塊。
import {$} from './jquery.js'
$對(duì)象就是jquery中export暴露的對(duì)象。
import命令接受一個(gè)對(duì)象(用大括號(hào)表示),里面指定要從其他模塊導(dǎo)入的變量名。注意:大括號(hào)里面的變量名,必須與被導(dǎo)入模塊對(duì)外接口的名稱(chēng)相同。
如果想為輸入的變量重新取一個(gè)名字,import命令要使用as關(guān)鍵字,將輸入的變量重命名。
import { New as $ } from './jquery.js';
注意,import命令具有提升效果,會(huì)提升到整個(gè)模塊的頭部,首先執(zhí)行。
class
ES6引入了類(lèi)的概念,有了class這個(gè)關(guān)鍵字,當(dāng)然,類(lèi)只是基于原型的面向?qū)ο竽J降恼Z(yǔ)法糖,為了方便理解和開(kāi)發(fā)而已,類(lèi)的實(shí)質(zhì)還是函數(shù)對(duì)象,類(lèi)中的方法和對(duì)象其實(shí)都是掛在對(duì)應(yīng)的函數(shù)對(duì)象的prototype屬性下。
我們定義一個(gè)類(lèi):
//定義類(lèi)
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
setSex(_sex) {
this.sex=_sex;
}
}
constructor方法,就是構(gòu)造方法,也就是ES5時(shí)代函數(shù)對(duì)象的主體,而this關(guān)鍵字則代表實(shí)例對(duì)象,將上述類(lèi)改寫(xiě)成ES5格式就是:
function Person(name, age){
this.name = name;
this.age = age;
}
Person.prototype. setSex = function (_sex) {
this.sex=_sex;
}
所以說(shuō),類(lèi)不算什么新玩意,大多數(shù)類(lèi)的特性都可以通過(guò)之前的函數(shù)對(duì)象與原型來(lái)推導(dǎo)。
1.所有類(lèi)都有constructor函數(shù),如果沒(méi)有顯式定義,一個(gè)空的constructor方法會(huì)被默認(rèn)添加(有點(diǎn)類(lèi)似java了)。當(dāng)然所有函數(shù)對(duì)象都必須有個(gè)主體。
2.生成類(lèi)的實(shí)例對(duì)象的寫(xiě)法,與ES5通過(guò)構(gòu)造函數(shù)生成對(duì)象完全一樣,也是使用new命令。
class B {}
let b = new B();
3.在類(lèi)的實(shí)例上面調(diào)用方法,其實(shí)就是調(diào)用原型上的方法,因?yàn)轭?lèi)上的方法其實(shí)都是添加在原型上。
b.constructor === B.prototype.constructor // true
4.與函數(shù)對(duì)象一樣,Class也可以使用表達(dá)式的形式定義。
let Person = class Me {
getClassName() {
return Me.name;
}
};
相當(dāng)于
var Person = function test(){}
5.Class其實(shí)就是一個(gè)function,但是有一點(diǎn)不同,Class不存在變量提升,也就是說(shuō)Class聲明定義必須在使用之前。
全局變量
全局對(duì)象是最頂層的對(duì)象,在瀏覽器環(huán)境指的是window對(duì)象,在Node.js指的是global對(duì)象。
ES5之中,全局對(duì)象的屬性與全局變量是等價(jià)的,隱式聲明或者在全局環(huán)境下聲明的變量是掛在全局對(duì)象上的。
ES6規(guī)定,var命令,function命令以及隱式聲明的全局變量,依舊是全局對(duì)象的屬性;而let命令、const命令、class命令聲明的全局變量,不屬于全局對(duì)象的屬性。
var a = 1;
console.log(window.a) // 1
let b = 1;
console.log(window.b) // undefined
函數(shù)的形參
函數(shù)的形參,隱藏著在函數(shù)一開(kāi)始聲明了這些形參對(duì)應(yīng)的變量。
function a(x,y){}
可以看成
function a(){
var x=arguments.length <= 0 || arguments[0] === undefined ? undefined : arguments[0];
var y=arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1];
}
當(dāng)然在ES6下默認(rèn)聲明就是用的let了,所以函數(shù)a變成:
function a(){
let x=arguments.length <= 0 || arguments[0] === undefined ? undefined : arguments[0];
let y=arguments.length <= 1 || arguments[1] === undefined ? undefined : arguments[1];
}
所以在ES6中會(huì)有以下幾個(gè)問(wèn)題:
function a(x = y, y = 2) {
return [x, y];
}
a(); // 報(bào)錯(cuò),給X賦值時(shí)y還未被let聲明。
function a(x,y) {
let x;//相當(dāng)于重復(fù)聲明,報(bào)錯(cuò)。
}