博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
JavaScript面试题
阅读量:4309 次
发布时间:2019-06-06

本文共 4677 字,大约阅读时间需要 15 分钟。

问题1: 作用域(Scope)

1 (function() {
2 var a = b = 5;3 })();4 console.log(b);

 控制台(console)会打印出什么?

答案:

上述代码会打印出5

这个问题的陷阱就是,在立即执行函数表达式(IIFE)中,有两个赋值,但是其中变量a使用关键词var来声明。这就意味着a是这个函数的局部变量。与此相反,b被分配给了全局作用域(译注:也就是全局变量)。

这个问题另一个陷阱就是,在函数中没有使用”严格模式” ('use strict';)。如果 开启,那么代码就会报错 ” Uncaught ReferenceError: b is not defined” 。请记住,如果这是预期的行为,严格模式要求你显式地引用全局作用域。所以,你需要像下面这么写:

1 (function () {2   'use strict';3   var a = b = 5;4 }) ();5 console.log(b);

自己注:严格模式下,火狐依然是输出5,而360浏览器才报错是” Uncaught ReferenceError: b is not defined”。

问题2: 变量提升(Hoisting)

 执行以下代码的结果是什么?为什么?

function test() {  console.log(a);  console.log(foo());  var a = 1;  function foo() {    return 2;  }}test();

答案:

这段代码的执行结果是undefined 和 2

这个结果的原因是,变量和函数都被提升到了函数体的顶部。因此,当打印变量a时,它虽存在于函数体(因为a已经被声明),但仍然是undefined。换言之,上面的代码等同于下面的代码:

function test() { function foo() {    return 2;  } var a;  console.log(a);  console.log(foo());  a = 1;}test();

关于变量提升函数提升:

1、函数会首先被提升,然后才是变量。

2、变量提升,就是把变量提升提到函数的top的地方。只是提升变量的声明,并不会把赋值也提升上来。

3、函数提升是把整个函数都提到前面去。

4、解析器在向执行环境中加载数据时,解析器会率先读取函数声明,并使其在执行任何代码之前可用(可以访问);至于函数表达式,则必须等到解析器执行到它所在的代码行,才会真正被解释执行。(JavaScript高级程序设计111页)。

 

变量的问题,莫过于声明赋值两个步骤,而这两个步骤是分开的。

函数声明被提升时,声明和赋值两个步骤都会被提升,而普通变量却只能提升声明步骤,而不能提升赋值步骤。

变量被提升过后,先对提升上来的所有对象统一执行一遍声明步骤,然后再对变量执行一次赋值步骤。而执行赋值步骤时,会优先执行函数变量的赋值步骤,再执行普通变量的赋值步骤

当你明白这三点后,一切都豁然开朗了。

问题3: 在javascript中,`this`是如何工作的

以下代码的结果是什么?请解释你的答案。

var fullname = 'John Doe';var obj = {  fullname: 'Colin Ihrig',  prop: {    fullname: 'Aurelio De Rosa',    getFullname: function () {      return this.fullname;    }  }};console.log(obj.prop.getFullname());var test = obj.prop.getFullname;console.log(test());

答案:

这段代码打印结果是:Aurelio De Rosa 和 John Doe 。原因是,JavaScript中关键字this所引用的是函数上下文,取决于函数是如何调用的,而不是怎么被定义的。

在第一个console.log()getFullname()是作为obj.prop对象的函数被调用。因此,当前的上下文指代后者,并且函数返回这个对象的fullname属性。相反,当getFullname()被赋值给test变量时,当前的上下文是全局对象window,这是因为test被隐式地作为全局对象的属性。基于这一点,函数返回windowfullname,在本例中即为第一行代码设置的。

问题4: call() 和 apply()

修复前一个问题,让最后一个console.log() 打印输出Aurelio De Rosa.

答案:

这个问题可以通过运用call()或者apply()方法强制转换上下文环境。关于这两个方法的区别 。 下面的代码中,我用了call(),但apply()也能产生同样的结果:

console.log(test.call(obj.prop));

 问题5: 闭包(Closures)

考虑下面的代码:

var nodes = document.getElementsByTagName('button');for (var i = 0; i < nodes.length; i++) {  nodes[i].addEventListener('click', function () {    console.log('You clicked element #' + i);  });}

请问,如果用户点击第一个和第四个按钮的时候,控制台分别打印的结果是什么?为什么?

答案:

无论点击哪个按钮都是打印出 You clicked element #4。

也就是说,代码打印两次You clicked element #NODES_LENGTH,其中NODES_LENGTH是nodes的结点个数。原因是在for循环完成后,变量i的值等于节点列表的长度。此外,因为i在代码添加处理程序的作用域中,该变量属于处理程序的闭包。你会记得,闭包中的变量的值不是静态的,因此i的值不是添加处理程序时的值(对于列表来说,第一个按钮为0,对于第二个按钮为1,依此类推)。在处理程序将被执行的时候,在控制台上将打印变量i的当前值,等于节点列表的长度。

