文档库 最新最全的文档下载
当前位置:文档库 › 基础JavaScript面试问题及答案

基础JavaScript面试问题及答案

基础JavaScript面试问题及答案
基础JavaScript面试问题及答案

基础JavaScript面试问题及答案

1.使用typeof bar === "object"来确定bar是否是对象的潜在陷阱是什么?如何避免这个陷阱?

尽管typeof bar === "object"是检查bar是否对象的可靠方法,令人惊讶的是在JavaScript 中null也被认为是对象!

因此,令大多数开发人员惊讶的是,下面的代码将输出true (而不是false) 到控制台:

var bar = null;console.log(typeof bar === "object"); // logs true!

只要清楚这一点,同时检查bar是否为null,就可以很容易地避免问题:

console.log((bar !== null) && (typeof bar === "object")); // logs false

要答全问题,还有其他两件事情值得注意:

首先,上述解决方案将返回false,当bar是一个函数的时候。在大多数情况下,这是期望行为,但当你也想对函数返回true的话,你可以修改上面的解决方案为:

console.log((bar !== null) && ((typeof bar === "object") || (typeof bar === "function")));

第二,上述解决方案将返回true,当bar是一个数组(例如,当var bar = [];)的时候。在大多数情况下,这是期望行为,因为数组是真正的对象,但当你也想对数组返回false时,你可以修改上面的解决方案为:

console.log((bar !== null) && (typeof bar === "object") && (toString.call(ba

r) !== "[object Array]"));

或者,如果你使用jQuery的话:

console.log((bar !== null) && (typeof bar === "object") && (! $.isArray(ba

r)));

2.下面的代码将输出什么到控制台,为什么?

(function(){

var a = b = 3;

})();

console.log("a defined? " + (typeof a !== 'undefined'));console.log("b defin ed? " + (typeof b !== 'undefined'));

由于a和b都定义在函数的封闭范围内,并且都始于var关键字,大多数JavaScript开发人员期望typeof a和typeof b在上面的例子中都是undefined。

然而,事实并非如此。这里的问题是,大多数开发人员将语句var a = b = 3;错误地理解为是

以下声明的简写:

var b = 3;var a = b;

但事实上,var a = b = 3;实际是以下声明的简写:

b = 3;var a = b;

因此(如果你不使用严格模式的话),该代码段的输出是:

a defined? false

b defined? true

但是,b如何才能被定义在封闭函数的范围之外呢?是的,既然语句var a = b = 3;是语句 b = 3;和var a = b;的简写,b最终成为了一个全局变量(因为它没有前缀var关键字),因此仍

然在范围内甚至封闭函数之外。

需要注意的是,在严格模式下(即使用use strict),语句var a = b = 3;将生成ReferenceError:

b is not defined的运行时错误,从而避免任何否则可能会导致的headfakes /bug。(还是你为什么应该理所当然地在代码中使用use strict的最好例子!)

3.下面的代码将输出什么到控制台,为什么?

var myObject = {

foo: "bar",

func: function() {

var self = this;

console.log("outer func: this.foo = " + this.foo);

console.log("outer func: self.foo = " + self.foo);

(function() {

console.log("inner func: this.foo = " + this.foo);

console.log("inner func: self.foo = " + self.foo);

}());

}

};

myObject.func();

上面的代码将输出以下内容到控制台:

outer func: this.foo = bar

outer func: self.foo = bar

inner func: this.foo = undefined

inner func: self.foo = bar

在外部函数中,this和self两者都指向了myObject,因此两者都可以正确地引用和访问foo。在内部函数中,this不再指向myObject。其结果是,this.foo没有在内部函数中被定义,相反,指向到本地的变量self保持在范围内,并且可以访问。(在ECMA 5之前,在内部函数中的

this将指向全局的window对象;反之,因为作为ECMA 5,内部函数中的功能this是未定义的。)

4.封装JavaScript源文件的全部内容到一个函数块有什么意义及理由?

这是一个越来越普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)采用。这种技术创建了一个围绕文件全部内容的闭包,也许是最重要的是,创建了一个私有的命名空间,从而

有助于避免不同JavaScript模块和库之间潜在的名称冲突。

