一年经验前端面试记录

911 阅读10分钟

个人情况

本科毕业,二线公司工作一年,找前端工作,base成都,因为经验问题很多公司简历都过不了😔,整理出已经面试问过的问题

CSS相关

两栏布局

只写了一个,还可以通过float+负margin,flex等方法实现

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        div{
            height: 600px;
        }

        .left{
            width: 200px;
            float: left;
            background-color: blue;
        }

        .right{
            margin-left: 200px;
            background-color: red;
        }
    </style>
</head>
<body>
        <div class="left"></div>
        <div class="right"></div>
</body>
</html>

垂直水平居中

通过flex实现,inner基于outer居中,inner的文字居中

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <style>
        .outer{
            width: 400px;
            height: 600px;
            display: flex;
            justify-content: center;
            align-items: center;
            border: 1px solid red;
        }
        .inner{
            background: red;
            width: 200px;
            height: 300px;
            border: 1px solid blue;
            text-align: center;
            line-height: 300px;
        }
    </style>
</head>
<body>
    <div class="outer">
        <div class="inner">test</div>
    </div>
</body>
</html>

BFC

BFC的特性:

  1. 内部的容器会在垂直方向上放置
  2. margin决定了垂直方向上的距离
  3. BFC的区域不会与float区域重叠
  4. 计算BFC高度时,浮动元素会参与计算

形成BFC的条件:

  1. 除了float:none以外的浮动元素
  2. 绝对定位和固定定位(absolute和fixed)也会形成BFC
  3. display:inline-block/table-cell/table-caption
  4. overflow除了visible意外的也会

JS相关(手写)

模拟一下new的实现过程

function myNew(){
    const o = new Object();//创建一个全新的object
    const FunctionConstructor = [].shift.call(arguments);//拿到构造函数
    o.__proto__ = FunctionConstructor.prototype;//绑定原型
    const res = FunctionConstructor.apply(o,arguments);
    return typeof o === 'object' ? res : o;
}

function Player(name){
    this.name = name;
}

const p = myNew(Player,"hhhhhh");
console.log(p);//Player { name: 'hhhhhh' }

实现一个instanceof

因为instanceof是一个关键字,这里通过函数实现,instanceof其实就是一个无限循环顺着原型链往上查找的过程,退出条件为找到一样的原型或者null

function myInstanceof(left,right){
    let proto = Object.getPropertyOf(left);//拿到目标的原型
    while(1){
        if(proto === null) return false;
        if(proto === right.prototype) return true;
        proto = Object.getPropertyOf(proto);
    }
}

let f = function(){};
console.log(myInstanceOf(f,Array))//false

实现一个promiseAll

考察对promiseAll的理解

function promiseAll(promiseList) {
    //传入promise的数组
    if (!Array.isArray(promiseList)) {
        throw new Error('error')
    }
    //results用于保存每一个结果
    let results = [];
    //count记录执行情况,count===len的时候就应该把整个results resolve出来
    let count = 0;
    let len = promiseList.length;
    //返回一个promise
    return new Promise((resolve, reject) => {
        promiseList.forEach((item, index) => {
            Promise.resolve(item).then(res => {
                count++;
                results[index] = res;
                if (count === len) {
                    return resolve(results);
                }
            }, (err) => {
                reject(err);
            })
        });
    });
}

function fn1() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(1);
        }, 2000);
    })
}

function fn2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2)
        }, 1000);
    })
}

promiseAll([fn1(), fn2()])
    .then(res => {
        console.log(res)
    })
    .catch(err => {
        console.log(err)
})

用数组的reduce实现map

考察对reduce的理解

function myMap(fn,arr=[]){
    if(typeof fn !== 'function'){
        throw new Error(`${fn} is not a function`)
    }
    return arr.reduce((pre,cur,index,list) => {
        return pre.concat(fn.call(arr,cur,index,list));
    },[]);
}

const arr = [1,2,3];
console.log(myMap(item => item * 2,arr));//[ 2, 4, 6 ]

如何让一个对象可以用for of遍历呢

考察对Iterator的理解,Iterator是一个可迭代对象,也是一些数据可以用forof遍历的标志

const obj = {
    count:0,
    [Symbol.iterator]:() => {
        return {
            next:() => {
                obj.count++;
                if(obj.count < 10){
                    return {
                        value:obj.count,
                        done:false
                    }
                } else {
                    return {
                        value:undefined,
                        done:true
                    }
                }
            }
        }
    }
}

for(let item of obj){
    console.log(item)
}

实现一个Iterator

function generateIterator(arr){
    let nextIndex = 0;
    return {
        next : () => nextIndex < arr.length ? {
            value:arr[nextIndex++],
            done:false
        } : {
            value:undefined,
            done:true
        }
    }
}

