js 變量聲明易混淆的幾點知識
這是我 JavaScript 學習過程中遇到的一些容易混淆的地方,趁著有時間,做了一個整理。
變量提升
變量與函數名提升優先級
js 作用域內有變量,這個很好理解,但有一些細節需要註意。
console.log(foo); // 函數
function foo(){
console.log("函數聲明");
}
console.log(foo); // 函數
var foo = "變量";
console.log(foo); // 變量
當變量名與函數名同名,且都提升上去了,那最終結果是哪個聲明起作用呢?
有兩個知識點:
- var foo;並不會覆蓋之前的變量
- 函數提升優先級比變量提升要高,且不會被變量聲明覆蓋,但是會被變量賦值覆蓋,所以上面的代碼實際上是
function foo(){ // 優先級最高,提升到最前面
console.log("函數聲明");
}
var foo; // 只提升聲明,不提升賦值,且不能覆蓋函數聲明
console.log(foo);
console.log(foo);
foo = "變量"; // 可以覆蓋函數聲明
console.log(foo);
連等賦值的變量提升
var num1 = 1;
function fn(num3){
console.log(num1); //output undefined
console.log(num3); //output 4
console.log(num4); //throw error “num4 is not defined”
console.log(num2); //throw error “num2 is not defined”
var num1 = num4 = 2; // js 連等賦值 num4 不會被提升
num2 = 3; // 沒有 var 會掛載到全局作用域,但不會提升,所以之前會報錯
var num3= 5;
}
fn(4);
if 判斷內變量提升
if (true) {
function fn (){ return 1; }
}else {
if(false){
function fn(){ return 2; }
}
}
console.log(fn.toString());
console.log(fn())
以下是從找到這個例子的原文中摘抄的內容:
chrome和ie一均為function fn(){ return 2;},而firefox中依然報錯。
可見三者處理並不相同。ff中會提前變量的聲明,但不會提前塊級作用域中的函數聲明。而chrome和ie下就會提前塊級作用域中的函數聲明,而且後面的聲明會覆蓋前面的聲明。
我寫這篇文章時,測試的結果 IE10 及以下,返回2,IE11和谷歌,火狐新版,返回1
var fn;
console.log(fn); // undefined
if (true) {
function fn(){ return 1; }
}else {
if(false){
function fn(){ return 2; }
}
}
console.log(fn.toString()); // 函數 1
console.log(fn()) // 1
if判斷內函數並沒有提升
對有名函數賦值
function test5() {
var fn = function fn1() {
log(fn === fn1); // true
log(fn == fn1); // true
}
fn();
log(fn === fn1); // fn1 is not defined
log(fn == fn1); // fn1 is not defined
}
test5();
在function內部,fn完全等於fn1
在function外面,fn1則是 not defined
!兼容
b();
var a = function b() {alert(‘this is b‘)};
在ie8及以下瀏覽器是可以執行b的. 說明不同瀏覽器在處理函數表達式細節上是有差別的.
return 後聲明的提升
函數 return 語句後的變量、函數聲明提升
function text6() {
var a = 1;
function b() {
a = 10;
return;
function a() {}
}
b();
console.log(a); // 1
}
text6();
函數的作用域內賦值
在js中,提到變量賦值,就要先說作用域,而作用域,在es6之前,只有函數才會形成獨立的作用域,然後函數的嵌套形成了 js 的作用域鏈。子作用域內可以訪問父級作用域內的元素。函數的作用域在函數確定的時候就已經確定,與調用無關。
// test1
var x = 1;
function foo(x) {
var x = 3;
var y = function() {
x = 2;
console.log(x)
}
y();
console.log(x);
return y
}
var z = foo() // 2 2
z() // 2
這段函數會輸出三個 2 ,指向同一個 x,甚至,將 x 改為對象,就更明顯了
// test2
var x = "abc";
function foo(x) {
var x = c;
var y = function() {
return x;
}
return y;
}
var c = {a:1}
var z = foo();
var b = z();
console.log(b === c); // true
上面例子中,foo 函數執行後,返回 y 函數並賦值給 z,z 指向 y 函數(函數體),此時,z 並不在 foo 函數的作用域內,在此作用域不能訪問到 x,但 z 只是引用類型數據的一個指針,只是同 x 指向了同一個對象而已。而執行 z 函數,則會返回 x 的值,這個值是函數 y 作用域內訪問到的 x 的值,是根據函數的書寫位置確定的作用域,並不會因為調用位置不同,而改變變量的指向。
但是同時要註意,雖然函數作用域在函數寫出來時就已經確定,但具體的值卻跟調用的時機有關。
// test3
var x = "abc";
function foo(x) {
var x = c;
var y = function() {
x.a++;
return x;
}
return y
}
var c = {a:1}
var z = foo();
console.log(z()) // {a: 2}
console.log(z()) // {a: 3}
console.log(z()) // {a: 4}
這個例子中,輸出的三次都是同一個對象,但輸出的值不同,這是因為輸出的時候的值不同,這就和調用時的實際值有關了。
同時,函數的另一個值也與函數的調用情況相關,就是 this。函數的 this 指向函數的調用者,與函數在哪裏定義沒有關系。
var x = "abc";
var b = {x:"def"}
function foo(x) {
var x = c;
var y = function() {
x.a++;
return this.x;
}
console.log(y()); // abc
return y
}
var c = {a:1}
var z = foo();
console.log(z()) // abc
b.a = z;
console.log(b.a()) // def
b.b = {
x: "hig",
z: z
}
console.log(b);
console.log(b.b.z()); // hig
上面的例子就說明了,函數內的 this 始終指向本次調用函數的對象,如果沒有,就指向全局對象,而如果指向的對象本身是另一個對象的屬性,這並不影響,甚至,將上個例子裏的 b.b 的 x 屬性刪除,最後調用時 b.b.z() 的 this 依然指向 b.b 所指的對象,即使該對象對象沒有 x 值,會返回 undefined,而不會向上級尋找。
function fn(){
var array=[];
var b = {a:1}
for(var i=0;i<10;i++){
array[i]=function(){
var abc = {def: "ghi"};
return b;
}
}
return array;
}
var a = fn();
console.log(a) //[?, ?, ?, ?, ?, ?, ?, ?, ?, ?]
console.log(a[0].toString()) //f
console.log(a[1] === a[2]) // false
console.log(a[1] == a[2]) // false
console.log(a[1].toString() === a[2].toString()) // true
console.log(a[1]() === a[2]()) // true
兩個函數相等的比較:兩個函數的作用域相同並且函數內容相同,那麽,比較結果為這兩個函數相等,否則,不相等!不是我們直接用 == 去比較,而是按照這個規則去進行比較。
函數是對象,復合型的值。一般比較引用,同一個引用,就相等;否則不等。比較toString幾乎沒任何意義:因為對於函數調用來說,作用域只是一個不可見的透明的規則
對 arguments 賦值
function fn(a, b) {
var a = 1;
var b = 2;
console.log(arguments[0]);
console.log(arguments[1]);
arguments[0] = 3;
arguments[1] = 4;
console.log(a);
console.log(b);
}
fn(5,6) // 1 2 3 4 傳入實參,實參和形參指向相同
fn() // unde unde 1 2 沒有傳入實參,arguments 指向 undefined,形參與實參不再相關
function fn(a, b) {
‘use strict‘
var a = 1;
var b = 2;
console.log(arguments[0]);
console.log(arguments[1]);
arguments[0] = 3;
arguments[1] = 4;
console.log(a);
console.log(b);
}
fn(5,6) // 5 6 1 2 嚴格模式下 arguments 指向實參,與形參不相關
fn() // unde unde 1 2
javascript中Arguments對象是函數的實際參數,arguments對象的長度是由實參個數而不是形參個數決定的。形參是函數內部重新開辟內存空間存儲的變量,但是其與arguments對象內存空間並不重疊。
在es5規範下的非嚴格模式下,函數調用時傳入實參,函數內部使用 形參 與 arguments 指向相同, arguments 的具體屬性改變, 形參的值也會改變,但如果沒有傳入實參,arguments 與 形參的指向就沒有關系了。
es5規範的嚴格模式下,形參與arguments 分別存放,不會相關
但自es6之後,非嚴格模式下效果也同es5 嚴格模式一樣,即arguments和參數符號分別存放。
而es6的嚴格模式下,修改arguments會報錯。
函數內 形參、變量、函數 同名的問題
function aa(a,b,c) {
console.log(arguments); // {0: 函數a, 1: 2, 2: 3} 通過更改形參的值,可以更改 arguments
function a(){console.log(a)}
console.log(a); // 函數 a
console.log(aa); // undefined, var aa 被提升
console.log(arguments); // 同上
var a = "ee";
var aa = "444";
arguments = 6;
console.log(a); // ee
console.log(aa); // 444
console.log(arguments) // 6
}
aa(1,2,3)
function aa(a,b,c) {
‘use strict‘ // 調用嚴格模式
console.log(arguments); // 指向arguments 對象,{0:1,1:2,2:3}
function a(){console.log(a)}
console.log(a); // 函數a
console.log(aa); // undefined
console.log(arguments); // 同上
var a = "ee";
var aa = "444";
arguments[0] = 6;
// arguments = 6; 嚴格模式 不能直接對 arguments 進行賦值
console.log(a); // ee
console.log(aa); // 444
console.log(arguments) // // 指向arguments 對象,{0:6,1:2,2:3}
}
aa(1,2,3)
從網上找到的一些解釋
填充變量的順序是: 函數的形參 -> 函數聲明->變量聲明, 當變量聲明遇到VO中已經有同名的時候,不會影響已經存在的屬性。
函數形參————由名稱和對應值組成的一個變量對象的屬性被創建;沒有傳遞對應參數的話,那麽由名稱和undefined值組成的一種變置對象的屬性性也將被創建
函數聲明————由名稱和對應值(函數對象(function-object))組成一個變量對象的屬性被創建;如果變置對象已經存在相同名稱的厲性,則完全替換這個屬性
變量聲明————由名稱和對應值(undefined〉組成一個變量對象的屬性被創建;如果變量名稱跟已經聲明的形式參數或函數相同,則變量聲明不會幹擾已經存在的屬性
一般情況下,會按照四種方式依次解析
語言內置:
形式參數:
函數聲明:
變量聲明:
也有例外:
內置的名稱arguments表現得很奇怪,看起來應該是聲明在形參之後,但是卻在聲明之前。這是說,如果形參裏面有arguments,它會比內置的那個優先級高。所以盡可能不要在形參裏面使用arguments;
在任何地方定義this變量都會出語法錯誤
如果多個形式參數擁有相同的名稱,最後的那個優先級高,即便是實際運行的時候它的值是undefined;
資料:
https://segmentfault.com/q/1010000006727575?_ea=1111028
http://www.cnblogs.com/sharpxiajun/archive/2011/09/16/2179010.html
https://www.cnblogs.com/zhouyongtao/archive/2012/11/22/2783089.html
https://www.cnblogs.com/luqin/p/5164132.html
https://segmentfault.com/q/1010000006135524
https://www.cnblogs.com/Eric1997/p/7499819.html
js 變量聲明易混淆的幾點知識