这个题集是关于JS一些有点深度,且容易让人犯错的题目,每日更新一道,相信你们也能从中学到知识。
1.如下代码的输出结果:
console.log(1 + "2" + "2");
console.log(1 + +"2" + "2");
console.log("A" - "B" + "2");
console.log("A" - "B" + 2);
知识点:隐式强制类型转换。“+”可以进行强制类型转换,而“-”号没有。
2.下面关于块内声明函数的做法哪些是正确的?
A: if(x){
function foo(){
// ...
}
}
B: if(x){
var foo=function(){
// ..
}
}
C: if(x){
foo=function(){
//..
}
}
D: ECMAScript明确规范了块内函数,javascript实现了这个规范。
答案:B
解:一般我们尽量不要在if语句内声明函数,当必须声明函数时使用函数表达式,不要用函数声明方式。不用函数声明方式是为了防止函数声明提升,导致if条件没有起作用。
3.如何判断一个js对象是否是Array,arr是Array实例,请用最准确的方法判断:
let arr = ["小李"];
A: typeof arr
B: arr instanceof Array
C: arr.toString === "[object Array]"
D: Object.prototype.toString.call(arr) === "[object Array]"
console.log(typeof arr);
console.log(arr instanceof Array);
console.log(arr.toString === "[object Array]");
console.log(Object.prototype.toString.call(arr) === "[object Array]");
答案:D
在常规数组中,B、D都可以判别数组,但D是最准确的。
分析:
4.以下立即执行函数的执行结果:
(function (foo) {
console.log(foo.bar);
console.log(typeof foo.bar);
})({
foo: {
bar: 1
}
});
这个答案有可能会有人认为是this指针造成的,但仔细看传入的参数就会得出正确结果。
传入后的参数,实际为
参数:foo={
foo:{
bar:1
}
};
所以正确调用是:
console.log(foo.foo.bar);
console.log(typeof foo.foo.bar);
5.下面代码中a在什么情况下会打印1:
var a=?;
if(a==1&&a==2&&a==3){
console.log(1);
}
答案:
var a = {
i: 1,
toString: function () {
return a.i++;
},
};
if (a == 1 && a == 2 && a == 3) {
console.log(1);
}
解析:
因为这里使用的是“= =”,会进行隐式类型转换;而不是 “ = = =”完全相等,所以这里重写toString()就可以了。隐式转换会默认调用toString()获取返回值,所以在判断a == 1 && a == 2 && a == 3时,就会三次调用toString()函数,所以依次返回1、2、3使得条件成立,打印1 。
6.以下代码会输出什么:
var a = 10;
(function () {
console.log(a);
a = 5;
console.log(window.a);
var a = 20;
console.log(a);
});
答案:
解析:在立即执行函数中,var a=20;语句定义了一个局部变量a,由于js的变量提升机制,局部变量a的声明会提升至立即执行函数体内的最上方,即第一句console.log(a)的上方。
注意:变量提升并不包括赋值,所以最先打印undefined。
7.下面两处打印输出什么:
var user = {
count: 1,
getCount: function () {
return this.count;
},
};
console.log(user.getCount());
var func = user.getCount;
console.log(func());
解析:这是this指针的问题。func是在全局window中被执行,由于全局中并没有count属性,所以访问不到this.count。如何确保user对象的属性总是能访问到,正确的方法使用时Function.prototype.bind
8.请输出以下代码的执行结果:
const promise = new Promise((resolve, reject) => {
console.log(1);
resolve();
console.log(2);
});
promise.then(() => {
console.log(3);
});
console.log(4);
解析:Promise构造函数是同步进行的,promise.then中的函数是异步执行的。
9.为什么下面这个代码会爆栈,而使用setTimeout来实现就不会爆栈?
function foo(){
foo();
}
解析:这样会爆栈是因为js的栈模型,由于采用栈调用函数,只有一个函数运行完成才会出栈,而上面的递归由于一直调用foo,所以foo不断进栈,且没有一个foo是执行完成出栈的,导致爆栈。
而当:
function foo(){
setTimeout(()=>{
foo();
},10)
}
由于setTimeout是异步函数,所以当执行完setTimeout后,foo就执行完了,执行完则出栈,当同步执行完之后,才会回调setTimeout中的回调函数,此时新的foo方法才会进栈,一进一出所以不会爆栈。
10.直接往script标签里放值(即:< script >let a=“xxx”< /script >)和< script src=“a.js”>,有啥区别?
答案:
11.React和Vue项目在列表组件中写key,作用是什么?
key是给每一个vnode的唯一标识,可以依靠key,更准确,更快的拿到oldVnode中对应的vnode节点。
12.已知以下副本,编写一个程序将它进行分隔化去除其中重复的部分数组,得到升序和不重复的数组:
副本:
var arr=[
[11,22,22],
[13,14,15,15],
[16,17,18,19,[12,13,[14]]],
12
]
答案:flat方法接收一个参数n,将深度为n的数组扁平化,去重可以通过set方法,该方法返回一个Set类数组对象,Array.from()方法就是将一个类数组对象或者可遍历对象转换成一个真正的数组。排序可以通过sort方法。
var newArray=Array.from(
new Set(arr.flat(Infinity))
).sort((a,b)=>{
a-b;
})
13.请使用原型链相关知识实现b继承n=1;c继承n=2;m=3:
var obj = function () {
};
obj.prototype.n = 1;
var b = new obj();
obj.prototype = {
n: 2,
m: 3,
};
var c = new obj();
console.log(b.n, b.m, c.n, c.m);
14.实现一个函数add,满足以下的输出结果:
add(1);//1
add(1)(2);//3
add(1)(2)(3);//6
add(1)(2,3)//6
add(1,2)(3);//6
add(1,2,3)(4);//10
理解:即多次调用,比如add(1)(2)(3),先调用add(1),然后返回一个func函数,再调用func(2),然后同样返回一个函数,调用func(3)。
答案:
function add() {
var args = [...arguments];
var func = function () {
args.push(...arguments);
return func;
};
func.toString = function () {
return args.reduce((x, y) => {
return x + y;
});
};
return func;
}
console.log(add(1)(2)(3));
console.log(add(1, 2, 3)(4));
15.一堆扑克牌,将牌堆第一张放到桌子上,再将接下来的牌堆的第一张放到牌底,如此交替;
最后桌上的牌顺序为:(牌底)1,2,3,4,5,6,7,8,9,10,11,12,13(牌顶);原来那堆牌的顺序,用函数实现。
答案:
function reverse(arr) {
let i = 1;
let out = [];
while (arr.length) {
if (i % 2) {
out.unshift(arr.pop());
} else {
out.unshift(out.pop());
}
i++;
}
return out;
}
console.log(reverse([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13]));
16.写一个多重展开函数:如输入arr = [1, [2, [3, 4, 2], 2], 5, [6]],则输出[1,2,3,4,2,2,5,6]
答案(递归):
var arr = [1, [2, [3, 4, 2], 2], 5, [6]];
function flatten(arr) {
var array = [];
arr.forEach((item) => {
if (Array.isArray(item)) {
array.push(...flatten(item)); //如果item是一个数组,则进行递归,返回一个数组,使用扩展运算符展开数组
} else {
array.push(item);
}
});
return array;
}
console.log(flatten(arr));
上面使用的是自己写的利用递归实现的,还有一种简单的方法:就是利用Array.flat(Infinity)的方法(在第12题使用过)
var arr = [1, [2, [3, 4, 2], 2], 5, [6]];
var array = arr.flat(Infinity);
console.log(array);
17.Promise.all中任何一个Promise出错的时候都会执行拒绝,导致其他正常返回的数据,也无法使用。你有什么办法解决吗?
参考:
关键函数:warp函数
const p1 = new Promise((resolve, reject) => {
setTimeout(resolve, 200, {
data: 1 });
});
const p2 = new Promise((resolve, reject) => {
setTimeout(resolve, 500, {
data: 2 });
});
const p3 = new Promise((resolve, reject) => {
setTimeout(reject, 300, {
data: 3 });
});
function warp(p) {
return new Promise((resolve, reject) => {
p.then((res) => {
return resolve(res);
}).catch(() => {
resolve(false);
});
});
}
(function getData() {
Promise.all([warp(p1), warp(p2), warp(p3)])
.then((res) => {
console.log("异步请求完成", res);
})
.catch((err) => {
console.error("error:", err);
});
})();
即当Promise.all()中有一个promise出现错误时,使用resolve(false)保证所有promise都是resolved状态。
18.在JS中什么是伪数组?如何将伪数组转换为标准片段?
参考:
伪数组:伪数组是一个对象,这个对象必须有一个length属性,且length的值为Number类型,如果length值不为0,则这个对象的属性名必须使用下标(0~n),伪数组无法直接使用数组的内置方法。
看看函数的参数:
function getData() {
console.log(arguments);
}
getData(1, "jaja", 6, "ko55");
var rightArrary= Array.prototype.slice.call(伪数组)
实例:
function getData() {
var rightArray = Array.prototype.slice.call(arguments);
rightArray.push("我是真数组,可以使用数组内置方法push");
const bool = Object.prototype.toString.call(rightArray) === "[object Array]";
console.log("我是真数组吗?", bool);
console.log(rightArray);
}
getData(1, "jaja", 6, "ko55");
参考: