js中变量注意事项
变量声明
ECMAScript中的变量是弱类型的(同一个变量可以保存任何数据类型)。
- 通过var关键字来定义变量;
- 未经过初始化的变量的默认值是undefined;
- 使用var声明的变量是当前作用域的局部变量(如果在函数中定义一个变量,在函数执行完成后该变量就会被销毁);
- 没通过var关键字声明的变量会被自动声明为全局变量。
1 // 3 2 var iNum; 3 console.log(iNum); // undefined 4 5 // 4 6 function fnTest(){ 7 var sMsg = 'hum'; 8 console.log(sMsg); // hum 9 } 10 fnTest(); 11 console.log(sMsg); // sMsg is not defined 12 13 // 5 14 function fnTest1(){ 15 sMsg1 = 'hum'; // 未通过var关键字声明的变量 16 console.log(sMsg1); // hum 17 } 18 fnTest1(); 19 console.log(sMsg1); // hum 在函数外部能访问
NOTE:为声明的变量只能进行typeof和delete操作,其他任何操作都会抛出错误。
1 console.log(typeof iName); // undefined 2 console.log(delete iName); // true(居然返回true) 3 console.log(iName); // iName is not defined
严格模式注意事项:
- 给未经声明的变量赋值会抛出ReferenceError错误;
- 声明名为eval和arguments的变量会导致语法错误;
- 给未声明的变量进行delete操作会导致语法错误。
变量类型
ECMAScript由三种简单数据类型(Boolean、Number、String)、两种特殊类型(Undefined、Null)和一种复杂数据类型(Object)组成。
typeof操作符检测变量数据类型返回的结果类型:
- undefined(变量未定义或定义了未赋值);
- boolean(布尔值);
- string(字符串);
- number(数值);
- object(对象或null);
- function(函数);
变量作用域
要深入理解作用域我们先来看看执行环境和作用域链。
每个执行环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中(该对象存在后台,代码无法访问)。
全局执行环境:ECMAScript的宿主环境(浏览器就是window),全局执行环境在程序退出时才会被销毁。
局部执行环境:每个函数都有自的执行环境,函数执行时,函数会被推入一个环境栈中。函数执行完成后,环境栈被弹出,把控制权交给之前的执行环境。
代码进入一个执行环境时会创建变量对象的一个作用域链。该作用域链的前端是当前执行环境的变量对象,下一个是外部执行环境的变量对象,一直到全局执行环境的变量对象。
标识符解析是沿着作用域链一级一级向上检索,直到找到该标识符为止(如果没有找到就会发生错误)。
1 var iNum1 = 1; 2 3 function fnOuter(){ 4 var iNum2 = 11; 5 6 function fnInner(){ 7 var iNum3 = 111; 8 9 console.log(iNum1); // 作用域链查找:fnInner(无)->fnOuter(无)->window(1) 10 console.log(iNum2); // 作用域链查找:fnInner(无)->fnOuter(11) 11 console.log(iNum3); // 作用域链查找:fnInner(111) 12 } 13 fnInner(); 14 15 console.log(iNum1); // 作用域链查找:fnOuter(无)->window(1) 16 console.log(iNum2); // 作用域链查找:fnOuter(11) 17 console.log(iNum3); // 作用域链查找:fnOuter(无)->window(无)报错 18 } 19 fnOuter(); 20 console.log(iNum1); // 作用域链查找:window(1) 21 console.log(iNum2); // 作用域链查找:window(无)报错 22 console.log(iNum3); // 作用域链查找:window(无)报错
变量声明提升
ECMAScript进入一个执行环境会把执行环境中所有的变量的声明提升到当前作用域的最前端。
表现一:变量声明提升
1 var iNum = 1; 2 function fnTest(){ 3 console.log(iNum); // undefined 4 var iNum = 11; 5 } 6 fnTest();
上面的代码输出了undefined。原因是当执行函数fnTest时会检索函数中所有变量的声明并优先执行。上面的代码就类似于下面的代码。
var iNum = 1; function fnTest(){ var iNum; // **变量声明提升 console.log(iNum); // undefined iNum = 11; } fnTest();
表现二:函数声明的提升
1 fnTest(); // fnTest在声明之前也可以调用 2 function fnTest(){ 3 console.log('fnTest'); // fnTest 4 }
表现三:函数表达式赋值未提升
1 fnTest(); // undefined is not a function(此时fnTest只是声明了还未赋值所以是undefined) 2 var fnTest = function (){ 3 console.log('fnTest'); 4 }
函数中变量解析顺序
- 内置对象this和arguments;
- 形式参数;
- 函数声明;
- 变量声明。
1 fnTest(1); 2 function fnTest(iNum){ 3 console.log(this); // window 4 console.log(arguments); // [1] 5 console.log(iNum); // 1 6 console.log(sMsg); // undefined 7 console.log(fnInner); // function fnInner(){alert('fnInner');} 8 console.log(fnInner1); // undefined 9 10 var sMsg = 'test'; 11 function fnInner(){ 12 alert('fnInner'); 13 } 14 var fnInner1 = function(){ 15 alert('fnInner1'); 16 } 17 }
在同一执行环境中声明变量名和函数名相同的变量时,函数声明会覆盖变量声明的提升。
1 fnTest(); 2 function fnTest(){ 3 console.log(vNum); // function vNum(){alert('a');} 4 5 var vNum = 1; 6 console.log(vNum); // 1 7 function vNum(){ 8 alert('a'); 9 } 10 var vNum = function(){ 11 alert('b'); 12 } 13 console.log(vNum); // function vNum(){alert('b');} 14 }
函数表达式中的表达式有函数名时,该函数名代表的函数在当前作用域不会提升也不能访问。
1 fnTest(); 2 function fnTest(){ 3 console.log(fnInner); // undefined 4 console.log(fn); // fn is not defined 5 6 var fnInner = function fn(){ 7 console.log(fn); // 函数内部能访问fn function fn(){console.log(fn);} 8 } 9 fn(); // fn is not defined 10 fnInner(); 11 }
函数表达式中的表达式有函数名时,函数内部一旦有该函数名的变量或函数声明该函数名就被覆盖了。
1 fnTest(); 2 function fnTest(){ 3 var fnInner = function fn(){ 4 console.log(fn); // function fn(){} 5 function fn(){ 6 7 } 8 } 9 fnInner(); 10 }
1 fnTest(); 2 function fnTest(){ 3 var fnInner = function fn(){ 4 console.log(fn); // undefined 5 var fn = 2; 6 } 7 fnInner(); 8 }