04-面试之前端 js 简单代码

求结果

异步笔试题

请写出下面代码的运行结果:

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
// 今日头条面试题
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')
});

async1();

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

console.log('script end')

题目的本质,就是考察 setTimeout、promise、async await 的实现及执行顺序,以及 JS 的事件循环的相关问题。

答案:

1
2
3
4
5
6
7
8
script start
async1 start
async2
promise1
script end
async1 end
promise2
settimeout

代码解释题

参考答案

题目:

1
2
3
4
var min = Math.min();
max = Math.max();
console.log(min < max);
// 写出执行结果,并解释原因

答案
false

解析

  • 按常规的思路,这段代码应该输出 true,毕竟最小值小于最大值。但是却输出 false
  • MDN 相关文档是这样解释的
    • Math.min 的参数是 0 个或者多个,如果多个参数很容易理解,返回参数中最小的。如果没有参数,则返回 Infinity,无穷大。
    • Math.max 没有传递参数时返回的是-Infinity.所以输出 false

代码解析题

题目

1
2
3
4
5
6
7
var company = {
address: 'beijing'
};
var yideng = Object.create(company);
delete yideng.address;
console.log(yideng.address);
// 写出执行结果,并解释原因

答案

beijing

解析

这里的 yideng 通过 prototype 继承了 company 的 address。yideng 自己并没有 address 属性。所以 delete 操作符的作用是无效的。

扩展

  1. delete 使用原则:delete 操作符用来删除一个对象的属性。

  2. delete 在删除一个不可配置的属性时在严格模式和非严格模式下的区别:

    1. 在严格模式中,如果属性是一个不可配置(non-configurable)属性,删除时会抛出异常;
    2. 非严格模式下返回 false。
  3. delete 能删除隐式声明的全局变量:这个全局变量其实是 global 对象(window)的属性

  4. delete 能删除的:
    (1)可配置对象的属性
    (2)隐式声明的全局变量
    (3)用户定义的属性
    (4)在ECMAScript 6中,通过 const 或 let 声明指定的 “temporal dead zone” (TDZ) 对 delete 操作符也会起作用

  5. delete 不能删除的:
    (1)显式声明的全局变量
    (2)内置对象的内置属性
    (3)一个对象从原型继承而来的属性

  6. delete 删除数组元素:
    (1)当你删除一个数组元素时,数组的 length 属性并不会变小,数组元素变成undefined
    (2)当用 delete 操作符删除一个数组元素时,被删除的元素已经完全不属于该数组。
    (3)如果你想让一个数组元素的值变为 undefined 而不是删除它,可以使用 undefined 给其赋值而不是使用 delete 操作符。此时数组元素是在数组中的

  7. delete 操作符与直接释放内存(只能通过解除引用来间接释放)没有关系。

手写代码

