Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

面试题解JavaScript(一): 写出以下代码打印结果(京东) #1

Closed
campcc opened this issue Jul 19, 2019 · 0 comments
Closed
Labels
IIFE 立即调用函数表达式 变量提升 变量提升

Comments

@campcc
Copy link
Owner

campcc commented Jul 19, 2019

请写出以下代码的打印结果:

var name = 'Tom';
(function () {
  if (typeof name == 'undefined') {
    var name = 'Jack';
    console.log('Goodbye ' + name);
  } else {
    console.log('Hello ' + name);
  }
})();

答案:Goodbye Jack

立即调用函数表达式

立即调用函数表达式,也叫 IIFE ,顾名思义是一个在定义时就会立即执行的 JavaScript 函数,这样的设计模式也被成为 自执行匿名函数,常见于各种类库的封装,通过一对圆括号包裹一个匿名函数,在声明后立即调用, 为什么会有这样的一种设计模式?

(function () {})(); // 一对圆括号包裹的一个匿名函数

IIFE 的出现更像是在弥补 JavaScript 语言在 访问控制 设计上的不足,相较于其他大部分面向对象的语言,直到 ES6 之前,JavaScript 只有 全局作用域函数作用域,没有 块级作用域 。也就是说,在 JavaScript 中,你只能通过函数实现 作用域隔离。IIFE 以一种较优雅的方式,通过函数作用域模拟实现了 块级作用域,由于匿名函数拥有自己独立的词法作用域,不仅避免了外界访问 IIFE 中的变量,而且也不会污染全局作用域。

此外,IIFE 还有以下优点:

  • 隔离作用域,避免全局污染,命名冲突
  • 减少闭包占用的内存问题,IIFE 中定义的变量和函数,都会在执行完后被销毁,因为没有指向匿名函数的引用,只要函数执行完毕,就会立即销毁其作用域链了

变量提升

变量提升(Hoisting)被认为是,JavaScript 中执行上下文工作方式的一种认识,单从概览来理解比较晦涩,简单来说,就是指声明的变量和函数在编译阶段被放入内存中。

什么是执行上下文?

JavaScript 引擎遇到可执行代码(executable code)时,会创建执行环境,执行上下文(execution context)可以理解为当前执行代码的环境 / 作用域。

可执行代码的类型只有三种:

  • 全局代码
  • 函数代码
  • Eval代码

对应的执行上下文也有三种:

  • 全局执行上下文
  • 函数执行上下文
  • Eval函数执行上下文

一段可执行代码中可能有多个执行上下文,每个函数执行时都会创建一个执行上下文,为了管理执行上下文,JavaScript 引擎还会创建 执行上下文栈(execution context stack),执行栈本质上就是一个普通的栈,只是它里面存放的是执行上下文。

回到变量提升,怎么去理解 声明的变量和函数在编译阶段被放入内存中

JavaScript 引擎在正式执行代码前,会进行一次"预编译",在内存中开辟一些空间,用来存放变量和函数,具体的步骤如下:

  • 创建 GO 全局对象(Global Object)Window
  • 加载第一个脚本文件
  • 脚本加载完成后,进行语法分析
  • 开始预编译,查找函数声明,变量声明,函数和变量会被赋值为全局对象的一个属性,其中函数声明的值为函数体,变量的值为 undefined
  • 词法分析
  • 加载第二个脚本文件
  • ... ...

可以看出,JavaScript 引擎仅提升声明,而不会提升初始化,所以如果先使用变量,再声明初始化它,变量的值将会是 undefined:

console.log(foo); // undefined
var foo = 'foo';

上面的代码相当于:

var foo;
console.log(foo);
foo = 'foo';

此外,函数和变量相比,会被优先提升,与声明的先后顺序无关:

console.log(foo) // ƒ foo () {}
var foo = 'foo'
function foo () {}

作用域链查找

对于每一个执行上下文,有三个重要的属性:

  • 变量对象(variable object)
  • 作用域链(scope chain)
  • this

什么是作用域链

JavaScript 引擎在查找变量时,会先从当前执行上下文的变量对象中去找,如果没有找到,就会从父级(词法层面上的父级)执行上下文的变量对象中去找,一直找到全局上下文的变量对象,也就是全局对象,由多个执行上下文的变量对象构成的一个链表就叫作用域链。

也就是说,作用域链查找是从作用域链最底层开始查找,当遇到同名变量时,查找到的是距离当前执行上下文最近的变量:

var name = 'Tom'
function () {
  var name = 'Jack'
  console.log(name) // Jack
}

解析

回到我们刚开始的题目,由于变量提升,上述的代码相当于:

var name = 'Tom';
(function () {
  var name
  if (typeof name == 'undefined') {
    name = 'Jack';
    console.log('Goodbye ' + name);
  } else {
    console.log('Hello ' + name);
  }
})();

匿名函数内的变量 name 被提升至当前执行上下文(匿名函数)的最顶端,由于仅提升声明,不提示初始化,所以 name 的初始值为 undefined

执行 typeof name 会查找 “name” 这个变量,通过作用域链查找,发现当前匿名函数的执行上下文就有 name,所以这里的 typeof 相当于:

typeof undefined

需要注意的是 typeof 返回的值是字符串,所以最终打印结果为 Goodbye Jack

下一篇文章

面试题解JavaScript(二): new 的实现原理是什么

勘误与提问

如果有疑问或者发现错误,可以在相应的 issues 进行提问或勘误

如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励

(完)

@campcc campcc added interview interview javascript javascript labels Jul 19, 2019
@campcc campcc added IIFE 立即调用函数表达式 变量提升 变量提升 and removed interview interview javascript javascript labels Jul 26, 2019
@campcc campcc closed this as completed Apr 15, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
IIFE 立即调用函数表达式 变量提升 变量提升
Projects
None yet
Development

No branches or pull requests

1 participant