一个重要的知识点:闭包只能取得包含函数中任何变量的最后一个值。

问题6: 闭包(Closures)

修复上题的问题,使得点击第一个按钮时输出0,点击第二个按钮时输出1,依此类推。

答案:

有多种办法可以解决这个问题,下面主要使用两种方法解决这个问题。

第一个解决方案使用立即执行函数表达式(IIFE)再创建一个闭包,从而得到所期望的i的值。实现此方法的代码如下:

var nodes = document.getElementsByTagName('button');for (var i = 0; i < nodes.length; i++) {  nodes[i].addEventListener('click', (function (i) {    return function () {      console.log('You clicked element #' + i);    }  }) (i));};

另一个解决方案不使用IIFE,而是将函数移到循环的外面。这种方法由下面的代码实现:

var nodes = document.getElementsByTagName('button');function handlerWrapper(i) {  return function () {    console.log('You clicked element #' + i);  }}for (var i = 0; i < nodes.length; i++) {  nodes[i].addEventListener('click', handlerWrapper(i));}

 问题7:事件循环

下面代码运行结果是什么?请解释。

function printing() {  console.log(1);  setTimeout(function () {    console.log(2);  }, 1000);  setTimeout(function () {    console.log(3);  }, 0);  console.log(4);}printing();

答案:

输出结果:

1432

想知道为什么输出顺序是这样的,你需要弄了解setTimeout()做了什么,以及浏览器的事件循环原理。浏览器有一个事件循环用于检查事件队列,处理延迟的事件。UI事件(例如,点击,滚动等),Ajax回调,以及提供给setTimeout()setInterval()的回调都会依次被事件循环处理。因此,当调用setTimeout()函数时,即使延迟的时间被设置为0,提供的回调也会被排队。回调会呆在队列中,直到指定的时间用完后,引擎开始执行动作(如果它在当前不执行其他的动作)。因此,即使setTimeout()回调被延迟0毫秒,它仍然会被排队,并且直到函数中其他非延迟的语句被执行完了之后,才会执行。

有了这些认识,理解输出结果为“1”就容易了,因为它是函数的第一句并且没有使用setTimeout()函数来延迟。接着输出“4”,因为它是没有被延迟的数字,也没有进行排队。然后,剩下了“2”,“3”,两者都被排队,但是前者需要等待一秒,后者等待0秒(这意味着引擎完成前两个输出之后马上进行)。这就解释了为什么“3”在“2”之前。

原文链接:

 问题8:

以下 JavaScript 代码,在浏览器中运行的结果是

var foo ={n:1};(function(foo){  console.log(foo.n);  foo.n = 3;  var foo = {n:2};  console.log(foo.n);})(foo);console.log(foo.n);

答案:依次输出 1 2 3

解析:

var foo={n:1}; //全局作用域下的foo变量,是一个对象,引用类型//然后是一个立即执行的匿名函数,传入上面的foo,由于是引用类型//所以匿名函数参数的foo即是对上面那个foo的引用(function(foo){    console.log(foo.n);//这里的foo就是全局作用域下的foo,n为1,所以第一次打印出1    foo.n=3;//这里是设置全局作用域下的foo中n属性,将n变为3    var foo={n:2};//这里是在这个匿名函数作用域内新建的一个foo变量,由于作用域不同,不覆盖全局作用域下的foo变量    console.log(foo.n);//这里由于是在匿名函数作用域内,首选打印匿名函数作用域内的foo.n,也就是上面的2})(foo);console.log(foo.n);//最后,由于匿名函数中改变了全局作用域中的foo.n为3,所以打印出3

 

转载于:https://www.cnblogs.com/jongsuk0214/p/6884023.html

你可能感兴趣的文章
Mysql出现Table 'performance_schema.session_status' doesn't exist
查看>>
MySQL innert join、left join、right join等理解
查看>>
vivado模块封装ip/edf
查看>>
sdc时序约束
查看>>
Xilinx Jtag Access/svf文件/BSCANE2
查看>>
NoC片上网络
查看>>
开源SoC整理
查看>>
【2020-3-21】Mac安装Homebrew慢,解决办法
查看>>
influxdb 命令行输出时间为 yyyy-MM-dd HH:mm:ss(年月日时分秒)的方法
查看>>
已知子网掩码,确定ip地址范围
查看>>
判断时间或者数字是否连续
查看>>
docker-daemon.json各配置详解
查看>>
Docker(一)使用阿里云容器镜像服务
查看>>
Docker(三) 构建镜像
查看>>
FFmpeg 是如何实现多态的?
查看>>
FFmpeg 源码分析 - avcodec_send_packet 和 avcodec_receive_frame
查看>>
FFmpeg 新旧版本编码 API 的区别
查看>>
RecyclerView 源码深入解析——绘制流程、缓存机制、动画等
查看>>
Android 面试题整理总结(一)Java 基础
查看>>
Android 面试题整理总结(二)Java 集合
查看>>