文章目录
前言
作者最近在往中级前端进发,持续学习以保证自身竞争力,因此刷了一些面试题,有难有易,因此更一篇博文,与大家共同成长。
1. 隐式类型转换
题目:请问输出什么?为什么?
js
console.log([] == ![]);
结果:true
解释:
!的优先级比==高,所以先计算!,右边得到false(对象转布尔永远是true,取反得到false)
现在计算[] == false,js进行比较时要先转换为原始值,左边转换:[].tostring() = ''
现在变成'' == false
在[]情况下,''和false都是0,所以得到true
2.this 指向
题目:请问输出什么?
js
const User = {
name: '张三',
sayName: function() {
console.log(this.name);
},
sayNameArrow: () => {
console.log(this.name);
}
};
User.sayName();
User.sayNameArrow();
输出:
张三
undefined
解释:
这是一个this的指向性问题,sayName由于是function函数,因此它的this指向User,所以打印张三
而sayNameArrow是箭头函数,箭头函数没有自己的this,所以指向全局,全局对象是没有name的,因此打印undefined
3.闭包实战
面试场景:面试官问"你写过防抖吗?"这不仅仅是考代码,是考你对闭包的应用。
场景描述:用户在搜索框疯狂打字,每打一个字就发一次 API 请求,服务器炸了。你需要做一个机制:等用户停手 500ms 后再发请求。
javascript
function debounce(fn, delay) {
let timer = null;
return function(...args) {
if (timer) clearTimeout(timer);
timer = setTimeout(() => {
fn.apply(this, args);
}, delay);
};
}
闭包的定义:debounce 函数执行完了,按理说 timer 变量应该被垃圾回收销毁。
神奇之处:但因为返回的那个新函数引用了 timer,所以 timer 被迫留在了内存里。
实际效果:每次你打字调用这个返回的函数,它们访问的都是同一个 timer。这就是闭包最大的作用------保存状态。
4.事件冒泡
场景:你需要给一个无限滚动的长列表(比如 10,000 个商品)绑定点击事件,点击跳转详情。
javascript
ul.addEventListener('click', function(e) {
// e.target 是你实际点击的那个小元素(比如 li 里面的 span)
// 核心:利用事件冒泡,往上找,看看是不是 li
if (e.target.tagName === 'LI') {
console.log('点击了', e.target.innerText);
}
});
事件在 DOM 中像泡泡一样,从最底层的元素(Target)一直往上冒(Bubble)到父元素。 我们只需要像"守门员"一样站在父元素门口,不管下面谁被点了,泡泡总会冒上来被我抓到。
5.let和var
题目:下面代码打印什么
javascript
var name = 'World';
(function() {
if (typeof name === 'undefined') {
var name = 'Jack'; // 注意这里用了 var
console.log('Goodbye ' + name);
} else {
console.log('Hello ' + name);
}
})();
结果:Goodbye Jack
var赋值的变量会被提升到顶层,此时它在函数内部,但是声明被提升到了顶层,因此name在外面就变成了undefined,所以进入了if分支,重新被赋值,从而打印出Goodbye Jack。
6. 连等赋值
题目:下面的代码输出什么?
javascript
let a = { n: 1 };
let b = a;
a.x = a = { n: 2 };
console.log(a.x);
console.log(b.x);
结果:
undefined
{ n: 2 }
js中.的优先级高于=,因此a.x 先准备好,指向了堆内存中 {n:1} 这个对象的一个新属性 x(此时为undefined),然后a = { n: 2 } 执行,变量 a 的指针变了,指向了新对象。最后执行赋值,把 {n:2} 赋给第一步中准备好的那个 x。但是b 依然指向旧对象(现在旧对象变成了 {n:1, x: {n:2}}),而 a 指向了新对象 {n:2}(新对象里没有 x)。
7.类型转换与运算
题目:下面代码会打印什么
javascript
console.log(1 + "1");
console.log(1 - "1");
结果:
11
0
解释:加号 (+):如果有一方是字符串,它就变成字符串拼接。1 变成了 "1"。
减号 (-):它会尽最大努力把两边都转成数字进行数学运算。"1" 变成了数字 1。
8.JSON键的理解
题目:下面代码会打印什么
javascript
var a = {}, b = '123', c = 123;
a[b] = 'b';
a[c] = 'c';
console.log(a[b]);
结果:c
解释:因为json的键名只能是字符串,所以a[c]实际上是添加了一个'123':'c'。打印自然也是c
9. 原型链
题目:下面的代码打印什么
javascript
function Foo() {
Foo.a = function() { console.log(1) }; // 静态方法
this.a = function() { console.log(2) }; // 实例方法
}
Foo.prototype.a = function() { console.log(3) }; // 原型方法
Foo.a = function() { console.log(4) }; // 覆盖静态方法
Foo.a();
let obj = new Foo();
obj.a();
结果:
4
2
解释:Foo.a():调用的是构造函数上的静态属性。它先被定义打印 1,后被覆盖打印 4。
obj.a():new 出来的实例 obj。查找属性遵循:实例自身 -> 原型链。因为构造函数里 this.a 给实例自身加了方法,所以直接执
行,打印 2。如果构造函数里没写 this.a,才会去原型上找打印 3。
10.数组拍平
场景:把数组[1, [2, [3, 4]]]变为:[1, 2, 3, 4]
javascript
const arr = [1, [2, [3, 4]]];
function flatten(ary) {
return ary.reduce((pre, cur) => {
return pre.concat(Array.isArray(cur) ? flatten(cur) : cur);
}, []);
}
console.log(flatten(arr));
解释:这里结合了递归。如果当前项是数组,就递归调用 flatten,否则直接拼接到结果里。
注:ES2019 直接出了 arr.flat(Infinity),但在面试中手写递归依然是加分项。
总结
今天的面试题就到这里,作为作者的学习记录,也希望能帮到大家。