经典js面试题解答汇总
1.JS重复输出一个给定的字符串
方法一:while循环
function repeatStringNumTimes(string, times) {
var repeatedString = "";
while (times > 0) {
repeatedString += string;
times--;
}
return repeatedString;
}
repeatStringNumTimes("abc", 3);
方法二:for循环
function repeatStringNumTimes(string, times) {
var repeatedString = "";
for(var i = 0; i < times ;i++) {
repeatedString += string;
}
return repeatedString;
}
repeatStringNumTimes("abc", 3)
方法三:递归
function repeatStringNumTimes(string, times) {
if(times < 0)
return "";
if(times === 1)
return string;
else
return string + repeatStringNumTimes(string, times - 1);
}
repeatStringNumTimes("abc", 3);
方法四:使用ES6 repeat()
方法
function repeatStringNumTimes(string, times) {
return times > 0 ? string.repeat(times) : "";
}
repeatStringNumTimes("abc", 3);
repeat() 方法构造并返回一个新字符串,该字符串包含被连接在一起的指定数量的字符串的副本。 这个方法有一个参数 count
表示重复次数,介于0和正无穷大之间的整数 : [0, +∞)
。表示在新构造的字符串中重复了多少遍原字符串。重复次数不能为负数。重复次数必须小于 infinity,且长度不会大于最长的字符串
2.函数声明相关
第一题:无返回值
var x = 1,
y = 0,
z = 0;
function add(n) {
n = n + 1;
}
console.log(y);//0
y = add(x);
z = x + y;
console.log("y1:" + y);//undefined
console.log("z1:" + z);//NaN
function add(n) {
n = n + 3;
}
y = add(x);
z = x + y;
console.log("y2:" + y);//undefined
console.log("z2:" + z);//NaN
add函数没有返回值,而没有明确返回值的,全部返回undefined,所以y为undefined,z是一个数和undefined相加,所以输出为NaN
这道题还有要注意的地方!!
js存在变量和函数的声明提权,所以add函数存在同名覆盖的情况
第二题:有返回值和提升问题
var x=1,
y=0,
z=0;
function add(n){
return n=n+1;
}
y=add(x);
z=x+y;
console.log("y1:"+y);//4
console.log("z1:"+z);//5
function add(n){
return n=n+3;
}
y=add(x);
z=x+y;
console.log("y2:"+y);//4
console.log("z2:"+z);//5
这道题与上面类似,如果不考虑变量和函数提升的话,很可能会做错。
看似调用的是不同的函数,实际上覆盖后只有第二个add函数存在,所以最后都输出为4,5
第三题:有返回值和不提升问题
var x = 1,
y = 0,
z = 0;
var add = function (n) {
return n = n + 1;
}
y = add(x);
z = x + y;
console.log("y1:" + y);//2
console.log("z1:" + z);//3
var add = function (n) {
return n = n + 3;
}
y = add(x);
z = x + y;
console.log("y2:" + y);//4
console.log("z2:" + z);//5
这三道题类似,但考点不同,上面说了变量和函数存在提升的问题,本题为什么又没有提升呢?
- 仔细观察可以发现第二题和第三题定义函数的方式不同,题二用的函数声明,题三用的函数表达式,而只有函数声明才存在函数提升!
- 函数表达式可以理解为给一个变量赋值,所以var add变量仍然要提升,但赋值的匿名函数却不提升。按照顺序执行,给add赋值的函数不同,运行结果不同
第四题:混合
function x() {
alert(2)
};
x(); //output 3
var x = function () {
alert(0)
};
x(); //output 0
var x = function () {
alert(1)
};
x(); //output 1
function x() {
alert(3)
};
x(); //output 1
注意:函数提升在变量提升之前,这道题提升后的过程如下:
function x() {
alert(3)
};
var x;
x(); //output 3
x = function () {
alert(0)
};
x(); //output 0
x = function () {
alert(1)
};
x(); //output 1
x(); //output 1
第五题
console.log(a);
console.log(a());
var a = 3;
function a() {
console.log(10)
}
console.log(a)
a = 6;
console.log(a());
函数提升不会被变量声明覆盖,但是会被变量赋值之后覆盖
本题相当于如下过程,第一次输出a是个函数相当于var
a = function(){};第二次调用a,a函数本身会输出10,由于a函数没有返回值,前面已经讲过,函数没有返回值的返回undefined;第三次输出a,由于a已经被变量赋值操作所覆盖,所以输出3;第四次由于a=6覆盖,a不是个函数,所以再调用会报错。
function a() {
console.log(10)
}
var a
console.log(a);
console.log(a());
a = 3;
console.log(a)
a = 6;
console.log(a());
输出结果:
3.作用域范围
第一题:全局变量
(function () {
var a = b = 5;
})();
console.log(b);//5
在js函数中,没有声明直接使用的变量默认为全局变量,所以a为局部变量,b为全局变量。要想声明多个变量时,用逗号隔开:var a=5,b=5。
当使用严格模式时,是不允许使用未定义的变量得,会报错
第二题:
var a = 6;
setTimeout(function () {
alert(a);//66
a = 666;
}, 1000);
a = 66;
闭包问题前面已经讲过了,当顺序执行完代码后a=66,然后任务队列里的匿名函数开始执行,就近原则向上查找a的值,所以为66
4.创建 “原生(native)” 方法
其实就是通过原型扩展数组、字符串等的内置方法
第一题:为数组增加sum求和
Array.prototype.sum = function() {
var sum = 0;
for (var i = 0; i < this.length; i++) {
sum += this[i];
}
return sum;
};
//此时数组对象中已经存在sum()方法了 可以用数组.sum()进行数据的求和
第二题:为String增加重复字符串的方法
String.prototype.repeatify = String.prototype.repeatify || function(times) {
var str = '';
for (var i = 0; i < times; i++) {
str += this;
}
return str;
};
为了避免重写可能已经定义了的方法,可以通过在定义自己的方法之前,检测方法是否已经存在,这一点对为旧浏览器实现向后兼容的函数时十分有用。
5.this问题
第一题:
var fullname = 'John Doe';
var obj = {
fullname: 'Colin Ihrig',
prop: {
fullname: 'Aurelio De Rosa',
getFullname: function() {
return this.fullname;
}
}
};
console.log(obj.prop.getFullname());//Aurelio De Rosa
var test = obj.prop.getFullname;
console.log(test());//John Doe
这道题目并不好理解,参考原博主所说:
JavaScript中关键字this所引用的是函数上下文,取决于函数是如何调用的,而不是怎么被定义的。
在第一个console.log(),getFullname()是作为obj.prop对象的函数被调用。因此,当前的上下文指代后者,并且函数返回这个对象的fullname属性。
当getFullname()被赋值给test变量时,当前的上下文是全局对象window,这是因为test被隐式地作为全局对象window的方法。基于这一点,函数返回window的fullname,在本例中即为第一行代码设置的。
第二题:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
return function(){
return this.name;
};
}
};
console.log(object.getNameFunc()());//The Window
最后一句改写成下面的方式比较好理解
var retFn = object.getNameFunc()
console.log(retFn());
- 我们知道在JavaScript中,我们声明的JavaScript全局对象、全局函数以及全局变量均自动成为window对象的成员。
- 本题通过object.getNameFunc()调用对象的方法,返回了一个函数,通过refFn接收
- retFn是全局变量,接收函数后自动成为了window对象的一个方法
- 所以当调用retFn时,调用的对象时window,因此输出也是window对象下的name属性:The Window
第三题:
var name = "The Window";
var object = {
name : "My Object",
getNameFunc : function(){
var that = this;
return function(){
return that.name;
};
}
};
alert(object.getNameFunc()());
object.getNameFunc()函数中将this(指向的是对象object)赋值给了that,最后返回函数后输出的就是object里面的name,所以为My Object
详细参考链接:添加链接描述
6.call()和apply()
修复前一个问题,让最后一个console.log() 打印输出Aurelio De Rosa.
console.log(test.call(obj.prop));//Aurelio De Rosa
console.log(test.apply(obj.prop));//Aurelio De Rosa
扩展:bind()方法虽然不能调用函数,但它也能改变函数内部的this指向,返回的是原函数改变this之后产生的新函数。如果只是想改变 this 指向,并且不想调用这个函数的时候,可以使用bind方法
call、apply、bind三者的异同
-
共同点 : 都可以改变this指向
-
不同点:
- call 和 apply 会调用函数, 并且改变函数内部this指向.
- call 和 apply传递的参数不一样,call传递参数使用逗号隔开,apply使用数组传递
- bind 不会调用函数, 可以改变函数内部this指向.
-
应用场景
- call 经常做继承.
- apply经常跟数组有关系. 比如借助于数学对象实现数组最大值最小值
- bind 不调用函数,但是还想改变this指向. 比如改变定时器内部的this指向
7.数据类型问题
console.log(typeof null);//object
console.log(typeof {});//object
console.log(typeof []);//object
console.log(typeof undefined);//undefined
8.事件循环
function printing() {
console.log(1);
setTimeout(function() { console.log(2); }, 1000);
setTimeout(function() { console.log(3); }, 0);
console.log(4);
}
printing();
答案:1 4 3 2
原博主回答:
想知道为什么输出顺序是这样的,你需要弄了解setTimeout()做了什么,以及浏览器的事件循环原理。浏览器有一个事件循环用于检查事件队列,处理延迟的事件。UI事件(例如,点击,滚动等),Ajax回调,以及提供给setTimeout()和setInterval()的回调都会依次被事件循环处理。因此,当调用setTimeout()函数时,即使延迟的时间被设置为0,提供的回调也会被排队。回调会呆在队列中,直到指定的时间用完后,引擎开始执行动作(如果它在当前不执行其他的动作)。因此,即使setTimeout()回调被延迟0毫秒,它仍然会被排队,并且直到函数中其他非延迟的语句被执行完了之后,才会执行。