100道面试题

本文详细介绍了前端面试中常见的优化和编程问题,涵盖首屏优化策略如路由懒加载、服务端渲染,以及处理大数据量渲染、文字省略、Vue优化技巧、性能排查方法。此外,还涉及到JavaScript基础、数组操作和字符串统计等编程题目。
摘要由CSDN通过智能技术生成

一、首屏优化的方法

路由懒加载

如果是SPA(single page web application,单页面应用)优先保证首页加载。(不适用多页面应用)
路由懒加载指的是打包部署时将资源按照对应的页面进行划分,需要的时候加载对应的页面资源,而不是把所有的页面资源打包部署到一块。避免不必要资源加载。

服务端渲染SSR

传统的spa前后端分离,也就是CSR(Client-Side Rendering),渲染页面的过程十分复杂,浏览器输入url地址后,加载html,css,js代码后,还要向服务器请求数据,浏览器收到数据才可以
SSR指的是服务端渲染(Server-Side Rendering),是在服务器端渲染网页内容,并且将渲染后的HTML发送给浏览器,而不是在浏览器端渲染。渲染过程简单,如果是纯H5页面,SSR是性能优化的终极方案,但是会增大服务器压力。ssr其实是一门古老的前端技术,前后端分离后,项目越来越复杂,又发现ssr的好处
ssr和csr各有优缺点,不再述说

App预取

预取数据一般是标题,首页文本,不包括图片,视频
在列表页就开始预取详情页的数据,进入详情页后即可直接渲染,缺点是可能会造成流量浪费

分页

首屏内容尽量少,分页显示

图片懒加载

先加载内容,再加载图片,窗口滚到哪里图片就加载到哪里。另外,注意提前设置图片容器尺寸,避免重排

离线包hybrid

提前下载html、css、js等等文件,当在 App 内打开页面时,webview 使用 `file://` 协议加载本地的 html css js ,然后再 ajax 请求数据,再渲染

二、渲染 10w 条数据

可以针对hr的问题提出自己的反问、质疑。
要主动沟通,表达观点。
后端的问题要用后端的思维去解决

这是什么应用场景。然后判断这个技术方案是否合理,为什么不能分页

自定义中间层

  • 自定义nodejs中间层,获取并拆分10w条数据
  • 前端对接nodejs中间层,而不是服务器
  • 成本比较高

虚拟列表

  • 只渲染可视区域 DOM
  • 其他隐藏区域不渲染,只用一个 <div> 撑开高度
  • 监听容器滚动,随时创建和销毁 DOM
  • 实现起来很复杂,借用第三方lib,如Vue-virtual-scroll-list等等。虚拟列表是一个无奈的选择,能不用就不用,因为实现复杂而效果不一定好(低配手机)

三、文字超出省略

单行文字

#box1 {
    border: 1px solid #ccc;
    width: 100px;
    white-space: nowrap; /* 不换行 */
    overflow: hidden;
    text-overflow: ellipsis; /* 超出省略 */
}

多行文字

#box2 {
    border: 1px solid #ccc;
    width: 100px;
    overflow: hidden;
    display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
    -webkit-box-orient: vertical; /* 设置子元素排列方式 */
    -webkit-line-clamp: 3; /* 显示几行,超出的省略 */
}

四、前端常用的设计模式?什么场景?

设计原则

  • 最重要的思想:开放封闭原则
  • 对外扩展开放
  • 对修改封闭

设计模式

  1. 工厂模式,用一个工厂函数,创建一个实例,封装创建的过程。
  2. 单例模式,提供全局唯一的对象,无论获取多少次
  3. 代理模式,使用者不能直接访问真实数据,而是通过一个代理层来访问
  4. 观察者模式,即常说的绑定事件,如addEventListenler
  5. 发布订阅模式,即常说的自定义事件
  6. 装饰器模式

五、Vue优化

v-if 和 v-show

一般情况下使用 v-if 即可,普通组件的销毁、渲染不会造成性能问题
如果组件创建时需要大量计算,或者大量渲染(如复杂的编辑器、表单、地图等),可以考虑 v-show

