1. Dotnet9首页
  2. 大前端
  3. 前端语言
  4. JavaScript

JS面试、技巧总结点一-变量提升/函数提升

先抛出一个问题:先有鸡还是先有蛋:直觉上会认为 JavaScript 代码在执行时是由上到下一行一行执行的。但实际上这并不完全正确,有一种特殊情况会导致这个假设是错误的。

考虑以下代码:

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

你认为 console.log(..) 声明会输出什么呢?

很多人会认为是 undefined,因为 var a 声明在 a = 2 之后,他们自然而然地认为变量被重新赋值了,因此会被赋予默认值 undefined。但是,真正的输出结果是 2。

考虑另外一段代码:

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

你可能会认为这个代码片段也会有同样的行为而输出 2。还有人可能会认为,由于变量 a 在使用前没有先进行声明, 因此会抛出 ReferenceError 异常。不幸的是两种猜测都是不对的。输出来的会是 undefined

为什么会出现这种情况?

什么是变量/函数提升

  • 包括变量和函数在内的所有声明都会在任何代码被执行前首先被处理,这种现象称为提升。
  • 但只有声明本身会被提升,而赋值或其他运行逻辑会留在原地
  • javascript并不是严格的自上而下执行的语言

变量声明提升:

  • JavaScript的变量提升是针对var的,而letconst不存在变量提升这一特性(letconst具有一个临时死区的概念,后续在es6的总结中会提到)
  • 通过var定义的变量,在定义语句之前就可以访问到 值:undefined
  • 变量提升就是变量会被提升到作用域的最顶上去,也就是该变量不管是在作用域的哪个地方声明的,都会提升到作用域的最顶上去。

变量提升详解

  • 当你看到 var a = 2; 时,可能会认为这是一个声明。但 JavaScript 实际上会将其看成两个声明:var a; 和 a = 2;。
  • JS 解释器会找出需要提升的变量和函数,并且给他们提前在内存中开辟好空间,函数的话会将整个函数存入内存中,变量只声明并且赋值为 undefined

第一个定义声明是在编译阶段进行的。第二个赋值声明会被留在原地等待执行阶段。第一个代码片段会以如下形式进行处理:

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

其中第一部分是编译,而第二部分是执行。

类似地,我们的第二个代码片段实际是按照以下流程处理的:

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

打个比方,这个过程就好像变量和函数声明从它们在代码中出现的位置被“移动” 到了最上面。这个过程就叫作提升。

JavaScript只会将变量声明提升,但是不会把初始化提升。但如果变量一直都没有声明过,则会抛出ReferenceError,比如直接输出:

console.log(b) // Uncaught ReferenceError: b is not defined

另外值得注意的是,每个作用域都会进行提升操作

var a = 100

function fn () {
    console.log(a)
    var a = 200
    console.log(a)
}

fn()
console.log(a)
var a
console.log(a)
var a = 300
console.log(a)

这段代码将会依次输出 undefined 200 100 100 300

在fn()函数中由于声明了var a = 200, 所以 var a会被提升到fn的作用域顶端,第一输出则为undefined

下面这段代码,由于es6之前,js是没有块级作用域的,所以 if 中声明的a变量会被当成全局变量处理

var a = 1
if (true) {
    var a = 2
}
console.log(a) // 2

比较下面两段代码

var a = 10
function fx () {
    console.log(a) // undefined
    var a = 20
    console.log(a) // 20
}
fx()
console.log(a) // 10

====================================

var a = 10
function fx () {
    console.log(a) // 10
    a = 20
    console.log(a) // 20
}
fx()
console.log(a) // 20

第二段代码 fx() 中的a没有使用var定义,会造成fx函数中没有变量声明,所以 fx 里面访问的变量a,其实都是访问的全局变量aa = 20 又相当于给全局变量a重新赋值20

函数声明提升

通过function声明的函数,在之前就可以直接调用

fx() // fx is a great girl 之前之后都可调用
function fx () {
    console.log('fx is a great girl')
}
fx() // fx is a great girl 之前之后都可调用

但如果是这种写法:函数表达式声明的函数

console.log(fx) // undefined
var fx = function () {
    console.log('fx is a great girl')
}

==========================

fx() // 不是 ReferenceError, 而是 TypeErr
var fx = function () {
    console.log('fx is a great girl')
}

这段程序中的变量标识符 fx() 被提升并分配给所在作用域(在这里是全局作用域),因此 fx() 不会导致 ReferenceError。但是 fx 此时并没有赋值(如果它是一个函数声明而不是函数表达式,那么就会赋值)。fx() 由于对 undefined 值进行函数调用而导致非法操作, 因此抛出 TypeError 异常。

当前函数声明和变量声明使用同一个变量名称时,函数的优先级高于变量的优先级

console.log(fx) // 会输出 fx 定义的函数
    function fx () {
        console.log('fx is a great girl')
    }
    var fx = 'fx'
    console.log(fx) // fx

有多个同名函数声明的时候,是由最后面的函数声明来替代前面的

fx() // fx is a great girl
    function fx () {
        console.log('fx')
    }

    function fx () {
        console.log('fx is a great girl')
    }

下面列举几道题,理解了之后就很好懂

function fx () {
    console.log(a) // undefined
    if (false) {
        var a = 1
    }
    console.log(a) // undefined
    console.log(b) // Uncaught ReferenceError: b is not defined
}
fx()

即使if语句的条件是false,也一样不影响a变量提升

function fx () {
        console.log('fx is a great girl')
    }
    var fx
    console.log(typeof fx) // function

===========================

function fx () {
    console.log('fx is a great girl')
}
var fx = 'good girl'
console.log(typeof fx) // string

===========================

function fx () {
    console.log('fx is a great girl')
}
var fx = 'good girl'
console.log(typeof fx) // string

===========================

console.log(typeof fx) // function
var fx = 'good girl'
function fx () {
    console.log('fx is a great girl')
}
console.log(typeof fx) // string
console.log(typeof fx) // function
var fx
function fx () {
    console.log('fx is a great girl')
}
console.log(typeof fx) // function
if(!(fx in window)) {
    var fx = 1
}
console.log(fx) // undefined
var c = 1
function c(c) {
	console.log(c)
}
c(2) // c is not a function

========

c(2) // 2
var c = 1
function c(c) {
	console.log(c)
}

========

var c = function(c) {
	console.log(c)
}
c(2) // 2
var c = 1

========

var c = function(c) {
	console.log(c)
}
var c = 1
c(2) // Uncaught TypeError: c is not a function

========

c(2) // Uncaught TypeError: c is not a function
var c = function(c) {
	console.log(c)
}
var c = 1

原文出处:微信公众号【miofly Will good】

原文链接:https://mp.weixin.qq.com/s/6eqFXV3m2EIiYOnwXeK6ww

本文观点不代表Dotnet9立场,转载请联系原作者。

发表评论

登录后才能评论