容易出错的JavaScript题目集锦
1.typeof(null) 会得到什么?
object,在JavaScript中null被认为是一个对象。
2.下列代码将输出控制台的是什么?为什么?1
2
3
4
5
6(function(){
var a = b = 3;
})();
console.log("a defined? " + (typeof a !== 'undefined'));
console.log("b defined? " + (typeof b !== 'undefined'));
由于a和b都在函数的封闭范围内定义,并且由于它们所在的行以var关键字开头,
因此大多数JavaScript开发人员会希望typeof a和typeof b在上面的示例中都未定义。
但是,情况并非如此。这里的问题是大多数开发人员错误地理解语句var a = b = 3;以下简写为:1
2var b = 3;
var a = b;
但实际上,var a = b = 3;其实是速记:1
2b = 3;
var a = b;
因此(如果您不使用严格模式),代码片段的输出将为:1
2a defined? false
b defined? true
但是如何在封闭函数的范围之外定义b?那么,因为声明var a = b = 3;是语句b = 3的简写;并且var a = b;
b最终成为一个全局变量(因为它不在var关键字后面),因此它仍然在作用域内,即使在封闭函数之外。
注意,在严格模式下(即,使用strict),语句var a = b = 3;会产生一个ReferenceError的运行时错误:
b没有定义,从而避免了可能导致的任何头headfakes/bugs。 (这就是为什么你应该在你的代码中使用strict,一个重要的例子!)
3.下面的代码将输出到控制台的是什么?,为什么?1
2
3
4
5
6
7
8
9
10
11
12
13var 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();
在控制台将会输出如下结果1
2
3
4outer func: this.foo = bar
outer func: self.foo = bar
inner func: this.foo = undefined
inner func: self.foo = bar
在外面函数中self和this都指向myObject,因此都能访问到myObject中的foo属性。
在内部函数中,此时this的指向已经是window,而window中没有foo属性,所以为undefined。
4.考虑下面的两个函数。他们都会返回同样的值吗?为什么或者为什么不?1
2
3
4
5
6
7
8
9
10
11
12function foo1(){
return {
bar: "hello"
};
}
function foo2(){
return
{
bar: "hello"
};
}
令人惊讶的是,这两个函数不会返回相同的结果。而是:1
2
3
4console.log("foo1 returns:");
console.log(foo1());
console.log("foo2 returns:");
console.log(foo2());
1 | foo1 returns: |
这不仅令人惊讶,而且特别令人烦恼的是,foo2()返回未定义而没有引发任何错误。
原因与JavaScript中分号在技术上是可选的事实有关(尽管忽略它们通常是非常糟糕的形式)。因此,在foo2()中遇到包含return语句的行
(没有其他内容)时,会在return语句之后立即自动插入分号。
由于代码的其余部分是完全有效的,即使它没有被调用或做任何事情(它只是一个未使用的代码块,它定义了一个属性栏,它等于字符串“hello”),
所以不会抛出任何错误。
这种行为也被认为是遵循了在JavaScript中将一行开头大括号放在行尾的约定,而不是在新行的开头。如此处所示,这不仅仅是JavaScript中的一种风格偏好。
5.下面的代码输出什么?解释你的答案。1
2console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);
上面提供的示例是演示此问题的经典案例。令人惊讶的是,它会打印出来:1
20.30000000000000004
false
一个典型的解决方案是比较两个数字与特殊常数Number.EPSILON之间的绝对差值:1
2
3
4function areTheNumbersAlmostEqual(num1, num2) {
return Math.abs( num1 - num2 ) < Number.EPSILON;
}
console.log(areTheNumbersAlmostEqual(0.1 + 0.2, 0.3));
Math.abs()是取得数据的绝对值,Number.EPSILON 属性表示 1 和大于 1 的最小值(可表示为 Number)的差值。
6.编写一个简单的函数(少于160个字符),返回一个布尔值,指示字符串是否是回文。
如果str是回文,以下一行函数将返回true;否则,它返回false。1
2
3
4function isPalindrome(str) {
str = str.replace(/\W/g, '').toLowerCase();
return (str == str.split('').reverse().join(''));
}
1 | console.log(isPalindrome("level")); // 'true' |
7.考虑下面的代码片段1
2
3
4
5
6for (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) 当用户点击“按钮4”时,什么被记录到控制台?为什么?
(b) 提供一个或多个可按预期工作的替代实现。
答:
(a) 无论用户点击哪个按钮,数字5将始终记录到控制台。这是因为,在调用onclick方法(对于任何按钮)时,for循环已经完成,并且变量i已经具有值5.
(如果受访者知道足够的话就可以获得奖励点数关于执行上下文,变量对象,激活对象和内部“范围”属性如何影响闭包行为。)
(b) 使这项工作的关键是通过将它传递给新创建的函数对象来捕获每次通过for循环的i的值。以下是四种可能的方法来实现这一点:1
2
3
4
5
6
7
8
9
10for (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:1
2
3
4
5
6
7
8
9
10for (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);
}
最后,最简单的解决方案,如果你在ES6 / ES2015上下文中,就是使用let i而不是var i:1
2
3
4
5
6for (let 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);
}
8.下面的代码将输出到控制台,输出结果是什么?1
2
3
4
5
6console.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);
输出的结果为:1
2
3
4
5
6"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”转换为数值, “ - ”B“产生NaN,然后与字符串”2“串联产生”NaN2“。
例6:“A” - “B”+2输出:NaN说明:在前面的例子中,“A” - “B”产生NaN。但是任何运算符应用于NaN和其他数字操作数仍然会产生NaN。
9.以下代码的输出是什么:1
2
3for (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。
通过为每次迭代创建一个唯一的作用域,可以使用闭包来防止这个问题,并将该变量的每个唯一值存储在其作用域中,如下所示:1
2
3
4
5for (var i = 0; i < 5; i++) {
(function(i){
setTimeout(function() { console.log(i); }, i * 1000);
})(i)
}
在ES2015上下文中,您可以在原始代码中简单地使用let而不是var:1
2
3for (let i = 0; i < 5; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}
10.以下代码的输出是什么?解释你的答案。1
2
3
4
5
6
7
8var 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]都等价于[“[object Object]”],并且可以互换使用。因此,设置或引用[c]与设置或引用[b]完全相同。
11.以下代码将输出到控制台以及为什么1
2
3
4
5
6
7
8
9
10
11var hero = {
_name: 'John Doe',
getSecretIdentity: function (){
return this._name;
}
};
var stoleSecretIdentity = hero.getSecretIdentity;
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());
这段代码有什么问题,以及如何解决这个问题。
该代码将输出:1
2undefined
John Doe
第一个console.log打印未定义,因为我们从hero对象中提取方法,所以stoleSecretIdentity()在window上下文的执行环境中被调用,而window中没有定义_name属性,所以为underfined。
修复stoleSecretIdentity()函数的一种方法如下:1
2
3
4
5
6
7
8
9
10
11var hero = {
_name: 'John Doe',
getSecretIdentity: function (){
return this._name;
}
};
var stoleSecretIdentity = hero.getSecretIdentity.bind(hero);
console.log(stoleSecretIdentity());
console.log(hero.getSecretIdentity());
12.以下输出的是什么?1
2
3
4
5
6
7
8
9
10
11
12
13
14var length = 10;
function fn() {
console.log(this.length);
}
var obj = {
length: 5,
method: function(fn) {
fn();
arguments[0]();
}
};
obj.method(fn, 1);
输出:1
210
2
为什么不是10和5?
首先,由于fn作为函数方法的参数传递,函数fn的作用域(this)是窗口。 var length = 10;在窗口级别声明。它也可以作为window.length或length或this.length来访问(当这个===窗口时)。
方法绑定到Object obj,obj.method用参数fn和1调用。虽然方法只接受一个参数,但调用它时已经传递了两个参数;第一个是函数回调,其他只是一个数字。
当在内部方法中调用fn()时,该函数在全局级别作为参数传递,this.length将有权访问在Object obj中定义的var length = 10(全局声明)而不是length = 5。
现在,我们知道我们可以使用arguments []数组访问JavaScript函数中的任意数量的参数。
因此arguments0只不过是调用fn()。在fn里面,这个函数的作用域成为参数数组,并且记录参数[]的长度将返回2。
13.考虑下面的代码。输出是什么,为什么?1
2
3
4
5
6
7
8
9
10
11
12
13(function () {
try {
throw new Error();
} catch (x) {
var x = 1, y = 2;
console.log(x);
}
console.log(x);
console.log(y);
})();
1
undefined
2
var语句被挂起(没有它们的值初始化)到它所属的全局或函数作用域的顶部,即使它位于with或catch块内。但是,错误的标识符只在catch块内部可见。它相当于:1
2
3
4
5
6
7
8
9
10
11
12(function () {
var x, y; // outer and hoisted
try {
throw new Error();
} catch (x /* inner */) {
x = 1; // inner x, not the outer one
y = 2; // there is only one y, which is in the outer scope
console.log(x /* inner */);
}
console.log(x);
console.log(y);
})();
14.以下几行输出什么,为什么?1
2console.log(1 < 2 < 3);
console.log(3 > 2 > 1);
第一条语句返回true,如预期的那样。
第二个返回false是因为引擎如何针对<和>的操作符关联性工作。它比较从左到右,所以3> 2> 1 JavaScript翻译为true> 1. true具有值1,因此它比较1> 1,这是错误的。
15.如何在数组的开头添加元素?最后如何添加一个?1
2
3
4var myArray = ['a', 'b', 'c', 'd'];
myArray.push('end');
myArray.unshift('start');
console.log(myArray); // ["start", "a", "b", "c", "d", "end"]
使用ES6,可以使用扩展运算符:1
2myArray = ['start', ...myArray];
myArray = [...myArray, 'end'];
或者,简而言之:1
myArray = ['start', ...myArray, 'end'];
15.代码返回后会怎么样?1
console.log(typeof typeof 1);
打印结果:string
typeof 1将返回“number”,typeof“number”将返回字符串。
16.以下代码输出什么?为什么?1
2
3
4
5
6
7
8
9
10
11var b = 1;
function outer(){
var b = 2
function inner(){
b++;
var b = 3;
console.log(b)
}
inner();
}
outer();
输出到控制台将是“3”。
在这个例子中有三个闭包,每个都有它自己的var b声明。当调用变量时,将按照从本地到全局的顺序检查闭包,直到找到实例。由于内部闭包有自己的b变量,这就是输出。
此外,由于提升内部的代码将被解释如下:1
2
3
4
5
6function inner () {
var b; // b is undefined
b++; // b is NaN
b = 3; // b is 3
console.log(b); // output "3"
}
以上来自于xiaosheng222
17.以下代码输出什么?1
2
3
4
5
6
7var animal = function(){};
var dog = function(){};
animal.price = 2000;
dog.prototype = animal;
var tiddy = new dog();
alert(dog.price);/*underfined*/
alert(tiddy.price);/*2000*/
因为原型链是依赖于proto,而不是prototype。dog是函数对象,本身没有price属性,此时dog的proto属性指向的是其构造函数的原型。 dog的构造函数就是Function,因为var dog = function(){};语句实际上是var dog = new Function();,所以,dog.proto === Function.prototype;⽽而Function.prototype并没有price属性,如果加一句:Function.prototype.price = 123;那么第一个打印就是123;
如此依赖,tidy是一个普通对象,由dog函数构造而来,因此tidyproto == dog.prototype == animal;所以当tidy上找不到price属性时,会从proto寻找原型上的方法,找到animal对象, animal对象有price属性,则返回。
18.以下题目弹出的值是多少?1
2
3
4
5
6var a=b=0;
var c=d={x:1};
b = 1;
d.x = 1;
alert("a的值为:"+a+" b的值为:"+b);
alert("c.x的值为:"+c.x+" d.x的值为:"+d.x);
弹出的结果为:1
2a的值为:0 b的值为:1
c.x的值为:1 d.x的值为:1
此题考查的是基本类型和引用类型 a 声明变量时不同的内存分配。
1) 原始值:存储在栈(stack)中的简单数据段,也就是说,它们的值直接存储在变量访问的位置。这是因为这些原始类型占据的空间是固定的,所以可将他们存储在较小的内存区域 – 栈中。这样存储便于迅速查寻变量的值。
2) 引用值:存储在堆(heap)中的对象,也就是说,存储在变量处的值是⼀一个指针(point),指向存储对象的内存地址。
19.以下代码执行结果是多少?1
2
3
4
5
6
7if(1 == "1"){
var x = 1;
}else {
var z = 1;
}
console.log(x);
console.log(z);
由于变量提升的缘故,相当于1
2
3
4
5
6
7
8
9var x;
var z;
if(1 == "1"){
x = 1;
}else {
z = 1;
}
console.log(x);
console.log(z);
所以输出结果x为1,z为undefined
20.如何使数组去重?1
2
3
4
5
6
7
8var arr = [1,2,3,4,3,2,2,1];
var newArr = [];
for(var i=0; i<arr.length;i++){
if(newArr.indexOf(arr[i]) == -1){
newArr.push(arr[i])
}
}
console.log(newArr)
21.如何返回一个字符串中出现次数多的字母1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25var str = "absbdsbdudbsu";
function count(str){
if(str.length == 1){
return str;
}else {
var wordNum = {};
for(var i=0;i<str.length;i++){
if(!wordNum[str.charAt(i)]){
wordNum[str.charAt(i)] = 1;
}else {
wordNum[str.charAt(i)]++;
}
}
var wordMax = '',
max = 0;
for(var i in wordNum){
if(wordNum[i]>max){
wordMax = i;
max = wordNum[i]
}
}
return wordMax
}
}
console.log(count(str))