面试题收集


当我们在浏览器中输入一个URL后,发生了什么

前置知识:

URL:Uniform Resource Locator 统一资源定位符。网络上用来标识具体资源的一个地址,包含了用户查找该资源的信息。由协议、服务器地址(域名或IP+端口)、路径和文件名组成。

IP:用来在网络中标记一台电脑的一串数字

递归查询:一种DNS 服务器的查询模式,在该模式下DNS 服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。如果DNS 服务器本地没有存储查询DNS 信息,那么该服务器会询问其他服务器,并将返回的查询结果提交给客户机。

迭代查询:DNS 服务器会向客户机提供其他能够解析查询请求的DNS 服务器地址,当客户机发送查询请求时,DNS 服务器并不直接回复查询结果,而是告诉客户机另一台DNS 服务器地址,客户机再向这台DNS 服务器提交请求,依次循环直到返回查询的结果为止。

  • 1.DNS(Domain Name Server 域名服务器)查询。DNS的作用就是通过域名查询到具体的IP。DNS查询顺序如下,若其中一步查询成功就到进入建立TCP连接的部分

  • 2.TCP 连接。

    前置知识:

    1.SYN:synchronous 建立联机

    2.ACK:acknowledgement 确认

    3.SYN_SENT:请求连接

    4.SYN_RCVD:服务端被动打开后,接收到了客户端的SYN并且发送了ACK时的状态

    • TCP三次握手
      • 第一次握手——客户端将标志位SYN置为1,随机产生一个值seq=j,并将该数据包发送给服务端,进入SYN_SENT状态,等待服务端确认
      • 第二次握手——服务端收到数据包后,将标志位SYN和ACK都置为1,ack=j+1,随机产生一个值seq=k,并将该数据包发给客户端确认连接请求,进入SYN_RCVD状态
      • 第三次握手——客户端收到确认后,检查ack是否为j+1,ACK是否为1,如果正确则将标志位ACK置为1,ack=k+1,并将该数据包发送给服务端。服务端检查ack是否为k+1,ACK是否为1,如果正确则连接建立成功,双方都进入ESTABLISHED状态,完成三次握手,可以进行双工通信
    • 三次握手的好处
      • 发送方(发送带有SYN标志的那一方)可以确认接收方仍然在线,不会因为白发送而浪费资源

    后置知识:

    socket(插座):套接字,一组实现TCP/UDP通信的接口API,也就是说无论TCP还是UDP,通过对socket的编程,都可以实现TCP/UDP通信,作为一个通信链的句柄(标识),它包含网络通信必备的5种信息:

    1.连接的协议

    2.本地主机的IP地址

    3.本地进程的协议端口

    4.远程主机的IP地址

    5.远程进程的协议端口

    端口的作用:一台主机(对应一个IP地址)可以提供很多服务,比如web服务,ftp服务等等。如果只有一个IP,无法区分不同的网络服务,所以我们采用”IP+端口号”来区分不同的服务。

    端口的定义:端口号是标识主机内唯一的一个进程,IP+端口号就可以标识网络中的唯一进程。在我们通常用的Socket编程中,IP+端口号就是套接字。

  • 3.浏览器(应用层)发送HTTP请求 [ 请求经过TCP/IP四层模型的每一层都要进行头封装,加上不同的头部,到达数据链路层封装成帧,传输到接收方 ]

    • 请求报文包含四部分
      • 请求行 (方法 URL HTTP版本)
      • 请求首部(Cookie、Cache—Control、If-None-Match、If-Modified-Since)
      • 空行
      • 请求体
    • HTTP 1.0 :浏览器每次请求都要与服务器建立一个TCP连接
    • HTTP 1.1 : 持久连接和管线化
    • HTTP 2.0 : 多路复用、头部压缩、二进制分帧层、服务器推送
  • 4.服务器处理请求并发送响应 [接收方在数据链路层收到数据包后,经过TCP/IP四层模型的每一层进行头拆解,最后获取到请求报文 ]

    • 响应报文包含四部分
      • 响应行 (HTTP版本 状态码 状态码含义)
      • 响应首部(Expires、Etag、Last-Modified、Cache-Control、Content-Security-Policy)
      • 空行
      • 响应体
  • 5.客户端收到响应,若状态码表示请求成功,就渲染页面 ——浏览器渲染原理

    • 浏览器收到HTML文件并转换为DOM树 ( 字节数据——字符串——Token——Node——DOM)

    • 将CSS文件转换为CSSOM树(字节数据——字符串——Token——Node——CSSOM)

    • DOM 树 + CSSOM树 就生成 Render Tree

    • 涉及知识点:阻塞渲染、重绘、回流

