8个有意思的JavaScript面试题


Posted in Javascript onJuly 30, 2019

JavaScript 是一种有趣的语言,我们都喜欢它,因为它的性质。浏览器是JavaScript的主要运行的地方,两者在我们的服务中协同工作。JS有一些概念,人们往往会对它掉以轻心,有时可能会忽略不计。原型、闭包和事件循环等概念仍然是大多数JS开发人员绕道而行的晦涩领域之一。正如我们所知,无知是一件危险的事情,它可能会导致错误。

接下来,来看看几个问题,你也可以试试想想,然后作答。

问题1:浏览器控制台上会打印什么?

var a = 10;
function foo() {
 console.log(a); // ??
 var a = 20;
}
foo();

问题2:如果我们使用 let 或 const 代替 var,输出是否相同?

var a = 10;
function foo() {
 console.log(a); // ??
 let a = 20;
}
foo();

问题3:“newArray”中有哪些元素?

var array = [];
for (var i = 0; i < 3; i++) {
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // ??

问题4:如果我们在浏览器控制台中运行'foo'函数,是否会导致堆栈溢出错误?

function foo() {
 setTimeout(foo, 0); // 是否存在堆栈溢出错误?
}

问题5: 如果在控制台中运行以下函数,页面(选项卡)的 UI 是否仍然响应

function foo() {
 return Promise.resolve().then(foo);
}

问题6: 我们能否以某种方式为下面的语句使用展开运算而不导致类型错误

var obj = { x: 1, y: 2, z: 3 };
[...obj]; // TypeError

问题7:运行以下代码片段时,控制台上会打印什么?

var obj = { a: 1, b: 2 };
Object.setPrototypeOf(obj, { c: 3 });
Object.defineProperty(obj, "d", { value: 4, enumerable: false });

// what properties will be printed when we run the for-in loop?
for (let prop in obj) {
 console.log(prop);
}

问题8:xGetter() 会打印什么值?

var x = 10;
var foo = {
 x: 90,
 getX: function() {
 return this.x;
 }
};
foo.getX(); // prints 90
var xGetter = foo.getX;
xGetter(); // prints ??

答案

现在,让我们从头到尾回答每个问题。我将给您一个简短的解释,同时试图揭开这些行为的神秘面纱,并提供一些参考资料。

问题1: undefined

使用var关键字声明的变量在JavaScript中会被提升,并在内存中分配值undefined。 但初始化恰发生在你给变量赋值的地方。 另外,var声明的变量是函数作用域的,而letconst是块作用域的。 所以,这就是这个过程的样子:

var a = 10; // 全局使用域
function foo() {
 // var a 的声明将被提升到到函数的顶部。
 // 比如:var a

 console.log(a); // 打印 undefined

 // 实际初始化值20只发生在这里
 var a = 20; // local scope
}

问题 2:ReferenceError:a undefined

letconst声明可以让变量在其作用域上受限于它所使用的块、语句或表达式。与var不同的是,这些变量没有被提升,并且有一个所谓的暂时死区(TDZ)。试图访问TDZ中的这些变量将引发ReferenceError,因为只有在执行到达声明时才能访问它们。

var a = 10; // 全局使用域
function foo() {
 // TDZ 开始

 // 创建了未初始化的'a'
 console.log(a); // ReferenceError

 // TDZ结束,'a'仅在此处初始化,值为20
 let a = 20;
}

下表概述了与JavaScript中使用的不同关键字声明的变量对应的提升行为和使用域:

8个有意思的JavaScript面试题

问题 3: [3, 3, 3]

for循环的头部声明带有var关键字的变量会为该变量创建单个绑定(存储空间)。 阅读更多关于闭包的信息。 让我们再看一次for循环。

// 误解作用域:认为存在块级作用域
var array = [];
for (var i = 0; i < 3; i++) {
 // 三个箭头函数体中的每个`'i'`都指向相同的绑定,
 // 这就是为什么它们在循环结束时返回相同的值'3'。
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [3, 3, 3]

如果使用 let 声明一个具有块级作用域的变量,则为每个循环迭代创建一个新的绑定。

// 使用ES6块级作用域
var array = [];
for (let i = 0; i < 3; i++) {
 // 这一次,每个'i'指的是一个新的的绑定,并保留当前的值。
 // 因此,每个箭头函数返回一个不同的值。
 array.push(() => i);
}
var newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

解决这个问题的另一种方法是使用闭包。

let array = [];
for (var i = 0; i < 3; i++) {
 array[i] = (function(x) {
  return function() {
   return x;
  };
 })(i);
}
const newArray = array.map(el => el());
console.log(newArray); // [0, 1, 2]

问题4 : 不会溢出

JavaScript并发模型基于“事件循环”。 当我们说“浏览器是 JS 的家”时我真正的意思是浏览器提供运行时环境来执行我们的JS代码。

浏览器的主要组件包括调用堆栈,事件循环,任务队列和Web API。 像setTimeoutsetIntervalPromise这样的全局函数不是JavaScript的一部分,而是 Web API 的一部分。 JavaScript 环境的可视化形式如下所示:

8个有意思的JavaScript面试题

JS调用栈是后进先出(LIFO)的。引擎每次从堆栈中取出一个函数,然后从上到下依次运行代码。每当它遇到一些异步代码,如setTimeout,它就把它交给Web API(箭头1)。因此,每当事件被触发时,callback 都会被发送到任务队列(箭头2)。

事件循环(Event loop)不断地监视任务队列(Task Queue),并按它们排队的顺序一次处理一个回调。每当调用堆栈(call stack)为空时,Event loop获取回调并将其放入堆栈(stack )(箭头3)中进行处理。请记住,如果调用堆栈不是空的,则事件循环不会将任何回调推入堆栈。

现在,有了这些知识,让我们来回答前面提到的问题:

步骤调用 foo()会将foo函数放入调用堆栈(call stack)。在处理内部代码时,JS引擎遇到setTimeout。然后将foo回调函数传递给WebAPIs(箭头1)并从函数返回,调用堆栈再次为空计时器被设置为0,因此foo将被发送到任务队列(箭头2)。由于调用堆栈是空的,事件循环将选择foo回调并将其推入调用堆栈进行处理。进程再次重复,堆栈不会溢出。

运行示意图如下所示:

8个有意思的JavaScript面试题

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

问题5 : 不会响应

大多数时候,开发人员假设在事件循环图中只有一个任务队列。但事实并非如此,我们可以有多个任务队列。由浏览器选择其中的一个队列并在该队列中处理回调

在底层来看,JavaScript中有宏任务和微任务。setTimeout回调是宏任务,而Promise回调是微任务。

主要的区别在于他们的执行方式。宏任务在单个循环周期中一次一个地推入堆栈,但是微任务队列总是在执行后返回到事件循环之前清空。因此,如果你以处理条目的速度向这个队列添加条目,那么你就永远在处理微任务。只有当微任务队列为空时,事件循环才会重新渲染页面、

现在,当你在控制台中运行以下代码段

function foo() {
 return Promise.resolve().then(foo);
}

每次调用'foo'都会继续在微任务队列上添加另一个'foo'回调,因此事件循环无法继续处理其他事件(滚动,单击等),直到该队列完全清空为止。 因此,它会阻止渲染。

问题6 : 会导致TypeError错误

展开语法 和 for-of 语句遍历iterable对象定义要遍历的数据。ArrayMap 是具有默认迭代行为的内置迭代器。对象不是可迭代的,但是可以通过使用iterable和iterator协议使它们可迭代。

在Mozilla文档中,如果一个对象实现了@@iterator方法,那么它就是可迭代的,这意味着这个对象(或者它原型链上的一个对象)必须有一个带有@@iterator键的属性,这个键可以通过常量Symbol.iterator获得。

上述语句可能看起来有点冗长,但是下面的示例将更有意义:

var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function() {
 // iterator 是一个具有 next 方法的对象,
 // 它的返回至少有一个对象
 // 两个属性:value&done。

 // 返回一个 iterator 对象
 return {
  next: function() {
   if (this._countDown === 3) {
    const lastValue = this._countDown;
    return { value: this._countDown, done: true };
   }
   this._countDown = this._countDown + 1;
   return { value: this._countDown, done: false };
  },
  _countDown: 0
 };
};
[...obj]; // 打印 [1, 2, 3]

还可以使用 generator 函数来定制对象的迭代行为:

var obj = { x: 1, y: 2, z: 3 };
obj[Symbol.iterator] = function*() {
 yield 1;
 yield 2;
 yield 3;
};
[...obj]; // 打印 [1, 2, 3]

问题7 : a, b, c

for-in循环遍历对象本身的可枚举属性以及对象从其原型继承的属性。 可枚举属性是可以在for-in循环期间包含和访问的属性。

var obj = { a: 1, b: 2 };
var descriptor = Object.getOwnPropertyDescriptor(obj, "a");
console.log(descriptor.enumerable); // true
console.log(descriptor);
// { value: 1, writable: true, enumerable: true, configurable: true }

现在你已经掌握了这些知识,应该很容易理解为什么我们的代码要打印这些特定的属性

var obj = { a: 1, b: 2 }; //a,b 都是 enumerables 属性

// 将{c:3}设置为'obj'的原型,并且我们知道
// for-in 循环也迭代 obj 继承的属性
// 从它的原型,'c'也可以被访问。
Object.setPrototypeOf(obj, { c: 3 });

// 我们在'obj'中定义了另外一个属性'd',但是
// 将'enumerable'设置为false。 这意味着'd'将被忽略。
Object.defineProperty(obj, "d", { value: 4, enumerable: false });

for (let prop in obj) {
 console.log(prop);
}
// 打印
// a
// b

问题8 : 10

在全局范围内初始化x时,它成为window对象的属性(不是严格的模式)。看看下面的代码:

var x = 10; // global scope
var foo = {
 x: 90,
 getX: function() {
  return this.x;
 }
};
foo.getX(); // prints 90
let xGetter = foo.getX;
xGetter(); // prints 10

咱们可以断言:

window.x === 10; // true

this 始终指向调用方法的对象。因此,在foo.getx()的例子中,它指向foo对象,返回90的值。而在xGetter()的情况下,this指向 window对象, 返回 window 中的x的值,即10

要获取 foo.x的值,可以通过使用Function.prototype.bindthis的值绑定到foo对象来创建新函数。

let getFooX = foo.getX.bind(foo);
getFooX(); // 90

就这样! 如果你的所有答案都正确,那么干漂亮。 咱们都是通过犯错来学习的。 这一切都是为了了解背后的“原因”。

代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

总结

以上所述是小编给大家介绍的8个有意思的JavaScript面试题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Javascript 相关文章推荐
一个用javascript写的select支持上下键、首字母筛选以及回车取值的功能
Sep 09 Javascript
JavaScript Event学习第二章 Event浏览器兼容性
Feb 07 Javascript
multiSteps 基于Jquery的多步骤滑动切换插件
Jul 22 Javascript
JS和Jquery获取和修改label的值的示例代码
Jan 15 Javascript
利用javascript实现全部删或清空所选的操作
May 27 Javascript
jQuery中parentsUntil()方法用法实例
Jan 07 Javascript
js图片模糊切换显示特效的方法
Feb 17 Javascript
总结Node.js中的一些错误类型
Aug 15 Javascript
微信小程序  TLS 版本必须大于等于1.2问题解决
Feb 22 Javascript
Vue.js中轻松解决v-for执行出错的三个方案
Jun 09 Javascript
webpack项目轻松混用css module的方法
Jun 12 Javascript
详解处理bootstrap4不支持远程静态框问题
Jul 20 Javascript
开源一个微信小程序仪表盘组件过程解析
Jul 30 #Javascript
原生js添加一个或多个类名的方法分析
Jul 30 #Javascript
vue2.0项目集成Cesium的实现方法
Jul 30 #Javascript
Koa从零搭建到Api实现项目的搭建方法
Jul 30 #Javascript
js实现的格式化数字和金额功能简单示例
Jul 30 #Javascript
JS实现点击发送验证码 xx秒后重新发送功能
Jul 30 #Javascript
微信小程序渲染性能调优小结
Jul 30 #Javascript
You might like
丧钟首部独立剧集《丧钟:骑士与龙》北美正式开播,场面血腥
2020/04/09 欧美动漫
PHP与已存在的Java应用程序集成
2006/10/09 PHP
PHP类的使用 实例代码讲解
2009/12/28 PHP
php 查找数组元素提高效率的方法详解
2017/05/05 PHP
laravel框架数据库操作、查询构建器、Eloquent ORM操作实例分析
2019/12/20 PHP
Nigma vs Alliance BO5 第五场2.14
2021/03/10 DOTA
JavaScript网页制作特殊效果用随机数
2007/05/22 Javascript
Highslide.js是一款基于js实现的网页中图片展示插件
2020/03/30 Javascript
深入理解JavaScript系列(6):S.O.L.I.D五大原则之单一职责SRP
2012/01/15 Javascript
如何用ajax来创建一个XMLHttpRequest对象
2012/12/10 Javascript
JS+DIV实现鼠标划过切换层效果的实例代码
2013/11/26 Javascript
利用js(jquery)操作Cookie的方法说明
2013/12/19 Javascript
Angularjs全局变量被作用域监听的正确姿势
2016/02/06 Javascript
jQuery插件实现文字无缝向上滚动效果代码
2016/02/25 Javascript
javascript帧动画(实例讲解)
2017/09/02 Javascript
AngularJS集合数据遍历显示的实例
2017/12/27 Javascript
Vue cli+mui 区域滚动的实例代码
2018/01/25 Javascript
浅谈webpack devtool里的7种SourceMap模式
2019/01/14 Javascript
jquery实现二级导航下拉菜单效果实例
2019/05/14 jQuery
Vue实现根据hash高亮选项卡
2019/05/27 Javascript
关于layui的下拉搜索框异步加载数据的解决方法
2019/09/28 Javascript
vue 项目@change多个参数传值多个事件的操作
2021/01/29 Vue.js
[02:32]DOTA2亚洲邀请赛 C9战队出场宣传片
2015/02/07 DOTA
pyqt4教程之实现半透明的天气预报界面示例
2014/03/02 Python
详解python中 os._exit() 和 sys.exit(), exit(0)和exit(1) 的用法和区别
2017/06/23 Python
python实现Windows电脑定时关机
2018/06/20 Python
pandas 透视表中文字段排序方法
2018/11/16 Python
浅谈Django2.0 加xadmin踩的坑
2019/11/15 Python
matplotlib绘制鼠标的十字光标的实现(内置方式)
2021/01/06 Python
Pat McGrath Labs官网:世界上最有影响力的化妆师推出的彩妆品牌
2018/01/07 全球购物
中国领先的汽车保养服务平台:途虎养车
2019/10/18 全球购物
什么是表空间(tablespace)和系统表空间(System tablespace)
2013/02/25 面试题
审计工作个人的自我评价
2013/12/25 职场文书
护理不良事件检讨书
2014/02/06 职场文书
营销团队口号
2014/06/06 职场文书
聊一聊Redis与MySQL双写一致性如何保证
2021/06/26 Redis