2021我所遇到的前端面试题

661 阅读5分钟

前端面试

1、js顺序执行多个异步函数

// 1、定义异步函数
const p1 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p1");
    }, 1000);
});

const p2 = () => new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve("p2");
    }, 500);
});

const p3 = () => new Promise(() => {
    setTimeout(() => {
        resolve("p3");
    }, 100);
}, 100);

// 2、将异步函数,按照自己定义的顺序,放入数组里
const fnArr = [p1, p2, p3];

// 3、定义执行函数
const run = (arr, start = 0) => {
    // 参数start不能超过arr.length
    if(start > arr.length || start < 0) {
        return;
    }
    
    const next = (i) => {
        if (i < arr.length) {
            const fn = arr[i];
            fn().then((res) => {
                console.log(res);
                i++;
                next(i);
            });
        }
    };
    
    next(start);
};

// 4、执行
run(fnArr);

在线地址:[codesandbox.io/s/competent…]

2、vue中在computed中更改data中的数据

// 在computed修改data数据的时候回报错。报错的大概意思是,不可以修改data数据的属性值
new Vue({
    data() {
        return {
            abc: '123'
        };
    },
    computed: {
        // 使用get和set更改data
        doubleAbc: {
            get: function() {
                return this.abc * 2;
            },
            set: function(newVal) {
                this.abc = newval;
            }
        }
    }
})

3、防抖

const debounce = (fn, waitTime) => {
    let timer = null;
    return function() {
        const _this = this;
        const args = arguments;
        
        clearTimeout(timer);
        
        timer = setTimeout(() => {
            fn.call(_this, args);
        }, waitTime)
    }
};

4、this问题

如何准确判断this指向的是什么?

1、函数时否在new中调用(new绑定),如果是,那么this绑定的是新创建的对象

2、函数是否通过call、apply调用,或者使用了bind,如果是,那么this绑定的就是指定的对象

3、函数是否在某个上下文对象中调用(隐式绑定),如果是的话,this绑定的是那个上下文对象

4、如果以上都不是,那么使用默认绑定,如果在严格模式下,则绑定到undefined,否则绑定到全局对象

5、如果把null或者undefined作为this绑定对象传入call、apply或者bind,这些值在调用时会被忽略,实际应用的是默认绑定规则

6、如果是箭头函数,this继承自外层代码块的this

    // 在函数中以函数作为参数传递,例如setTimeout和setInterval等,
    这些函数中触动你的函数中的this,向,在非严格模式下指向的是全局对象
    
    var name1 = 'koala';
    var person1 = {
        name: '程序员成长指北',
        sayHi: sayHi
    }
    function sayHi(){
        setTimeout(function(){
            console.log('Hello,', this.name1);
        })
    }
    person1.sayHi(); // Hello, koala
    setTimeout(function(){
        person1.sayHi();
    }, 200); // Hello, koala

5、TypeScript中的interface和type到底有何区别?

相同点

1、都可以描述一个对象或函数

interface

interface User {
    name: string;
    age: number;
}

inteface SetUser {
    (name: string, age: number): void;
}

type

type User = {
    name: string;
    age: number;
};

type SetUser = (name: string, age: number) => void;

2、都允许扩展(extends)

interface和type都可以扩展,并且二者并不是相互独立的,也就是说interface可以extends type,type也可以extends inteface。虽然效果差不多,但是二者语法不同

interface extends interface

interface Name {
    name: string;
}

interface User extends Name {
    age: number;
}

type extends type

type Name = {
    name: string;
}

type User = Name & { 
    age: number; 
}

interface extends type

type Name = {
    name: string;
}

interface User extends Name {
    age: number;
}

type extends interface

interface Name {
    name: string;
}

type User = Name & {
    age: number;
}

不同点

type可以而不行

  1. type可以声明基本类型别名,联合类型,元组等类型
// 基本类型
type Name = string;

// 联合类型
interface Dog {
    wong();
}

interface Cat {
    miao();
}

type Pet = Dog | Cat;

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet];
  1. type语句中还可以使用typeof获取实例的类型进行赋值
// 当你想获取一个变量的类型时,可以使用typeof
let div = document.createElement('div');
type B = typeof div;
  1. 其他骚操作
type StringOrNumber = string | number;
type Text = string | {text: string};
type NameLookUp = Dictionary<string, Person>;
type Callback<T> = (data: T) => void;
type Pair<T> = [T, T];
type Coordinates = Par<number>;
type Tree<T> = T | { left: Tree<T>, right: Tree<T> };

interface可以而type不行

  1. interface能够声明合并
interface User {
    name: string;
    age: number;
}

interface User {
    sex: string;
}

const user: User = {
    name: '测试',
    age: 12,
    sex: '男'
};

6、函数柯里化

const curry = (func: Function, ...args: any[]) => {
    // 获取函数的参数个数
    const fnLen: number = func.length; // length是js函数对象的一个属性,该值是指`该函数有多少个必须传入的参数`,即形参的个数
    
    return function(...innerArgs: any[]) {
        innerArgs = args.concat(innerArgs);
        
        // 参数为搜集足的话,继续递归搜集
        if(innerArgs.length < fnLen) {
            // @ts-ignore
            return curry.call(this, func, ...innerArgs);
        } else {
            // 否则拿到搜索的参数调用func
            // @ts-ignore
            func.apply(this, innerArgs);
        }
    };
};