v-for 使用 key

key 可以优化内部的 diff 算法。注意,遍历数组时 key 不要使用 index

computed 缓存

computed 可以缓存计算结果,data 不变则缓存不失效。

keep-alive

<keep-alive> 可以缓存子组件,只创建一次。通过 activated 和 deactivated 生命周期监听是否显示状态

局部频繁切换的组件,如 tabs
不可乱用 <keep-alive> ,缓存太多会占用大量内存,而且出问题不好 debug

异步组件

对于体积大的组件(如编辑器、表单、地图等)可以使用异步组件

拆包,需要时异步加载,不需要时不加载
减少 main 包的体积,页面首次加载更快

路由懒加载

对于一些补偿访问的路由,或者组件提交比较大的路由,可以使用路由懒加载

SSR

SSR 让网页访问速度更快,对 SEO 友好。
但 SSR 使用和调试成本高,不可乱用。

六、如果一个 h5 很慢,你该如何排查问题?

通过工具分析性能参数

PS:建议在 Chrome 隐身模式测试,避免其他缓存的干扰
Performance 可以检测到下述的性能指标,并且有网页快照截图

  1. First Paint (FP),从开始加载到浏览器首次绘制像素到屏幕上的时间,也就是页面在屏幕上首次发生视觉变化的时间。输入url到页面发生变化
  2. First Contentful Paint(FCP),浏览器首次绘制来自 DOM 的内容的时间,内容必须是文本、图片(包含背景图)、非白色的 canvas 或 SVG,也包括带有正在加载中的 Web 字体的文本。输入url到文字或图片开始被渲染
  3. NetWork 可以看到各个资源的加载时间
  4. First Meaningful Paint(FMP),页面的主要内容绘制到屏幕上的时间。这是一个更好的衡量用户感知加载体验的指标,但无法统一衡量,因为每个页面的主要内容都不太一致。出现有意义的渲染,弃置不用,没有技术手段判断,改用lcp
  5. DomContentLoaded(DCL),即 DOMContentLoaded 触发时间,DOM 全部解析并渲染完。页面dom渲染完成
  6. Largest Contentful Paint(LCP),可视区域中最大的内容元素呈现到屏幕上的时间,用以估算页面的主要内容对用户可见时间。最重要,最大的内容被渲染出来
  7. Load(L),即 window.onload 触发时间,页面内容(包括图片)全部加载完成。

识别问题

网页慢,到底是加载慢,还是渲染慢?—— 分清楚很重要,因为前后端不同负责。
如果FP加载很慢,就是请求文件加载慢,如果是FP到L很慢,就是页面渲染慢
除了浏览器自带的performance工具,还有lighthouse工具,会生成一份详细明了的性能测试报告

解决问题

加载慢

  • 优化服务端接口
  • 使用 CDN
  • 压缩文件
  • 拆包,异步加载

渲染慢(可参考“首屏优化”)

  • 根据业务功能,继续打点监控
  • 如果是 SPA 异步加载资源,需要特别关注网络请求的时间

持续跟进

分析、解决、测试,都是在你本地进行,网站其他用户的情况你看不到。
所以要增加性能统计,看全局,不只看自己。

七、写一个函数,实现 Array flatten 扁平化,只减少一个嵌套层级

        let arr=[1, 2, [3, 4, [100, 200], 5], 6];
        let newArr=[];
        arr.forEach(item=>{
            if(Array.isArray(item)){
                item.forEach(i=>{
                    newArr.push(i)
                })
            }else{
                newArr.push(item)
            }
        })
        console.log(newArr)//[1, 2, 3, 4, Array(2), 5, 6]
        let arr=[1, 2, [3, 4, [100, 200], 5], 6];
        let newArr=[];
        arr.forEach(item=>{
            newArr=newArr.concat(item);
        })
        console.log(newArr)//[1, 2, 3, 4, Array(2), 5, 6]

追问:彻底扁平化数组

        let arr=[1, 2, [3, 4, [100, 200], 5], 6];
        let newArr=[];
        newArr=arr.toString()
        newArr=newArr.split(",")
        console.log(newArr)//['1', '2', '3', '4', '100', '200', '5', '6']

