面试中关于JavaScript作用域的5个坑

Posted hugo233

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试中关于JavaScript作用域的5个坑相关的知识,希望对你有一定的参考价值。

javascript 中,代码块、函数或模块为变量创建作用域。例如 if 代码块为变量 message 创建作用域:

`if (true) {  
  const message = 'Hello';  
  console.log(message); // 'Hello'  
}  
console.log(message); // throws ReferenceError  
`

if 代码块作用域内可以访问 message。但是在作用域之外,该变量不可访问。

以下是 5 种有趣的情况,其中 JavaScript 作用域的行为与你预期的不同。你可能会研究这些案例以提高对作用域的了解,或者只是为面试做准备。

1. for 循环内的 var 变量

思考以下代码片段:

`const colors = ['red', 'blue', 'white'];  
  
for (let i = 0, var l = colors.length; i < l; i++) {  
  console.log(colors[i]); // 'red', 'blue', 'white'  
}  
console.log(l); // ???  
console.log(i); // ???  
`

当你打印 li 变量时会发生什么?

答案

console.log(l) 输出数字 3 ,而 console.log(i) 则抛出 ReferenceError

l 变量是使用 var 语句声明的。你可能已经知道,var 变量仅受函数体作用域限制而并非代码块。

相反,变量 i 使用 let 语句声明。因为 let 变量是块作用域的,所以 i 仅在 for 循环作用域内才可访问。

修复

l 声明从 var l = colors.length 改为 const l = colors.length。现在变量 l 被封装在 for 循环体内。

2. 代码块中的函数声明

在以下代码段中:

`// ES2015 env  
{  
  function hello() {  
    return 'Hello!';  
  }  
}  
  
hello(); // ???  
`

调用 hello() 会怎样?(代码段在 ES2015 环境中执行)

答案

因为代码块为函数声明创建了作用域,所以在 ES2015 环境中调用 hello() 会引发  ReferenceError: hello is not defined

有趣的是,在 ES2015 之前的环境中,在执行上述代码段时不会抛出错误。你知道为什么吗?请在下面的评论中写下你的答案!

3. 你可以在哪里导入模块?

你可以在代码块中导入模块吗?

`if (true) {  
  import { myFunc } from 'myModule'; // ???  
  myFunc();  
}  
`

答案

上面的脚本将触发错误:'import' and 'export' may only appear at the top-level

你只能在模块文件的最顶级作用域(也称为模块作用域)中导入模块。

修复

始终从模块作用域导入模块。另外一个好的做法是将 import 语句放在源文件的开头:

`import { myFunc } from 'myModule';  
  
if (true) {  
  myFunc();  
}  
`

ES2015 的模块系统是静态的。通过分析 JavaScript 源代码而不是执行代码来确定模块的依赖关系。所以在代码块或函数中不能包含 import 语句,因为它们是在运行时执行的。

4. 函数参数作用域

思考以下函数:

`let p = 1;  
  
function myFunc(p = p + 1) {  
  return p;  
}  
  
myFunc(); // ???  
`

调用 myFunc() 会发生什么?

答案

当调用函数 myFunc() 时,将会引发错误:ReferenceError: Cannot access 'p' before initialization

发生这种情况是因为函数的参数具有自己的作用域(与函数作用域分开)。参数 p = p + 1 等效于 let p = p + 1

让我们仔细看看 p = p + 1

首先,定义变量 p。然后 JavaScript 尝试评估默认值表达式 p + 1,但此时绑定  p  已经创建但尚未初始化(不能访问外部作用域的变量  let p = 1)。因此抛出一个错误,即在初始化之前访问了 p

修复

为了解决这个问题,你可以重命名变量 let p = 1 ,也可以重命名功能参数 p = p + 1

让我们选择重命名函数参数:

`let p = 1;  
  
function myFunc(q = p + 1) {  
  return q;  
}  
  
myFunc(); // => 2  
`

函数参数从 p 重命名为 q。当调用 myFunc() 时,未指定参数,因此将参数 q 初始化为默认值 p + 1。为了评估 p +1,访问外部作用域的变量 pp +1 = 1 + 1 = 2

5. 函数声明与类声明

以下代码在代码块内定义了一个函数和一个类:

`if (true) {  
  function greet() {  
    // function body  
  }  
  
  class Greeter {  
    // class body  
  }  
}  
  
greet();       // ???  
new Greeter(); // ???  
`

是否可以在块作用域之外访问 greetGreeter?(考虑 ES2015 环境)

答案

functionclass 声明都是块作用域的。所以在代码块作用域外调用函数 greet() 和构造函数 new Greeter() 就会抛出 ReferenceError

6. 总结

必须注意 var 变量,因为它们是函数作用域的,即使是在代码块中定义的。

由于 ES2015 模块系统是静态的,因此你必须在模块作用域内使用 import 语法(以及 export)。

函数参数具有其作用域。设置默认参数值时,请确保默认表达式内的变量已经用值初始化。