// 测试
const add = curry((num1: number, num2: number, num3: number) => {
    console.log(num1, num2, num3, num1 + num2 + num3);
});

// @ts-ignore
add(1)(2)(3) // 1 2 3 6
// @ts-ignore
add(1, 2)(3) // 1 2 3 6
add(1, 2, 3) // 1 2 3 6
// @ts-ignore
add(1)(2, 3) // 1 2 3 6

在线预览: [codesandbox.io/s/admiring-…]

7、列表转树形结构

interface ArrayItem {
    id: number;
    name: string;
    pid: number;
}

/**
 * 列表转树形结构
 * @param array 传入数组
 * @param itemId 子项id对应的key
 * @param parentId 父项id在子项中对应的key
 */
const arrayToTree = (array: ArrayItem[], itemId = 'id', parentId = 'pid') => {
    const hashMap: any = {};
    const result: any[] = [];
    
    array.forEach((it: ArrayItem) => {
       const id = it[itemId];
       const pid = it[parentId];
       
       // 不存在时,先声明children树形
       if (!hashMap[id]) {
           hashMap[id] = {
               children: []
           };
       }
       
       hashMap[id] = {
           ...it,
           children: hashMap[id].children
       };
       
       // 处理当前的item
       const treeIt = hashMap[id];
       
       // 根节点,则直接push
       if (pid === 0) {
           result.push(treeIt);
       } else {
           // 也有可能当前节点的父节点还没有加入hashMap,所以需要单独处理一下
           if (!hashMap[pid]) {
               hashMap[pid] = {
                   chilren: []
               };
           }
           // 非根节点的话,找到父节点,把自己塞到父节点的children中即可
           hashMap[pid].children,push(treeIt);
       }
    });
    
    return result;
};

// 测试
const data: ArrayItem[] = [
    { id: 2, name: '部门2', pid: 1 },
    { id: 1, name: '部门1', pid: 0 },
    { id: 3, name: '部门3', pid: 1 },
    { id: 4, name: '部门4', pid: 3 },
    { id: 5, name: '部门5', pid: 4 },
    { id: 7, name: '部门7', pid: 6 },
]

console.log(arrayToTree(data));

8、树形结构转列表

// 树形结构转列表
const treeToList = (tree: any[]) => {
    const list = [];
    const queue = [...tree];

    while (queue.length) {
        // 从前面开始取出节点
        const node = queue.shift();
        const children = node.children;
        // 取出当前节点的子节点,放入队列中,等待下一次循环
        if (children.length) {
            queue.push(...children);
        }
        // 删除多余的children树形
        delete node.children;
        // 放入列表
        list.push(node);
    }

    return list;
};

const dataTree = [
    {
        "id": 1,
        "name": "部门1",
        "pid": 0,
        "children": [
            {
                "id": 2,
                "name": "部门2",
                "pid": 1,
                "children": []
            },
            {
                "id": 3,
                "name": "部门3",
                "pid": 1,
                "children": [
                    {
                        "id": 4,
                        "name": "部门4",
                        "pid": 3,
                        "children": [
                            {
                                "id": 5,
                                "name": "部门5",
                                "pid": 4,
                                "children": []
                            }
                        ]
                    }
                ]
            }
        ]
    }
];
console.log(treeToList(dataTree));

9、何为原型链

在JavaScript中万物都是对象,对象和对象之间也有关系,并不是孤立存在的。对象之间的继承关系,在JavaScript中是通过prototype对象指向父类对象,直到指向Object对象为止,这样就形成一个原型指向的链条,专业术语称之为原型链。

10、深拷贝

// 深拷贝
function checkType(any) {
    return Object.prototype.toString.call(any).slice(8, -1)
}
function deepClone(any){
    if(checkType(any) === 'Object') { // 拷贝对象
        let o = {};
        for(let key in any) {
            o[key] = deepClone(any[key])
        }
        return o;
    } else if(checkType(any) === 'Array') { // 拷贝数组
        let arr = []
        for(let i = 0,length = any.length;i<length;i++) {
            arr[i] = deepClone(any[i])
        }
        return arr;
    } else if(checkType(any) === 'Function') { // 拷贝函数
        return new Function('return '+any.toString()).call(this)
    } else if(checkType(any) === 'Date') { // 拷贝日期
        return new Date(any.valueOf());
    } else if(checkType(any) === 'RegExp') { // 拷贝正则
        return new RegExp(any)
    } else if(checkType(any) === 'Map') { // 拷贝Map 集合
        let m = new Map()
        any.forEach((v,k)=>{
            m.set(k, deepClone(v))
        })
        return m
    } else if(checkType(any) === 'Set') { // 拷贝Set 集合
        let s = new Set()
        for(let val of any.values()) {
            s.add(deepClone(val))
        }
        return s
    }
    return any;
}

const o = {
    name: '张三',
    skills: ['踢球', '跑步', '打羽毛球'],
    age: 18,
    love: {
        name: '小红',
        age: 16
    },
    map: new Map([['aaa', '123']]),
    fn:function(a){
        console.log(`我的名字叫${this.name}` + a)
    },
    set: new Set([1,2,3,4,5]),
    date: new Date(),
    reg: new RegExp('abc')
}
const cloneO = deepClone(o);
cloneO.fn('测试');
console.log(cloneO);