const arr = generateIterator([1,2]);

console.log(arr.next());//{ value: 1, done: false }
console.log(arr.next());//{ value: 2, done: false }
console.log(arr.next());//{ value: undefined, done: true }

用到防抖的场景,并实现一个

场景:

  1. search的时候,因为内容和请求相关,每一次onChange的时候都会去发一个请求,这个时候用防抖,每隔一定时间将内容用于请求一次
  2. 窗口resize的时候也会用到,也是为了防止改变窗口大小的时候不断触发事件 实现:
function myDebounce(fn,delay){
    let timer = null;
    return (...args) => {
        clearTimeout(timer);//调用时清零
        timer = setTimeout(() => {
            fn.apply(this,args);//重新绑定回调并设置延时
        },delay)
    }
}

分析异步代码的执行结果

考察对宏任务/微任务和event loop的理解

async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
}

async function async2() {
    console.log('async2');
}

console.log('script start');//解析代码的时候遇到console,所以第一个肯定是这里

setTimeout(function () {
    console.log('setTimeout');//遇到setTimeout,创建一个任务,当前任务执行完再去执行
}, 0);

async1();//执行到异步函数,将控制权交给协程,输出async1 start,遇到async2,输出async2,返回一个promise并放到微任务队列

new Promise(function (resolve) {
    console.log('promise1');//直接执行
    resolve();
}).then(function () {
    console.log('promise2');//返回的then放到微任务队列
});

console.log('script end');//到这里执行完成之后,就应该按顺序执行微任务队列了,微任务执行完成后开始下一个任务,也就是setTimeout
/*
script start
async1 start
async2
promise1
script end
async1 end
promise2
setTimeout
*/

React相关

useEffect和useLayoutEffect的区别

在commit阶段的mutation阶段会执行useLayoutEffect hook的函数销毁,而useLayoutEffect hook从上次更新的销毁函数调用本次更新的回调函数是同步执行的,而useEffect需要先调度(before mutation阶段调度),在layout阶段结束之后异步执行

hooks的依赖项(比如useEffect)

  1. 如果有依赖项时,在每次依赖项发生变化时,触发副作用
  2. 当依赖项为[]时,在本次渲染中只执行一次
  3. 当没有依赖项时,只要本组件更新,都会触发(也可能是父组件更新导致的本组件更新)
  4. 当依赖项为引用类型时,即使依赖项不发生变化,也会执行,因为useEffect进行浅比较

实现一个useState

let stateArray = [];//维护全局的状态列表和游标,如果放在函数里,每次执行都会重置,所以要全局
let cursor = 0;

function useStateMock(initialState){
    const currentCursor = cursor;//获取当前游标
    stateArray[currentCursor] = stateArray[currentCursor] || initialState;//如果state存在,直接用,不存在则使用初始值

    function setState(newState){//setState
        stateArray[currentCursor] = newState;
        // render();//在短时间内去set多次,会进行合并   
    }

    ++cursor;//执行完当前state之后,往后移

    return [stateArray[currentCursor],setState];//返回state和setState
}

useCallback useMemo memo讲一下

useCallback和useMemo用于缓存函数,useMemo是返回函数执行后的结果,useCallback是返回函数 memo第一个参数是函数,函数的props变化会重新渲染,第二个参数是对应的规则,例如(prev,next) => prev.value === next.value,当value相同的时候缓存,即不必重复渲染

exm:

import React, { memo, useCallback, useState } from "react";

export default function UseCallback(){
    const [count,setCount] = useState(0);
    const [value,setValue] = useState(0);

    const onClick = () => {
        // console.log('parent');
        setCount(count + 1);
    }

    const onChange = useCallback( e => {
        // console.log('sub');
        setValue(e.target.value)
    },[count])//如果count发生变化 虽然子组件没有用到count 但是onChange在子组件里面重新创建了 所以子组件也会重新渲染

    return (
        <div>
            <button onClick={onClick}>count发生变化 {count}</button>
            <UseCallbackSub onChange={onChange} value={value} />
        </div>
    )
}

const UseCallbackSub = memo(({ onChange,value }) => {
    console.log('子元素渲染',value)
    return <input type="number" onChange={onChange} value={value} />;
},(prev,next) => prev.value === next.value)

redux用过吗?讲一下

了解过,没用过。。。 参考官方文档吧。

react如何实现requestIdleCallback?

通过messageChannel(宏任务),因为宏任务是在下次事件循环中执行,不会阻塞本次页面更新。而微任务是在本次页面更新前执行,与同步执行无异,不会让出主线程

为什么不用setTimeout(fn, 0) ?

因为setTimeout不可以在两个任务间通信,而且递归执行 setTimeout(fn, 0) 时,最后间隔时间会变成 4 毫秒,而不是最初的 1 毫秒