在 ES2015 运行时环境中,函数和类声明是块作用域的。但是在 ES2015 之前的环境中,函数声明仅在函数作用域内。

希望这些陷阱能够帮你巩固作用域知识!

作为前端开发,JS是重中之重,最近结束了面试的高峰期,基本上offer也定下来了就等开奖,趁着这个时间总结下91道JavaScript面试题,这些都是高频面试题,希望对你能有所帮助。

一、原型链

1.1,创建对象有几种方法?
1.2,instanceof的原理?

二、类

2.1,类的声明?
2.2,生成实例?/ 声明一个类,怎么生成类的实例?

三、继承

3.1,call、apply的共同点与区别?
3.2,用javascript实现对象的继承/ 继承的几种方式,这几种方式的优缺点?

四、作用域

4.1,说说你对作用域链的理解?
4.2,this?
4.3,请说出下列的值?

五、javaScripti闭包

5.1,闭包的特征?
5.2,闭包应用场景?
5.3,实际开发中闭包的应用?
5.4,请说出下列的值?

六、js运行机制/ 单线程/ 异步

6.1,如何理解js的单线程?
6.2,js为什么是单线程的?
6.3,同步和异步的区别是什么?分别举一个同步和异步的例子?
6.4,何时需要异步?
6.5,什么是任务队列?
6.6,请说出下列值?
6.7,栈和队列的区别?
6.8,栈和堆的区别?
6.9,什么是event loop?
6.10,event-loop流程?
6.11,哪些语句会放入异步任务队列中?
6.12,何时被放入任务队列?

七、js数据类型

7.1,js使用typeof能得到的哪些类型?
7.2,如何准确判断一个变量是数组类型?
7.3,js变量按照存储方式区分为哪些类型,并描述其特点?
7.4,null和undefined的区别?
7.5,undefined的典型用法?
7.6,null的典型用法?
7.7,chrome60+浏览器中,a===b的是哪项?

八、js中的内置函数/内置对象

8.1,js中有哪些内置函数/ 数据封装类对象?
8.2,js中有哪些内置对象?
8.3,js变量按照存储方式区分为哪些类型,并描述其特点?
8.4,数组方法/ Array对象方法?
8.5,数组API?
8.6,对象API?

九、数组去重

9.1,数组怎么去重?(方法)
9.2,对上述数组去重方法速度比较?(性能)
9.3,一句话数组去重?
9.4,保留数组中非重复元素?
9.5,保留数组中重复元素?

十、js逻辑判断

10.1,请写出下面的答案?

十一、内存泄漏

11.1,哪些操作会造成内存泄漏?
11.2,js内存泄漏的解决方式

十二、dom

12.1,dom是哪种基本的数据结构?
12.2,dom操作的常用api有哪些?
12.3,dom节点的attribute和property有何区别?
12.4,dom结构操作/ 怎样添加、移除、移动、复制、创建和查找节点/ dom操作的常用api?
12.5,dom事件的级别?
12.6,dom事件模型?
12.7,dom事件流?
12.8,描述dom事件捕获的具体流程?
12.9,event对象的常见应用?
12.10,自定义事件/ 模拟事件?
12.11,通用事件绑定/ 编写一个通用的事件监听函数?

十三、bom

13.1,bom常用属性?

十四、通信

14.1,什么是同源策略及限制?
14.2,前后端如何通信?
14.3,跨域通信的几种方式?
14.4,jsonp实现?
14.5可以跨域的三个标签?
14.6,三个可跨域的标签的使用场景?

十五、ajax

15.1,ajax请求的原理/ 手写一个ajax请求?
15.2,readyState?
15.3,ajax异步与同步的区别?
15.4,ajax传递中文用什么方法?

十六、错误监控

16.1,前端错误的分类/ 如何检测js错误/ 如何保证你的产品质量?
16.2,错误的捕获方式?
16.3,上报错误的基本原理?

十七、模块化

17.1,amd、cmd区别?
17.2,amd、commonJs区别?

十八、虚拟dom

18.1,vdom的如何应用,核心api是什么?
18.2,虚拟dom转换成真实dom?
18.3,diff实现过程?

十九、js基础

19.1,对js的理解?
19.2,请说出以下代码输出的值?
19.3,把以下代码,改写成依次输出0-9
19.4,如何区分数组对象,普通对象,函数对象
19.5,面向对象、面向过程
19.6,面向对象的三大基本特性
19.7XML和JSON的区别?
19.8,Web Worker 和webSocket?
19.9,Javascript垃圾回收方法?
19.10,new操作符具体干了什么呢?
19.11,js延迟加载的方式有哪些?
19.12,WEB应用从服务器主动推送Data到客户端有那些方式?

需要的朋友只需要你点赞支持,然后点击这里免费获取

以上是关于面试中关于JavaScript作用域的5个坑的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript 关于变量作用域的一道面试题

JS中关于换行的2个坑

面试中关于 synchronized 关键字的 5 连击

带你整理面试过程中关于数据库的索引的相关知识点

javaScript作用域

js的闭包中关于执行环境和作用链的理解