|
|
|
|
移动端

世界杯中國最好成績:8道经典JavaScript面试题解析,你真的掌握JavaScript了吗?

本文来源:http://www.133845.com/tech_xcar_com_cn/

申博官网下载直营网,  商标“乔丹”会不会被撤销?  王军认为,不能孤立地来看此案,在现在的商业环境下,它具有一定典型性。他是为数不多的和后来拳王泰森一样的“良心拒服兵役者”(conscientiousobjector),却以另外的方式履行着自己作为军人和爱国者的职责。深港通首日总交易金额达34.86亿元人民币。但是这里笔者需要提醒你的是,你一定要控制好海外资产的投资比例,海外资本市场并不像你想的那样遍地是黄金。

与当今许多锂电池组相比,该款蓄电池据说对反复性快速充电的反应更为积极迅速。原标题:新媒疯狂猜测装甲车被扣事件新学者:给新加坡发出警告【环球时报驻新加坡特约记者辛斌环球时报驻香港特约记者凌德】“中国军方可能趁机窥探新加坡的尖端军事技术”——随着“香港海关扣留新加坡AV-81运兵装甲车”事件进入第五天,新加坡方面的焦虑越来越明显,《联合早报》27日提出这样的“担心”。雪村夫妇12月7日,雪村与妻子俞晴、父母亮相电影《东北人活雷锋》在北京召开的发布会。  澎湃新闻从权威渠道获取的一份《关于建立党员干部及公职人员饮酒报告备案制度的通知》显示,备案对象为全县各级党员干部及公职人员。

  而当天阻止记者拍摄的鹿邑县法院法警大队大队长张诚对群众诉求不积极应对,态度恶劣、简单粗暴,损害了党群、干群关系,造成严重不良后果,给予其党内严重警告处分,建议鹿邑县人民法院免去其县法院法警大队队长职务。中方将继续积极参与叙利亚问题政治解决进程,为缓解叙利亚紧张局势、推动叙利亚各方尽快重启和谈做出我们自己的努力。多地采购了这一新型工具,雾炮车也逐步以治霾神器的面貌为公众熟知。党中央、国务院始终高度重视个体私营等非公有制经济发展,制定实施了一系列鼓励、支持、引导的政策措施,近期又出台了《关于完善产权制度依法保护产权的意见》,不断深化简政放权、放管结合、优化服务,大力推进商事制度改革,个体私营等非公有制经济发展又迎来新的巨大机遇。

JavaScript是前端开发中非常重要的一门语言,浏览器是他主要运行的地方。JavaScript是一个非常有意思的语言,但是他有很多一些概念,大家经常都会忽略。所以今天,就来和大家看看下面几个问题,大家可以先思考一下,尝试作答。

作者:WEB开发阿靖来源:segmentfault|2019-08-13 08:43

JavaScript是前端开发中非常重要的一门语言,浏览器是他主要运行的地方。JavaScript是一个非常有意思的语言,但是他有很多一些概念,大家经常都会忽略。比如说,原型,闭包,原型链,事件循环等等这些概念,很多JS开发人员都研究不多。

所以今天,就来和大家看看下面几个问题,大家可以先思考一下,尝试作答。

八道面试题

问题1:下面这段代码,浏览器控制台上会打印什么?

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

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

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

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

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

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

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

答案

前面的问题我们都举例出来了,接下来我们会从头到尾,一个个来分析我们这些问题的答案,给大家一些学习的思路。

问题1:

使用var关键字声明的变量在JavaScript中会被提升,并在内存中开辟空间,由于没有赋值,无法定义数值类型,所以分配默认值undefined。var声明的变量,真正的数值初始化,是发生在你确定赋值的位置。同时,我们要知道,var声明的变量是函数作用域的,也就是我们需要区分局部变量和全局变量,而let和const是块作用域的。所以我们这道题的运行过程是这样的:

  1. var a = 10; / 全局作用域,全局变量。a=10  
  2. function foo() {  
  3. / var a   
  4. /的声明将被提升到到函数的顶部。  
  5. / 比如:var a  
  6. console.log(a); / 打印 undefined  
  7. / 实际初始化值20只发生在这里  
  8.    var a = 20; / local scope  

图解在下面,好理解一点。

所以问题1的答案是:undefined

问题 2:

let和const声明可以让变量在其作用域上受限于它所在的块、语句或表达式中。和var不同的地方在于,这两个声明的变量,不会被提升。并且我们会有一个称为暂时死区(TDZ)。如果访问TDZ中的变量的话,就会报ReferenceError,因为他们的的作用域是在他们声明的位置的,不会有提升。所以必须在执行到声明的位置才能访问。

  1. var a = 10; / 全局使用域  
  2. function foo() { / TDZ 开始  
  3. / 创建了未初始化的'a'  
  4.     console.log(a); / ReferenceError  
  5. / TDZ结束,'a'仅在此处初始化,值为20  
  6.     let a = 20 

图解:

问题2答案:ReferenceError:a undefined。

问题3:

这个问题,是循环结构会给大家带来一种块级作用域的误区,在for的循环的头部使用var声明的变量,就是单个声明的变量绑定(单个存储空间)。在循环过程中,这个var声明的i变量是会随循环变化的。但是在循环中执行的数组push方法,最后实际上是push了i最终循环结束的3这个值。所以最后push进去的全都是3。

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

图解:

如果想记录每一次循环的值下来,可以使用let声明一个具有块级作用域的变量,这样为每个循环迭代创建一个新的绑定。

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

还有解决这个问题的另外一种解决方案就是使用闭包就好了。

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

问题3答案:3,3,3

问题4

JavaScript的并发模式基于我们常说的”事件循环“。

浏览器是提供运行时环境来给我们执行JS代码的。浏览器的主要组成包括有调用堆栈,事件循环,任务队列和WEB API。像什么常用的定时器setTimeout,setInterval这些全局函数就不是JavaScript的一部分,而是WEB API给我们提供的。

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

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

好了,现在有了前面这些知识,我们可以看一下这道题的讲解过程:

实现步骤:

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

问题4答案:堆栈不会溢出。

问题5:

在很多时候,很多做前端开发的同学都是认为循环事件图中就只会有一个任务列表。但事实上不是这样的,我们是可以有多个任务列表的。由浏览器选择其中一个队列并在该队列进行处理回调。

从底层来看,JavaScript中是可以有宏认为和微任务的,比如说setTimeout回调是宏任务,而Promise回调是微任务。

他们有什么区别呢?

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

然后我们再回到我们前面讲的问题5中:

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

我们这段代码,每次我们去调用【foo】的时候,都会在微任务队列上加另一个【foo】的回调,因此事件循环没办法继续去处理其他的事件了(比如说滚动,点击事件等等),直到该队列完全清空位置。因此,不会执行渲染,会被阻止。

问题5答案:不会响应

问题6:

在我们做面试题的时候,展开语法和for-of语句去遍历iterable对象定义要遍历的数据。其中我们要使用迭代器的时候,Array和Map都是有默认迭代操作的内置迭代器的。

但是,对象是不可迭代的,也就是我们这道题里的,这是一个对象的集合。但是我们可以使用iterable和iterator协议来把它变成可以迭代的。

在我们研究对象的时候,如果一个对象他实现了@@iterator方法,那么它就是可以迭代的。这意味着这个对象(在他的原型链上的一个对象)必须是又@@iterator键的属性的,然后我们就可以利用这个键,通过常量Symbol.iterator获得。

下面是这道题的举例写法:

  1. var obj = { x: 1, y: 2, z: 3 };  
  2. obj[Symbol.iterator] = function() {  
  3.     / iterator 是一个具有 next 方法的对象,  
  4.     / 它的返回至少有一个对象  
  5.     / 两个属性:value&done。  
  6.     / 返回一个 iterator 对象  
  7.     return {  
  8.         next: function() {  
  9.             if (this._countDown === 3) {  
  10.                const lastValue = this._countDown;  
  11.                return { value: this._countDown, done: true };  
  12.               }  
  13.             thisthis._countDown = this._countDown + 1;  
  14.             return { value: this._countDown, done: false };  
  15.         },  
  16.         _countDown: 0  
  17.     };  
  18. };  
  19. [...obj]; / 打印 [1, 2, 3] 

问题6答案:如上是一种方案,可以避免TypeError异常

问题7:

在看这个问题的时候,我们要先理解for-in循环遍历本身的可枚举属性和对象从原来的原型继承来的属性。可枚举属性是可以在for-in循环期间可以访问的属性。

当我们知道这个知识点前提了之后,我们在看这道题,你就知道这道题打印的其实就是只能打印这些特定的属性。

  1. var obj = { a: 1, b: 2 }; /a,b 都是可枚举属性  
  2. / 将{c:3}设置为'obj'的原型,  
  3. / 并且我们知道for-in 循环也迭代 obj 继承的属性  
  4. / 从它的原型,'c'也可以被访问。  
  5. Object.setPrototypeOf(obj, { c: 3 });  
  6. / 我们在'obj'中定义了另外一个属性'd',  
  7. / 但是将'enumerable'可枚举设置为false。 这意味着'd'将被忽略。  
  8. Object.defineProperty(obj, "d", { value: 4, enumerable: false });  
  9. /所以最后使用for-in遍历这个对象集合,那就是只能遍历出可枚举属性  
  10. for (let prop in obj) {  
  11.     console.log(prop);  
  12.  
  13. / 也就是只能打印  
  14. / a  
  15. / b  
  16. / c 

图解

问题7答案:a、b、c

问题8:

首先我们可以看到var x是一个全局遍历,在不是严格模式下,这个X就直接是window对象的属性了。在这段代码里,我们最重要是要理解this的对象指向问题,this始终是指向调用方法的对象的。所以,在foo,xGetter()的情况下,this指向的是foo对象,返回的就是在foo中的属性x,值就是90。但是在xGetter()的情况下,他是直接调用的foo的getx()方法,但是其中this的指向是在xGetter的作用域,就是指向的window对象中,这时指向的就是全局变量x了,值也就是10。

  1. var x = 10; / 全局变量  
  2. var foo = {  
  3.     x: 90,/foo对象的内部属性  
  4.     getX: function() {  
  5.          return this.x;  
  6.     }  
  7. };  
  8. foo.getX(); / 此时是指向的foo对象,  
  9. /所以打印的是X属性 值就是90  
  10. let xGetter = foo.getX;/xGetter是在全局作用域,  
  11. /这里的this就是指向window对象  
  12. xGetter(); / 打印 10 

问题8答案:10

最后

ok,我们的8道问题都解决了,如果你前面写的答案全部都正确,那么你非常棒!去面试前端工作起码12k起步了。就算做不出来或者做错了也没有关系,我们都是不断通过犯错来学习的,一步步的理解错误,理解背后的原因,才能进步。

【责任编辑:申博官网下载直营网庞桂玉 TEL:(010)68476606】

点赞 0
大家都在看
猜你喜欢

订阅专栏+更多

16招轻松掌握PPT技巧

16招轻松掌握PPT技巧

GET职场加薪技能
共16章 | 晒书包

348人订阅学习

20个局域网建设改造案例

20个局域网建设改造案例

网络搭建技巧
共20章 | 捷哥CCIE

723人订阅学习

WOT2019全球人工智能技术峰会

WOT2019全球人工智能技术峰会

通用技术、应用领域、企业赋能三大章节,13大技术专场,60+国内外一线人工智能精英大咖站台,分享人工智能的平台工具、算法模型、语音视觉等技术主题,助力人工智能落地。
共50章 | WOT峰会

0人订阅学习

读 书 +更多

Linux命令、编辑器与Shell编程

本书是目前所能找到的最实用、最全面的Linux指南和参考手册,也是唯一一本提供以下全部内容的书籍: 更好更实用的示例覆盖了实际工作中需...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO播客

申博138登入 申博登录不了 太阳城官网 申博电子游戏 菲律宾申博游戏直营网 菲律宾申博开户登入
申博官网代理登入 新版太阳城申博开户 777老虎机支付宝充值 旧版太阳城直营网 菲律宾申博网址登入 申博游戏手机版登入
申博电子游戏直营网 正规申博开户登入 太阳成申博官网登入 菲律宾太阳网城上娱乐 www.44msc.com www.sbc188.com