数组去重

  1. 利用ES6 Set去重(ES6中最常用)

    1
    2
    3
    4
    5
    function unique (arr) {
    return Array.from(new Set(arr))
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) //[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]

    不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。

  2. 利用 for 嵌套 for,然后 splice 去重(ES5中最常用)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function unique(arr){            
    for(var i=0; i<arr.length; i++){
    for(var j=i+1; j<arr.length; j++){
    if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
    arr.splice(j,1);
    j--;
    }
    }
    }
    return arr;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了

    双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。

  3. 利用 indexOf 去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function unique(arr) {
    if (!Array.isArray(arr)) {
    console.log('type error!');
    return
    }
    var array = [];
    for (var i = 0; i < arr.length; i++) {
    if (array .indexOf(arr[i]) === -1) {
    array .push(arr[i])
    }
    }
    return array;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重

    新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。

  4. 利用 sort()

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    function unique(arr) {
    if (!Array.isArray(arr)) {
    console.log('type error!');
    return;
    }
    arr = arr.sort();
    var arrry= [arr[0]];
    for (var i = 1; i < arr.length; i++) {
    if (arr[i] !== arr[i-1]) {
    arrry.push(arr[i]);
    }
    }
    return arrry;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重

    利用 sort() 排序方法,然后根据排序后的结果进行遍历及相邻元素比对。

  5. 利用对象的属性不能相同的特点进行去重(这种数组去重的方法有问题,不建议用,有待改进)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    function unique(arr) {
    if (!Array.isArray(arr)) {
    console.log('type error!');
    return
    }
    var arrry= [];
    var obj = {};
    for (var i = 0; i < arr.length; i++) {
    if (!obj[arr[i]]) {
    arrry.push(arr[i]);
    obj[arr[i]] = 1
    } else {
    obj[arr[i]]++
    }
    }
    return arrry;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", 15, false, undefined, null, NaN, 0, "a", {…}] //两个true直接去掉了,NaN和{}去重
  6. 利用 includes

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function unique(arr) {
    if (!Array.isArray(arr)) {
    console.log('type error!');
    return
    }
    var array =[];
    for(var i = 0; i < arr.length; i++) {
    if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
    array.push(arr[i]);
    }
    }
    return array
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重
  7. 利用 hasOwnProperty

    1
    2
    3
    4
    5
    6
    7
    8
    function unique(arr) {
    var obj = {};
    return arr.filter(function(item, index, arr){
    return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
    })
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了

    利用 hasOwnProperty 判断是否存在对象属性

  8. 利用filter

    1
    2
    3
    4
    5
    6
    7
    8
    function unique(arr) {
    return arr.filter(function(item, index, arr) {
    // 当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
    return arr.indexOf(item, 0) === index;
    });
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
  9. 利用递归去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    function unique(arr) {
    var array= arr;
    var len = array.length;
    array.sort(function(a,b){ //排序后更加方便去重
    return a - b;
    });

    function loop(index){
    if(index >= 1){
    if(array[index] === array[index-1]){
    array.splice(index,1);
    }
    loop(index - 1); //递归loop,然后数组去重
    }
    }
    loop(len-1);
    return array;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) // [1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
  10. 利用Map数据结构去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function arrayNonRepeatfy(arr) {
    let map = new Map();
    let array = new Array(); // 数组用于返回结果
    for (let i = 0; i < arr.length; i++) {
    if(map .has(arr[i])) { // 如果有该key值
    map .set(arr[i], true);
    } else {
    map .set(arr[i], false); // 如果没有该key值
    array .push(arr[i]);
    }
    }
    return array ;
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)) //[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]

    创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果。

  11. 利用 reduce+includes

    1
    2
    3
    4
    5
    function unique(arr){
    return arr.reduce((prev,cur) => prev.includes(cur) ? prev : [...prev,cur],[]);
    }
    var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
    console.log(unique(arr)); // [1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}]
  12. [...new Set(arr)]

    1
    2
    [...new Set(arr)];
    // 代码就是这么少----(其实,严格来说并不算是一种,相对于第一种方法来说只是简化了代码)

手写一个发布订阅

参考答案

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
// 发布订阅中心, on-订阅, off取消订阅, emit发布, 内部需要一个单独事件中心caches进行存储;
interface CacheProps {
[key: string]: Array<((data?: unknown) => void)>;
}

class Observer {
private caches: CacheProps = {}; // 事件中心
on (eventName: string, fn: (data?: unknown) => void){ // eventName事件名-独一无二, fn订阅后执行的自定义行为
this.caches[eventName] = this.caches[eventName] || [];
this.caches[eventName].push(fn);
}

emit (eventName: string, data?: unknown) { // 发布 => 将订阅的事件进行统一执行
if (this.caches[eventName]) {
this.caches[eventName].forEach((fn: (data?: unknown) => void) => fn(data));
}
}

off (eventName: string, fn?: (data?: unknown) => void) { // 取消订阅 => 若fn不传, 直接取消该事件所有订阅信息
if (this.caches[eventName]) {
const newCaches = fn ? this.caches[eventName].filter(e => e !== fn) : [];
this.caches[eventName] = newCaches;
}
}
}

手写数组转树

问题:

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
38
// 例如将 input 转成output的形式
let input = [
{id: 1, val: '学校', parentId: null},
{id: 2, val: '班级1', parentId: 1},
{id: 3, val: '班级2', parentId: 1},
{id: 4, val: '学生1', parentId: 2},
{id: 5, val: '学生2', parentId: 2},
{id: 6, val: '学生3', parentId: 3},
];

let output = {
id: 1,
val: '学校',
children: [{
id: 2,
val: '班级1',
children: [
{
id: 4,
val: '学生1',
children: []
},
{
id: 5,
val: '学生2',
children: []
}
]
}, {
id: 3,
val: '班级2',
children: [{
id: 6,
val: '学生3',
children: []
}]
}]
}

答案

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
// 代码实现
function arrayToTree(array) {
let root = array[0];
array.shift();
let tree = {
id: root.id,
val: root.val,
children: array.length > 0 ? toTree(root.id, array) : []
};
return tree;
}

function toTree(parenId, array) {
let children = [];
let len = array.length;
for (let i = 0; i < len; i++) {
let node = array[i];
if (node.parentId === parenId) {
children.push({
id: node.id,
val: node.val,
children: toTree(node.id, array)
})
}
}
return children
}

console.log(arrayToTree(input))

04-面试之前端 js 简单代码
https://flepeng.github.io/interview-20-开发语言类-21-frontend-04-面试之前端-js-简单代码/
作者
Lepeng
发布于
2020年8月8日
许可协议