javascript技巧

关注公众号 jb51net

关闭
首页 > 网络编程 > JavaScript > javascript技巧 > js var let const

javascript中的var、let、const最佳实践

作者:前端风云志

var,let and const是JavaScript中三种定义变量的方式,它们之间有什么区别呢?下面给大家介绍javascript中的var、let、const最佳实践,感兴趣的朋友一起看看吧

简介

varlet and const是JavaScript中三种定义变量的方式,它们之间有什么区别呢?这是前端面试中常见的一道题,今天我们来一文说透它。
letconst区别不大,主要是const声明的是常量,不可修改,而let声明的变量是可修改的。所以我们重点放在varlet上。

变量初始化

声明变量的同时为其赋值叫做初始化。

// `var`和`let`声明的变量可以不赋值,此时变量的值为`undefined`。
var num; // num的值是undefined
num = 1; // num的值是1
let str; // str的值是undefined
str = 'hello'; // str的值是'hello' 
// `const`声明的变量必须赋值,否则会报错。
const a; // SyntaxError: Missing initializer in const declaration

变量提升 - Hoisting

Hoisting这个词中文译为提升,就是将变量的声明提升到其作用域的顶部,注意提升的是声明,而不是赋值。

如果var是在全局作用域声明的,那么它会被提升到全局作用域的顶部。

console.log(name); // undefined
var name = 'Philip';

以上代码等价于:

var name; // `var`声明的变量会被提升到其作用域顶部。
console.log(name); // undefined
name = 'Philip';

如果var是在函数作用域声明的,那么它会被提升到函数作用域的顶部。

function printName() {
  console.log(name); // undefined
  var name = 'Philip';
}
printName();

以上代码等价于:

function printName() {
  var name; // `var`声明的变量会被提升到其作用域顶部。
  console.log(name); // undefined
  name = 'Philip';
}
printName();

let和const声明的变量不会被提升。

对于letconst,它们不会被提升,所以下面代码会报错。

console.log(num); // ReferenceError: Cannot access 'num' before initialization
const num = 1;

前面说过,关于letconst是否被提升有争议。

比如下面的代码:

const x = 1;
{
  console.log(x); // ReferenceError: Cannot access 'x' before initialization
  const x = 2;
}

这段代码会报错,但是如果我们把{}内的const x = 2;注释掉,那么代码就不会报错。如果const x = 2没有被提升的话,那么console.log(x)应该可以访问到全局的const x = 1,而不会报错。换句话说:因为const x = 2被提升了,所以console.log(x)访问的是提升后的x,而此时x还没有被初始化,所以报错。

提升只针对变量声明,不包括赋值。

下面的代码会报错,因为x = 1是赋值,并不是声明,所以不会提升。(注意:如果变量声明前没有加varletconst,那么其实产生的是一个意外的全局变量。)

console.log(x); // ReferenceError: x is not defined
x = 1;

如果有同名函数和变量,那么提升后,变量位于函数之前(或者说函数会覆盖变量)。

以下代码中有一个同名的函数和变量。

console.log(foo); // [Function: foo], not undefined.
function foo() {
  console.log('function foo');
}
var foo = 1;

提升后代码如下:

var foo;
function foo() {
  console.log('function foo');
}
console.log(foo);
foo = 1;

面试题

看几道面试题,以下几段代码输出什么?

a = 2;
var a;
console.log(a); // 2

解决var提升的问题很简单,就是按照提升规则将代码重写一下,上面的代码等价于如下代码,结果一目了然。

var a;
a = 2;
console.log(a); // 2
var a = true;
foo();
function foo() {
  if (a) {
    var a = 10;
  }
  console.log(a);
}

只要函数内部有var声明的变量,那么所有全局声明的var变量都会被忽略,以上代码提升后等价于如下代码(注意function也有提升),函数内部的var永远会覆盖全局的var

var a = true;
function foo() {
  var a; // value of a is `undefined`
  if (a) {
    a = 10; // never executed.
  }
  console.log(a);
}
foo();
function fn() {
  console.log(typeof foo);
  var foo = 'variable';
  function foo() {
    return 'function';
  }
  console.log(typeof foo);
}
fn();

还是那句话,此类题目的解法就是按照提升规则把代码重新写一遍,以上代码提升后等价于如下代码:

function fn() {
  var foo;
  function foo() {
    return 'function';
  }
  console.log(typeof foo);
  foo = 'variable';
  console.log(typeof foo);
}
fn();

所以输出结果是functionstring

变量的作用域

面试题

第一题

以下代码输出什么?

let x = 1;
{
  let x = 2;
}
console.log(x);

答案:1,因为let有块级作用域,所以let x = 2只在{}内有效。

第二题

以下代码输出什么?

var x = 1;
{
  var x = 2;
}
console.log(x);

答案:2,因为var没有块级作用域,所以var x = 2会覆盖外部的var x = 1

第三题

以下代码输出什么?

let name = 'zdd';
{
  console.log(name); 
  let name = 'Philip';
}

答案:ReferenceError: Cannot access 'name' before initialization。因为let有块级作用域,所以console.log(name);访问的是let name = 'Philip';之前的name,而此时name还没有被初始化,处于暂时性死区中,所以报错。

第四题

以下代码输出什么?

'use strict';
{
  function foo() {
    console.log('foo');
  }
}
foo();

答案:ReferenceError: foo is not defined。因为foo是在块级作用域内声明的,所以在外部无法访问。但是如果我们把'use strict';去掉,那么代码就可以正常运行。因为在非严格模式下,函数声明会被提升到全局作用域。

第五题

以下代码输出什么?

(() => {
  let x;
  let y;
  try {
    throw new Error();
  } catch (x) {
    x = 1;
    y = 2;
    console.log(x);
  }
  console.log(x);
  console.log(y);
})();

答案:1 undefined 2。因为catch中的x是一个新的变量,不是外部的x,所以x = 1只会改变catch中的x,而不会改变外部的x。而y = 2不是catch的参数,只是在catch中赋值的,所以会改变外部的y

暂时性死区 - Temporal Dead Zone

TDZ即Temporal Dead Zone - 中文名暂时性死区,是指letconst声明的变量在其作用域开始到变量声明之间的这段区域。在暂时性死区内无法访问变量,访问会报错。

function foo() {
  console.log(b); // ReferenceError: Cannot access 'b' before initialization
  let a = 1;
  const b = 2;
}
foo();

对于以上代码,常量b的暂时性死区开始于函数的第一行,终止于b的声明,而console.log(b);这句恰恰在暂时性死区内访问了b,所以会报错。

面试题

以下代码输出什么?

function foo() {
  console.log(typeof bar);
  const bar = 1;
}
foo();

答案:
ReferenceError: Cannot access 'bar' before initialization因为console.log(typeof bar);这句在bar的暂时性死区内访问了bar,所以会报错。可以看到,即使强如typeof这种几乎不会报错的操作符也无法规避暂时性死区。

如果我们把const bar = 1;去掉,那么代码就不会报错。typeof操作符对于没有声明的变量不会报错,而是返回undefined

function foo() {
  console.log(typeof bar); // 输出undefined
}

重新声明- Redeclaration

面试题

看几道面试题,以下几段代码输出什么?

var a = 1;
function foo() {
  var a = 2;
  {
    var a = 3;
    console.log(a);
  }
  console.log(a);
}
foo();
console.log(a);

答案:3 3 1, 这个题主要考察两个知识点:

以上代码提升后等价于如下代码:

var a;
a = 1;
function foo() {
  var a;
  var a; // redeclaration
  a = 2;
  {
    a = 3;
    console.log(a);
  }
  console.log(a);
}
foo();
console.log(a);

注意:面试题中凡事用{}包裹var的都是障眼法,var没有块级作用域。

第二题

这道题比较简单,考察的是let的块级作用域,代码输出2, 1。因为let有块级作用域。let a = 2只在{}内有效。

function foo() {
  let a = 1;
  {
    let a = 2;
    console.log(a);
  }
  console.log(a);
}
foo();

意外的全局变量

如果我们声明变量的时候忘记了写varlet或者const,那么这个变量就是所谓的Accidental Global Variables,意思是意外的全局变量

function f1() {
  b = 2; // accident global variable
}
f1();
console.log(b); // 2

面试题

以下代码输出什么?

for (var i = 0; i < 10; i++) {
  setTimeout(() => {
    console.log(i);
  })
}

答案:3 3 3
因为var没有块级作用域,所以setTimeout内的i都是指向同一个i,而setTimeout是异步的,其回调函数代码需要先进入宏任务队列,待for循环结束后才能执行,此时i已经是3了。关于这道题的详细解释,请看这篇

最佳实践

到此这篇关于javascript中的var、let、const的文章就介绍到这了,更多相关js var let const内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文