快手一面面试

今天主要是面快手的一面,小哥哥人超好,也给了我很多指导,接下来不要说感情了,梳理一下任务,在编码的时候,被推荐了ESlint、prettier去做编码规范的工作。Eslint用过了,立马增加插件prettier。前辈和我说有问题要深入,深入的了解一些优缺点和好处。(虚拟滚动)

下述有关css属性position的属性值的描述,说法错误的是?

CSS中Position属性有四个可选值,它们分别是:static、absolute、fixed、relative。

◆position:static 无定位

该属性值是所有元素定位的默认情况,在一般情况下,我们不需要特别的去声明它,但有时候遇到继承的情况,我们不愿意见到元素所继承的属性影响本身,从而可以用position:static取消继承,即还原元素定位的默认值。

◆position:absolute 绝对定位

使用position:absolute,能够很准确的将元素移动到你想要的位置,

◆position:fixed 相对于窗口的固定定位

这个定位属性值是什么意思呢?元素的定位方式同absolute类似,但它的包含块是视区本身。在屏幕媒体如WEB浏览器中,元素在文档滚动时不会在浏览器视察中移动。例如,它允许框架样式布局。在页式媒体如打印输出中,一个固定元素会出现于第一页的相同位置。这一点可用于生成流动标题或脚注。我们也见过相似的效果,但大都数效果不是通过CSS来实现了,而是应用了JS脚本。

请特别注意,IE6不支持CSS中的position:fixed属性。真的非常遗憾,要不然我们就可以试试这种酷酷的效果了。

◆position:relative 相对定位

所谓相对定位到底是什么意思呢,是基于哪里的相对呢?我们需要明确一个概念,相对定位是相对于元素默认的位置的定位。既然是相对的,我们就需要设置不同的值来声明定位在哪里,top、bottom、left、right四个数值配合,来明确元素的位置。

1、promise和async/await 优缺点好处

优点:

  • async/await 做到了真正的串行同步的写法,代码阅读相对容易。

但是javascript的90%都是在异步的场合都是ajax,ajax就一定要考虑异常,很可能需要try…catch来处理异常。

  • 对于条件语句和其他流程的语句比较好,可以直接写在判断条件里。而且await a()没有出错的可能性,还可以省掉try…catch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function a(){
return new preomise((resolve, reject) =>{
setTimeout(() =>{
resolve(222)
},1000)
})
};

async function f(){
try {
if( await a() === 222){
console.log('yes, it is!')
}
} catch(err){
//...
}
}

f();
  • 在处理复杂流程时,在代码清晰度方面有优势

如果有这样一个业务场景:一个数组变量,如果它的length大于0,就遍历它进行下一步操作,如果length等于0,说明没有经历过ajax请求,则先ajax请求并赋值内容,然后再遍历它进行下一把操作;如果ajax的结果依旧为空,则显示toast,并中断流程。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let arr = [];

new Promise((resolve) => {
if(arr.length) {
resolve();
}else {
ajax().then((res) =>{
if( res.data.length){
arr = res.data;
resolve();
}else {
showToast("数据为空")
}
})
}
}).then(() => {
arr.forEach(()=>{});
})

如果我换成async/await呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let arr = [];

async function f(){
if(!arr.length){
const res = await ajax();
if(res.data.length) {
arr = res.data;
}else {
showToast("数据为空")
}
}
arr.forEach(()=>{})
}

fn()

Promise写法,必须有if(arr.length){resolve();},而async/await不必考虑这个分支。

Promise写法的代码不仅冗长,而且这还是在省略了一部分代码的前提下,showToast('数据为空')这个分支永远是pending状态,可能会带来一些问题。

特点一:async/await无所谓优缺点的特点一:无法处理promise返回的reject对象,要借助try…catch…

1
2
3
4
5
6
7
8
9
10
11
12
13
function g(){
return new Promise((resolve,reject) => {
setTimeout(()=>{
reject(222)
},1000)
})
};

async function f(){
const y = await g();
console.log(y);
}
f();

await g()会直接报错,必须使用try…catch…捕获。

那么假定有3个ajax串行请求,Promise模式与async/await的对比如下:

1、允许统一处理reject的话:

Promise(伪代码):

1
2
3
4
5
6
7
8
9
10
11
ajax1().then(() => {
console.log('ajax1 sucess')
return ajax2();
}).then(() => {
console.log('ajax2 sucess')
return ajax3();
}).then(()=> {
console.log('ajax suncess')
}).catch(err) => {
console.log('可能打印ajax1或2或3的fail',err);
}

async/await(伪代码):

1
2
3
4
5
6
7
8
9
10
11
12
13
async function a(){
try{
await ajax1();
console.log('ajax1 success')
await ajax2();
console.log('ajax2 success')
await ajax3();
console.log('ajax3 success')
}.catch(err) {
console.log('可能打印ajax1或2或3的reject',err);
}
}
a(s)

对比结果:从代码量上说,大同小异,就看你是否用的惯try…catch….。

