javascript经典题⽬25道
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(bar) !== “[object Array]”));
或者,如果你使⽤jQuery的话:
console.log((bar !== null) && (typeof bar === “object”) && (! $.isArray(bar)));
2.下⾯的代码将输出什么到控制台,为什么?
(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 在上⾯的例⼦中都是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允许你使⽤Conflict(),来禁⽤ 利⽤这种闭包技术,如下所⽰:
(function( */ } )(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());
将产⽣:
foo1 returns:Object {bar: “hello”}foo2 returns:undefined
这不仅是令⼈惊讶,⽽且特别让⼈困惑的是, foo2()返回undefined却没有任何错误抛出。
原因与这样⼀个事实有关,即分号在JavaScript中是⼀个可选项(尽管省略它们通常是⾮常糟糕的形式)。其结果就是,当碰到 foo2()中包含 return语句的代码⾏(代码⾏上没有其他任何代码),分号会⽴即⾃动插⼊到返回语句之后。
也不会抛出错误,因为代码的其余部分是完全有效的,即使它没有得到调⽤或做任何事情(相当于它就是是⼀个未使⽤的代码块,定义了等同于字符串 “hello”的属性 bar)。
这种⾏为也⽀持放置左括号于JavaScript代码⾏的末尾,⽽不是新代码⾏开头的约定。正如这⾥所⽰,这不仅仅只是JavaScript中的⼀个风格偏好。
1. 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) { und(x) === x; }
请注意 il() 和 Math.floor() 在上⾯的实现中等同于 und()。
或:
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 = place(/\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 = ateElement(‘button’);
btn.ateTextNode(‘Button ’ + i));
btn.addEventListener(‘click’, function(){ console.log(i); }); document.body.appendChild(btn);javascript全局数组
}
(a)当⽤户点击“Button 4”的时候会输出什么到控制台,为什么?(b)提供⼀个或多个备⽤的可按预期⼯作的实现⽅案。
(a)⽆论⽤户点击什么按钮,数字5将总会输出到控制台。这是因为,当 onclick ⽅法被调⽤(对于任何按钮)的时候, for 循环已经结束,变量 i 已经获得了5的值。(⾯试者如果能够谈⼀谈有关如何执⾏上下⽂,可变对象,激活对象和内部“范围”属性贡有助于闭包⾏为,则可以加分)。
(b)要让代码⼯作的关键是,通过传递到⼀个新创建的函数对象,在每次传递通过 for 循环时,捕捉到 i 值。下⾯是三种可能实现的⽅法:
for (var i = 0; i < 5; i++) { var btn = ateElement(‘button’);
btn.ateTextNode(‘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 = ateElement(‘button’);
btn.ateTextNode(‘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 = ateElement(‘button’);
btn.ateTextNode(‘Button ’ + i));
btn.addEventListener(‘click’, function() { console.log(i); }); document.body.appendChild(btn);
});
14.下⾯的代码将输出什么到控制台,为什么?
var arr1 = “john”.split(”);var arr2 = verse();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 引⽤的是同⼀个对象。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论