这种技术的另一个特点是,允许一个易于引用的(假设更短的)别名用于全局变量。这通常用于,

例如,jQuery插件中。jQuery允许你使用jQuery.noConflict(),来禁用$引用到jQuery命名空间。在完成这项工作之后,你的代码仍然可以使用$利用这种闭包技术,如下所示:

(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);

5.在JavaScript源文件的开头包含use strict有什么意义和好处?

对于这个问题,既简要又最重要的答案是,use strict是一种在JavaScript代码运行时自动实

行更严格解析和错误处理的方法。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常。通常而言,这是一个很好的做法。

严格模式的一些主要优点包括:

使调试更加容易。那些被忽略或默默失败了的代码错误,会产生错误或抛出异常,因此尽早提醒你代码中的问题,你才能更快地指引到它们的源代码。

防止意外的全局变量。如果没有严格模式,将值分配给一个未声明的变量会自动创建该名称的全局变量。这是JavaScript中最常见的错误之一。在严格模式下,这样做的话会抛出错

误。

消除this强制。如果没有严格模式,引用null或未定义的值到this值会自动强制到全局变量。这可能会导致许多令人头痛的问题和让人恨不得拔自己头发的bug。在严格模式下,引用null或未定义的this值会抛出错误。

不允许重复的属性名称或参数值。当检测到对象(例如,var object = {foo: "bar", foo: "baz"};)中重复命名的属性,或检测到函数中(例如,function foo(val1, val2, val1){})重复命名的参数时,严格模式会抛出错误,因此捕捉几乎可以肯定是代码中的bug可以避免浪费大量的跟踪时间。

使eval()更安全。在严格模式和非严格模式下,eval()的行为方式有所不同。最显而易见的是,在严格模式下,变量和声明在eval()语句内部的函数不会在包含范围内创建(它们会在非严格模式下的包含范围中被创建,这也是一个常见的问题源)。

在delete使用无效时抛出错误。delete操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格

模式将在这样的情况下抛出异常。

6.考虑以下两个函数。它们会返回相同的东西吗?为什么相同或为什么不

相同?

function foo1(){

return {

bar: "hello"

};

}

function foo2(){

return

{

bar: "hello"

};

}

出人意料的是,这两个函数返回的内容并不相同。更确切地说是:

console.log("foo1 returns:");console.log(foo1());console.log("foo2 returns: ");console.log(foo2());

将产生:

foo1returns:Object {bar: "hello"}foo2returns:undefined

这不仅是令人惊讶,而且特别让人困惑的是,foo2()返回undefined却没有任何错误抛出。

原因与这样一个事实有关,即分号在JavaScript中是一个可选项(尽管省略它们通常是非常糟糕

的形式)。其结果就是,当碰到foo2()中包含return语句的代码行(代码行上没有其他任何代码),分号会立即自动插入到返回语句之后。

也不会抛出错误,因为代码的其余部分是完全有效的,即使它没有得到调用或做任何事情(相当

于它就是是一个未使用的代码块,定义了等同于字符串"hello"的属性bar)。

这种行为也支持放置左括号于JavaScript代码行的末尾,而不是新代码行开头的约定。正如这里

所示,这不仅仅只是JavaScript中的一个风格偏好。

7. NaN是什么?它的类型是什么?你如何可靠地测试一个值是否等于NaN?NaN属性代表一个“不是数字”的值。这个特殊的值是因为运算不能执行而导致的,不能执行的

原因要么是因为其中的运算对象之一非数字(例如,"abc" / 4),要么是因为运算的结果非数

字(例如,除数为零)。

虽然这看上去很简单,但NaN有一些令人惊讶的特点,如果你不知道它们的话,可能会导致令人

头痛的bug。

首先,虽然NaN意味着“不是数字”,但是它的类型,不管你信不信,是Number:

console.log(typeof NaN === "number"); // logs "true"

此外,NaN和任何东西比较——甚至是它自己本身!——结果是false:

console.log(NaN === NaN); // logs "false"

一种半可靠的方法来测试一个数字是否等于NaN,是使用内置函数isNaN(),但即使使

用isNaN()依然并非是一个完美的解决方案。

一个更好的解决办法是使用value !== value,如果值等于NaN,只会产生true。另外,ES6提供了一个新的Number.isNaN()函数,这是一个不同的函数,并且比老的全局isNaN()函数更可靠。

