|
|
51CTO旗下网站
|
|
移动端

JavaScript九大面试问题集锦,助你顺利通关!

本文将介绍一些面试时关于JavaScript的常见问题,以及一些突发难题。当然,每次面试都是不同的,你也可能不会遇见这类问题。但是知道的越多,准备的就越充分。

作者:读芯术来源:读芯术|2019-07-19 08:10

人们认为JavaScript是最适合初学者的语言。一部分原因在于JavaScript在互联网中运用广泛,另一部分原因在于其自身特性使得即使编写的代码不那么***依然可以运行:无论是否少了一个分号或是内存管理问题,它都不像许多其他语言那样严格,但在开始学习之前,要确保你已经知道JavaScript的来龙去脉,包括可以自动完成的事情和“幕后”的操作。

本文将介绍一些面试时关于JavaScript的常见问题,以及一些突发难题。当然,每次面试都是不同的,你也可能不会遇见这类问题。但是知道的越多,准备的就越充分。

JavaScript

***部分:突发难题

如果在面试中突然问到下列问题,似乎很难回答。即便如此,这些问题在准备中仍发挥作用:它们揭示了JavaScript的一些有趣的功能,并强调在提出编程语言时,首先必须做出的一些决定。

了解有关JavaScript的更多功能,建议访问https://wtfjs.com。

1. 为什么Math.max()小于Math.min()?

Math.max()> Math.min()输出错误这一说法看上去有问题,但其实相当合理。

如果没有给出参数,Math.min()返回infinity(无穷大),Math.max()返回-infinity(无穷小)。这只是max()和min()方法规范的一部分,但选择背后的逻辑值得深议。了解其中原因,请看以下代码:

  1. Math.min(1) // 1  
  2. Math.min(1, infinity)// 1  
  3. Math.min(1, -infinity)// -infinity 

如果-infinity(无穷小)作为Math.min()的默认参数,那么每个结果都是-infinity(无穷小),这毫无用处! 然而,如果默认参数是infinity(无穷大),则无论添加任何参数返回都会是该数字 - 这就是我们想要的运行方式。

2. 为什么0.1+0.2不等于0.3

简而言之,这与JavaScript在二进制中存储浮点数的准确程度有关。在Google Chrome控制台中输入以下公式将得到:

  1. 0.1 + 0.2// 0.30000000000000004   
  2. 0.1 + 0.2 - 0.2// 0.10000000000000003   
  3. 0.1 + 0.7// 0.7999999999999999 

如果是简单的等式,对准确度没有要求,这不太可能产生问题。但是如果需要测试相等性,即使是简单地应用也会导致令人头疼的问题。解决这些问题,有以下几种方案。

(1) Fixed Point固定点

例如,如果知道所需的***精度(例如,如果正在处理货币),则可以使用整数类型来存储该值。因此,可以存储499而非4.99美元,并在此基础上执行任何等式,然后可以使用类似result =(value / 100).toFixed(2)的表达式将结果显示给最终用户,该表达式返回一个字符串。

(2) BCD代码

