前端题目整理

有趣的前端面试题收集整理, 问题及答案来源于github

(京东)下面代码中 a 在什么情况下会打印 1?

1
2
3
4
var a = ?;
if(a == 1 && a == 2 && a == 3){
console.log(1);
}

这题考察的应该是类型的隐式转换,考引用类型在比较运算符时候,隐式转换会调用本类型toString或valueOf方法.

1
2
3
4
5
6
7
8
9
10
var a = {
i: 1,
toString() {
return a.i++;
}
}

if( a == 1 && a == 2 && a == 3 ) {
console.log(1);
}

改造下面的代码,使之输出0 - 9,写出你能想到的所有解法。

1
2
3
4
5
for (var i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}

解题方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
for (var i = 0; i< 10; i++){
setTimeout((i) => {
console.log(i);
}, 1000,i)
}

for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}

for (var i = 0; i< 10; i++){
((i) => {
setTimeout(() => {
console.log(i);
}, 1000)
})(i)
}

for (let i = 0; i< 10; i++){
setTimeout(() => {
console.log(i);
}, 1000)
}

请把俩个数组 [A1, A2, B1, B2, C1, C2, D1, D2] 和 [A, B, C, D],合并为 [A1, A2, A, B1, B2, B, C1, C2, C, D1, D2, D]

正经解法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const arr1 = ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
const arr2 = ['A', 'B', 'C', 'D']
const ret = []
let tmp = arr2[0]
let j = 0
for (let i=0;i<arr1.length;i++) {
if (tmp === arr1[i].charAt(0)){
ret.push(arr1[i])
}else {
ret.push(tmp)
ret.push(arr1[i])
tmp=arr2[++j]
}
if(i===arr1.length-1){
ret.push(tmp)
}
}
console.log(ret)

面向题目解法:

1
2
3
4
5
6
7
8
9
10
11
let a1 =  ['A1', 'A2', 'B1', 'B2', 'C1', 'C2', 'D1', 'D2']
let a2 = ['A', 'B', 'C', 'D'].map((item) => {
return item + 3
})

let a3 = [...a1, ...a2].sort().map((item) => {
if(item.includes('3')){
return item.split('')[0]
}
return item
})

(头条)常见异步笔试题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//请写出输出内容
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}

console.log('script start');

setTimeout(function() {
console.log('setTimeout');
}, 0)

async1();

new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');


/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

难点解析:
很多人以为await会一直等待之后的表达式执行完之后才会继续执行后面的代码,实际上await是一个让出线程的标志。await后面的表达式会先执行一遍,将await后面的代码加入到microtask中,然后就会跳出整个async函数来执行后面的代码。
由于因为async await 本身就是promise+generator的语法糖。所以await后面的代码是microtask。所以对于本题中的

1
2
3
4
5
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}

等价于

1
2
3
4
5
6
async function async1() {
console.log('async1 start');
Promise.resolve(async2()).then(() => {
console.log('async1 end');
})
}

使用 sort() 对数组 [3, 15, 8, 29, 102, 22] 进行排序,输出结果

1
[102, 15, 22, 29, 3, 8]

根据MDN上对Array.sort()的解释,默认的排序方法会将数组元素转换为字符串,然后比较字符串中字符的UTF-16编码顺序来进行排序。所以’102’ 会排在 ‘15’ 前面。以下是MDN中的解释原文:

The sort() method sorts the elements of an array in place and returns the array. The default sort order is built upon converting the elements into strings, then comparing their sequences of UTF-16 code units values.

下面代码输出什么

1
2
3
4
5
6
7
8
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()

依次输出:undefined -> 10 -> 20
解析:
在立即执行函数中,var a = 20; 语句定义了一个局部变量 a,由于js的变量声明提升机制,局部变量a的声明会被提升至立即执行函数的函数体最上方,且由于这样的提升并不包括赋值,因此第一条打印语句会打印undefined,最后一条语句会打印20。
由于变量声明提升,a = 5; 这条语句执行时,局部的变量a已经声明,因此它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10,