8.下列代码将输出什么?并解释原因。

console.log(0.1 + 0.2);console.log(0.1 + 0.2 == 0.3);

一个稍微有点编程基础的回答是:“你不能确定。可能会输出“0.3”和“true”,也可能不会。JavaScript中的数字和浮点精度的处理相同,因此,可能不会总是产生预期的结果。“

以上所提供的例子就是一个演示了这个问题的典型例子。但出人意料的是,它会输出:

0.30000000000000004false

9.讨论写函数isInteger(x)的可能方法,用于确定x是否是整数。

这可能听起来是小菜一碟,但事实上,这很琐碎,因为ECMAScript 6引入了一个新的正以此为目的Number.isInteger()函数。然而,之前的ECMAScript 6,会更复杂一点,因为没有提供

类似的Number.isInteger()方法。

问题是,在ECMAScript规格说明中,整数只概念上存在:即,数字值总是存储为浮点值。

考虑到这一点,最简单又最干净的ECMAScript6之前的解决方法(同时也非常稳健地返

回false,即使一个非数字的值,如字符串或null,被传递给函数)如下:

function isInteger(x) { return (x^0) === x; }

下面的解决方法也是可行的,虽然不如上面那个方法优雅:

function isInteger(x) { return Math.round(x) === x; }

请注意Math.ceil()和Math.floor()在上面的实现中等同于Math.round()。

或:

function isInteger(x) { return (typeof x === 'number') && (x % 1 === 0);

相当普遍的一个不正确的解决方案是:

function isInteger(x) { return parseInt(x, 10) === x; }

虽然这个以parseInt函数为基础的方法在x取许多值时都能工作良好,但一旦x取值相当大的时候,就会无法正常工作。问题在于parseInt()在解析数字之前强制其第一个参数到字符串。

因此,一旦数目变得足够大,它的字符串就会表达为指数形式(例如,1e+21)。因此,parseInt()函数就会去解析1e+21,但当到达e字符串的时候,就会停止解析,因此只会返回值1。注意:

> String(1000000000000000000000)'1e+21'

> parseInt(1000000000000000000000, 10)1

> parseInt(1000000000000000000000, 10) === 1000000000000000000000false

10.下列代码行1-4如何排序,使之能够在执行代码时输出到控制台?为什么?

(function() {

console.log(1);

setTimeout(function(){console.log(2)}, 1000);

setTimeout(function(){console.log(3)}, 0);

console.log(4);

})();

序号如下:

1

4

3

2

让我们先来解释比较明显而易见的那部分:

1和4之所以放在前面,是因为它们是通过简单调用console.log()而没有任何延迟输出的

2之所以放在3的后面,是因为2是延迟了1000毫秒(即,1秒)之后输出的,而3是延迟了0毫秒之后输出的。

好的。但是,既然3是0毫秒延迟之后输出的,那么是否意味着它是立即输出的呢?如果是的话,

那么它是不是应该在4之前输出,既然4是在第二行输出的?

要回答这个问题,你需要正确理解JavaScript的事件和时间设置。

浏览器有一个事件循环,会检查事件队列和处理未完成的事件。例如,如果时间发生在后台(例

如,脚本的onload事件)时,浏览器正忙(例如,处理一个onclick),那么事件会添加到队列中。当onclick处理程序完成后,检查队列,然后处理该事件(例如,执行onload脚本)。

同样的,setTimeout()也会把其引用的函数的执行放到事件队列中,如果浏览器正忙的话。

当setTimeout()的第二个参数为0的时候,它的意思是“尽快”执行指定的函数。具体而言,函

数的执行会放置在事件队列的下一个计时器开始。但是请注意,这不是立即执行:函数不会被执

行除非下一个计时器开始。这就是为什么在上述的例子中,调用console.log(4)发生在调

用console.log(3)之前(因为调用console.log(3)是通过setTimeout被调用的,因此会稍微延迟)。

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.l og(isPalindrome("levels")); // logs 'false'console.log(isPal indrome("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".sp lit('');

arr2.push(arr3);console.log("array 1: length=" + arr1.length + " last=" + arr 1.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...

相关文档