变量声明问题


代码执行的两个阶段

理解这两个概念,要从 JavaScript 代码的执行过程说起,这在平时开发中并不会涉及,但对于我们理解 JavaScript 语言和运行机制非常重要,请各位细心阅读。
JavaScript 执行主要分为两个阶段:

  • 代码预编译阶段
  • 代码执行阶段

预编译阶段是前置阶段,这个时候由编译器将 JavaScript 代码编译成可执行的代码。 注意,这里的预编译和传统的编译并不一样,传统的编译非常复杂,涉及分词、解析、代码生成等过程 。这里的预编译是 JavaScript 中独特的概念,虽然 JavaScript 是解释型语言,编译一行,执行一行。但是在代码执行前,JavaScript 引擎确实会做一些「预先准备工作」。
执行阶段主要任务是执行代码,执行上下文在这个阶段全部创建完成。
在通过语法分析,确认语法无误之后,JavaScript 代码在预编译阶段对变量的内存空间进行分配,我们熟悉的变量提升过程便是在此阶段完成的。如下代码:
经过预编译过程,我们应该注意三点:

  • 预编译阶段进行变量声明;
  • 预编译阶段变量声明进行提升,但是值为 undefined;
  • 预编译阶段所有非表达式的函数声明进行提升。

函数参数默认值的顺序问题

(其实也相当于按顺序赋值)

  1. function foo(arg1 = arg2, arg2) {
  2. console.log(`${arg1} ${arg2}`)
  3. }
  4. foo(undefined, 'arg2')
  5. // Uncaught ReferenceError: arg2 is not defined
  6. function foo( arg2,arg1 = arg2) {
  7. console.log(`${arg1} ${arg2}`)
  8. }
  9. // undefined
  10. foo( 'arg2',undefined)
  11. //arg2 arg2

变量重声明

  1. function foo(arg1) {
  2. let arg1
  3. }
  4. foo('arg')
  5. // Uncaught SyntaxError: Identifier 'arg1' has already been declared
  6. function foo(arg1) {
  7. var arg1
  8. }
  9. foo('arg')
  10. // undefined

变量重声明

  1. foo(10)
  2. function foo (num) {
  3. console.log(foo)
  4. foo = num;//相当于内部变量
  5. console.log(foo)
  6. var foo
  7. }
  8. console.log(foo)
  9. // undefined
  10. // 10
  11. // ƒ foo (num) {
  12. // console.log(foo)
  13. // foo = num
  14. // console.log(foo)
  15. // var foo
  16. //}

函数作用域体现在声明时的环境

  1. var fn = null
  2. var a=9
  3. const foo = () => {
  4. var a = 2
  5. function innerFoo() { //箭头函数也是一样
  6. console.log(a)
  7. }
  8. fn = innerFoo
  9. }
  10. const bar = () => {
  11. fn()
  12. }
  13. foo()
  14. bar()
  15. 答案:2

什么是垃圾

一般来说没有被引用的对象就是垃圾,就是要被清除, 有个例外如果几个对象引用形成一个环,互相引用,但根访问不到它们,这几个对象也是垃圾,也要被清除。

如何检垃圾

标记清除

大部分浏览器以此方式进行垃圾回收,当变量进入执行环境(函数中声明变量)的时候,垃圾回收器将其标记为“进入环境”,当变量离开环境的时候(函数执行结束)将其标记为“离开环境”,在离开环境之后还有的变量则是需要被删除的变量。标记方式不定,可以是某个特殊位的反转或维护一个列表等。
垃圾收集器给内存中的所有变量都加上标记,然后去掉环境中的变量以及被环境中的变量引用的变量的标记。在此之后再被加上的标记的变量即为需要回收的变量,因为环境中的变量已经无法访问到这些变量。

引用计数

这种方式常常会引起内存泄漏,低版本的IE使用这种方式。机制就是跟踪一个值的引用次数,当声明一个变量并将一个引用类型赋值给该变量时该值引用次数加1,当这个变量指向其他一个时该值的引用次数便减一。当该值引用次数为0时就会被回收。
该方式会引起内存泄漏的原因是它不能解决循环引用的问题:
function sample(){
var a={};
var b={};
a.prop = b;
b.prop = a;
}
这种情况下每次调用sample()函数,a和b的引用计数都是2,会使这部分内存永远不会被释放,即内存泄漏。