八、实现一个 `getType` 函数,传入一个变量,能准确的获取它的类型

        function getType(data){
            const originData=Object.prototype.toString.call(data);
            const beforeIdex=originData.indexOf(" ");
            const type=originData.slice(beforeIdex,-1).toLowerCase();
            return type;
        }
        console.log(getType("1"))//string

九、手写new过程

  1. 创建一个空的简单JavaScript对象即{}
  2. 链接该对象(即设置该对象的构造函数)到另一个对象。
  3. 将步骤1新创建的对象作为this的上下文context
  4. 如果该函数没有返回对象,则返回步骤1创建的对象。
        function _new(base,...args){
            var obj = {};
            obj.__proto__ = base.prototype;
            base.apply(obj, args);
            return obj;
        }

十、offsetHeight scrollHeight clientHeight 区别

offset是偏移量的意思(border-box)
offsetLeft:距离body或者有定位的父元素的左侧距离,会包含父元素的边框值
offsetTop:距离body或者有定位的父元素的上侧距离,会包含父元素的边框值
offsetHeight:包括border、padding、content的元素高度
offsetWidth: 包括border、padding、content的元素宽度
offsetParent:返回带有定位的父元素,没有则返回body

client是可视区的意思(content-box)
clientWidth:客户端宽度,width+paddding
clientHeight:客户端高度,height+padding
clientLeft:左边框大小,border
clientTop:上边框大小,border
e.clientX:鼠标距离浏览器窗口可视区X坐标
e.clientY:鼠标距离浏览器窗口可视区Y坐标

e.pageX:鼠标距离页面X坐标,滚动条
e.pageY:鼠标距离页面Y坐标,滚动条

e.screenX:鼠标距离电脑屏幕X坐标
e.screenY:鼠标距离电脑屏幕Y坐标

scroll是滚动的意思
scrollHeight:内容的高度,内容有可能超过盒子高度,content+padding
scrollWeight:内容的宽度,内容有可能超过盒子宽度,content+padding
scrollTop:被隐去的内容高度

十一、[10 , 20 , 30].map(parseInt)

结果:[10 , NaN , NaN] 

parseInt('1', 0) // 1 ,radix === 0 按 10 进制处理
parseInt('2', 1) // NaN ,radix === 1 非法(不在 2-36 之内)
parseInt('3', 2) // NaN ,2 进制中没有 3

十二、文字超出省略,用哪个 CSS 样式

单行文字

```css
#box1 {
    border: 1px solid #ccc;
    width: 100px;
    white-space: nowrap; /* 不换行 */
    overflow: hidden;
    text-overflow: ellipsis; /* 超出省略 */
}
```

多行文字

```css
#box2 {
    border: 1px solid #ccc;
    width: 100px;
    overflow: hidden;
    display: -webkit-box; /* 将对象作为弹性伸缩盒子模型显示 */
    -webkit-box-orient: vertical; /* 设置子元素排列方式 */
    -webkit-line-clamp: 3; /* 显示几行,超出的省略 */
}
```

十三、如何统一监听 Vue 组件报错?

window.onerror

可以监听当前页面所有的 JS 报错,jQuery 时代经常用。<br>
注意,全局只绑定一次即可。不要放在多次渲染的组件中,这样容易绑定多次。

 errorCaptured 生命周期

会监听所有下级组件的错误。可以返回 `false` 阻止向上传播,因为可能会有多个上级节点都监听错误。

errorHandler

全局的错误监听,所有组件的报错都会汇总到这里来
errorHandler 会阻止错误走向 window.onerror 
组件内的异步错误 `errorHandler` 监听不到,还是需要 `window.onerror`

十四、定义一个函数,实现数组的旋转。

