面试下15个最基本的JavaScript面试问题及答案
Posted 百知教育
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试下15个最基本的JavaScript面试问题及答案相关的知识,希望对你有一定的参考价值。
11.写一个简单的函数(少于80个字符),要求返回一个布尔值指明字符串是否为回文结构。
下面这个函数在 str
是回文结构的时候返回true,否则,返回false。
function isPalindrome(str) { str = str.replace(/W/g, '').toLowerCase(); return (str == str.split('').reverse().join('')); }
例如:
console.log(isPalindrome("level")); // logs 'true'console.log(isPalindrome("levels")); // logs 'false'console.log(isPalindrome("A car, a man, a maraca")); // logs 'true'
12.写一个 sum
方法,在使用下面任一语法调用时,都可以正常工作
console.log(sum(2,3)); // Outputs 5console.log(sum(2)(3)); // Outputs 5
(至少)有两种方法可以做到:
方法1
function sum(x) { if (arguments.length == 2) { return arguments[0] + arguments[1]; } else { return function(y) { return x + y; }; } }
在javascript中,函数可以提供到 arguments
对象的访问,arguments
对象提供传递到函数的实际参数的访问。这使我们能够使用 length
属性来确定在运行时传递给函数的参数数量。
如果传递两个参数,那么只需加在一起,并返回。
否则,我们假设它被以 sum(2)(3)
这样的形式调用,所以我们返回一个匿名函数,这个匿名函数合并了传递到 sum()
的参数和传递给匿名函数的参数。
方法2
function sum(x, y) { if (y !== undefined) { return x + y; } else { return function(y) { return x + y; }; } }
当调用一个函数的时候,JavaScript不要求参数的数目匹配函数定义中的参数数量。如果传递的参数数量大于函数定义中参数数量,那么多余参数将简单地被忽略。另一方面,如果传递的参数数量小于函数定义中的参数数量,那么缺少的参数在函数中被引用时将会给一个 undefined
值。所以,在上面的例子中,简单地检查第2个参数是否未定义,就可以相应地确定函数被调用以及进行的方式。
13.请看下面的代码片段:
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', function(){ console.log(i); }); document.body.appendChild(btn); }
(a)当用户点击“Button 4”的时候会输出什么到控制台,为什么?(b)提供一个或多个备用的可按预期工作的实现方案。
(a)无论用户点击什么按钮,数字5将总会输出到控制台。这是因为,当 onclick
方法被调用(对于任何按钮)的时候, for
循环已经结束,变量 i
已经获得了5的值。(面试者如果能够谈一谈有关如何执行上下文,可变对象,激活对象和内部“范围”属性贡有助于闭包行为,则可以加分)。
(b)要让代码工作的关键是,通过传递到一个新创建的函数对象,在每次传递通过 for
循环时,捕捉到 i
值。下面是三种可能实现的方法:
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', (function(i) { return function() { console.log(i); }; })(i)); document.body.appendChild(btn); }
或者,你可以封装全部调用到在新匿名函数中的 btn.addEventListener
:
for (var i = 0; i < 5; i++) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); (function (i) { btn.addEventListener('click', function() { console.log(i); }); })(i); document.body.appendChild(btn); }
也可以调用数组对象的本地 forEach
方法来替代 for
循环:
['a', 'b', 'c', 'd', 'e'].forEach(function (value, i) { var btn = document.createElement('button'); btn.appendChild(document.createTextNode('Button ' + i)); btn.addEventListener('click', function() { console.log(i); }); document.body.appendChild(btn); });
14.下面的代码将输出什么到控制台,为什么?
var arr1 = "john".split('');var arr2 = arr1.reverse();var arr3 = "jones".split(''); arr2.push(arr3);console.log("array 1: length=" + arr1.length + " last=" + arr1.slice(-1));console.log("array 2: length=" + arr2.length + " last=" + arr2.slice(-1));
输出结果是:
"array 1: length=5 last=j,o,n,e,s""array 2: length=5 last=j,o,n,e,s"
arr1
和 arr2
在上述代码执行之后,两者相同了,原因是:
调用数组对象的
reverse()
方法并不只返回反顺序的阵列,它也反转了数组本身的顺序(即,在这种情况下,指的是arr1
)。reverse()
方法返回一个到数组本身的引用(在这种情况下即,arr1
)。其结果为,arr2
仅仅是一个到arr1
的引用(而不是副本)。因此,当对arr2
做了任何事情(即当我们调用arr2.push(arr3);
)时,arr1
也会受到影响,因为arr1
和arr2
引用的是同一个对象。
这里有几个侧面点有时候会让你在回答这个问题时,阴沟里翻船:
传递数组到另一个数组的 push()
方法会让整个数组作为单个元素映射到数组的末端。其结果是,语句 arr2.push(arr3);
在其整体中添加 arr3
作为一个单一的元素到 arr2
的末端(也就是说,它并没有连接两个数组,连接数组是 concat()
方法的目的)。
和Python一样,JavaScript标榜数组方法调用中的负数下标,例如 slice()
可作为引用数组末尾元素的方法:例如,-1下标表示数组中的最后一个元素,等等。
15.下面的代码将输出什么到控制台,为什么?
console.log(1 + "2" + "2");console.log(1 + +"2" + "2");console.log(1 + -"1" + "2");console.log(+"1" + "1" + "2");console.log( "A" - "B" + "2");console.log( "A" - "B" + 2);
上面的代码将输出以下内容到控制台:
"122""32""02""112""NaN2"NaN
原因是…
这里的根本问题是,JavaScript(ECMAScript)是一种弱类型语言,它可对值进行自动类型转换,以适应正在执行的操作。让我们通过上面的例子来说明这是如何做到的。
例1:1 + "2" + "2"
输出:"122"
说明: 1 + "2"
是执行的第一个操作。由于其中一个运算对象("2"
)是字符串,JavaScript会假设它需要执行字符串连接,因此,会将 1
的类型转换为 "1"
, 1 + "2"
结果就是 "12"
。然后, "12" + "2"
就是 "122"
。
例2: 1 + +"2" + "2"
输出: "32"
说明:根据运算的顺序,要执行的第一个运算是 +"2"
(第一个 "2"
前面的额外 +
被视为一元运算符)。因此,JavaScript将 "2"
的类型转换为数字,然后应用一元 +
号(即,将其视为一个正数)。其结果是,接下来的运算就是 1 + 2
,这当然是 3
。然后我们需要在一个数字和一个字符串之间进行运算(即, 3
和 "2"
),同样的,JavaScript会将数值类型转换为字符串,并执行字符串的连接,产生 "32"
。
例3: 1 + -"1" + "2"
输出: "02"
说明:这里的解释和前一个例子相同,除了此处的一元运算符是 -
而不是 +
。先是 "1"
变为 1
,然后当应用 -
时又变为了 -1
,然后将其与 1
相加,结果为 0
,再将其转换为字符串,连接最后的 "2"
运算对象,得到 "02"
。
例4: +"1" + "1" + "2"
输出: "112"
说明:虽然第一个运算对象 "1"
因为前缀的一元 +
运算符类型转换为数值,但又立即转换回字符串,当连接到第二个运算对象 "1"
的时候,然后又和最后的运算对象"2"
连接,产生了字符串 "112"
。
例5: "A" - "B" + "2"
输出: "NaN2"
说明:由于运算符 -
不能被应用于字符串,并且 "A"
和 "B"
都不能转换成数值,因此,"A" - "B"
的结果是 NaN
,然后再和字符串 "2"
连接,得到 "NaN2"
。
例6: "A" - "B" + 2
输出: NaN
说明:参见前一个例子, "A" - "B"
结果为 NaN
。但是,应用任何运算符到NaN与其他任何的数字运算对象,结果仍然是 NaN
。
16.下面的递归代码在数组列表偏大的情况下会导致堆栈溢出。在保留递归模式的基础上,你怎么解决这个问题?
var list = readHugeList();var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... nextListItem(); } };
潜在的堆栈溢出可以通过修改nextListItem
函数避免:
var list = readHugeList();var nextListItem = function() { var item = list.pop(); if (item) { // process the list item... setTimeout( nextListItem, 0); } };
堆栈溢出之所以会被消除,是因为事件循环操纵了递归,而不是调用堆栈。当 nextListItem
运行时,如果 item
不为空,timeout函数(nextListItem
)就会被推到事件队列,该函数退出,因此就清空调用堆栈。当事件队列运行其timeout事件,且进行到下一个 item
时,定时器被设置为再次调用 nextListItem
。因此,该方法从头到尾都没有直接的递归调用,所以无论迭代次数的多少,调用堆栈保持清空的状态。
17.JavaScript中的“闭包”是什么?请举一个例子。
闭包是一个可以访问外部(封闭)函数作用域链中的变量的内部函数。闭包可以访问三种范围中的变量:这三个范围具体为:(1)自己范围内的变量,(2)封闭函数范围内的变量,以及(3)全局变量。
下面是一个简单的例子:
var globalVar = "xyz"; (function outerFunc(outerArg) { var outerVar = 'a'; (function innerFunc(innerArg) { var innerVar = 'b'; console.log( "outerArg = " + outerArg + " " + "innerArg = " + innerArg + " " + "outerVar = " + outerVar + " " + "innerVar = " + innerVar + " " + "globalVar = " + globalVar); })(456); })(123);
在上面的例子中,来自于 innerFunc
, outerFunc
和全局命名空间的变量都在 innerFunc
的范围内。因此,上面的代码将输出如下:
outerArg = 123innerArg = 456outerVar = ainnerVar = bglobalVar = xyz
18.下面的代码将输出什么:
for (var i = 0; i < 5; i++) { setTimeout(function() { console.log(i); }, i * 1000 ); }
解释你的答案。闭包在这里能起什么作用?
上面的代码不会按预期显示值0,1,2,3,和4,而是会显示5,5,5,5,和5。
原因是,在循环中执行的每个函数将整个循环完成之后被执行,因此,将会引用存储在 i
中的最后一个值,那就是5。
闭包可以通过为每次迭代创建一个唯一的范围,存储范围内变量的每个唯一的值,来防止这个问题,如下:
for (var i = 0; i < 5; i++) { (function(x) { setTimeout(function() { console.log(x); }, x * 1000 ); })(i); }
这就会按预期输出0,1,2,3,和4到控制台。
19.以下代码行将输出什么到控制台?
console.log("0 || 1 = "+(0 || 1));console.log("1 || 2 = "+(1 || 2));console.log("0 && 1 = "+(0 && 1));console.log("1 && 2 = "+(1 && 2));
并解释。
该代码将输出:
0 || 1 = 11 || 2 = 10 && 1 = 01 && 2 = 2
在JavaScript中, ||
和 &&
都是逻辑运算符,用于在从左至右计算时,返回第一个可完全确定的“逻辑值”。
或( ||
)运算符。在形如 X||Y
的表达式中,首先计算X
并将其解释执行为一个布尔值。如果这个布尔值true
,那么返回true
(1),不再计算 Y
,因为“或”的条件已经满足。如果这个布尔值为false
,那么我们仍然不能知道 X||Y
是真是假,直到我们计算 Y
,并且也把它解释执行为一个布尔值。
因此, 0 || 1
的计算结果为true(1),同理计算1 || 2
。
与( &&
)运算符。在形如 X&&Y
的表达式中,首先计算 X
并将其解释执行为一个布尔值。如果这个布尔值为 false
,那么返回 false
(0),不再计算 Y
,因为“与”的条件已经失败。如果这个布尔值为true
,但是,我们仍然不知道 X&&Y
是真是假,直到我们去计算 Y
,并且也把它解释执行为一个布尔值。
不过,关于 &&
运算符有趣的地方在于,当一个表达式计算为“true”的时候,那么就返回表达式本身。这很好,虽然它在逻辑表达式方面计算为“真”,但如果你希望的话也可用于返回该值。这就解释了为什么,有些令人奇怪的是, 1 && 2
返回 2
(而不是你以为的可能返回 true
或 1
)。
20.执行下面的代码时将输出什么?请解释。
console.log(false == '0')console.log(false === '0')
代码将输出:
truefalse
在JavaScript中,有两种等式运算符。三个等于运算符 ===
的作用类似传统的等于运算符:如果两侧的表达式有着相同的类型和相同的值,那么计算结果为true。而双等于运算符,会只强制比较它们的值。因此,总体上而言,使用 ===
而不是 ==
的做法更好。 !==
vs !=
亦是同理。
21.以下代码将输出什么?并解释你的答案。
var a={}, b={key:'b'}, c={key:'c'}; a[b]=123; a[c]=456; console.log(a[b]);
这段代码将输出 456
(而不是 123
)。
原因为:当设置对象属性时,JavaScript会暗中字符串化参数值。在这种情况下,由于 b
和 c
都是对象,因此它们都将被转换为"[object Object]"
。结果就是, a[b]
和a[c]
均相当于a["[object Object]"]
,并可以互换使用。因此,设置或引用 a[c]
和设置或引用 a[b]
完全相同。
22.以下代码行将输出什么到控制台?
console.log((function f(n){return ((n > 1) ? n * f(n-1) : n)})(10));
并解释你的答案。
代码将输出10!的值(即10!或3628800)。
原因是:
命名函数 f()
递归地调用本身,当调用 f(1)
的时候,只简单地返回1
。下面就是它的调用过程:
f(1): returns n, which is 1f(2): returns 2 * f(1), which is 2f(3): returns 3 * f(2), which is 6f(4): returns 4 * f(3), which is 24f(5): returns 5 * f(4), which is 120f(6): returns 6 * f(5), which is 720f(7): returns 7 * f(6), which is 5040f(8): returns 8 * f(7), which is 40320f(9): returns 9 * f(8), which is 362880f(10): returns 10 * f(9), which is 3628800
23.请看下面的代码段。控制台将输出什么,为什么?
(function(x) { return (function(y) { console.log(x); })(2) })(1);
控制台将输出 1
,即使从来没有在函数内部设置过x
的值。原因是:
正如我们在JavaScript招聘指南中解释过的那样,闭包是一个函数,连同在闭包创建的时候,其范围内的所有变量或函数一起。在JavaScript中,闭包是作为一个“内部函数”实施的:即,另一个函数主体内定义的函数。闭包的一个重要特征是,内部函数仍然有权访问外部函数的变量。
因此,在本例中,由于 x
未在函数内部中定义,因此在外部函数范围中搜索定义的变量 x
,且被发现具有1
的值。
24.下面的代码将输出什么到控制台,为什么:
var hero = { _name: 'John Doe', getSecretIdentity: function (){ return this._name; } };var stoleSecretIdentity = hero.getSecretIdentity;console.log(stoleSecretIdentity());console.log(hero.getSecretIdentity());
代码有什么问题,以及应该如何修复。
代码将输出:
undefinedJohn Doe
第一个 console.log
之所以输出 undefined
,是因为我们正在从 hero
对象提取方法,所以调用了全局上下文中(即窗口对象)的 stoleSecretIdentity()
,而在此全局上下文中, _name
属性不存在。
其中一种修复stoleSecretIdentity()
函数的方法如下:
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);
25.创建一个给定页面上的一个DOM元素,就会去访问元素本身及其所有子元素(不只是它的直接子元素)的函数。对于每个被访问的元素,函数应该传递元素到提供的回调函数。
此函数的参数为:
DOM元素
回调函数(将DOM元素作为其参数)
访问树(DOM)的所有元素是经典的深度优先搜索算法应用。下面是一个示范的解决方案:
function Traverse(p_element,p_callback) { p_callback(p_element); var list = p_element.children; for (var i = 0; i < list.length; i++) { Traverse(list[i],p_callback); // recursive call } }
译文链接:http://www.codeceo.com/article/25-essential-javascript-interview-questions.html
英文原文:25 Essential JavaScript Interview Questions
回复【福利】拼手气领取小黄车月卡。
百知教育
品牌传承
百知教育是中关村高薪技术企业,中关村软件园人才基地合作伙伴,中关村人才协会互联网+人才平台承办单位,清华大学软件工程微学位合作伙伴,百度大数据与人工智能合作伙伴。所有培训课程,均由百知联合百度、腾讯、用友等高端IT企业联合打造。
企业优势
百知教育拥有专职行业名师50多名,来自腾讯、百度、IBM等知名企业的总裁级课程顾问30余名。采用基于费曼学习方法的项目引领式实训方式,每年输送人才3000人。平均年薪10w+ 。
以上是关于面试下15个最基本的JavaScript面试问题及答案的主要内容,如果未能解决你的问题,请参考以下文章