V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
hutchins
V2EX  ›  求职

考验多方面技能的前端面试题(一)

  •  
  •   hutchins · 2018-10-09 15:48:34 +08:00 · 2170 次点击
    这是一个创建于 1997 天前的主题,其中的信息可能已经有所发展或是发生改变。

    考验多方面技能的前端面试题(一)

    顺序上传文件

    其实就是顺序执行异步操作。在这里先抛出一个小问题,用递归方法计算链表的长度。链表是最简单的动态数据结构,也是非常适用于递归解法的一种数据结构。计算其长度也应该是基本技能了,在这里直接贴出代码

    function calNodeListLength(head) {
        // 首先要对临界值进行判断
        if (head===null) {
            return 0
        }
        return 1 + calNodeListLength(head.next)
    }
    

    那顺着这个思路去想,我们就有了一种顺序上传文件的实现方法:上传 n 个文件,等到一个头文件上传完毕后调用函数再处理 n-1 个文件,然后等到 n-1 文件中的头文件传递完毕后再去处理剩下 n-2 个文件....

    // 使用回调函数
    function uploadFiles(files) {
        // 这里使用伪代码
        ajax({
            // 要传递的数据
            data: files[0],
            success: function() {
                // 删除传递过的文件
                files.shift()
                // 处理剩下的文件
                uploadFiles(files)
            }
        })
    }
    // 使用 promise 的回调
    function uploadFile(files) {
        new Promise(resolve=>{
            ajax({
                data: files[0],
                success: function() {
                    resolve()
                }
            })
        }).then(res=>{
            files.shift()
            uplpadFiles(files)
        })
    }
    

    以上方法只是顺着递归求链表的思路延伸出来的,其实也有很多更好的方法,举这个例子也是想让自己和阅读此文章的人对数据结构和算法更加的重视,学习好数据结构和算法是基本,从而也要提高将其运用到实际的项目中的能力,知识永远也学不完,更多的还是要提高自己运用已学知识去解决问题的能力。当然也不是说新知识不重要,比如接下来的实现方法就是用的「新知识和新语法」

    ;(async () => {
        try {
            for (let file of files) {
                // uploadFile 返回的是一个 promise
                await uploadFile(file)
            }
        } catch (e) {
            console.log('嘤嘤嘤')
        }
    })()
    

    从输入 url 到页面加载发生了什么「 HTTP 」

    浏览器先根据 url 去查找缓存

    首先检查是否有缓存。比如在 chrome 的网址中输入 chrome://cache ,就可以看到缓存文件,如果找不到文件则证明无缓存

    如果有缓存,判断缓存是否过期。一般通过两个字段,HTTP 1.0 中的 expires 「表明过期时间,但是因为客户端的时间和服务器时间有可能不同,会导致出现差错」和 HTTP 1.1 中的 cache-control ,首先查看是否有 cache-control 如果没有,则根据 expires 比较过期时间,如果有 cache-control 则根据 cache-control 的属性 max-age 「 设置缓存的最大有效时间,max-age 会覆盖掉 expires」或 s-maxage 「用于代理缓存,会覆盖掉 max-ageexpires」,如果没有过期,则就使用客户端缓存,也就是「强缓存」。

    强缓存存在的问题及解决方案

    DNS 解析 url 获取对应 ip

    如果没有缓存或者缓存已经过期,浏览器会发出一个 DNS 请求到本地 DNS 服务器,本地服务器也会首先去查看有没有缓存记录,如果有的话直接将 ip 返回。如果没有缓存,这里以 www.baidu.com 为例说明,事实上浏览器在请求 DNS 服务器时的真正网址是 www.baidu.com. , 最后的 . 对应的就是根服务器,因为每次默认请求都会添加 . , 因此为了方便用户都会将其省略掉。所以网址的解析过程就为 . -> com. -> baidu.com. -> www.baidu.com. ,由根服务器开始查询,如果没有找到则向 com 域名服务器查询,直到查询到。

    阮一峰 DNS 原理入门

    现在一般 DNS 服务器优化都会用到「负载均衡」,引用阿里云的一句话

    多台服务器经常被用做提供同一个服务,从而减轻单台服务器所承受的访问压力。这样分散每台服务器上的压力,将访问流量分配到多台服务器的方法就是负载均衡。

    打开一个 socket 与目标 ip 地址连接

    socket 「套接字」 : 两个进程要进行通讯基本前提就是能够唯一标识一个进程,PID 只在本地中唯一,网络中冲突非常大。因此网络中是使用 ip 地址 + 协议 + 端口号唯一标识网络中的进程。socket 也就是由这三个部分组成。

    Tcp 三次握手连接

    • 客户端发送一个 SYN 到服务器端口 「你好,听的到吗」
    • 服务器端发送 ACK + SYN 到客户端 「嗯可以,你能听到我吗」
    • 客户端发送 ACK 到服务器 「嗯可以」
    发送 HTTP 请求

    浏览器向服务器发送请求,服务器根据响应头判断是否包含缓存验证信息。利用的是 Last-ModifiedIf-Modified-SinceETagIf-None-Match , 浏览器第一次请求资源的时候,服务器会在其响应头上添加 last-mdified 或者 etag , 当浏览器再次请求此资源时,会在请求头上附带上 if-modified-sinceif-none-match ,其值就是对应的服务器响应值,然后服务器会比对对应值去判断文件是否改变,if-modified-since 是去比对文件的最后修改时间,但有可能存在文件时间修改,单文件内容没有变的情况,所以出现了 etag 「内容的标识符,内容修改就会变化」服务器判断,如果文件没有改变,服务器返回 304,不会返回资源,浏览器收到响应后再去读取缓存

    如果已过期则服务器重新读取发送资源或者操作数据库,然后返回相应信息

    如果数据传输完毕,浏览器关闭 TCP 连接

    TCP 四次挥手断开连接,主动方可以是客户端也可以是服务器端,这里以浏览器为主动方解释

    • 浏览器发送 FIN 到服务器端 「信息发送完毕」
    • 服务器发送 ACK 到客户端 「收到,但是我这边还要再处理一下,等我消息」
    • 服务器发送 FIN 到客户端 「 OK,我这边数据也发完了,准备关闭了」
    • 客户端发送 ACK 到服务器端 「 OK,断吧」,发送后客户端会再等待一下,如果服务端没有回复则证明服务端已关闭,那么客户端此时也会关闭
    处理响应数据

    首先浏览器检查响应状态码,如果资源可缓存,则进行缓存,然后根据资源类型相应的去处理

    假设资源为 HTML,接下来处理 HTML 资源

    浏览器解析和渲染过程可以同时进行,浏览器解析 HTML 文档构建 DOM 树, 解析 CSS 资源构建 COM 树,如果遇到图片、样式表、js 文件等启动下载。然后根据 DOM 树和 COM 树,DOM 与 COM 构造呈现树,然后进行布局并渲染绘制到屏幕上。

    这里涉及到 「回流」 与 「重绘」,当盒模型的位置大小等属性确定后,浏览器会计算其大小和位置,这就是「回流」,当盒模型的外观和风格等不会影响布局的属性确定后,就会引发 「重绘」。

    展示页面

    关于 vue 2.0 版本的双向绑定

    思路

    可以分为三个步骤

    • 输入框以及文本节点与 data 中的数据绑定
    • 输入框内容变化时,data 中的数据同步变化,这步实现了 view => model
    • data 中的数据变化时,文本节点的内容同步变化,实现 model => view
    数据绑定

    通过 documentFragment 将挂载目标的所有子节点进行「劫持」,经过统一处理之后再整体插入挂载目标

    将「劫持」到的 dom 对象进行遍历,将有 v-model 属性名的元素和被 {} 包裹的文本提取出来赋值给 Vue 「实例对象」 的 data 对象中

    V => M

    如果 input 框上 v-model 属性,那么会调用其 change 方法,实时更新 data ,在「实例对象」的 data 对象中创建的值会用 defineProperty对其 setget 进行改写,在此步骤中 set 只需要更新属性的值。

    M => V

    使用订阅发布模式「观察者模式」,发布者发布通知 => 主题对象「包含所有订阅者」收到通知推送给订阅者=>订阅者执行相应操作

    因此,data 中每有一个属性被创建,相应也会创建其主题对象 dep , 在处理「劫持」到的 html 时,每当与 data 中的属性绑定会在其对应的 dep 对象中添加一个订阅者 watcher,其中需要通过 Dep 定义一个全局的 target 属性暂存新建的 watcher ,添加后移除。 watcher 触发实例对象的 get 方法从而将 Dep.target 存储进去

    作用域原理及闭包

    图解作用域与闭包

    其实我相信大家看过大大小小的面试图谱和面试题都已经很多遍了,但是从我个人而言,我只看和背是很难记住和记牢的,经常是上个星期记忆过的知识这个星期就说不连贯了,只能再找到别人的面试图谱去再去背,再去理解。

    因此我推荐大家将看到的很好的总结结合自己的理解再去撰写一遍,碰到模糊的再去查阅相关的技术文章。通过整理别人优秀的总结,查阅相关模糊的知识后,自己就也能根据自己的理解去将一些知识点进行一些专业的描述。最后整理出适合自己的面试图谱,一方面提高了自己的写作水平,一方面也让知识的掌握不是须于表面,难以从自己的口中系统化的讲解出来。

    觉得还可以的话可以给个小小的 star~

    原文

    5 条回复    2018-10-10 09:32:26 +08:00
    carlclone
        1
    carlclone  
       2018-10-09 16:14:58 +08:00 via Android
    赞👍
    deepkolos
        2
    deepkolos  
       2018-10-10 08:36:26 +08:00
    这样的顺序上传只是为了避免上传任务顺序和后端接收是顺序不一致, 目的是避免文件间顺序信息的丢失, 所以可以考虑, 弄一个上传任务池, 设置最大并发上传 N 个, 文件顺序就用过单独的数组来指定就好了, 兼顾了速度和顺序信息
    hutchins
        3
    hutchins  
    OP
       2018-10-10 09:20:48 +08:00 via Android
    @deepkolos 嗯嗯,递归只是一个思路,目的是想要说明要学会把简单的数据结构应用到实际中,肯定有更好的方法。🤗
    deepkolos
        4
    deepkolos  
       2018-10-10 09:29:31 +08:00
    @hutchins 其实我觉得主语应该是任务, 递归只是任务触发的方式, 也可以使用 for + async await 来完成的
    hutchins
        5
    hutchins  
    OP
       2018-10-10 09:32:26 +08:00
    @deepkolos 嗯嗯,递归下面有用 async 和 await 的实现
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   935 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 20:53 · PVG 04:53 · LAX 13:53 · JFK 16:53
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.