为什么说用的惯呢,看这段代码,这段代码在上文粘贴过,想象一下,假如if ( await a () === 222) ,{}里的内容有20行,会怎样——你会发现,trycatch相距22行,很远,很难阅读,并且,内容体里面如果还有try…catch…怎么办?这就成了try…catch…的嵌套圣诞树,更加难以阅读,最终解决办法只能是:如果if的内容体太长,尤其是try…catch…的嵌套圣诞树,就放弃if(await a() === 222)这种优雅的写法,改成const res == await a();这种写法,然后把这句单独做try…catch…。

特点二:Promise可以轻松做到并行:

1
2
3
4
ajax1();
ajax2();

Promise.all([ajax1(),ajax()])

但是await做不到,它一定是阻塞的。await甚至可以阻塞for循环:

1
2
3
4
5
6
7
async function f(){
for(var i = 0;i < 10;i++){
const y = await g();
console.log(y);
}
}
f();

注意:await做不到并行,不代表async就不能并行。只要await不在同一个async的函数里就可以并行。

比如:

1
2
3
4
5
6
7
8
function f() {
[1,1,1,1,1,1,1,1,1,1].forEach(async (v)=>{
const y = await g();
console.log(y);
})
}

f();

特点三:async/await全局捕获错误必须用window.onerror,不像Promise可以专用window.addEventLister('unhandledrejection' , function),而window.onerror会捕获各种稀奇古怪的错误,造成系统浪费。

尽管window.onerror的开销大,但是一个成熟的系统是一定要利用window.onerror做错误监控系统,所以,无所谓了。

async缺点一:try…catch内部的变量无法传递给下一个try…catch

Promise和then/catch内部定义的变量,能通过then链条的参数传递到下一个then/catch,但是async/await的try内部的变量,如果用letconst定义则无法传递到下一个try…catch,只能在外层作用域先定义好。

async缺点二:async/await无法简单实现Promise的各种原生方法,比如.race()之类

如果真的要编写一些工具库,确实可以实现Promise的各种原生方法,但是放着Promise原生方法不用,却要写工具库,完全没有必要。

什么时候用什么呢?

1.需要用到promise各种便捷的方法(比如。race()之类)的时候,一定用Promise

2、并行的请求最好用Promise

3、不需要并行的场合,如果要传递参数,最好用Primise

4、其他ajax场合,看你喜好决定try…catch,还是.catch()

额外讨论:ajax异常全部用拦截器处理,是否可以避免使用try…catch?

拦截器对于后端业务代码错误,例如500错误,应当怎么处理呢?

如果:拦截器把200和500都归类为resolve

优点:共同的好处是只需要考虑200的状态,所以确实不需要写try…catch,也不需要.catch()

缺点: 对两个方案都有缺点,500归为resolve的话,语义比较拧巴,而且业务代码永远需要这种代码:

1
2
3
if(res.code === 200){
//
}else if(code === 500)

如果:拦截器只将500错误归为reject,而200依然属于resolve

优点:共同的好处是不用一遍遍的写if(res.code ===XXX),因为try里面是200的处理代码,catch里面是500的处理代码,天然就分开了。而且,500错误归为reject,从语义上说不拧巴。

缺点:对两个方案都有缺点,必须用try…catch或.catch()捕获reject,不能省略。

2、数组的浅复制可以如何做?有哪些方法?

给我谈了几个array的方法

3、项目的优化方面,提了个建议:使用虚拟滚动来优化文件树

4、拍平对象

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
39
40
41
42
43
44
45
46
const data = [
{
value: '1',
children: [
{
value: '1-1'
},
{
value: '1-2',
children: [
{
value: '1-2-1'
}
]
}
]
},
{ value: '2' }
];

// 返回
const res = [
{ value: '1' },
{ value: '1-1' },
{ value: '1-2' },
{ value: '1-2-1' },
{ value: '2' }
];

function foo(res) {
let arr = [];
res.forEach((item) => {
if (item.children) {
arr.push(newobj(item))
arr = arr.concat(foo(item.children));
} else {
arr.push(item);
};
})
return arr;
}
function newobj(item){
return {value: item.value}
}

console.log(foo(data))

补充数组扁平化:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
let arr = [1, 2, [3, 4, 5, [6, 7, 8], 9], 10, [11, 12]];

function fn(arr) {
let arr1 = []
arr.forEach((val) => {
if (val instanceof Array) {
arr1 = arr1.concat(fn(val))
}
else {
arr1.push(val)
}
})
return arr1
}

function foo(arr) {
return arr.toString().split(',').map(item => +item);
}
console.log(foo(arr))

5、数组浅拷贝的方法

序号 方式 例子 说明
1 concat let res = arr.concat(); 连接数组返回一个新数组
2 slice let res = arr.slice(); 返回选定的元素默认从0开始到结尾
3 解构 let res = […arr]; 创建了一个新的数组赋值为当前数组的值
4 装x写法 let […res] = arr; 同上
5 map let res = arr.map(i=>i); 遍历数组逐个返回元素给res
6 Array.of let res = Array.of(…arr); 该方式将一堆数字转化成数组返回