如果精度非常重要,另一种方法是使用二进制编码的十进制(BCD)格式,可以使用BCD库(https://formats.kaitai.io/bcd/javascript.html)访问JavaScript。每个十进制值分别存储在一个字节(8位)中。鉴于一个字节可以存储16个单独值,而该系统仅使用0-9位,所以这种方法效率低下。但是,如果十分注重精确度,采用何种方法都值得考量。

3. 为什么018减017等于3?

018-017返回3实际是静默类型转换的结果。这种情况,讨论的是八进制数。

(1) 八进制数简介

你或许知道计算中使用二进制(base-2)和十六进制(base-16)数字系统,但是八进制(base-8)在计算机历史中的地位也举足亲重:在20世纪50年代后期和 20世纪60年代间,八进制被用于简化二进制,削减高昂的制造系统中的材料成本。

不久以后Hexadecimal(十六进制)开始登上历史舞台:

1965年发布的IBM360迈出了从八进制到十六进制的决定性一步。我们这些习惯八进制的人对这一举措感到震惊!

沃恩·普拉特(Vaughan Pratt)

(2) 如今的八进制数

但在现代编程语言中,八进制又有何作用呢?针对某些案例,八进制比十六进制更具优势,因为它不需要任何非数字(使用0-7而不是0-F)。

一个常见用途是Unix系统的文件权限,其中有八个权限变体:

  • 4 2 1
  • 0 - - - no permissions
  • 1 - - x only execute
  • 2 - x - only write
  • 3 - x x write and execute
  • 4 x - - only read
  • 5 x - x read and execute
  • 6 x x - read and write
  • 7 x x x read, write and execute

出于相似的原由,八进制也用于数字显示器。

(3) 回到问题本身

在JavaScript中,前缀0将所有数字转换为八进制。但是,八进制中不使用数字8,任何包含8的数字都将自动转换为常规十进制数。

因此,018-017实际上等同于十进制表达式:18-15,因为017使用八进制而018使用十进制。

第二部分:常见问题

本节中,将介绍面试中一些更加常见的JavaScript问题。***次学习JavaScript时,这些问题容易被忽略。但在编写***代码时,了解下述问题用处颇大。

4. 函数表达式与函数声明有哪些不同?

函数声明使用关键字function,后跟函数的名称。相反,函数表达式以var,let或const开头,后跟函数名称和赋值运算符=。请看以下代码:

  1. // Function Declaration 
  2. function sum(x, y) { 
  3.   return x + y; 
  4. }; 
  5.  
  6. // Function Expression: ES5 
  7. var sum = function(x, y) { 
  8.   return x + y; 
  9. }; 
  10.   
  11. // Function Expression: ES6+ 
  12. const sum = (x, y) => { return x + y }; 

实际操作中,关键的区别在于函数声明要被提升,而函数表达式则没有。这意味着JavaScript解释器将函数声明移动到其作用域的顶部,因此可以定义函数声明并在代码中的任何位置调用它。相比之下,只能以线性顺序调用函数表达式:必须在调用它之前解释。

如今,许多开发人员偏爱函数表达式有如下几个原因:

  • 首先,函数表达式实施更加可预测的结构化代码库。当然,函数声明也可使用结构化代码库; 只是函数声明让你更容易摆脱凌乱的代码。
  • 其次,可以将ES6语法用于函数表达式:这通常更为简洁,let和const可以更好地控制是否重新赋值变量,我们将在下一个问题中看到。

5. var,let和const有什么区别?

自ES6发布以来,现代语法已进入各行各业,这已是一个极其常见的面试问题。Var是***版JavaScript中的变量声明关键字。但它的缺点导致在ES6中采用了两个新关键字:let和const。

这三个关键字具有不同的分配,提升和域 - 因此我们将单独讨论。

(1) 分配

最基本的区别是let和var可以重新分配,而const则不能。这使得const成为不变变量的***选择,并且它将防止诸如意外重新分配之类的失误。注意,当变量表示数组或对象时,const确实允许变量改变,只是无法重新分配变量本身。

Let 和var都可重新分配,但是正如以下几点应该明确的那样,如果不是所有情况都要求更改变量,多数选择中,let具有优于var的显著优势。

(2) 提升

与函数声明和表达式(如上所述)之间的差异类似,使用var声明的变量总是被提升到它们各自的顶部,而使用const和let声明的变量被提升,但是如果你试图在声明之前访问,将会得到一个TDZ(时间死区)错误。由于var可能更容易出错,例如意外重新分配,因此运算是有用的。请看以下代码:

  1. var x = "global scope"
  2.  
  3. function foo() { 
  4.   var x = "functional scope"
  5.   console.log(x); 
  6.  
  7. foo(); // "functional scope" 
  8. console.log(x); // "global scope" 

这里,foo()和console.log(x)的结果与预期一致。但是,如果去掉第二个变量又会发生什么呢?

  1. var x = "global scope"
  2.  
  3. function foo() { 
  4.   x = "functional scope"
  5.   console.log(x); 
  6. foo(); // "functional scope" 
  7. console.log(x); // "functional scope" 

尽管在函数内定义,但x =“functional scope”已覆盖全局变量。需要重复关键字var来指定第二个变量x仅限于foo()。

(3) 域

虽然var是function-scoped(函数作用域),但let和const是block-scoped(块作用域的:一般情况下,Block是大括号{}内的任何代码,包括函数,条件语句和循环。为了阐明差异,请看以下代码:

  1. var a = 0
  2. let b = 0
  3. const c = 0
  4. if (true) { 
  5.   var a = 1
  6.   let b = 1
  7.   const c = 1
  8. console.log(a); // 1 
  9. console.log(b); // 0 
  10. console.log(c); // 0 

在条件块中,全局范围的var a已重新定义,但全局范围的let b和const c则没有。一般而言,确保本地任务保持在本地执行,将使代码更加清晰,减少出错。

6. 如果分配不带关键字的变量会发生什么?

如果不使用关键字定义变量,又会如何?从技术上讲,如果x尚未定义,则x = 1是window.x = 1的简写。

要想完全杜绝这种简写,可以编写严格模式,——在ES5中介绍过——在文档顶部或特定函数中写use strict。后,当你尝试声明没有关键字的变量时,你将收到一条报语法错误:Uncaught SyntaxError:Unexpected indentifier。

7. 面向对象编程(OOP)和函数式编程(FP)之间的区别是什么?

JavaScript是一种多范式语言,即它支持多种不同的编程风格,包括事件驱动,函数和面向对象。

编程范式各有不同,但在当代计算中,函数编程和面向对象编程最为流行 - 而JavaScript两种都可执行。

(1) 面向对象编程

OOP以“对象”这一概念为基础的数据结构,包含数据字段(JavaScript称为类)和程序(JavaScript中的方法)。

一些JavaScript的内置对象包括Math(用于random,max和sin等方法),JSON(用于解析JSON数据)和原始数据类型,如String,Array,Number和Boolean。

无论何时采用的内置方法,原型或类,本质上都在使用面向对象编程。

(2) 函数编程

FP(函数编程)以“纯函数”的概念为基础,避免共享状态,可变数据和副作用。这可能看起来像很多术语,但可能已经在代码中创建了许多纯函数。

输入相同数据,纯函数总是返回相同的输出。这种方式没有副作用:除了返回结果之外,例如登录控制台或修改外部变量等都不会发生。

至于共享状态,这里有一个简单的例子,即使输入是相同的,状态仍可以改变函数的输出。设置一个具有两个函数的代码:一个将数字加5,另一个将数字乘以5。

  1. const num = { 
  2.  val: 1 
  3. }; 
  4. const add5 = () => num.val += 5; 
  5. const multiply5 = () => num.val *= 5; 

如果先调用add5在调用乘以5,则整体结果为30。但是如果以相反的方式执行函数并记录结果,则输出为10,与之前结果不一致。

这违背了函数式编程的原理,因为函数的结果因Context调用方法而异。 重新编写上面的代码,以便结果更易预测:

  1. const num = { 
  2.  val: 1 
  3. }; 
  4. const add5 = () => Object.assign({}, num, {val: num.val + 5}); const multiply5 = () => Object.assign({}, num, {val: num.val * 5}); 

现在,num.val的值仍然为1,无论Context调用的方法如何,add5(num)和multiply5(num)将始终输出相同的结果。

8. 命令式和声明性编程之间有什么区别?

关于命令式编程和声明式编程的区别,可以以OOP(面向对象编程)和FP(函数式编程)为参考。

这两种是描述多种不同编程范式共有特征的概括性术语。FP(函数式编程)是声明性编程的一个范例,而OOP(面向对象编程)是命令式编程的一个范例。

从基本的意义层面,命令式编程关注的是如何做某事。它以最基本的方式阐明了步骤,并以for和while循环,if和switch陈述句等为特征。

  1. const sumArray = array => { 
  2.   let result = 0
  3.   for (let i = 0; i < array.length; i++) { 
  4.      result += array[i] 
  5.   }; 
  6.   return result; 

相比之下,声明性编程关注的是做什么,它通过依赖表达式将怎样做抽出来。这通常会产生更简洁的代码,但是在规模上,由于透明度低,调试会更加困难。

这是上述的sumArray()函数的声明方法。

  1. const sumArray = array => { return array.reduce((x, y) => x + y) }; 

9. 是什么基于原型的继承?

***,要讲到的是基于原型的继承。面向对象编程有几种不同的类型,JavaScript使用的是基于原型的继承。该系统通过使用现有对象作为原型,允许重复运行。

即使是***遇到原型这一概念,使用内置方法时也会遇到原型系统。 例如,用于操作数组的函数(如map,reduce,splice等)都是Array.prototype对象的方法。实际上,数组的每个实例(使用方括号[]定义,或者 -不常见的 new Array())都继承自Array.prototype,这就是为什么map,reduce和spliceare等方法都默认可用的原因。

几乎所有内置对象都是如此,例如字符串和布尔运算:只有少数,如Infinity,NaN,null和undefined等没有类或方法。

在原型链的末尾,能发现 Object.prototype,几乎JavaScript中的每个对象都是Object的一个实例。比如Array. prototype和String. prototype都继承了Object.prototype的类和方法。

要想对使用prototype syntax的对象添加类和方法,只需将对象作为函数启动,并使用prototype关键字添加类和方法:

  1. function Person() {}; 
  2. Person.prototype.forename = "John"
  3. Person.prototype.surname = "Smith"

是否应该覆盖或扩展原型运算?

可以使用与创建扩展prototypes同样的方式改变内置运算,但是大多数开发人员(以及大多数公司)不会建议这样做。

如果希望多个对象进行同样的运算,可以创建一个自定义对象(或定义你自己的“类”或“子类”),这些对象继承内置原型而不改变原型本身。如果打算与其他开发人员合作,他们对JavaScript的默认行为有一定的预期,编辑此默认行为很容易导致出错。

总的来说,这些问题能够帮助你更好理解JavaScript,包括其核心功能和其他鲜为人知的功能 ,并且望能助你为下次的面试做好准备。

【编辑推荐】

  1. 2019年用于JavaScript的6大机器学习库
  2. 成为架构师之前,你得先学一门编程语言
  3. 探索JavaScript 数组的隐藏潜力
  4. 前端开发者常用的9个JavaScript图表库
  5. 用户数量急速增加的6个Javascript库
【责任编辑:赵宁宁 TEL:(010)68476606】

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

订阅专栏+更多

16招轻松掌握PPT技巧

16招轻松掌握PPT技巧

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

292人订阅学习

20个局域网建设改造案例

20个局域网建设改造案例

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

647人订阅学习

WOT2019全球人工智能技术峰会

WOT2019全球人工智能技术峰会

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

0人订阅学习

读 书 +更多

WebWork in Action中文版

本书是一本从头至尾都使用现实世界例子讲述有关编写Web应用程序的书籍。WebWork所强调的内容是:利用框架实现你的项目,而不是被框架所阻碍...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊

51CTO服务号

51CTO播客