前端性能优化
  • DNS层面

    • DNS查询方面
      • 减少DNS的查询(建议在一个网站里面使用至少2个域,但不多于4个域来提供资源。域名过多会增加DNS查找,域名过少会出现下载资源排队的现象)
      • DNS预解析。通过预解析的方式来预先获得域名的IP。(< link rel=”dns-prefetch” href=”//yuchengkai.cn” >)
  • HTTP请求层面

    • 请求过程优化(HTTP请求优化,减小文件体积)

      • 构建工具性能优化(webpack优化)

        1.不要让 loader 做太多事情。

        • 用 include 或 exclude 来帮我们避免不必要的转译。
        • 选择开启缓存将转译结果缓存至文件系统,则至少可以将 babel-loader 的工作效率提升两倍。
        1
        2
        exclude: /(node_modules|bower_components)/
        loader: 'babel-loader?cacheDirectory=true'

        2.不要放过第三方库( DllPlugin )

        • DLLPlugin 它能把第三方库代码分离开,并且每次文件更改的时候,它只会打包该项目自身的代码。所以打包速度会更快。
        • 在 webpack.dll.config.js 中使用DLLPlugin,把所有的第三方库依赖打包到一个bundle的dll文件里面,还会生成一个名为 manifest.json文件
        • 在 webpack.config.js 中使用DllReferencePlugin,读取vendor-manifest.json文件,看看是否有该第三方库。vendor-manifest.json文件就是有一个第三方库的一个映射而已。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        //webpack.dll.config.js   打包第三方依赖的库文件
        plugins: [
        new webpack.DllPlugin({
        // DllPlugin的name属性需要和libary保持一致
        name: '[name]_[hash]',
        path: path.join(__dirname, 'dist', '[name]-manifest.json'),
        // context需要和webpack.config.js保持一致
        context: __dirname,
        }),
        ]

        //webpack.config.js
        // dll相关配置
        plugins: [
        new webpack.DllReferencePlugin({
        context: __dirname,
        // manifest就是我们第一步中打包出来的json文件
        manifest: require('./dist/vendor-manifest.json'),
        })
        ]

        3.Happypack——将 loader 由单进程转为多进程 (webpack 是单线程的, CPU 是多核的)

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        plugins: [
        ...
        new HappyPack({
        // 这个HappyPack的“名字”就叫做happyBabel,和楼上的查询参数遥相呼应
        id: 'happyBabel',
        // 指定进程池
        threadPool: happyThreadPool,
        loaders: ['babel-loader?cacheDirectory']
        })
        ]

        4.Tree-Shaking ——删除冗余代码

        • 采用es6的模块语法,因为es6的模块采用的是静态分析,也就是从字面量对代码进行分析。适合用来处理模块级别的冗余代码。
        • 粒度更细的冗余代码的去除,往往会被整合进 JS 或 CSS 的压缩或分离过程中。在 webpack4 中,我们是通过配置optimization.minimize 和optimization.minimizer 来自定义压缩相关的操作的。

        5.Code-Spliting ——按需加载

        • 核心方法 :require.ensure(dependencies, callback, chunkName)
        1
        2
        3
        4
        5
        6
        a: function() {
        require.ensure(['./a'], (require) => {
        let a = require('./a');
        //do something
        })
        }
      • Gzip压缩原理

        • Gzip 是高效的,压缩后通常能帮我们减少响应 70% 左右的大小
        • Webpack 中 Gzip 压缩操作的存在,事实上就是为了在构建过程中去做一部分服务器的工作,为服务器分压
        • 开启 Gzip 的做法 :设置 request headers 的 accept-encoding : gzip
      • 图片优化(寻求一个图片质量与性能之间的平衡点)
        • 色彩丰富的图片使用 JPG 格式 (有损压缩,不支持透明)。适用于呈现色彩丰富的图片,在我们日常开发中,JPG 图片经常作为大的背景图、轮播图或 Banner 图出现。但当它处理矢量图形Logo 等线条感较强、颜色对比强烈的图像时,人为压缩导致的图片模糊会相当明显。
        • Logo使用 PNG 格式(无损压缩,支持透明) 。 PNG-8 和 PNG-24,8 位的 PNG 最多支持 256 种颜色,而 24 位的可以呈现约 1600 万种颜色。呈现小的 Logo、颜色简单且对比强烈的图片或背景等。
        • 小图使用Base64格式。通过对图片进行 Base64 编码,我们可以直接将编码结果写入 HTML 或者写入 CSS,从而减少 HTTP 请求的次数。
        • 大量小图标使用雪碧图(CSS Sprites)
        • 尽量使用WebP格式的图片。注意WebP的兼容性处理。
  • 减少网络请求 (缓存和存储)

    • 浏览器的缓存机制
      • 缓存位置
        • Service Worker Cache 运行在浏览器背后的独立线程,一般用于实现缓存功能。传输协议必须是HTTPS。优点是可以自由缓存,并且缓存是持续性的
        • Memory Cache 内存中的缓存,读取高效,但是持续性很短,会随着进程的释放而释放。一旦我们关闭页面,内存中的缓存也就被释放了。
        • Disk Cache 磁盘中的缓存,读取速度慢点,但是较之Memory cache 胜在容量和持续性(存储时效性)上。
        • Push Cache HTTP/2中的内容,当以上三种缓存都没有命中时,它才会被使用。并且缓存时间很短暂,只在会话中存在,一旦会话结束就被释放
      • 缓存策略(强缓存和协商缓存)
        • 强缓存:利用 http 头中的 Expires 和 Cache-Control 两个字段来控制的。在缓存期间,不会再与服务端发生通信。
          • Expires :是一个时间戳,受限于本地时间。继续使用 expires 的唯一目的就是向下兼容
          • Cache-Control:HTTP 1.1出现,优先级更高
        • 协商缓存:浏览器与服务器合作之下的缓存策略。 If-Modified-Since 和 Last-Modified , If-None-Match 和 Etag。
          • Last-Modified 存在两个弊端
          • Etag 在感知文件变化上比 Last-Modified 更加准确,优先级也更高。当 Etag 和 Last-Modified 同时存在时,以 Etag 为准。
    • 离线存储技术
      • Cookie : “维持状态”,它可以携带用户信息,当服务器检查 Cookie 的时候,便可以获取到客户端的状态。同一个域名下的所有请求,都会携带 Cookie。大小只有4K,不适宜用来做存储。
      • Web Storage : Local Storage 与 Session Storage。Web Storage 保存的数据内容和 Cookie 一样,是文本内容,以键值对的形式存在
        • 存储数据:setItem()、读取数据: getItem()、删除某一键名对应的数据: removeItem()、清空数据记录:clear()
        • Local Storage 的特点之一是持久。可以用来存储一些内容稳定的资源。比如图片内容丰富的电商网站会用它来存储 Base64 格式的图片字符串,有的网站还会用它存储一些不经常更新的 CSS、JS 等静态资源。
        • Session Storage 更适合用来存储生命周期和它同步的会话级别的信息
      • IndexDB : 运行在浏览器上的非关系型数据库。它不仅可以存储字符串,还可以存储二进制数据。
  • 渲染层面

    • 浏览器的渲染机制

      • CSS性能方案
        • 把样式表放在<head>中,让页面渐进渲染,尽早呈现视觉反馈,给用户加载速度很快的感觉
        • 不要使用CSS表达式
        • 使用<link>替代@import。区别如下
          • 区别1:link除了引用样式文件,还可以引用图片等资源文件,而import只引用样式文件
          • 区别2:link引用CSS时,在页面载入时同时加载;@import需要页面网页完全载入以后加载。
          • 区别3:link是XHTML标签,无兼容问题;@import是在CSS2.1提出的,低版本的浏览器不支持。
          • 区别4:link支持使用Javascript控制DOM去改变样式;而@import不支持。
      • JS性能方案
        • 把脚本放在页面底部。浏览器解析到script标签时,会暂停构建DOM。为避免阻塞渲染,可使用defer或asyn属性。【 async,加载和渲染后续文档元素的过程将和 script.js 的加载与执行并行进行(异步)。 defer,加载后续文档元素的过程将和 script.js 的加载并行进行(异步),但是 script.js 的执行要在所有元素解析完成之后,DOMContentLoaded 事件触发之前完成。】
        • 减少绑定事件监听的节点,如通过事件委托(事件代理)
        • 尽早处理事件,在DOMContentLoaded即可进行,不用等到load以后
        • 函数的防抖和节流
    • DOM优化

      • 减少DOM操作,涉及到两个进程之间的通信。文档片段和虚拟滚动。
      • 缓存DOM的查询,不要把DOM查询语句放在循环里面
    • 减少重绘和回流

      • 使用transform 替代 top

      • 使用 visibility 替换 display:none

      • CSS 选择器从右往左匹配查找,避免层级过多

      • 将频繁重绘或者回流的节点设置为图层

HTTP和HTTPS

HTTP版本

  • HTTP 1.0 只支持GET POST HEAD 方法

  • HTTP 1.1 加了 OPTIONS PUT DELETE 等方法 长连接 管线化

  • HTTP 2.0 帧 流 多路复用 头部压缩 服务器推送 二进制分帧层

原生DOM操作
  • 获取DOM结点 : getElementById、getElementsByTagName、getElementsByClassName、querySelectorAll

  • 获取父节点、获取子节点 :parentElement、childNodes

  • 新增节点,删除节点 : appendChild 、removeChild

AST
babel
  • 一个JS编译器

  • 很多浏览器目前还不支持ES6的代码,babel的作用就是把浏览器不支持的代码编译成支持的代码

  • 注意:Babel 只是转译新标准引入的语法(语法糖),比如ES6的箭头函数转译成ES5的函数。但是,对于新标准引入的新的原生对象,部分原生对象新增的原型方法,新增的API等(如Set、Promise),这些Babel是不会转译的,需要引入polyfill来解决。

  • shim:将一个新的API引入到一个旧的环境中,而且仅靠旧环境中已有的手段实现

  • polyfill:一个用在浏览器API上的shim。我们通常的做法是先检查当前浏览器是否支持某个API,如果不支持的话就加载对应的polyfill.然后新旧浏览器就都可以使用这个API了。

  • 编译过程

    • 解析(Parsing):将代码字符串解析成抽象语法树(babylon将ES6/ES7代码解析成AST,babylon在babel7中被更名为@babel/parser)。

    • 转换(Transformation):对抽象语法树进行转换操作(babel-traverse对AST进行遍历转译,得到新的AST)。

    • 生成(Code Generation): 根据变换后的抽象语法树再生成代码字符串(新的AST通过 babel-generator转换成ES5)。

Ajax
  • 流程
    • 创建一个ajax对象(拥有一个手机)
    • 连接到服务器(拨号)
    • 发送请求(说)
    • 接收返回值(听)
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
function ajax(url,fnSucc,fnFail){
//创建oAjax对象(拥有一个手机)
if(window.XMLHttpRequest){
var oAjax=new XMLHttpRequest();//非IE6
}else{
var oAjax=new ActiveXObject('MicroSoft.XMLHTTP');//IE6
}

//连接到服务器(拨号)
oAjax.open("GET",url,true);
//发送请求(说)
oAjax.send();
//接收返回值(听)
oAjax.onreadystatechange=function (){
if(oAjax.readyState==4){
if(oAjax.status==200){
fnSucc(oAjax.responseText);
}else{
if(fnFail){
fnFail(oAjax.status);
}
}
}
}
}
  • readyState(服务器和浏览器,进行到哪一步了,请求状态)

    • 0 ——未初始化,还没调用open方法,没有连接到服务器

    • 1——(载入)已调用send()方法,正在发送请求

    • 2——(载入完成)send()方法完成,浏览器已接收到全部响应内容

    • 3——(解析)正在解析响应内容

    • 4——(完成)响应内容解析完成,还要判断是否解析成功

null 和 undefined
  • null 表示”没有对象”,即该处不应该有值。典型用法是:

    • (1) 作为函数的参数,表示该函数的参数不是对象。
    • (2) 作为对象原型链的终点。
  • undefined表示”缺少值”,就是此处应该有一个值,但是还没有定义。典型用法是:

    • (1)变量被声明了,但没有赋值时,就等于undefined。

    • (2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。

    • (3)对象没有赋值的属性,该属性的值为undefined。

    • (4)函数没有返回值时,默认返回undefined。

栈和堆内存

栈会自动分配内存空间,会自动释放,存放的是基本类型,简单的数据段,占据固定大小的空间。

基本类型:null,undefined,number,string,boolean,symbol

堆是动态分配的内存,大小不定也不会自动释放,存放的是引用类型,其地址存放在栈内存中,值存放在堆内存中。

引用类型:Object,Array,Function

瀑布流

参考资料:原生js实现瀑布流效果

基本原理:定位,动态设置元素的top值、left值

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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
<script>
var box = document.getElementById('box');
var items = box.children;
// 定义每一列之间的间隙 为10像素
var gap = 10;
window.onload = function() {
// 一进来就调用一次
waterFall();
// 封装成一个函数
function waterFall() {
// 1- 确定列数 = 页面的宽度 / 图片的宽度
var pageWidth = getClient().width;
var itemWidth = items[0].offsetWidth;
var columns = parseInt(pageWidth / (itemWidth + gap));
var arr = [];
for (var i = 0; i < items.length; i++) {
if (i < columns) {
// 2- 确定第一行
items[i].style.top = 0;
items[i].style.left = (itemWidth + gap) * i + 'px';
arr.push(items[i].offsetHeight);

} else {
// 其他行
// 3- 找到数组中最小高度 和 它的索引
var minHeight = arr[0];
var index = 0;
for (var j = 0; j < arr.length; j++) {
if (minHeight > arr[j]) {
minHeight = arr[j];
index = j;
}
}
// 4- 设置下一行的第一个盒子位置
// top值就是最小列的高度 + gap
items[i].style.top = arr[index] + gap + 'px';
// left值就是最小列距离左边的距离
items[i].style.left = items[index].offsetLeft + 'px';

// 5- 修改最小列的高度
// 最小列的高度 = 当前自己的高度 + 拼接过来的高度 + 间隙的高度
arr[index] = arr[index] + items[i].offsetHeight + gap;
}
}
}
// 页面尺寸改变时实时触发
window.onresize = function() {
waterFall();
};
// 当加载到第30张的时候
window.onscroll = function() {
if (getClient().height + getScrollTop() >= items[items.length - 1].offsetTop) {
// 模拟 ajax 获取数据
var datas = [
"../image/瀑布流/001.jpg",
...
"../image/瀑布流/030.jpg"
];
for (var i = 0; i < datas.length; i++) {
var div = document.createElement("div");
div.className = "item";
div.innerHTML = '<img src="' + datas[i] + '" alt="">';
box.appendChild(div);
}
waterFall();
}

};
};

// clientWidth 处理兼容性
function getClient() {
return {
width: window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth,
height: window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
}
}
// scrollTop兼容性处理
function getScrollTop() {
return window.pageYOffset || document.documentElement.scrollTop;
}
</script>
懒加载
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
<script type="text/javascript">
var imgs = document.querySelectorAll('img');
var lazyload = function(){
var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
var winTop = window.innerHeight;
for(var i=0;i < imgs.length;i++){
if(imgs[i].offsetTop < scrollTop + winTop ){
imgs[i].src = imgs[i].getAttribute('data-src');
}
}
}
function throttle(fn,delay){
let timer=null,context,args;
return function(){
context=this;
args=[...arguments];
if(!timer){
timer=setTimeout(function(){
timer=null;
fn.apply(context,args);
},delay);
}
}
//把页面滚动函数赋值给元素window.onload,在所有元素加载完以后再进行操作。若函数调用时,img.onload没有完成,img元素没有高度
window.onload =window.onscroll = throttle(lazyload,200);
</script>
事件代理
1
2
3
4
5
6
7
8
9
var ulItem = document.getElementById("ulItem");
ulItem.onclick = function(e){
e = e || window.event;//这一行和下一行是为了兼容IE8以及之前版本
var target = e.target || e.srcElement;
if(target.nodeName.toLowerCase() === "li"){
alert(target.innerHTML);
alert("位置为:"+getElementPosition(target).x+","+getElementPosition(target).y);
}
}
文档片段
1
2
3
4
5
6
7
8
9
10
11
//合并 DOM 插入
var listNode = document.getElementById('list');
//要插入 10 个 li 标签,一般情况要进行10次 DOM 插入
var frag=document.createDocumentFragment();
var x,li;
for(x=0;x<10;x++){
li = document.createElement('li');
li.innerHTML = "List item" + x;
frag.appendChild(li);//这个插入不会触发 DOM 操作
}
listNode.appendChild(frag);//这里触发一次DOM插入
前端监控和前端埋点

前言:在线上项目中,需要统计产品中用户行为和使用情况,从而可以从用户和产品的角度去了解用户群体,从而升级和迭代产品,使其更加贴近用户。

用户行为数据可以通过前端数据监控的方式获得,除此之外,前端还需要实现性能监控和异常监控。性能监控包括首屏加载时间、白屏时间、http请求时间和http响应时间。异常监控包括前端脚本执行报错等。

  • 监控:目的是获取用户行为以及跟踪产品在用户端的使用情况,并以监控数据为基础,指明产品优化的方向

    • 数据监控

      • PV/UV:PV(page view),即页面浏览量或点击量。UV:指访问某个站点或点击某条新闻的不同IP地址的人数
      • 用户在每一个页面的停留时间
      • 用户通过什么入口来访问该网页
      • 用户在相应的页面中触发的行为

      统计这些数据是有意义的,比如我们知道了用户来源的渠道,可以促进产品的推广,知道用户在每一个页面停留的时间,可以针对停留较长的页面,增加广告推送等等。

    • 性能监控

      • 不同用户,不同机型和不同系统下的首屏加载时间:首屏时间 = 地址栏输入网址后回车 - 浏览器第一屏渲染完成。影响首屏时间的因素:白屏时间,资源下载执行时间。由于浏览器解析HTML是按照顺序解析的,当解析到某个元素的时候,觉得首屏完成了,就在此元素后面加入< scrip t>计算首屏完成时间
      • 白屏时间:白屏时间 = 地址栏输入网址后回车 - 浏览器出现第一个元素。影响白屏时间的因素:网络、服务端性能,前端页面结构设计。通常认为浏览器开始渲染< body >或者解析完< head >的时间是白屏结束的时间点
      • http等请求的响应时间:TTFB 是 Time to First Byte 的缩写,指的是浏览器开始收到服务器响应数据的时间(后台处理时间+重定向时间),是反映服务端响应速度的重要指标。
      • 静态资源整体下载时间
      • 页面渲染时间:阻塞渲染、重绘和重排
      • 页面交互动画完成时间
    • 异常监控

      ​ 产品的前端代码在执行过程中也会发生异常,及时上报异常情况,可以避免线上故障的发生。异常可以通过try catch的方式捕获,但是比如内存泄漏以及其他偶现的异常难以捕获。

  • 埋点

    • 代码埋点/手动埋点:以嵌入代码的形式进行埋点,比如需要监控用户的点击事件,会选择在用户点击时,插入一段代码,保存这个监听行为或者直接将监听行为以某一种数据格式直接传递给server端。
    • 可视化埋点:通过可视化交互的手段,代替代码埋点。将业务代码和埋点代码分离,提供一个可视化交互的页面,输入为业务代码,通过这个可视化系统,可以在业务代码中自定义的增加埋点事件等等,最后输出的代码耦合了业务代码和埋点代码。
    • 无埋点:无埋点并不是说不需要埋点,而是全部埋点,前端的任意一个事件都被绑定一个标识,所有的事件都别记录下来。通过定期上传记录文件,配合文件解析,解析出来我们想要的数据,并生成可视化报告供专业人员分析因此实现“无埋点”统计。从语言层面实现无埋点也很简单,比如从页面的js代码中,找出dom上被绑定的事件,然后进行全埋点。

    参考:前端监控和前端埋点方案设计


作业帮面经

  • 知道的设计模式有哪些?

1.简单工厂模式:又叫静态工厂方法模式,由一个工厂对象决定创建某一种产品对象类的实例。工厂起到的作用就是隐藏了创建实例的复杂度,只需要提供一个接口,简单清晰。

代码示例

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
47
48
49
50
51
//篮球基类
class Baseketball {
constructor() {
this.intro = '篮球盛行于美国'
}
getMember() {
console.log('每个队伍需要5名队员');
}
getBallSize() {
console.log('篮球很大');
}
}
//足球基类
class Football {
constructor() {
this.intro = '足球在世界范围内很流行';
}
getMember() {
console.log('每个队伍需要11名队员');
}
getBallSize() {
console.log('足球很大')
}
}
//网球基类
class Tennis {
constructor() {
this.intro = '每年有很多网球系列赛';
}
getMember() {
console.log('每个队伍需要1名队员');
}
getBallSize() {
console.log('网球很小');
}
}
//运动工厂
class SportFactory {
static create(name) {
switch (name) {
case 'NBA':
return new Baseketball();
case 'worldCup':
return new Football();
case 'FrenchOpen':
return new Tennis();
}
}
}

SportFactory.create('worldCup').getMember();

2.单例模式:核心保证全局只有一个对象可以访问,比如全局状态管理vuex、redux等

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Singleton{
constructor(){}
}

Singleton.getInstance = (function(){
let instance;
return function(){
if(!instance){
instance = new Singleton();
}
return instance;
}
})();

let s1 = new Singleton.getInstance();
let s2 = new Singleton.getInstance();
console.log(s1 === s2);

3.代理模式:为了控制对对象的访问,不让外部直接访问到对象。比如事件代理,vue 3.0 proxy

4.发布订阅模式:通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。比如 vue 2.0 响应式

5.适配器模式:解决两个接口不兼容的情况,不需要改变已有接口,通过包装一层的方式实现两个接口的正常协作。比如 vue 的computed 转换过程。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Plug{
getName(){
return '港版插头'
}
}

class Target{
constructor(){
this.plug = new Plug();
}
getName(){
return this.plug.getName() + '适配器转二脚插头'
}
}
let target = new Target();
console.log(target.getName());

6.装饰模式:不需要改变已有接口,作用是给对象添加功能。

代码示例

1
2
3
4
5
6
7
8
9
10
11
12
function readonly(target,key,descriptor){
descriptor.writable=false;
return descriptor;
}

class Meal {
@readonly
entree = 'steak';
}

var dinner = new Meal();
dinner.entree = 'salmon';

7.外观模式:提供了一个接口,隐藏了内部逻辑,更加方便外部调用。比如实现一个兼容多种浏览器添加事件的方法

代码示例

1
2
3
4
5
6
7
8
9
10
11
function addEvent(elm,evType,fn,useCapture){
if(elm.addEventListener){
elm.addEventListener(evType,fn,useCapture);
return true;
}else if(elm.attachEvent){
let r = elm.attachEvent("on"+evType,fn);
return r;
}else{
elm["on"+evType]=fn;
}
}
  • 写个判断是不是回文数的函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function isPalindrome(num){
if(num<0) return false
return Array.from(String(num)).reverse().join('')===String(num)
}
console.log(isPalindrome(123));

function isPalindrome1(num){
if(num<0 || (num!=0 && num % 10 ===0)){
return false
}else if(num>=0 && num<10){
return true
}
let newNum=0,original=num,flag;
while(num!=0){
newNum = num % 10 + newNum * 10;
num = Math.floor(num /10);
}
flag = newNum === original ? true : false;
return flag;
}
console.log(isPalindrome1(121));
  • 项目中的前后端鉴权如何做的(token),token失效?

参考:

前后端的几种鉴权方式

  • 什么是cookie?cookie是如何操作的,与session的区别?

参考:

什么是cookie,什么是session,cookie和session的区别

  • 常见的类数组?什么是类数组?写代码实现一个类数组

  • 浏览器缓存,Etag与lastModified 为什么同时需要,一个不行吗?

  • 常见的跨域

  • 如何实现滚动数据无缝加载?

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
47
//html代码
<div id="box">
<ul id="list1">
<li>星期一</li>
<li>星期二</li>
<li>星期三</li>
<li>星期四</li>
<li>星期五</li>
<li>星期六</li>
<li>星期天</li>
</ul>
<ul id="list2"></ul>
</div>

//js
<script type="text/javascript">
var box=document.getElementById("box");
var l1=document.getElementById("list1");
var l2=document.getElementById("list2");
l2.innerHTML=l1.innerHTML;//克隆list1的数据,使得list2和list1的数据一样
function scrollup(){
if(box.scrollTop>=l1.offsetHeight){ //滚动条距离顶部的值恰好等于list1的高度时,达到滚动临界点,此时将让scrollTop=0,让list1回到初始位置,实现无缝滚动
box.scrollTop=0;
}else{
box.scrollTop++;
}
}

var scrollMove=setInterval(scrollup,30);//数值越大,滚动速度越慢

//鼠标经过时,滚动停止
box.onmouseover=function(){
clearInterval(scrollMove)
}

//鼠标离开时,滚动继续
box.onmouseout=function(){
scrollMove=setInterval(scrollup,30);
}
</script>

//css
<style>
*{margin:0; padding:0;}
ul,li{list-style: none;}
#box{ width: 200px; height:90px; overflow:hidden; border:1px solid red;}
</style>
  • 写代码:找出一个多维数组中,出现次数大于m的项
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function findGtM(arr,m){
arr = arr.flat(Infinity);
let map=new Map(),count=0,res=[];
for(let i=0;i<arr.length;i++){
if(map.has(arr[i])){
count=map.get(arr[i]);
count++;
map.set(arr[i],count)
}else {
map.set(arr[i],1)
}
}
console.log(map)
map.forEach((value,key)=>{
if(value>m){
res.push(key);
}
})
return res;
}
console.log(findGtM([1,2,3,4,1,2,2,7,5,1],2))