作用域和变量提升的一个知识点

587 阅读4分钟

该部分常见面试题目

// ------------------
console.log(a)
a = '林一一'
function fn(){
    console.log(a)
    a = 12
}
fn()
console.log(a)

// 输出 Uncaught ReferenceError: a is not defined

// ------------------------
var a = '林一一'
function fn(){
    if(!a){
        var a = 12
    }
    console.log(a)
}
fn()

// 输出 12 
// var a = 12 会变量提升 所以 !a = true; var a = 12 执行了

// -------------------
var a=12, b = 13, c = 14
function fn(a){
    a = 0
    var b = 0
    c = 0
}
fn(a)
console.log(a)
console.log(b)
console.log(c)

// 输出 12 13 14 
// a 为形参 b为局部变量 所以不会改变全局变量的值 c改变了全局变量的值

基础概念

作用域:作用域就是变量的访问的范围和权限,变量在哪个范围内可以访问到,这个范围就是他的作用域。全局下可以访问到就是全局作用域,仅函数内部可以访问到作用域就是函数体内。es6引入块级作用域。

变量提升:函数和使用var声明的变量,无论代码中在哪里定义该变量,都会被提升到作用域的顶部声明。在之前使用并不会报错。

核心原理

作用域:每个函数都有[[scope]]属性,指向的是当前函数的执行上下文集合。

作用域链:[[scope]]属性指向的执行上下文集合就是我们所说的作用域链

看一个函数的[[scope]]简单的分析:

function test() {
  var a = 'a'
}
var glob = 'global'
test()

当函数声明的时候,[[scope]]中只有全局一个作用域,表示函数在这个范围内可访问。 但是当test函数被调用的时候就形成了作用域链了,存在[[scope]]集合中

变量提升:

​ js在执行代码过程中,会有编译和执行2个阶段。

​ 其中编译阶段并不是把所有代码拿过来一起编译,而是执行一部分代码则编译一部分代码。

​ 但是由于这个短暂的编译过程,js引擎会收集所有的变量进行声明,并让所有的变量生效。剩下的语句则需要等到执行阶段执行到某一句的时候才会具体生效。

用来做什么的

  1. 用作划分变量访问范围,变量在哪些地方有访问权限
  2. 可以用作闭包

优缺点

作用域:

优点:

  1. 让变量有自己的访问范围,让代码更加灵活
  2. 作用域链的机制可以实现继承
  3. 保护自己作用域的变量,与其他作用域的变量隔离

缺点:

1. es5只有函数和全局作用域,循环和部分代码块中无作用域			

面试点分析

console.log(a)
a = '林一一'
function fn(){
    console.log(a)
    a = 12
}
fn()
console.log(a)

输出 Uncaught ReferenceError: a is not defined

变量a并无var声明为全局变量,不会提前声明,所以第一句console.log(a)会报错,后面代码都不会执行。

var a = '林一一'
function fn(){
    if(!a){
        var a = 12
    }
    console.log(a)
}
fn()

// 输出 12 
// var a = 12 会变量提升 所以 !a = true; var a = 12 执行了

输出 12. 执行fn()时,if语句里面的var不管执不执行,var a = 12都会提前声明。

即 if(!a) 中a 为undefined,所以经过if语句以后 a为局部变量 打印出12

var a=12, b = 13, c = 14
function fn(a){
    a = 0
    var b = 0
    c = 0
}
fn(a)
console.log(a)
console.log(b)
console.log(c)

// 输出 12 13 0
// a 为形参 b为局部变量 所以不会改变全局变量的值 c改变了全局变量的值

此体主要考察,函数调用的时候覆盖了哪些全局变量。

当调用fn时,全局的a作为形参传入函数,b用var声明即作为局部变量。

只有c是直接改变了全局的c。所以会打印出 12 13 0

拓展

闭包:函数作用域内的变量都是私有变量,函数外部是访问不到的,但是通过某种手段访问到该函数内部变量就形成了闭包。

当函数内部return 返回的函数被其他作用域引用的时候,该函数内部的私有变量不会被垃圾回收机制回收

由于垃圾回收机制不会回收,会导致内存消耗。

惰性函数、柯里化函数、compose函数都是用了闭包机制。