diff的限制你知道吗?

  1. 只对同级元素进行diff,如果一个dom节点在前后两次更新中跨越了层级,react将不会尝试复用它
  2. 两个不同类型的元素会产生不同的树,比如div -> p,react会销毁div及其子孙节点,创建p及其子孙节点
  3. 开发者可以通过key props来暗示哪些元素在不同的渲染下能够保持稳定

你是如何学习源码的?

卡老师《react技术揭秘》yyds!

浏览器相关

(经典问题)从输入url到页面渲染完成发生了什么

这里越详细越好,可以参考极客时间李兵老师的《浏览器工作原理与实践》

  1. 输入URL,浏览器判断是搜索内容还是网址,如果是搜索内容搜索内容+默认搜索引擎地址合成新的URL,如果输入内容符合URL规则,则拼接协议合成合法的URL

  2. 输入完内容,敲下回车之后,浏览器导航栏呈现loading状态,停留在前一个页面,因为新的资源还没获得

  3. 浏览器进程合成请求行信息,通过IPC(进程间通信)发送给网络进程

  4. 网络进程拿到URL和相关信息,查找缓存中是否有该URL对应的资源,如果有,拦截请求,返回200和缓存的资源文件,否则,进入网络请求阶段

  5. 网络进程请求DNS查找URL对应的IP,如果之前DNS缓存过这个URL的信息,就会直接返回缓存信息,否则,发起请求获取解析出来的IP和端口号

  6. 进行TCP连接,在正式连接之前,会检查该域名下是否超过了6个TCP连接,如果超过,进入等待连接状态,如果没有超过,则发起正式连接

  7. 发起正式TCP连接,这个过程中,数据包会带上TCP头信息--包括源端口号、目的端口号和用于确保数据包顺序的序列信息,向网络层传输

  8. 网络层给数据包头部加上IP头信息,向物理层传输

  9. 物理层通过物理网络传输到目的主机

  10. 目的主机网络层接收到数据包,解析出IP头,剩下的数据包向上传输给传输层

  11. 目的主机传输层接收到数据包,解析出TCP头,根据端口信息把数据传输给应用层

  12. 应用层解析出请求头信息,如果需要重定向,返回响应数据的状态301或者302,同时在location字段中加上重定向的地址信息,浏览器会根据location发起一次新的连接,如果不是重定向,服务器会根据请求头的If-None-Match来判断缓存资源是否需要更新,如果不需要更新,返回304,告诉浏览器可以用之前的缓存数据,如果有更新,带着更新信息一起返回给浏览器,并在响应头中加入字段:Cache-Control:Max-age=2000

数据又顺着应用层-网络层-传输层-传输层-网络层-应用层返回到浏览器网络进程

  1. 数据传输完成,TCP四次挥手断开连接,但如果浏览器或服务器在HTTP头中带上了:Connection:Keep-Alive,TCP就会保持连接,不会断开

  2. 网络层获取到数据包解析出Content-Type,如果是字节流类型,就提交给下载管理器,导航流程结束,如果是text/html类型,就通知浏览器进程准备进行渲染

  3. 浏览器进程收到通知,根据当前页面和打开的新页面是否是同一站点判断是否新开一个渲染进程

  4. 确认之后,浏览器进程会发出 “提交文档” 信息给渲染进程,渲染进程接收到消息后,打开和网络进程之间的管道,传输文档数据,传输完成后,渲染进程会告诉浏览器进程 “确认提交

  5. 浏览器进程收到“确认提交”消息后,更新浏览器的状态,包括地址栏URL、前进后退的历史状态,并更新web页面

  6. 渲染进程对文档进行页面解析和子资源加载,HTML 通过HTML解析器转成DOM Tree(二叉树类似结构的东西),CSS按照CSS 规则和CSS解释器转成CSSOM TREE,两个tree结合,形成render tree(不包含HTML的具体元素和元素要画的具体位置),通过Layout可以计算出每个元素具体的宽高颜色位置,结合起来,开始绘制,最后显示在屏幕中新页面显示出来

微任务

微任务是什么?

微任务就是一个需要异步执行的函数,执行时间是主函数执行结束之后,当前宏任务结束之前 这是因为当执行一段JavaScript脚本的时候,V8会为其创建一个全局执行上下文,在这同时V8也会创建一个微任务队列也就是说每个宏任务都关联了一个微任务队列

微任务的执行时机

在当前宏任务快要执行完成的时候,下一个宏任务开始之前,这是因为在JavaScript执行完成后,js引擎会检查微任务队列,如果有,会先执行完微任务,再退出全局执行上下文并清空调用栈

总结

求职难,难于上青天!