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 获取链接(url)参数的方法
Feb 15 Javascript
Js 中debug方式
Feb 07 Javascript
Dom与浏览器兼容性说明
Oct 25 Javascript
jquery弹出关闭遮罩层实例
Aug 06 Javascript
jquery+ajax+C#实现无刷新操作数据库数据的简单实例
Feb 08 Javascript
javascript 小数取整简单实现方式
May 30 Javascript
JavaScript的RequireJS库入门指南
Jul 01 Javascript
jquery实现很酷的网页顶部图标下拉菜单效果
Aug 22 Javascript
JavaScript 节流函数 Throttle 详解
Jul 04 Javascript
layui表格实现代码
May 20 Javascript
jquery获取select选中值的文本,并赋值给另一个输入框的方法
Aug 21 jQuery
微信小程序实现自定义底部导航
Nov 18 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
PHP $_FILES函数详解
2011/03/09 PHP
PHP生成随机用户名和密码的实现代码
2013/02/27 PHP
php浏览历史记录的方法
2015/03/10 PHP
简单了解PHP编程中数组的指针的使用
2015/11/30 PHP
PHP实现截取中文字符串不出现?号的解决方法
2016/12/29 PHP
Microsoft Ajax Minifier 压缩javascript的方法
2010/03/05 Javascript
extjs tabpanel限制选项卡数量实现思路及代码
2013/04/02 Javascript
javascript中callee与caller的区别分析
2015/04/20 Javascript
JS+CSS实现电子商务网站导航模板效果代码
2015/09/10 Javascript
基于MVC5和Bootstrap的jQuery TreeView树形控件(二)之数据支持json字符串、list集合
2016/08/11 Javascript
JS求解三元一次方程组值的方法
2017/01/03 Javascript
Bootstrap面板学习使用
2017/02/09 Javascript
React学习笔记之条件渲染(一)
2017/07/02 Javascript
js实现方块上下左右移动效果
2017/08/17 Javascript
基于Koa2写个脚手架模拟接口服务的方法
2018/11/27 Javascript
Python获取apk文件URL地址实例
2013/11/01 Python
在Python中调用ggplot的三种方法
2015/04/08 Python
分析Python中设计模式之Decorator装饰器模式的要点
2016/03/02 Python
Python爬取APP下载链接的实现方法
2016/09/30 Python
python使用for循环计算0-100的整数的和方法
2019/02/01 Python
Python进程,多进程,获取进程id,给子进程传递参数操作示例
2019/10/11 Python
python 计算积分图和haar特征的实例代码
2019/11/20 Python
Pandas时间序列:时期(period)及其算术运算详解
2020/02/25 Python
浅谈pytorch 模型 .pt, .pth, .pkl的区别及模型保存方式
2020/05/25 Python
Canvas与Image互相转换示例代码
2013/08/09 HTML / CSS
The North Face北面英国官网:美国著名户外品牌
2017/12/13 全球购物
NOTINO英国:在线购买美容和香水
2020/02/25 全球购物
Python面试题:Python里面如何生成随机数
2015/03/12 面试题
车辆工程专业求职信
2014/06/14 职场文书
大班上学期个人总结
2015/02/13 职场文书
2015年结对帮扶工作总结
2015/05/04 职场文书
2015年员工试用期工作总结
2015/05/28 职场文书
三八妇女节主持词
2015/07/04 职场文书
闭幕词的写作格式与范文!
2019/06/24 职场文书
MySQL 字符集 character
2022/05/04 MySQL
JS class语法糖的深入剖析
2022/07/07 Javascript