前端面试
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可以而不行
- type可以声明基本类型别名,联合类型,元组等类型
// 基本类型
type Name = string;
// 联合类型
interface Dog {
wong();
}
interface Cat {
miao();
}
type Pet = Dog | Cat;
// 具体定义数组每个位置的类型
type PetList = [Dog, Pet];
- type语句中还可以使用typeof获取实例的类型进行赋值
// 当你想获取一个变量的类型时,可以使用typeof
let div = document.createElement('div');
type B = typeof div;
- 其他骚操作
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不行
- 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);