输出以下代码执行的结果并解释为什么

1
2
3
4
5
6
7
8
9
10
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)

答案:
结果:[,,1,2], length为4
伪数组(ArrayLike)
所以我认为题目的解释应该是:

  1. 使用第一次push,obj对象的push方法设置 obj[2]=1;obj.length+=1
  2. 使用第二次push,obj对象的push方法设置 obj[3]=2;obj.length+=1
  3. 使用console.log输出的时候,因为obj具有 length 属性和 splice 方法,故将其作为数组进行打印
  4. 打印时因为数组未设置下标为 0 1 处的值,故打印为empty,主动 obj[0] 获取为 undefined
    第一第二步还可以具体解释为:因为每次push只传入了一个参数,所以 obj.length 的长度只增加了 1。push方法本身还可以增加更多参数,详见 MDN

输出以下代码的执行结果并解释为什么

1
2
3
4
5
6
var a = {n: 1};
var b = a;
a.x = a = {n: 2};

console.log(a.x)
console.log(b.x)

结果:
undefined
{n:2}

首先,a和b同时引用了{n:2}对象,接着执行到a.x = a = {n:2}语句,尽管赋值是从右到左的没错,但是.的优先级比=要高,所以这里首先执行a.x,相当于为a(或者b)所指向的{n:1}对象新增了一个属性x,即此时对象将变为{n:1;x:undefined}。之后按正常情况,从右到左进行赋值,此时执行a ={n:2}的时候,a的引用改变,指向了新对象{n:2},而b依然指向的是旧对象。之后执行a.x = {n:2}的时候,并不会重新解析一遍a,而是沿用最初解析a.x时候的a,也即旧对象,故此时旧对象的x的值为{n:2},旧对象为 {n:1;x:{n:2}},它被b引用着。
后面输出a.x的时候,又要解析a了,此时的a是指向新对象的a,而这个新对象是没有x属性的,故访问时输出undefined;而访问b.x的时候,将输出旧对象的x的值,即{n:2}。

输出以下代码运行结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// example 1
var a={}, b='123', c=123;
a[b]='b';
a[c]='c';
console.log(a[b]);

// example 2
var a={}, b=Symbol('123'), c=Symbol('123');
a[b]='b';
a[c]='c';
console.log(a[b]);

// example 3
var a={}, b={key:'123'}, c={key:'456'};
a[b]='b';
a[c]='c';
console.log(a[b]);

这题考察的是对象的键名的转换。

  • 对象的键名只能是字符串和 Symbol 类型。
  • 其他类型的键名会被转换成字符串类型。
  • 对象转字符串默认会调用 toString 方法。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // example 1
    var a={}, b='123', c=123;
    a[b]='b';

    // c 的键名会被转换成字符串'123',这里会把 b 覆盖掉。
    a[c]='c';

    // 输出 c
    console.log(a[b]);
1
2
3
4
5
6
7
8
9
10
11
// example 2
var a={}, b=Symbol('123'), c=Symbol('123');

// b 是 Symbol 类型,不需要转换。
a[b]='b';

// c 是 Symbol 类型,不需要转换。任何一个 Symbol 类型的值都是不相等的,所以不会覆盖掉 b。
a[c]='c';

// 输出 b
console.log(a[b]);
1
2
3
4
5
6
7
8
9
10
11
12
13
// example 3
var a={}, b={key:'123'}, c={key:'456'};

// b 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。
a[b]='b';

// c 不是字符串也不是 Symbol 类型,需要转换成字符串。
// 对象类型会调用 toString 方法转换成字符串 [object Object]。这里会把 b 覆盖掉。
a[c]='c';

// 输出 c
console.log(a[b]);

在输入框中如何判断输入的是一个正确的网址

1
2
3
4
5
6
7
8
const isUrl = urlStr => {
try {
const { href, origin, host, hostname, pathname } = new URL(urlStr)
return href && origin && host && hostname && pathname && true
} catch (e) {
return false
}
}
咸鱼也要有梦想,万一实现了呢
0%