前端面试题集
导读:
如下是收集整理的 JS 相关问题,若是文中有什么错误,欢迎大家指正批评,愿与大家在交流之*同进步。愈激烈,愈深刻。
1.使用 typeof bar === "object" 来确定 obj 是否是对象的缺陷是什么?如何避免?
在类型检测中,typeof 适合原始数据类型和 function 的检测。遇到 null 时会失效,会和数组的检测一样仅返回“Object”。所以在检测 obj 是否时对象时,也应该检测 obj 的值是否为 null。
var obj = { };
console.log((obj !== null) && (typeof obj === "object")); // logs ture
2.下面的代码控制台将输出什么,为什么?
(function(){
var a = b = 3;
})();
console.log(b);//logs 3
console.log(a);//报错
常常人们会认为 a、b 是在函数作用域中申明的局部变量,在全局作用域中是访问不到的,所以会认为上面两者都会输出 undefined。然而在非严格模式下,上面申明的变量 b 是全局变量。
3.下面的代码控制台将输出什么,为什么?
var obj = {
text:"something",
test:function(){
var obj2 = this;
console.log(this.text);
console.log(obj2.text);
(function(){
console.log(this.text);
console.log(obj2.text);
})()
}
};
obj.test()
//logs:something/something/undefined/something
在函数上下文中 this 的值决定于函数的调用方式,函数test 定义了 obj2 的值等于当前 this 的值,且该函数作为 obj 的方法被调用,所以 this.text 和 obj2.text 的值为“something”。而其中的自执行函数中的 obj2 的值在作用域链中可以找到值为“something”,因为该函数是直接调用的,所以 this 的值为 window。如下所示:
var text = "other something";
var obj = {
text:"something",
test:function(){
var obj2 = this;
console.log(this.text);
console.log(obj2.text);
(function(){
console.log(this.text);
console.log(obj2.text);
})()
}
};
obj.test()
//logs:something/something/other something/something
4.为什么使用严格模式?
"严格模式"体现了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操作符(用于从对象中删除属性)不能用在对象不可配置的属性上。当试图删除一个不可配置的属性时,非严格代码将默默地失败,而严格模式将在这样的情况下抛出异常。
5.封装JavaScript源文件的全部内容到一个函数块有什么意义及理由?
这是一个越来越普遍的做法,被许多流行的JavaScript库(jQuery,Node.js等)采用。这种技术创建了一个围绕文件全部内容的闭包,也许是最重要的是,创建了一个私有的命名空间,从而有助于避免不同JavaScript模块和库之间潜在的名称冲突。
这种技术的另一个特点是,允许一个易于引用的(假设更短的)别名用于全局变量。这通常用于,例如,jQuery插件中。jQuery允许你使用jQuery.noConflict(),来禁用 $ 引用到jQuery命名空间。在完成这项工作之后,你的代码仍然可以使用$ 利用这种闭包技术,如下所示:
(function($) { /* jQuery plugin code referencing $ */ } )(jQuery);
6.思考以下两个函数。它们会返回相同的东西吗? 为什么相同或为什么不相同?
function test1()
{
return {
text: "something"
};
}
function test2()
{
return
{
text: "something"
};
}
test1()
//logs {text: "something"}
test2()
//logs undefined
因为分号在 JavaScript 中是一个可选项(尽管省略它们通常是非常糟糕的形式)。其结果就是,当碰到 test2()中包含 return语句的代码行(代码行上没有其他任何代码),分号会立即自动插入到返回语句之后。也不会抛出错误,因为代码的其余部分是完全有效的,即使它没有得到调用或做任何事情(相当于它就是是一个未使用的代码块,定义了等同于字符串 "something"的属性 text)。这种行为也支持放置左括号于 JavaScript 代码行的末尾,而不是新代码行开头的约定。
7.NaN 是什么?它的类型是什么?你如何可靠地测试一个值是否等于 NaN ?
NaN 属性是代表非数字值的特殊值。这个特殊的值是因为运算不能执行而导致的,不能执行的原因要么是因为其中的运算对象之一非数字,要么是因为运算的结果非数字(例如,除数为零)。
NaN 虽然不是一个“数字”,但是它的类型是 Number。
console.log(typeof NaN === "number"); // logs "true"
NaN 与所有值都不相等,包括它自己。所以需要使用 isNaN() 来判断一个值是否是数字。
8.下面的代码控制台将输出什么,为什么?
console.log(0.1 + 0.2);
console.log(0.1 + 0.2 == 0.3);//logs 0.30000000000000004/false
JavaScript 采用 IEEE754 标准定义的 64 位浮点格式表示数字,并使用IEEE 754标准《二进制浮点数算法》。由于存在二进制和十进制的转换问题,具体的位数会发生变化,因此在 JavaScript 中小数做四则运算时,精度会丢失。
9.写一个 isInteger函数,用于判断 x 是否是整数。
ECMAScript 6引入了一个新的用来判断给定的参数是否为整数的 Number.isInteger() 函数。注意 NaN 和正负 Infinity 不是整数。而 ECMAScript6 之前的解决方法如下:
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);
//以parseInt(string, radix)为基础的方法在string取许多值时都能工作良好,但string取值相当大的时候,就会无法正常工作。
10.写一个 test方法,在使用下面任一语法调用时,都可以正常工作。
console.log(test(2,3)); // logs 5
console.log(test(2)(3)); // logs 5
首先在 JavaScript 中,函数可以提供到 arguments 对象的访问,arguments 对象提供对传递到函数的实际参数的访问。所以可使用 length 属性来确定传递给函数的参数数量。其次 JavaScript 不要求参数的数目匹配函数定义中的参数数量。多则被忽略。而缺少的则在引用时会给一个 undefined值。因此对应的两种方法分别为:
//方法一:
function test(x) {
if (arguments.length == 2) {
return arguments[0] + arguments[1];
} else {
return function(y) { return x + y; };
}
}
//方法二:
function test(x, y) {
if (y !== undefined) {
return x + y;
} else {
return function(y) { return x + y; };
}
}
11.写一个简单的函数,要求返回一个布尔值指明字符串是否为回文结构。例:
function isPalindrome(str) {
str = str.replace(/W/g, '').toLowerCase();
return (str == str.split('').reverse().join(''));
}
或者用 for循环 来处理:
function isPalindrome(str) {
var i = 0,
newStr = str.replace(/[\W_]/g).toLowerCase();
for ( i ; i< newStr.length ;i++) {
return ( newStr[i] === newStr[newStr.length-1-i])
}
}
12.创建一个给定页面上的一个DOM元素,就会去访问元素本身及其所有子元素(不只是它的直接子元素)的函数。对于每个被访问的元素,函数应该传递元素到提供的回调函数。例:
function test(element,callback) {
callback(element);
var list = element.children;
for (var i = 0; i < list.length; i++) {
test(list[i],callback); // recursive call
}
}
13.下面的代码控制台将输出什么,为什么?
var obj = {
name: "Anani",
getName: function (){
return this.name;
}
};
var stoleName = obj.getName;
console.log(stoleName());//logs undefined
console.log(obj.getName());//logs Anani
当函数以对象里的方法的方式调用函数时,它们的 this 是调用该函数的对象。所以后者的 this 指向对象 obj,在其中找到了变量 name 的值并输出。而前者 getName 函数被赋值到了另一个变量中,并没有作为 obj 的一个属性被调用,那么 this 的值就是 window,而变量 name 在全局环境中没有定义,所以输出 undefined。
14.下列代码行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 没有任何延迟输出的,所以放在前面,2 之所以放在 3 的后面,是因为输出 2 的延迟更久。
15.下面的代码控制台将输出什么,为什么?
(function(x) {
return (function(y) {
console.log(x);
})(2)
})(1);//logs 1
在Javascript语言中,内部函数可以直接读取外部函数的变量。所以 x 虽然在函数内部中未定义,却能在外部函数中取得变量 x 的值。
16.下面的代码控制台将输出什么,为什么?
console.log(
(function test(x){
return ((x > 1) ? x * test(x-1) : x)
})(10)
);//logs 3628800
函数 test 递归地调用本身,这也是递归函数的经典用法,用来实现阶乘。当 x=1 时,函数结束并输出 10 的阶乘即:3628800。
17.JavaScript中的“闭包”是什么?并举例?
闭包是指有权访问另一个函数作用域(当某个函数被调用时,会创建一个执行环境及相应的作用域链。)中的变量的函数。例:
function demo(){
var name = "Anani";
return function displayName(){
console.log(name);
}
}
var demoTest = demo();
demoTest();//闭包
更多关于闭包可以阅读这篇文章 JavaScript 闭包。
18.下面的代码控制台将输出什么,为什么?
var a={},
b={text:'b'},
c={text:'c'};
a[b]="something";
a[c]="other something";
console.log(a[b]);//logs other something
当设置对象属性时,JavaScript 会字符串化参数值。由于 b 和 c 都是对象,因此它们都将被转换为"[object Object]"。所以 a[b] 和 a[c] 均相当于 a["[object Object]"] ,并可以互换使用。于是设置或引用 a[c] 或 a[b] 完全相同。
19.下面的代码控制台将输出什么,为什么?
console.log(false == '0')//logs true
console.log(false === '0')//logs false
在 JavaScript 中使用运算符”==“时,两边值类型不同的时候,会先进行类型转换,再比较。而运算符”===“表示严格等于,只当类型和值都相等时返回”true“。
20.下面的代码控制台将输出什么,为什么?
console.log("0 || 1 = "+(0 || 1));
console.log("1 || 2 = "+(1 || 2));
console.log("0 && 1 = "+(0 && 1));
console.log("1 && 2 = "+(1 && 2));
//logs 0 || 1 = 1
//logs 1 || 2 = 1
//logs 0 && 1 = 0
//logs 1 && 2 = 2
如果布尔对象无初始值或者其值为:
- 0
- -0
- null
- ""
- false
- undefined
- NaN
那么对象的值为 false。否则,其值为 true(即使当变量值为字符串 "false" 时)。“||”运算如果为第一个为:true,则取第一个的值,如果第一个为false,则取第二个的值,也就是常说的”短路“原理。同样“&&” 运算如果为第一个为:true,则取第二个的值,如果第一个为false,则取第一个的值。
21.下面的代码控制台将输出什么,为什么?
for (var i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}//logs 3,3,3
因为 settimeout 是异步执行,会在指定的时间后往任务队列里面添加一个任务,只有主线上的任务全部执行完,才会执行任务队列里的任务,而当主线执行完成后,循环中的值已经变成了 3,所以几个函数取得的 i 的值皆为 3。
如果要得到预期的输出值:0,1 和 2,其中一个解决办法就是为每个函数创建一个额外的封闭环境来保存每一次循环对应产生的 i 的值:
for (var i = 0; i < 3; i++) {
(function(x) {
setTimeout(function() { console.log(x); }, x * 1000 );
})(i);
}//logs 0,1,2
另外,一个非常简单的办法就是使用 let 来代替 var,因为 let 声明的是块级作用域,因此每次 for-loop 的迭代都会创建一个新的标识符绑定。例:
for (let i = 0; i < 3; i++) {
setTimeout(function() { console.log(i); }, i * 1000 );
}//logs 0,1,2
22.根据下面的代码片段,回答相应的问题。
for (var i = 0; i < 3; i++) {
var btn = document.createElement('button');
btn.appendChild(document.createTextNode('Button ' + i));
btn.addEventListener('click', function(){ console.log(i); });
document.body.appendChild(btn);
}
- 当用户点击“Button 2”的时候会输出什么到控制台,为什么?
答:无论用户点击什么按钮,数字 3 将总会输出到控制台。这是因为,当 onclick 方法被调用(对于任何按钮)的时候, for 循环已经结束,变量 i 已经获得了 3 的值。 - 提供一个或多个备用的可按预期工作的解决方案。
解答的思路大致同 21题。因为 Javascript 在 ES6 之前没有块级作用域,匿名函数中访问的 i 是全局作用域中的 i,其值在循环后为 3。所以要得到预期的结果需要创建一个新的作用域用于保存每一次循环后对应产生的 i 的值。例:
//方法1 创建一个新的作用域:
for (var i = 0; i < 3; 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);
}
//方法2 封装全部调用到在新匿名函数中的 btn.addEventListener:
for (var i = 0; i < 3; 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);
}
//方法3 用数组对象的本地 forEach 方法来替代 for 循环:
['x', 'y', 'z'].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);
});
23.下面的代码控制台将输出什么,为什么?
var arr1 = "john".split('');
var arr2 = arr1.reverse();
var arr3 = "jones".split('');
arr2.push(arr3);
console.log("array 1.length:" + arr1.length + " result:" + arr1.slice(-1));
//logs array 1.length:5 result:j,o,n,e,s
console.log("array 2.length:" + arr2.length + " result:" + arr2.slice(-1));
//logs array 2.length:5 result:j,o,n,e,s
console.log(arr1===arr2);//logs true
- 调用数组对象的 reverse() 方法反转数组的元素顺序。
- reverse() 方法返回一个到数组本身的引用。在此,arr2 仅仅是一个到 arr1的引用。因为 arr1 和 arr2 引用的是同一个对象。所以对其中一个做什么事情都会影响彼此。
- 传递数组到另一个数组的 push() 方法会让整个数组作为单个元素映射到数组的末端。
24.下面的代码控制台将输出什么,为什么?
console.log(1 + "2" + "2");//logs 122
console.log(1 + +"2" + "2");//logs 32
console.log(1 + -"1" + "2");//logs 02
console.log(+"1" + "1" + "2");//logs 112
console.log( "a" - "b" + "2");//logs NaN2
console.log( "a" - "b" + 2);//logs NaN
JavaScript 是一种数据类型是非常弱的弱类型语言。在使用算术运算符时,它可对值进行自动类型转换,以适应正在执行的操作。字符串和数字相加结果是字符串,而-, *, /,和%等算术运算符都会把操作数转换成数字。在第三行和第四行的代码中,都含有一元运算符(位于其操作数前面,计算其操作数的数值,如果操作数不是一个数值,会尝试将其转换成一个数值)。而且一元运算符的优先级高于加减运算符,所以对于第三行代码,要执行的第一个运算是 +"2"(第一个 "2" 前面的额外 + 被视为一元运算符)。因此,JavaScript将 "2" 的类型转换为数字,然后应用一元 + 号(即,将其视为一个正数)。其结果是,接下来的运算就是 1 + 2 ,这当然是 3。然后我们需要在一个数字和一个字符串之间进行运算(即, 3 和 "2"),同样的,JavaScript会将数值类型转换为字符串,并执行字符串的连接,产生 "32"。第四行代码同理。
25.下面的递归代码在数组列表偏大的情况下会导致堆栈溢出。在保留递归模式的基础上,怎么解决这个问题?
var list = readHugeArray();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
nextListItem();
}
};
//避免方法:
var list = readHugeArray();
var nextListItem = function() {
var item = list.pop();
if (item) {
// process the list item...
setTimeout( nextListItem, 0);
}
};
首先要了解导致堆栈溢出的原因,函数调用的参数是通过栈空间来传递的,在调用过程中会占用线程的栈资源。而递归调用,只有走到最后的结束点后函数才能依次退出,而未到达最后的结束点之前,占用的栈空间一直没有释放,如果递归调用次数过多,就可能导致占用的栈资源超过线程的最大值,从而导致栈溢出,导致程序的异常退出。
提供的解决方法使得堆栈溢出之所以会被消除,是因为事件循环操纵了递归,而不是调用堆栈。当 nextListItem 运行时,如果 item不为空,timeout函数(nextListItem)就会被推到事件队列,该函数退出,因此就清空调用堆栈。当事件队列运行其timeout事件,且进行到下一个 item 时,定时器被设置为再次调用 nextListItem。因此,该方法从头到尾都没有直接的递归调用,所以无论迭代次数的多少,调用堆栈保持清空的状态。
上一篇: js+css模仿打字效果
下一篇: php 伪协议