<!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>
</head>
<body>
    <script>
        let arr1 = [0, 1, 2, 3, 4, 5, 6, 7]
        let arr2 = [0, 1, 2, 3, 4, 5, 6, 7]

        function fn1(arr,key){
            if(key==arr.length){
                console.log(arr)
                return
            }
            for (let k in arr){
                if(k <= key+1){
                    let n=arr.shift();
                    arr.push(n)
                }
            }
            console.log(arr)
        }
        fn1(arr1,3)//[5, 6, 7, 0, 1, 2, 3, 4]

        function fn2(arr,key){
            if(key==arr.length){
                console.log(arr)
                return
            }
            let arr1=arr.slice(0,key+2)
            let arr2=arr.slice(key+2,arr.length)
            console.log(arr2.concat(arr1))
        }
        fn2(arr2,3)//[5, 6, 7, 0, 1, 2, 3, 4]
    </script>
</body>
</html>

十五、二分法

    function binarySearch(arr,target) {
        const length = arr.length
        if (length === 0) return -1

        let startIndex = 0 // 开始位置
        let endIndex = length - 1 // 结束位置

        while (startIndex <= endIndex) {
            const midIndex = Math.floor((startIndex + endIndex) / 2)
            const midValue = arr[midIndex]
            if (target < midValue) {
                // 目标值较小,则继续在左侧查找
                endIndex = midIndex - 1
            } else if (target > midValue) {
                // 目标值较大,则继续在右侧查找
                startIndex = midIndex + 1
            } else {
                // 相等,返回
                return midIndex
            }
        }

        return -1
    }
    let a=binarySearch([0,1,3,4,5,7,9],5);
    console.log(a)
    function binarySearch2(arr, target, startIndex, endIndex) {
        const length = arr.length
        if (length === 0) return -1

        // 开始和结束的范围
        if (startIndex == null) startIndex = 0
        if (endIndex == null) endIndex = length - 1

        // 如果 start 和 end 相遇,则结束
        if (startIndex > endIndex) return -1

        // 中间位置
        const midIndex = Math.floor((startIndex + endIndex) / 2)
        const midValue = arr[midIndex]

        if (target < midValue) {
            // 目标值较小,则继续在左侧查找
            return binarySearch2(arr, target, startIndex, midIndex - 1)
        } else if (target > midValue) {
            // 目标值较大,则继续在右侧查找
            return binarySearch2(arr, target, midIndex + 1, endIndex)
        } else {
            // 相等,返回
            return midIndex
        }
    }

十六、两数之和

        let arr=[1, 2, 4, 7, 11, 15];
        function fn(arr,sum){
            for(let k1 in arr){
                for(let k2 in arr){
                    if(arr[k1]+arr[k2]==sum){
                        console.log(`第一个数字为${arr[k1]},第一个数字为${arr[k2]}`)
                        return
                    }
                }
            }
        }
        fn(arr,15)
        function findTowNumbers(arr, n){
            const res = []

            const length = arr.length
            if (length === 0) return res

            for (let i = 0; i < length - 1; i++) {
                const n1 = arr[i]
                let flag = false // 是否得到了结果

                for (let j = i + 1; j < length; j++) {
                    const n2 = arr[j]

                    if (n1 + n2 === n) {
                        res.push(n1)
                        res.push(n2)
                        flag = true
                        break
                    }
                }

                if (flag) break
            }

            return res
        }

十七、定义一个函数,将数组种所有的 `0` 都移动到末尾

        let arr=[1, 0, 3, 0, 11, 0]
        function fn(arr){
            let res1=[]
            let res2=[]
            for(let k in arr){
                if(arr[k]===0){
                    res1.push(arr[k])
                }else{
                    res2.push(arr[k])
                }
            }
            return res2.concat(res1)
        }
        console.log(fn(arr))//[1, 3, 11, 0, 0, 0]

十八、统计字符串中庸出现最多的字符

    let str='aabbcccddeeee11223';
    let obj={};
    for(let i=0;i<str.length;i++){
        if(str[i] in obj){
            obj[str[i]]++;
        }else{
            obj[str[i]]=1;
        }
    }
    let max=0;
    let char="";
    for(let k in obj){
        if(obj[k]>max){
            max=obj[k];
            char=k;
        }
    }
    console.log(max,char)//4 'e'

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值