【每日前端面经】2024-03-09
欢迎订阅我的前端面经专栏: 每日前端面经
本期题目来源: 牛客
文件上传
-
传统表单上传
- 浏览器支持度高
- 无需额外的 JS 代码
- 无法自定义上传页面
- 不适合实现高级功能
- 页面刷新后需要重新选择文件
-
XMLHttpRequest对象
jsconst fileInput = document.getElementById("fileInput"); const uploadButton = document.getElementById("uploadButton"); uploadButton.addEventListener("click", () => { const file = fileInput.files[0]; const xhr = new XMLHttpRequest(); xhr.open("POST", "/upload", true); xhr.onreadystatechange = () => { if (xhr.readySyaye === 4 && xhr.status === 200) { console.log("上传成功"); } } const formData = new FormData(); formData.append("file", file); xhr.send(formData); });
- 支持自定义上传页面和进度显示
- 可以实现高级功能,如断点续传、取消上传等
- 使用回调函数处理请求状态,代码繁琐
- 不支持 Promise,需要手动处理异步操作
-
Fetch
jsconst fileInput = document.getElementById("fileInput"); const uploadButton = document.getElementById("uploadButton"); uploadButton.addEventListener("click", () => { const file = fileInput.files[0]; const formData = new FormData(); formData.append("file", file); try { const response = await fetch("/upload", { method: "POST", body: formData }); if (response.ok) { console.log("上传成功"); } else { console.error("上传失败"); } } catch (error) { console.error("上传出错", error); } });
- 使用 Promise,便于理解
- 支持自定义上传界面和进度显示
- 可以实现高级功能,如断点续传、取消上传等
- 浏览器兼容性相对弱些
-
第三方库
按需加载
指当用户触发了动作时才加载对应的功能。触发的动作是要看具体的业务场景而言,包括但不限于以下几种情况:鼠标点击、输入文字、拉动滚动条、鼠标移动、窗口大小更改等。加载的文件,可以是JS、图片、CSS、HTML等
HTML 按需加载
按需解析 HTML,就是页面一开始不解析 HTML,根据需要来解析 HTML。解析 HTML 都是需要一定事件,特别是 HTML 中包含有 img 标签,引用了背景图片时,如果一开始就解析,那么势必会增加请求数。常见的有对话框、拉菜单、多标签的内容展示等,这些一开始是不需要解析,可以按需解析。实现按需解析,首先要用 <script type='text/x-template'>html</script>
这个标签来忽略对 HTML 的解析。然后根据触发的动作,把 script 里面的 HTML 获取出来,填充到对应的节点中
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>按需解析HTML</title>
</head>
<body>
<script type="text/x-template" id="suc_subscription">
<!--假设这里的样式box-dytz 引用了一张背景图--->
<div>
<!--这里暂且用这张图片作为测试,实际中,大家可以替换为任何图片-->
<img src="http://tid.tenpay.com/wp-content/uploads/2012/12/按需加载.jpg" />
</div>
</script>
<div id="success_dilog"></div>
<input type="button" value="点我展示HTML" onclick="showHTML()" />
<script>
function showHTML(){
document.getElementById('success_dilog').innerHTML = document.getElementById('suc_subscription').innerHTML;
}
</script>
</body>
</html>
图片按需加载
按需加载图片,就是让图片默认开始不加载,而且在接近可视区域范围时,再进行加载,也称之为懒加载
触发方式为当滚动条拉动到某个位置时,即将进入可视范围的图片需要加载
html
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>图片按需加载</title>
</head>
<body>
<style>
li {float:left;width:200px;}
</style>
<div style="widh:100%;height:1200px;border:1px solid #000">这里是空白的内容,高度为900像素,目的是方便出现滚动条</div>
<ul style="width:600px;">
<li> <img width="158" height="158" data-src="http://pic6.nipic.com/20100423/4537140_133035029164_2.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223245.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://www.bz55.com/uploads/allimg/100729/095KS455-0.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://www.hyrc.cn/upfile/3/200611/1123539053c7e.jpg"/> </li>
<li> <img width="158" height="158" data-src="http://www.mjjq.com/blog/photos/Image/mjjq-photos-903.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://c.cncnimg.cn/000/954/1416_2_b.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://www.jiujiuba.com/xxpict2/picnews/62223231.jpg" /> </li>
<li> <img width="158" height="158" data-src="http://www.mjjq.com/pic/20070530/20070530043314558.jpg" /> </li>
</ul>
<script>
var API = {
/**
* 兼容Mozilla(attachEvent)和IE(addEventListener)的on事件
* @param {String} element DOM对象 例如:window,li等
* @param {String} type on事件类型,例如:onclick,onscroll等
* @param {Function} handler 回调事件
*/
on: function(element, type, handler) {
return element.addEventListener ? element.addEventListener(type, handler, false) : element.attachEvent('on' + type, handler)
},
/**
* 为对象绑定事件
* @param {Object} object 对象
* @param {Function} handler 回调事件
*/
bind: function(object, handler) {
return function() {
return handler.apply(object, arguments)
}
},
/**
* 元素在页面中X轴的位置
* @param {String} element DOM对象 例如:window,li等
*/
pageX: function(El) {
var left = 0;
do {
left += El.offsetLeft;
} while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
return left;
},
/**
* 元素在页面中Y轴的位置
* @param {String} element DOM对象 例如:window,li等
*/
pageY: function(El) {
var top = 0;
do {
top += El.offsetTop;
} while(El.offsetParent && (El = El.offsetParent).nodeName.toUpperCase() != 'BODY');
return top;
},
/**
* 判断图片是否已加载
* @param {String} element DOM对象 例如:window,li等
* @param {String} className 样式名称
* @return {Boolean} 布尔值
*/
hasClass: function(element, className) {
return new RegExp('(^|\\s)' + className + '(\\s|$)').test(element.className)
},
/**
* 获取或者设置当前元素的属性值
* @param {String} element DOM对象 例如:window,li等
* @param {String} attr 属性
* @param {String} (value) 属性的值,此参数如果没有那么就是获取属性值,此参数如果存在那么就是设置属性值
*/
attr: function(element, attr, value) {
if (arguments.length == 2) {
return element.attributes[attr] ? element.attributes[attr].nodeValue : undefined
}
else if (arguments.length == 3) {
element.setAttribute(attr, value)
}
}
};
/**
* 按需加载
* @param {String} obj 图片区域元素ID
*/
function lazyload(obj) {
this.lazy = typeof obj === 'string' ? document.getElementById(obj) : document.getElementsByTagName('body')[0];
this.aImg = this.lazy.getElementsByTagName('img');
this.fnLoad = API.bind(this, this.load);
this.load();
API.on(window, 'scroll', this.fnLoad);
API.on(window, 'resize', this.fnLoad);
}
lazyload.prototype = {
/**
* 执行按需加载图片,并将已加载的图片标记为已加载
* @return 无
*/
load: function() {
var iScrollTop = document.documentElement.scrollTop || document.body.scrollTop;
// 屏幕上边缘
var iClientHeight = document.documentElement.clientHeight + iScrollTop;
// 屏幕下边缘
var i = 0;
var aParent = [];
var oParent = null;
var iTop = 0;
var iBottom = 0;
var aNotLoaded = this.loaded(0);
if (this.loaded(1).length != this.aImg.length) {
var notLoadedLen = aNotLoaded.length;
for (i = 0; i < notLoadedLen; i++) {
iTop = API.pageY(aNotLoaded[i]) - 200;
iBottom = API.pageY(aNotLoaded[i]) + aNotLoaded[i].offsetHeight + 200;
var isTopArea = (iTop > iScrollTop && iTop < iClientHeight) ? true : false;
var isBottomArea = (iBottom > iScrollTop && iBottom < iClientHeight) ? true : false;
if (isTopArea || isBottomArea) {
// 把预存在自定义属性中的真实图片地址赋给src
aNotLoaded[i].src = API.attr(aNotLoaded[i], 'data-src') || aNotLoaded[i].src;
if (!API.hasClass(aNotLoaded[i], 'loaded')) {
if ('' != aNotLoaded[i].className) {
aNotLoaded[i].className = aNotLoaded[i].className.concat(" loaded");
}
else {
aNotLoaded[i].className = 'loaded';
}
}
}
}
}
},
/**
* 已加载或者未加载的图片数组
* @param {Number} status 图片是否已加载的状态,0代表未加载,1代表已加载
* @return Array 返回已加载或者未加载的图片数组
*/
loaded: function(status) {
var array = [];
var i = 0;
for (i = 0; i < this.aImg.length; i++) {
var hasClass = API.hasClass(this.aImg[i], 'loaded');
if (!status) {
if (!hasClass)
array.push(this.aImg[i])
}
if (status) {
if (hasClass)
array.push(this.aImg[i])
}
}
return array;
}
};
// 按需加载初始化
API.on(window, 'load', function () {new lazyload()});
</script>
</body>
</html>
JS 代码按需加载
当某个动作触发后再执行相应的 JS。按需加载 JS 可以应用在下列场景:执行耗时比较久的 JS 代码,加载许多图片,加载 iframe,加载广告等
Webpack 配置
- webpack: Webpack 是一个模块打包器。在 Webpack 中会将前端的所有资源文件(js/json/css/img/less/...)都作为模块处理。它将根据模块的依赖关系进行分析,生成对应的资源
- 五个核心概念:
- 入口(entry):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始
- 输出(output):在哪里输出文件,以及如何命名这些文件
- Loader:处理那些非 JavaScript 文件(webpack 自身只能解析 JavaScript和json)
- 插件(plugins): 执行范围更广的任务,从打包到优化都可以实现
- 模式(mode): 有生产模式 production 和开发模式 development
- 对loader的理解: webpack 本身只能处理 JS、JSON 模块,如果要加载其他类型的文件(模块),就需要使用对应的 loader 。它本身是一个函数,接受源文件作为参数,返回转换的结果。loader 一般以 xxx-loader 的方式命名,xxx 代表了这个 loader 要做的转换功能,比如 css-loader
- 对 plugins 的理解: 插件可以完成一些 loader 不能完成的功能
- 配置文件: webpack.config.js -> 用于存储 webpack 配置信息
异步组件
在传统的 Vue.js 开发中,组件是在应用程序启动时就立即加载的。这种方式虽然简单,但是会导致应用程序的初始加载时间变长,影响用户体验。为了提高应用程序的性能和加载速度,Vue.js 提供了异步组件
异步组件是一种延迟加载组件的方式,即只有在需要使用该组件时才会进行加载。Vue.js 异步组件的实现方式是通过 import() 函数来实现的。在使用异步组件时,需要将组件定义为一个函数,返回一个 import() 函数,该函数会异步加载组件并返回一个 Promise 对象
- 减少应用程序的初始加载时间: 异步组件只有在需要使用该组件时才会进行加载,可以大大减少应用程序的初始加载时间,提高用户体验
- 提高应用程序的性能: 异步组件可以将组件的加载和渲染分开进行,可以提高应用程序的性能,避免不必要的渲染
- 优化代码的可维护性: 异步组件可以将组件按需加载,可以优化代码的可维护性,减少代码的复杂度
Cookie | LocalStorage | SessionStorage
- Cookie: Cookie 是小甜饼的意思。顾名思义,cookie 确实非常小,它的大小限制为4KB左右。它的主要用途有保存登录信息,比如你登录某个网站市场可以看到"记住密码",这通常就是通过在 Cookie 中存入一段辨别用户身份的数据来实现的
- localStorage: localStorage 是 HTML5 标准中新加入的技术,它并不是什么划时代的新东西。早在 IE 6 时代,就有一个叫 userData 的东西用于本地存储,而当时考虑到浏览器兼容性,更通用的方案是使用 Flash。而如今,localStorage 被大多数浏览器所支持,如果你的网站需要支持 IE6+,那以 userData 作为你的 polyfill 的方案是种不错的选择
- sessionStorage: sessionStorage 与 localStorage 的接口类似,但保存数据的生命周期与 localStorage 不同。做过后端开发的同学应该知道 Session 这个词的意思,直译过来是"会话"。而 sessionStorage 是一个前端的概念,它只是可以将一部分数据在当前会话中保存下来,刷新页面数据依旧存在。但当页面关闭后,sessionStorage 中的数据就会被清空
特性 | Cookie | localStorage | sessionStorage |
---|---|---|---|
生命周期 | 一般由服务器生成,可以设置失效事件 | 除非被清除,否则永久保存 | 仅在当前会话有效,关闭页面或浏览器后会被清除 |
数据大小 | 约为 4k | 约为 5MB | 约为 5MB |
数据通信 | 每次都会携带在 HTTP 头中 | 仅在浏览器中保存 | 仅在浏览器中保存 |
易用性 | 需要二次封装 | 原生接口易用 | 原生接口易用 |
重绘 | 重排
-
重绘:当一个元素的外观发生改变,但没有改变布局,重新把元素外观绘制出来的过程,叫做重绘
-
重排:当DOM的变化影响了元素的几何信息(元素的的位置和尺寸大小),浏览器需要重新计算元素的几何属性,将其安放在界面中的正确位置
- 页面初始渲染,这是开销最大的一次重排
- 添加/删除可见的 DOM 元素
- 改变元素位置
- 改变元素尺寸,比如边距、填充、边框、宽度和高度等
- 改变元素内容,比如文字数量,图片大小等
- 改变元素字体大小
- 改变浏览器窗口尺寸,比如 resize 事件发生时
- 激活 CSS 伪类(例如::hover)
- 设置 style 属性的值,因为通过设置 style 属性改变结点样式的话,每一次设置都会触发一次 reflow
- 查询某些属性或调用某些计算方法:offsetWidth、offsetHeight等,除此之外,当我们调用 getComputedStyle 方法,或者IE里的 currentStyle 时,也会触发重排,原理是一样的,都为求一个即时性和准确性
如何减少重排
减少范围
- 尽可能在低层级的DOM节点上,而不是像上述全局范围的示例代码一样,如果你要改变p的样式,class就不要加在div上,通过父元素去影响子元素不好
- 不要使用 table 布局,可能很小的一个小改动会造成整个 table 的重新布局。那么在不得已使用table的场合,可以设置table-layout:auto;或者是table-layout:fixed这样可以让table一行一行的渲染,这种做法也是为了限制reflow的影响范围
减少次数
- 样式集中改变: 不要频繁的操作样式,对于一个静态页面来说,明智且可维护的做法是更改类名而不是修改样式,对于动态改变的样式来说,相较每次微小修改都直接触及元素,更好的办法是统一在 cssText 变量中编辑。虽然现在大部分现代浏览器都会有 Flush 队列进行渲染队列优化,但是有些老版本的浏览器比如IE6的效率依然低下
- 分离读写操作:DOM 的多个读操作(或多个写操作),应该放在一起。不要两个读操作之间,加入一个写操作
- 将 DOM 离线
- 使用 display: none
- 通过 documentFragment 创建文档碎片在添加进文档中
- 复制节点 -> 操作副本 -> 替换
- 使用 absolute / fixed 脱离文档流:使用绝对定位会使的该元素单独成为渲染树中 body 的一个子元素,重排开销比较小,不会对其它节点造成太多影响。当你在这些节点上放置这个元素时,一些其它在这个区域内的节点可能需要重绘,但是不需要重排
- 优化动画
Get | Post
GET | POST | |
---|---|---|
后退按钮/刷新 | 无害 | 数据会被重新提交 |
书签 | 可收藏为书签 | 不可收藏为书签 |
缓存 | 可能会被缓存 | 不能缓存 |
编码类型 | application/x-www-form-urlencoded | application/x-www-form-urlencode 或 multipart/form-data |
历史 | 参数保留再浏览器历史中 | 参数不会被保留在浏览器历史中 |
数据长度限制 | URL最大长度为2048字符 | 无限制 |
数据类型限制 | 只允许 ASII 字符 | 无限制 |
安全性 | 较差,发送的数据是 URL 的一部分 | 相对安全,参数不会被保存在日志中 |
可见性 | 数据在 URL 中对所有人都可见 | 不会显示在 URL 中 |
状态码
- 信息响应(100-199)
- 100
- 101
- 102
- 103
- 成功响应(200-299)
- 200: 请求成功
- 201
- 202
- 203
- 205
- 206
- 207
- 208
- 226
- 重定向响应(300-399)
- 300
- 301
- 302
- 303
- 304: 响应没有被修改,继续使用缓存
- 307
- 308
- 客户端错误响应(400-499)
- 400
- 401
- 402
- 403: 没有访问权限
- 404: 找不到请求的资源
- 405: 目标不支持请求方法
- 406
- ...
- 服务端错误响应(500-599)
401 | 403
- 401: 这个响应意味着"unauthenticated"。也就是说,客户端必须对自身进行身份验证才能获得请求的响应
- 403: 客户端没有访问内容的权限;也就是说,它是未经授权的,因此服务器拒绝提供请求的资源
403 知道客户端身份,但是 401 不知道客户端身份
前端文件上传的多种方案详解
前端性能优化之按需加载
MDN
重排(reflow)和重绘(repaint)
JS 详解 Cookie、 LocalStorage 与 SessionStorage
Webpack配置全解
Vue.js 中的异步组件是什么?如何使用异步组件?
txt
新人发文,礼貌求关❤️