JavaScript 基础天花板:你真的知道 JS 变量吗?

50 阅读4分钟

一天,你去面试了,面试官问你,你的前端基础能力怎么样?

:我的基础很扎实。

面试官:变量相关的知识点能说出 10 个吗?

:哈,哦,我尝试说一下

:第一点,我们可以用 var、let、const、function 常规关键词声明变量,另外还有 class、import

:第二点,var 声明的变量会提升,function 声明的函数有同等变量提升的效果。其他变量声明关键词不会。什么是变量提升呢,举个例子:

function varUp() { 
 console.log(name); // 在 var 声明变量之前就直接使用变量,不会报错
 var name = '超人迪迦'; 
} 
varUp(); // 执行后打印 undefined ,不会报错


function functionUp(){
    console.log(functionA); // 在 function 声明之前就直接使用 function,不会报错
    function functionA(){}
}
functionUp(); // 执行后打印 ƒ functionA(){} ,不会报错

事实上,上面的代码等价于下面的代码:

function varUp() { 
 var name; // 执行时,JS 引擎偷偷的在背后帮你提前声明了变量,这就是变量提升
 console.log(name);
 name = '超人迪迦'; 
} 
varUp(); // 执行后打印 undefined ,不会报错


function functionUp(){
    function functionA(){} // JS 引擎在执行函数代码之前会把函数添加到作用域,类似变量提升功能
    console.log(functionA);
}
functionUp(); // 执行后打印 ƒ functionA(){} ,不会报错

:第三点,相比 varletconst 声明的变量必须声明时立即赋值,否则后面就无法赋值了;

:第四点,const 赋值后,无法重新赋值,不过,对于引用类型,还是可以修改其属性值的,请看:

图片转存失败,建议将图片保存下来直接上传

:第五点,var 声明的变量要么是函数级要么是全局作用域,而 letconst 则是块级(注意,块级是包含函数级和例如 if、for 等代码块级)。

/** ========== var 函数级变量 Start ========= */
function varFunctionLevel() { 
 var varFunction1 = '123'; 
 
 if(true){
    var varFunction2 = '456';
 }
 console.log(varFunction1); // 打印 “123”
 console.log(varFunction2); // 打印 “456”
} 
varFunctionLevel();

console.log(varFunction1); // 执行后报错,varFunction1 变量只是函数级,函数外无法访问
/** ========== var 函数级变量 End ========= */


/** ========== var 全局作用域 Start ========= */
// 直接在浏览器控制台输入
var varGlobal1 = '123'; // 省略 var 关键词可获得一样的效果
varGlobal2 = '456';
console.log(window.varGlobal1); // 打印 “123”
console.log(window.varGlobal2); // 打印 “456”
/** ========== var 全局作用域 End ========= */


/** ========== let、const 块级作用域 Start ========= */
function letBlockLevel(){
    let letA = 1;
    if(true){
        let letB = 2;
    }
    console.log(letA); // 打印 “1”
    console.log(letB); // 执行到此句会报错,letB 只能在 if 代码块内访问
}
letBlockLevel();
/** ========== let、const 块级作用域 End ========= */

:第六点,循环中的变量声明。循环中使用 var 声明的变量会提升到整个函数或者全局作用域内,而使用 let 声明则不会,其原因是 let 声明时,JS 引擎会在每次循环的时候创建一个新的变量给当次循环使用。

function varFor() {
    console.log(i); // 下面循环代码使用 var 定义的 i 变量会提升成函数级变量
    for (var i = 0; i < 3; ++i) { 
        setTimeout(() => console.log(i), 0);
    }
    console.log(i); // 循环代码使用 var 定义的 i 变量会提升成函数级变量
} 
varFor(); // 执行后,实际输出 1 次 undefined 和 4 次 “3”,而不是想当然的报错或者 “undefined”,“0”,“1”,“2”,“3”。setTimeout 是异步代码,其他代码是同步代码,同步代码执行完成后,i 的值已经是 3,此时再执行异步代码时,输出的自然是 3


function letFor1() { 
    console.log(i); // 执行到此处直接报错了,i 未定义
    for (let i = 0; i < 3; ++i) { 
        setTimeout(() => console.log(i), 0);
    }
    console.log(i);
} 
letFor1(); // 执行后,直接报错


function letFor2() { 
    for (let i = 0; i < 3; ++i) { 
        setTimeout(() => console.log(i), 0);
    }
    console.log(i); // 执行到此处后代码报错,因为 i 未定义(let 定义的变量不会提升)
} 
letFor2(); // 执行后,打印 “0”、“1”、“2” 后,报错

:第七点,使用 function 声明的函数,有一个不成文约定,声明的函数小写开头时即为普通函数,大写开头时即为构造函数,两者本质上无区别,就是命名上大小写区别。

/** 普通函数 */
function func(){}

/** 构造器函数 */
function Func(){}

:第八点,使用 function 声明函数时,可不提供函数名,此时声明的函数为匿名函数。

/** 匿名函数 */
const func = function(){
    console.log('这是一个匿名函数!');
};
func(); // 打印 “这是一个匿名函数!”

:第九点,使用 function 声明函数时,用括号包裹,然后在后面加上括号 () 就可以立即执行,我们称为“立即执行函数”。现代打包工具,如:Webpack 打包出来的产物便是如此。

/** 匿名立即执行函数 */
(function(){
    console.log('这是一个匿名立即执行函数!');
})(); // 打印 “这是一个匿名立即执行函数!”

:第十点,class 其实是 JS 的语法糖,忠于面向对象编程的程序员,那必须是最爱,用它可方便实现继承等功能。

:第十一点,至于最佳实践,那必须是尽可能放弃 var,放开怀抱拥抱 let 和 const,使用 const 定义枚举变量,那不是美滋滋。

面试官:口答:嗯。心想:这小子基础能力很 6 嘛!

期待面试官通知你面试!