Skip to content
On this page

1、什么是作用域?

  • 作用域就是代码执行的环境,在程序运行时代码中的某些特定部分中变量、函数和对象的可访问性

  • 作用域分为全局作用域跟函数作用域,js中没有块级作用域(ES6的let、const出现才实现了块级作用域);

1.1 全局作用域

  • 在任何地方都能访问到的对象称之为全局作用域;

  • window对象下的所有属性和方法都有全局作用域;

  • 最外层定义的变量跟方法具有全局作用据;

  • 所有未定义直接赋值的变量拥有全局作用域;

1.2 函数作用域

  • 定义函数时的花括号{}为函数作用域;

  • 在函数作用域定义的变量、方法,在函数作用域外部无法访问;

js
var a = '外层变量'
function fn() {
    // 函数作用域
    var b = '内层变量'
    c = '未定义直接赋值'
    console.log(a)
}
fn() // 外层变量
console.log(c) // 未定义直接赋值
console.log(b) // Uncaught ReferenceError: b is not defined
    at <anonymous>:10:13

代码解析:

  • a变量定义在最外层,拥有全局作用域,所以在函数fn里面可以访问到a,所以执行函数时,输出外层变量

  • b变量的定义在函数fn的作用域下,在作用域外时不能访问到b的,所以在外层访问b变量就会报错;

  • c变量没有定义直接赋值,所以c变量变成了全局作用域下的变量,所以在外部可以直接使用,输出未定义直接赋值

1.3 块级作用域

  • {}中的内容就被认为块级作用域;

  • 只有使用let声明的变量、方法才具备块级作用域;

  • if块、while块、function块、单独的块都可以被认为时let声明的块级作用域;

js
if (true) { let a; }
console.log(a); // ReferenceError: a 没有定义
while (true) { let b; } 
console.log(b); // ReferenceError: b 没有定义
function foo() { let c; }
console.log(c); // ReferenceError: c 没有定义
// 这不是对象字面量,而是一个独立的块,JavaScript 解释器会根据其中内容识别出它来
{
    let d;
}
console.log(d); // ReferenceError: d 没有定义

let与var的区别

  • let声明的变量不存在变量提升(严谨的说,let声明的变量会产生'暂时性死区'),不能在声明变量之前使用变量;

  • let定义的变量不能重复声明,否则会报错;

js
function fn() {
    console.log(a) // undefined
    // 不能在变量声明前使用变量
    console.log(b) // Uncaught ReferenceError: Cannot access 'b' before initialization
    var a = 1
    let b = 2
    // 如果重复声明同名变量会报错
    let b = 5 // // Uncaught SyntaxError: Identifier 'b' has already been declared
}
fn()

2、作用域链

  • 各个作用域访问变量和方法的顺序;

  • 当我们使用一个变量时,js会顺着作用域链一层一层的像上查找,直到最顶层的全局作用域,如果在全局作用域中没有找到该变量或者方法,就返回undefined;

  • 作用域链的存在,使得内部作用域可以顺着作用域链访问到外部作用域的变量或者方法,但是外部作用域去不能访问到内部作用域的变量或者方法;

js
var a = 10
var b = 20
var c = 30
function fn() {
    var b = 40
    function bar() {
        c = 50
        console.log(a + b + c)
    }
    return bar
}
var x = fn(),
b = 200
x() // 100
console.log(b) // 200
console.log(c) // 50

分析这个试题前,大家可以先看一下这个图

image.png

从图中可以看出,当前试题中存在三个嵌套的作用域

  • 执行fn()时,创建了一个局部变量b且值为40,同时把bar函数返回,赋值给x
  • 执行x(),即执行bar函数代码,这里的c并没有声明,直接赋值,所以顺着作用域链向上查找,在fn中没有找到c,继续向上查找,在全局作用域中找到了c,所以全局作用域下的c变量的值被改变为50;
  • 接下来输出a+b+c,同理,b变量在fn中找到,使用就是fn中的b,而不是全局作用域下的b,所以a+b+c就是全局作用域下的a、c加上fn中的b,最后输出200;
  • 在全局作用域下打印b,使用的全局作用域的b,所以输出200

3、试题

js
var a="aa";
function test(){
 console.log(a) // undefined
 var a = "bb"
 console.log(a) // “bb”
}
test()
console.log(a) // "aa"
js
var a = 1
var b = 2
var c = 3
function fn(a) {
    a = 4
    return a + b
}
function sum(a) {
    var b = 10
    a = c
    c = 12
    return a + b
}
b = 8
c = 9
var d = fn(6)
var e = sum(6)
console.log(c) // 12
console.log(d) // 12
console.log(e) // 19

3、闭包

一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围), 这样的组合就是闭包closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。

闭包的特点

  • 让外部访问函数内部变量成为可能;
  • 可以避免使用全局变量,防止全局变量污染;
  • 可以让局部变量常驻在内存中;
  • 会造成内存泄漏(有一块内存空间被长期占用,而不被释放)
js
// 闭包的经典应用,在es6 let 出来之前,解决for循环中的变量i是全局变量,在定时器中最后得到的i都是循环的最后结果
for(var i = 0; i < 10; i ++) {
    (function(j) {
        setTimeout(() => {
            console.log(j)
        }, 1000)
    })(i)
}

4、垃圾回收机制

  • 浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collecation),也就是说,执行环境会负责管理代码执行过程中使用的内存。其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存

  • 这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。

  • 不再使用的变量也就是生命周期结束的变量,当然只可能是局部变量,全局变量的生命周期直至浏览器卸载页面才会结束。局部变量只在函数的执行过程中存在,而在这个过程中会为局部变量在栈或堆上分配相应的空间,以存储它们的值,然后在函数中使用这些变量,直至函数结束

  • 闭包中由于内部函数的原因,外部函数的变量被内部函数引用,如果不手动清除则无法释放,从而会造成内存泄漏

1、标记清除

  • js中最常用的垃圾回收方式就是标记清除。当变量进入环境时,例如,在函数中声明一个变量,就将这个变量标记为“进入环境”。从逻辑上讲,永远不能释放进入环境的变量所占用的内存,因为只要执行流进入相应的环境,就可能会用到它们。而当变量离开环境时,则将其标记为“离开环境”。

  • 垃圾回收器在运行的时候会给存储在内存中的所有变量都加上标记(当然,可以使用任何标记方式)。然后,它会去掉环境中的变量以及被环境中的变量引用的变量的标记(闭包)。而在此之后再被加上标记的变量将被视为准备删除的变量,原因是环境中的变量已经无法访问到这些变量了。最后,垃圾回收器完成内存清除工作,销毁那些带标记的值并回收它们所占用的内存空间。 到目前为止,IE9+、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。

2、引用计数

  • 引用计数的含义是跟踪记录每个值被引用的次数。当声明了一个变量并将一个引用类型值赋给该变量时,则这个值的引用次数就是1。如果同一个值又被赋给另一个变量,则该值的引用次数加1。相反,如果包含对这个值引用的变量又取得了另外一个值,则这个值的引用次数减1。当这个值的引用次数变成0时,则说明没有办法再访问这个值了,因而就可以将其占用的内存空间回收回来。

参考资料

JavaScript高级程序设计(第4版) (豆瓣) (douban.com)

博客主要记录一些学习的文章,如有不足,望大家指出,谢谢。