赞
踩
目录
cookie,session,localStorage区别,cookie的长度限制
什么是cookie隔离?请求资源时不要带cookie怎么做?
常见的网络攻击方式有哪些?什么是xss攻击?什么是csrf攻击?
同源策略,什么是跨域?为什么浏览器要使用同源策略?跨域的几种解决方案?了解预检请求吗?
display:none,visibility:hidden和opcatity:0的区别
重排(回流/reflow)和重绘(repaint或redraw)?该怎么优化?怎么减少回流?
什么是windows对象?什么是document对象?(bom和dom)
js事件循环机制,宏任务微任务,async/await,读代码运行顺序
介绍一下JavaScript的原型链?js原型链的顶端是什么?Object的原型是什么?
说一下promise解决了什么问题。他的底层逻辑?如何实现?没有promise该怎么办?
promise,promise.all,promise.race源码手撕(让你写一个请求,5秒内执行完就返回执行结果,否则返回超时)
requestJS的核心原理是什么?如何动态加载的?如何避免多次重复加载?如何缓存?
js动画和css动画区别 ,为什么css动画比JavaScript高效?
怎么打包 ,webpack是用来干什么的? 除了webpack用过其他打包工具吗?
说一下什么是虚拟DOM?为什么要使用虚拟DOM?虚拟DOM是怎么工作的?为什么操作 DOM 慢?
shouldComponentUpdate 可以避免不必要的render
HTTPS为什么要使用一个对称加密和非对称加密相结合的一个方式?
TCP 和 UDP有什么特点?有哪些区别,大概适用于什么样的场合。
你有什么缺点(别傻傻的光说缺点,说说你是怎么克服,或者绕开它们的呀)
做个简单的比喻:进程=火车,线程=车厢
线程在进程下行进(单纯的车厢无法运行)
一个进程可以包含多个线程(一辆火车可以有多个车厢)
不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
html语义化是说用正确的标签做正确的事。
html标签可以分为:块级标签,行内标签,内联块状标签
行内元素与块级函数可以相互转换,通过修改display属性值来切换块级元素和行内元素,行内元素display:inline,块级元素display:block。块级标签:标签独占一行,宽度与高度撑满父元素。
<div>、<p>、<h1>...<h6>、<ol>、<ul>、<dl>、<table>、<address>、<blockquote>、<form>行内标签:标签在一行内,宽度与高度由内容决定,只有在内容超过HTML的宽度时,才会换行。
<a>、<span>、<i>、<em>、<strong>、<label>、<q>、<var>、<cite>、<code>内联块状标签:内联块状元素(inline-block)就是同时具备内联元素、块状元素的特点,
代码display:inline-block就是将元素设置为内联块状元素。(css2.1新增),<img>、<input>标签就是这种内联块状标签
1. HTML语义化,有助于爬虫抓取更多的有效信息,爬虫是依赖于标签来确定上下文和各个关键字的权重.
2. 本地存储,h5提供了sessionStorage、localStorage和cookie,使用之前应该先判断支持情况。注意:localStorage存储的数据是不能跨浏览器共用的,一个浏览器只能读取各自浏览器的数据,储存空间5M。
3. 离线web应用
4.表单新增功能。以往input中的name和value要随着form表单一起提交,form表单必须包裹input , 而现在可以通过input的form属性綁定.
5.地理定位,h5提供了Geolocation API访问地理位置,即通过window.navigator.geolocation来实现访问。这个对象有三个方法:
getCurrentPosition()、watchPosition()、clearWatch()
页面第一次使用这个api需要获得用户许可, watchPosition可以对位置变化事件进行监听。
绘制一个绿色的长方形?
<canvas id="canvas"></canvas>
<script>
// 获取HTML <canvas> 元素的引用。
const canvas = document.getElementById('canvas');
// 获取这个元素的context——图像稍后将在此被渲染。
const ctx = canvas.getContext('2d');
// fillStyle 属性让长方形变成绿色。
ctx.fillStyle = 'green';
// fillRect() 方法将它的左上角放在(10, 10),把它的大小设置成宽150高100。
ctx.fillRect(10, 10, 150, 100);
</script>
localStorage(本地存储)和sessionStorage(会话存储)不会自动把数据发给服务器
localStorage长期存储数据,浏览器关闭数据后不丢失,仅在本地保存,大小为一般为5MB;
sessionStorage仅在当前会话下有效,关闭页面或浏览器后被清除。存放数据大小为一般为5MB,如果两个页面存在关系,则可以共享sessionStorage;
cookie是网站为了标识用户身份而存储在用户本地终端(Client Side)上的数据(通常经过加密)。cookie始终在同源的http请求中携带(即使不需要)都会在浏览器和服务器端间来回传递,cookie数据大小不会超过4K。有期时间:localStorage存储持久数据,浏览器关闭后数据不丢失,除非自动删除数据。sessionStorage数据在当前浏览器窗口关闭后自动删除。cookie 设置的cookie过期时间之前一直有效,即使窗口或者浏览器关闭;
cookie和session的区别
1、cookie数据存放在客户的浏览器上,session数据放在服务器上
2、session保存在服务器,客户端不知道其中的信息;cookie保存在客户端,服务器能够知道其中的信息
3、session中保存的是对象,cookie中保存的是字符串
4、cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗,考虑*到安全应当使用session
5、session会在一定时间内保存在服务器上,当访问增多,会比较占用你服务器的性能,考虑到减轻服务器性能方面,应当使用cookie
6、单个cookie保存的数*据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie
7、建议将登录信息等重要信息存放为session,其他信息如果需要保留,可以放在cookie中
8、session不能区分路径,同一个用户在访问一个网站期间,所有的session在任何一个地方都可以访问到,而cookie中如果设置了路径参数,那么同一个网站中不同路径下的cookie互相是访问不到的。
9、COOKIE:是服务端向客户端写入的小的片段信息。cookie信息保存在服务器缓存区,不会在客户端显现。当你第一次登陆一个网站,服务器向你的机器写得片段信息。你可以在Internet选项中找到存放cookie的文件夹。如果不删除,cookie就一直在这个文件夹中。
10、session需要借助cookie才能正常工作。如果客户端完全禁止cookie,session将失效。 但是如果服务器端启用了url编码,也就是用 URLEncoder.encode()把所有的url编码了,则会在url后面出现如下类似的东西 index.jsp:jsessionid=fdsaffjdlks;jaf;lkdjsf 服务器通过这个进行session的判断.
11.Cookie支持跨域名访问,例如将domain属性设置为“.biaodianfu.com”,则以“.biaodianfu.com”为后缀的一切域名均能够访问该Cookie。跨域名Cookie如今被普遍用在网络中,例如Google、Baidu、Sina等,而Session则不会支持跨域名访问。Session仅在他所在的域名内有效。仅运用Cookie或者仅运用Session可能完成不了理想的效果。这时应该尝试一下同时运用Cookie与Session。Cookie与Session的搭配运用在实践项目中会完成很多意想不到的效果。
如果静态文件都放在主域名下,那静态文件请求的时候都带有的cookie的数据提交给server的,非常浪费流量,所以不如隔离开。
因为cookie有域的限制,因此不能跨域提交请求,故使用非主要域名的时候,请求头中就不会带有cookie数据,
这样可以降低请求头的大小,降低请求时间,从而达到降低整体请求延时的目的。
同时这种方式不会将cookie传入server,也减少了server对cookie的处理分析环节,
提高了server的http请求的解析速度。
Cookie是存放在客户端(浏览器),Session是保存在服务端。
Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息。可以设置有效期
Session是服务端记录用户状态时用于标识具体用户。在浏览器窗口关闭后这次的Session就消失了。Cookie的安全性不如Session
Session如果过多,会对服务端产生较大的压力,不是特别重要的数据,可以考虑使用Cookie存放
token和cookie一样都是首次登陆时,由服务器下发,都是当交互时进行验证的功能,作用都是为无状态的HTTP提供的持久机制。token存在哪儿都行,localstorage或者cookie。
当用户退出登录的时候cookie和服务器的session都会注销;但是用户退出登录的时候token只是注销浏览器信息,不查库。
前端存放服务端下发的cookie, 简单说就是写一个字段在cookie中表明已登录, 并设置失效日期
或使用后端返回的token, 每次ajax请求将token携带在请求头中, 这也是防范csrf的手段之一。
(1)浏览器渲染机制,减少重绘重排
(2)事件处理程序,使用事件委托
(3)网络请求优化,善用缓存,不重复加载相同的资源,减少 HTTP 请求,使用http2.0
(4)静态资源使用 内容分发网络(CDN)是一组分布在多个不同地理位置的 Web 服务器。我们都知道,当服务器离用户越远时,延迟越高。CDN 就是为了解决这一问题,在多个位置部署服务器,让用户离服务器更近,从而缩短请求时间。
DNS 解析:将域名解析成 IP 地址
TCP 连接:TCP 三次握手
发送 HTTP 请求
服务器处理请求并返回 HTTP 报文(html)
浏览器解析渲染页面
断开连接:TCP 四次挥手
TCP 三次握手结束后,开始发送 HTTP 请求报文。
根据 HTML 解析出 DOM 树
根据 CSS 解析生成 CSS 规则树
结合 DOM 树和 CSS 规则树,生成渲染树
根据渲染树计算每一个节点的信息。请求报文由请求行(request line)、请求头(header)、请求体 构成。
1.请求行包含请求方法、URL、协议版本
2.请求头包含请求的附加信息,由关键 字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。 比如:Host,表示主机名,虚拟主机;Connection,HTTP/1.1 增加的,使用 keepalive,即持久 连接,一个连接可以发多个请求;User-Agent,请求发出者,兼容性以及定制化 需求。
3.请求体,可以承载多个请求参数的数据,包含回车符、换行符和请求数据,并不是所有请求都具有请求数据。
CSS 不会阻塞 DOM 解析,但会阻塞 DOM 渲染。
CSS 会阻塞 JS 执行,并不会阻塞 JS 文件下载
CSSOM 作用
第一个是提供给 JavaScript 操作样式表的能力
第二个是为布局树的合成提供基础的样式信息 这个 CSSOM 体现在 DOM 中就是 document.styleSheets。
由之前讲过的浏览器渲染流程我们可以看出: DOM 和 CSSOM 通常是并行构建的,所以「CSS 加载不会阻塞 DOM 的解析」。 然而由于 Render Tree 是依赖 DOM Tree 和 CSSOM Tree 的,所以它必须等到两 者都加载完毕后,完成相应的构建,才开始渲染,因此,「CSS 加载会阻塞 DOM 渲染」。
由于 JavaScript 是可操纵 DOM 和 css 样式 的,如果在修改这些元素属性同 时渲染界面(即 JavaScript 线程和 UI 线程同时运行),那么渲染线程前后获 得的元素数据就可能不一致了。 因此为了防止渲染出现不可预期的结果,浏览器设置 「GUI 渲染线程与 JavaScript 引擎为互斥」的关系。
有个需要注意的点就是: 「有时候 JS 需要等到 CSS 的下载,这是为什么呢?」 仔细思考一下,其实这样做是有道理的,如果脚本的内容是获取元素的样式,宽 高等 CSS 控制的属性,浏览器是需要计算的,也就是依赖于 CSS。浏览器也无法 感知脚本内容到底是什么,为避免样式获取,因而只好等前面所有的样式下载完 后,再执行 JS。
JS 文件下载和 CSS 文件下载是并行的,有时候 CSS 文件很大,所以 JS 需要等待。 因此,样式表会在后面的 js 执行前先加载执行完毕,所以「css 会阻塞后面 js 的执行」。
1. 现代浏览器会并行加载js文件。
2.加载或者执行js时会阻塞对标签的解析,也就是阻塞了dom树的形成,只有等到js执行完毕,浏览器才会继续解析标签。没有dom树,浏览器就无法渲染,所以当加载很大的js文件时,可以看到页面很长时间是一片空白。所以会阻塞对标签的解析是因为加载的js中可能会创建,删除节点等,这些操作会对dom树产生影响,如果不阻塞,等浏览器解析完标签生成dom树后,js修改了某些节点,那么浏览器又得重新解析,然后生成dom树,性能比较差。
HTTP 协议有一个缺陷:通信只能由客户端发起,做不到服务器主动向客户端推送信息。
(1)建立在 TCP 协议之上,服务器端的实现比较容易。
(2)与 HTTP 协议有着良好的兼容性。默认端口也是80和443,并且握手阶段采用 HTTP 协议,因此握手时不容易屏蔽,能通过各种 HTTP 代理服务器。
(3)数据格式比较轻量,性能开销小,通信高效。
(4)可以发送文本,也可以发送二进制数据。
(5)没有同源限制,客户端可以与任意服务器通信。
(6)协议标识符是ws(如果加密,则为wss),服务器网址就是 URL。
浏览器缓存就是把一个已经请求过的资源拷贝一份存储起来,当下次需要该资源时,浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求。作用: 减少网络传输的损耗以及降低服务器压力。
强制缓存(本地缓存):给资源设置过期时间,客户端每次请求资源时都会查看是否过期,如果过期则向服务器再次发送请求。
协商缓存:当强缓存过期时,客户端向服务器发送请求时可以设置协商缓存,请求资源时,把本地该资源的唯一ID携带发给服务器,服务器和最新资源做对比,资源没变,返回304,浏览器读取本地缓存。资源改变,返回200,返回最新资源。
优先级: 强制缓存 > 协商缓存;(1)强缓存为什么要有两个呢 Expires, Cache-Control?Cache-Control有哪些属性?分别表示什么意思?
两者作用差不多,区别就在于 Expires 是http1.0的产物,Cache-Control是http1.1的产物,两者同时存在的话,Cache-Control优先级高于Expires;
Expires:过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中强缓存。
Cache-Control:则代表在这个请求正确返回时间(浏览器也会记录下来)的5分钟内再次加载资源,就会命中强缓存。(2)协商缓存出于什么原因有Last-Modified,Etag? etag的原理?
Etag是上一次加载资源时,该资源的一种唯一标识,只要资源有变化,Etag就会重新生成。
浏览器在下一次加载资源向服务器发送请求时,会将上一次返回的Etag值放到request ,会拿来跟该资源文件的Etag值做比较,如果相同,则表示资源文件没有发生改变,命中协商缓存。
Last-Modified是该资源文件最后一次更改时间,服务器会在response header里返回,同时浏览器会将这个值保存起来,在下一次发送请求时,放到request header里的If-Modified-Since里,服务器在接收到后也会做比对,如果相同则命中协商缓存。
display 属性在块级元素和行内元素的转换,display 有三个值:
inline:值为 inline 将变成行内元素,比如 div。不能设置宽高,和行内元素并排
block:值为 block 的,比如 span。能设置宽高(填充父级),独占一行。
inline-block:值为 inline-block 也就是变成行内块元素(1)块级元素:块元素通常会独占一行,或者多行,它的宽高,边距等都可以设置。
<caption> 定义表格标题
<address> 定义地址
<dd> 定义列表中定义条目
<div> 定义文档中的分区或节
<dl> 定义列表
<dt> 定义列表中的项目
<fieldset> 定义一个框架集
<form> 创建 HTML 表单
<h1>~<h6> 定义标题
<hr> 定义一条水平线
<p> 定义段落
<table> 定义表格
<li> 标签定义列表项目
<ul> 定义无序列表
<ol> 定义有序列表(2)行内元素:又称为内联元素,行内元素的大小是靠本身内容的大小,宽高都设置无效。
<a> 标签定义超链接
<abbr> 表示一缩写形式
<acronym> 定义只取首字母缩写
<b> 定义字体缩写
<bdo> 可覆盖默认的文本方向
<big> 定义大号字体加粗
<br> 定义换行
<code> 定义计算机代码文本
<label> 标签
<select> 创建单选或多选菜单
<span> 组合文档中的行内元素(3)行内块元素:行内块元素从文字上看像块级元素和行内元素结合似的,其实比较特殊,比如<input>,<img>,<td> 可以设置宽高属性,这种就称为行内块元素。
预加载:提前加载图片,当用户需要查看时可直接从本地缓存中渲染。
懒加载:懒加载的主要目的是作为服务器前端的优化,减少请求数或延迟请求数。
1.第一种是纯粹的延迟加载,使用setTimeOut或setInterval进行加载延迟.
2.第二种是条件加载,符合某些条件,或触发了某些事件才开始异步下载。
3.第三种是可视区加载,即仅加载用户可以看到的区域,这个主要由监控滚动条来实现,一般会在距用户看到某图片前一定距离遍开始加载,这样能保证用户拉下时正好能看到图片。两种技术的本质:两者的行为是相反的,一个是提前加载,一个是迟缓甚至不加载。
懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
Xss(cross-site scripting) 攻击:全称跨站脚本攻击,攻击者在web页面中会插入一些恶意的script代码。当用户浏览该页面的时候,那么嵌入到web页面中script代码会执行,因此会达到恶意攻击用户的目的。
CSRF (Cross Site Request Forgery),跨站请求伪造,攻击者借助用户的 Cookie 骗取服务器的信任,以用户名义伪造请求发送给服务器。如:在请求的 url 后加入一些恶意的参数。
防范CSRF:添加 token 验证:在 HTTP 请求中以参数的形式加入一个随机产生的 token,并在服务器端建立一个拦截器来验证这个 token,若请求无 token 或者 token 不正确,则认为可能是 CSRF 攻击而拒绝该请求。
同源策略: 协议/主机/端口
我们来看下面的页面是否与 http://store.company.com/dir/index.html 是同源的?
http://store.company.com/dir/index2.html 同源
http://store.company.com/dir2/index3.html 同源 虽然在不同文件夹下
https://store.company.com/secure.html 不同源 不同的协议(https)
http://store.company.com:81/dir/index.html 不同源 不同的端口(81)
http://news.company.com/dir/other.html 不同源 不同的主机(news)浏览器使用同源策略主要是为了安全,比如:如果没有同源限制存在浏览器中的cookie等其他数据可以任意读取。防止 XSS、CSFR 等攻击。
跨域是指一个域下的文档或脚本试图去请求另一个域下的资源。跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了。解决方案:
(1)jsonp:ajax受同源影响,利用JS中src属性连接访问跨域JS。不安全可能会遭受 XSS 攻击。只能get,不安全、有缓存、大小限制。
(2)CORS:服务端设置。
(3)nginx 反向代理:搭建一个中转 nginx 服务器,用于转发请求。
(4)webpack添加proxy
(5)location.hash:a与b跨域,通过中间页c,不同域:location.hash传值。不同域:js。预检请求: 需预检的请求要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。
(和西天取经一个道理,唐僧取经之前,国内想看经书只能跑老远去西天看经书,唐僧取经回来,再去国内各地传播,之后各地的人想听佛法就去最近的寺庙听就好了)
CDN :内容分发网络
CDN的核心点有两个,一个是缓存,一个是回源。
静态资源本身具有访问频率高、承接流量大的特点,因此静态资源加载速度始终是前端性能的一个非常关键的指标。
CDN 是静态资源提速的重要手段,在许多一线的互联网公司,“静态资源走 CDN”并不是一个建议,而是一个规定
DNS 主要的作用就是将人们所熟悉的网址 (域名) “翻译”成电脑可以理解的 IP 地址,这个过程叫做 DNS 域名解析,一个域名往往对应多个DNS地址。
DNS的规范规定了2种类型的DNS服务器,一个叫主DNS服务器,一个叫辅助DNS服务器。在一个区中主DNS服务器从自己本机的数据文件中读取该区的DNS数据信息,而辅助DNS服务器则从区的主DNS服务器中读取该区的DNS数据信息。当一个辅助DNS服务器启动时,它需要与主DNS服务器通信,并加载数据信息,这就叫做区域传送(zone transfer)。 这种情况下,使用TCP协议。
为什么域名解析用UDP协议?
因为UDP快啊!UDP的DNS协议只要一个请求、一个应答就好了。而使用基于TCP的DNS协议要三次握手、发送数据以及应答、四次挥手。但是UDP协议传输内容不能超过512字节。不过客户端向DNS服务器查询域名,一般返回的内容都不超过512字节,用UDP传输即可。为什么区域传送用TCP协议?
因为TCP协议可靠性好啊!你要从主DNS上复制内容啊,你用不可靠的UDP? 因为TCP协议传输的内容大啊,你用最大只能传512字节的UDP协议?万一同步的数据大于512字节,你怎么办?
物理层:
作用:通过媒介输出比特(bit)
协议:RJ45、CLOCK、IEEE802.3
设备:中继器、集线器
数据链路层:
作用:将比特组装成帧(Frame)和点对点传递
协议:PPP FR HDLC VLAN MAC
设备:网桥、交换机
数据链路层在概念上分为两个子层:逻辑链路控制子层(LLC)和媒体访问控制子层(MAC)。数据链路层负责分配MAC地址,或称为物理地址,由48比特长,12个16进制数字组成,0~23位。是厂商向IETF等机构申请用来标识厂商的代码。网络层:
作用:负责数据包从源到宿的传递和网际交互
协议:IP IPX ICMP IGMP ARP RARP OSPF
设备:网络层中继系统:路由器,网络层以上的中继系统:网关传输层:
作用:提供端到端的可靠报文传递和错误恢复
协议:
TCP (传输控制协议:面向连接的,数据传输的单位是报文段,提供可靠的交付),
UDP(用户控制协议:它是无连接的,数据传输的单位是用户数据报,它不能保证提供可靠的交付)
SCTP (流控制传输协议 Stream Control Transmission Protocol )会话层:
作用:建立管理和终止会话(会话协议的数据单元SPDU)
协议:NFS SQL NETBIOS RPC表示层:
作用:数据翻译、解密和压缩(表示协议数据单元PPDU)
协议:JPEG MPEG ASII
应用层:
作用:允许访问OSI环境的手段(应用协议数据单元APDU)
协议:FTP(文件传输协议)、DNS(域名解析协议)、Telnet(虚拟终端协议)、SMTP(电子邮件协议)、HTTP(超文本传输协议)、www、NFS
端口号小于256的一般为常用端口号
HTTP协议代理服务器常用端口号:80/8080/3128/8081/9098
FTP(文件传输)协议代理服务器常用端口号:21TOMCAT,默认端口号为8080
Oracle 数据库,默认的端口号为1521
@import是 CSS 提供的语法规则,只有导入样式表的作用;
link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等
w3c:宽度=内容的宽度(content)+ border + padding + margin
IE: 宽度=内容宽度(content+border+padding)+ margin
display:none隐藏后不占据额外空间,它会产生回流和重绘,而visibility:hidden和opacity:0元素虽然隐藏了,但它们仍然占据着空间,它们俩只会引起页面重绘。
display:none不会被子元素继承,但是父元素都不在了,子元素自然也就不会显示了,皮之不存,毛之安附~~
visibility:hidden 会被子元素继承,可以通过设置子元素visibility:visible 使子元素显示出来
opacity: 0 也会被子元素继承,但是不能通过设置子元素opacity: 0使其重新显
BFC就是让元素成为一个个独立的块,他们之间互不影响。 (bfc是块级格式上下文,它是页面中的一块渲染区域,并且有一套渲染规则,它决定了其子元素将如何定位,以及和其他元素的关系和相互作用。)通俗一点来讲,可以把 BFC 理解为一个封闭的大箱子,箱子内部的元素无论如何翻江倒海,都不会影响到外部。
影响:
同一个BFC下外边距会发生重叠:在网页制作过程中由于浏览器加载是自上而下的原因,外边距上下会取最大值,左右不受影响。
解决:此处用个div给包住,只通过一行代码(给外层的div 添加属性position:absolute) ps:inherit除外
第一种情况:两个同级元素,垂直排列,上面的盒子给margin-bottom,下面的盒子给margin-top,那么他们两个的间距会重叠,以大的那个计算。
解决这种情况的方法为:(1)两个外边距不同时出现.(2)用个div给包住,给外层的div 添加属性position:absolute第二种情况:两个父子元素,内部的盒子给margin-top,其父级也会受到影响,同时产生上边距,父子元素会进行粘连。
解决这种情况的方法为:父级添加一个css属性,overflow: hidden,禁止超出,外边距重叠就是margin-collapse。
解决方案:
1、为父盒子设置border,为外层添加border后父子盒子就不是真正意义上的贴合(可以设置成透明:border:1px solid ansparent);
2、为父盒子添加overflow: hidden;
3、为父盒子设定padding值;
4、为父盒子添加position: fixed;
5、为父盒子添加 display: table;
6、利用伪元素给父元素的前面添加一个空元素。
清除浮动主要是为了解决,父元素因为子级元素浮动引起的内部高度为0的问题
当父元素不给高度的时候,内部元素不浮动时会撑开,而浮动的时候,父元素变成一条线。
解决方法:
(1)使用after伪元素清除浮动(推荐使用)
.clearfix:after{/*伪元素是行内元素 正常浏览器清除浮动方法*/
content: "";
display: block;
height: 0;
clear:both;
visibility: hidden;
}
(2)使用before和after双伪元素清除浮动。
(3)父级添加overflow属性(父元素添加overflow:hidden)(不推荐)
(4)额外标签法(在最后一个浮动标签后,新加一个标签,给其设置clear:both;)(不推荐)
在属性后面使用 !important 会覆盖页面内任何位置定义的元素样式。
作为style属性写在元素内的样式
id选择器
类选择器
标签选择器
通配符选择器
浏览器自定义或继承
!important > 行内样式>ID选择器 > 类选择器 > 标签 > 通配符 > 继承 > 浏览器默认属性。同一级别中后写的会覆盖先写的样式
相对定位(relative):
相对定位的块状元素相对于原来位置移动,移动后仍然占据文档流的位置,不影响其他元素的布局绝对定位(absolute):
被绝对定位的对象将从文档流中脱离,绝对定位的参照位置看它的上级或上上级有没有定位了,如果父级没有设置定位属性,则会相对于html根元素进行定位,
水平居中:
(1)text-align:center
(2)flex
垂直居中:
(1)flex
<style>
div{
width: 200px;
height: 200px;
border-radius: 50%;
background-color: red;
}
</style>
<style>
div{
width: 0.1px;
border-top: 50px solid rgb(252, 2, 2);
border-bottom: 50px solid rgba(253, 253, 2);
border-left: 50px solid blue;
border-right: 50px solid rgb(1, 129, 1);
}
</style>
px,em,rem
px像素
em是相对长度单位。相对于当前对象内文本的字体尺寸。如当前对行内文本的字体尺寸未被人为设置,则相对于浏览器的默认字体尺寸。
em是CSS3新增的一个相对单位(root em,根em),使用rem为元素设定字体大小时,相对的是HTML根元素。可以做到只修改根元素就成比例地调整所有字体大小,又可以避免字体大小逐层复合的连锁反应。
重排必重绘!!!
重排(回流/reflow):对DOM树进行渲染,只要DOM树上的属性改变了浏览器布局,就会触发reflow,reflow的时候,浏览器会使被改变的那部分DOM树失效,并重新构造这部分。
重绘(repaint):当渲染树中的一些元素需要更新,但更新的属性不会改变浏览器布局(位置)的操作,直接可以为该元素绘制新的样式,跳过了回流环节,这个过程就叫重绘。如何减少回流、重绘?
减少回流、重绘就是减少对DOM的操作
1.IE中避免使用javascript表达式
2.如果动态改变样式,直接改变className,
3. 使用 transform 替代 top,实现动画性能更好,因为使用transform页面没有回流了.
4.让要操作的元素进行“离线处理”,处理完后一起更新
当使用DocumentFragment进行缓存操作,引发一次回流和重绘
使用display:none 技术,只引发两次回流和重绘
使用cloneNode(true or false)和replaceChild技术,引发一次回流和重绘
5.不要经常访问会引起浏览器flush队列的属性,如果你确实要访问,利用缓存
6.让元素脱离动画流,减少render 树的规模
7.牺牲平滑度换取速度
8.避免使用table布局
CSS3 中有三个关于动画的样式属性transform、transition和animation
(1)transform
transform可以用来设置元素的形状改变。
主要有以下几种变形:rotate(旋转)、scale(缩放)、skew(扭曲)、translate(移动)和matrix(矩阵变形)。(2)transition
transition是用来设置样式的属性值是如何从从一种状态变平滑过渡到另外一种状态,它有四个属性:
transition-property(变换的属性,即那种形式的变换:大小、位置、扭曲等);
transition-duration(变换延续的时间);
transition-timing-function(变换的速率)
transition-delay(变换的延时)(3)animation
animation比较类似于 flash 中的逐帧动画,逐帧动画就像电影的播放一样,表现非常细腻并且有非常大的灵活性。然而transition只是指定了开始和结束态,整个动画的过程也是由特定的函数控制。学习过 flash 的同学知道,这种逐帧动画是由关键帧组成,很多个关键帧连续的播放就组成了动画,在 CSS3 中是由属性keyframes来完成逐帧动画的。
animationName:动画名称,开发人员自己命名;
percentage:为百分比值,可以添加多个百分比值;
properties:样式属性名称,例如:color、left、width等等。
动画帧率可以作为衡量标准,一般来说画面在 60fps 的帧率下效果比较好。
flex-direction: row || column;
justify-content: 控制 `flex` 项在主轴上的位置。
center
flex-start
flex-end
space-around(沿着主轴均匀地分布,两端留下空间。)
space-between(沿着主轴均匀地分布,两端不留空间。)align-items:控制 flex 项在交叉轴上的位置。
center
flex-start
flex-end
所谓精灵图就是把很多的小图片合并到一张较大的图片里,所以在首次加载页面的时候,就不用加载过多的小图片,只需要加载出来将小图片合并起来的那一张大图片也就是精灵图即可,这样在一定程度上减少了页面的加载速度,也一定程度上缓解了服务器的压力。例如王者荣耀页面里的几个小logo。
其实说白了就是将精灵图设为一个大背景,然后通过background-position来移动背景图,从而显示出我们想要显示出来的部分。
精灵图虽然实现了缓解服务器压力以及用户体验等问题,但还是有一个很大的不足,那就是牵一发而动全身。这些图片的背景都是我们详细测量而得出来的,如果需要改动页面,将会是很麻烦的一项工作。。。
用于向某些选择器添加特殊的效果。
伪类:通过选择器找到那些不存在于DOM树中的信息以及不能被常规CSS选择器获取到的信息。
伪元素::创建了一些抽象元素,这些抽象元素是不存在于文档语言里的(可以理解为html源码)。(比如常见的::before,::after)。
但是放到head中没有任何效果。原因是因为文档还没加载,就读了js,js就不起作用了
想在head里用的话,利用 window.onload = function(){} 代码包裹,文档加载之后再执行,建议把js代码放到body的最底部,因为浏览器生成Dom树的时候是一行一行读HTML代码的,script标签放在最后面就不会影响前面的页面的渲染。
代码:window.onload() //在网页加载完毕后立刻执行的操作
<script defer>: 延迟加载,元素解析完成后执行
<script async>: 异步加载,但执行时会阻塞元素
window对象:代表浏览器中打开的一个窗口。也被称为BOM对象,常用的比如:history对象是window对象的一部分,包含用户在当前浏览器窗口中访问过的URL。
document对象:代表整个 html 文档,也被称为DOM对象,document对象使我们可以从脚本中对HTML页面中的所有元素进行访问。 实际上,document 对象是 window 对象的一个属性。
JavaScript 中运行任何的代码都是在执行上下文中运行。执行上下文有三种类型: (1)全局执行上下文 (2)函数执行上下文 (3)eval 执行上下文
代码执行过程:
在首次运行js的时候浏览器引擎会创建一个全局上下文,并添加到执行栈中,在退出浏览器的时候从执行栈中移除全局上下文。
js代码逐行 自上而下 执行。遇到函数时,函数执行上下文 (callee) 被 push 到执行栈顶层,全局执行上下文被挂起,控制权交给函数执行上下文。开始执行函数中的代码。
函数执行完后,函数执行上下文被 pop 移除出执行栈,控制权交还全局上下文,继续执行
迭代器的本身是一个对象,这个对象有 next( ) 方法返回结果对象,这个结果对象有下一个返回值 value、迭代完成布尔值 done。
闭包是有权限访问其他函数作用域的局部变量的一个函数
具体解释:由于在JS中,变量的作用域属于函数作用域,在函数执行后作用域就会被清理、内存也随之被收回,但是由于闭包是建立在一个函数内部的子函数,由于其可访问上级作用域的原因,即使上级函数执行完,作用域也不会随之销毁,这时的子函数—也就是闭包,便拥有了访问上级作用域中的变量的权限,即使上级函数执行完后,作用域内的值也不会被销毁。闭包解决了什么?
闭包就是将函数内部和函数外部连接起来的一座桥梁。闭包随处可见,一个 Ajax 请求的成功回调,一个事件绑定的回调方法,一个 setTimeout 的延时回调,或者一个函数内部返回另一个匿名函数,这些都是闭包。简而言之,无论使用何种方式对函数类型的值进行传递,当函数在别处被调用时,都有闭包的身影
1) 一个是可以读取函数内部的变量;
2) 另一个就是让这些变量的值始终保存在内存中。
(1)代码难以维护:闭包内部是可以访问上级作用域,而如果闭包又是异步执行的话,一定要清楚上级作用域都发生了什么,而这样就需要对代码的运行逻辑和JS运行机制相当了解才能弄明白究竟发生了什么。
(2)使用闭包的注意点:由于闭包会使得函数中的变量都保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄漏(虽然实际开发中从没遇到过内存泄漏的情况)。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(3)内存泄漏:程序的运行需要内存。对于持续运行的服务进程,必须及时释放不再用到的内存,否则占用越来越高,轻则影响系统性能,重则导致进程崩溃。不再用到的内存,没有及时释放,就叫做内存泄漏。
闭包的this指向的是window
1、作用域
作用域就是一个变量和函数可以使用的范围,主要分为全局作用域和局部(函数)作用域
全局作用域就是Js中最外层的作用域。
ES5中作用域有:全局作用域、函数作用域。没有块作用域的概念。因此也有一系列的问题。
ECMAScript 6(简称ES6)中新增了块级作用域。块作用域由{ }包括,if语句和for语句里面的{ }也属于块作用域。2、全局变量、局部变量:
1).全局变量:
在全局范围内声明的变量,如var a=1;
只有赋值没有声明的值,如a=2; (注:如果a=2在函数环境中,也是全局变量)
2)局部变量:
写入函数中的变量,叫做局部变量。
3)作用:
程序的安全。
内存的释放。3、自由变量:
当前作用域外的变量都是自由变量,(作用域链):一个变量在当前作用域没有定义,但是被使用了,就会向上级作用域,一层一层依次查找,直至找到为止,找到这个变量后就会停止,不会继续查找这个变量,如果全局作用域都没有找到这个变量就会报错。这个自由变量查找的过程就是作用域链。4、变量提升:
每个var声明的变量,function声明的函数存在变量提升。let const不存在变量提升(只提升声明,不提升赋值)。
javascript中声明并定义一个变量时,会把声明提前,以下会先打印出undefined,再打印出10
console.log(a)
var a = 10
console.log(a)//undefined
说一下ES6的新特性?let const var的区别?什么是块级作用域?如何用ES6的方法实现块级作用域?
在 ES5 只有全局作用域和函数作用域,没有块级作用域{}
var 声明的变量是全局或者整个函数块的,var 声明的变量存在变量提升
let,const 声明的变量是块级的变量,不存在变量提升。
let 声明的变量允许重新赋值,const 不允许。
Array.from(new Set(arr)) 数组去重
Math.max(arr) 数组最大值
arr.reverse() 反转数组
arr.slice() 截取数组
push() 在数组末尾添加一个或多个元素,并返回新数组长度
pop() 从数组末尾删除1个元素, 并返回被删除的元素
shift() 在数组开始添加一个或多个元素,并返回新数组长度
unshift() 在数组开始删除一个元素(删且只删除1个),并返回 被删除的元素
arr.toString() || arr.flat(Infinity)) 数组变一维
arr.sort((a,b)=>a-b) 数组升序排列
arr.sort((a,b)=>b-a) 数组降序排列
arr.indexOf(el) 返回目标元素在数组中的下标,找不到返回-1
arr.splice(i, j, “k”) 在数组的第i位开始,删除j位元素,添加“k”
arr.join(" ") 数组内元素连接成字符串
arr1.concat(arr2) 连接数组/字符串str.split("") 字符串分割为数组
JSON.parse(JSON.stringfy(obj)) 拷贝对象// 数学
parseInt(5.1234); // 将一个字符串转换为整数
Math.floor(5.1234); // 向下取整<= 该数值的最大整数
Math.ceil(5.1234); // 向上取整(有小数,整数部分就+1)
Math.round(5.1234); // 四舍五入(小数部分)
Math.max(...arr) // 求数组最大值Object.prototype.toString.call(A) // 判断类型
for(item in arr){} 遍历key
for(item of arr){} 遍历value.reduce(function(返回值acc, 当前值el, 当前索引index, 调用的数组arr){},返回值的初始值init)
// /对数组中每个元素执行提供的函数,将其结果汇总为单个返回值。.filter(function(当前值el, 当前索引index, 调用的数组arr){})
//创建一个新数组, 其包含通过所提供函数实现的测试的所有元素。.map(function(当前值el, 当前索引index, 调用的数组arr){})
//创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后 返回 的结果。.forEach(function(当前值el, 当前索引index, 调用的数组arr){})
// 会改变原数组,依次执行提供的回调函数。
for(let el in arr) // 遍历数组中的key
for(let el of arr) // 遍历数组中的value
switch(表达式) {
case n:
代码块
break;
case n:
代码块
break;
default:
默认代码块
}
forEach(): 针对每一个元素执行提供的函数。
map(): 创建一个新的数组,其中每一个元素由调用数组中的每一个元素执行提供的函数得来
1、Js 是单线程,高耗时操作就带来了进程阻塞问题。所以,Js 有两种任务的执行模式:同步模式和异步模式。
2、异步任务主要分为宏任务(Task)与微任务(Jobs)两种。
3、宏任务由宿主(浏览器、Node)发起,微任务由 JS 自身发起。
4、宏任务和微任务都是队列(先进先出),宏任务有script、setTimeout、setInterval等,微任务有Promise.then/ catch/finally、process.nextTick等。
5、一个宏任务执行完,判断是否有可执行的微任务,有则执行完所有微任务,否则执行下一个宏任务。
6、当程序执行到await方法时,会阻塞await方法后面的程序,进入await方法内部并执行到return前,然后跳出该async方法,执行与该async方法并列的同步任务。async function test1() {
console.log('1');
console.log(await test2());
console.log('2');
}
async function test2() {
console.log('3');
return await '4'
}
test1();console.log('5');
setTimeout(() => {
console.log('6');
}, 0);new Promise((resolve, reject) => {
console.log('7');
resolve();
}).then(() => {
console.log('8');
});console.log('9');
输出结果:135798426
原型(prototype): 可以简单的理解成对象的爹。在 Firefox 和 Chrome 中,每个 JavaScript 对象中都包含一个 __proto__ (非标准)的属性指向它爹(该对象的原型),可 obj.__proto__进行访问。
构造函数: 可以通过 new 来新建一个对象的函数。
实例: 通过构造函数和 new 创建出来的对象,便是实例。 实例通过__proto__ 指向原型,通过 constructor 指向构造函数
原型链是由原型对象组成,每个对象都有 __proto__ 属性,指向了创建该对象 的构造函数的原型,__proto__ 将对象连接起来组成了原型链。是一个用来实现 继承和共享属性的有限的对象链。
属性查找机制: 当查找对象的属性时,如果实例对象自身不存在该属性,则沿着 原型链往上一级查找,找到时则输出,不存在时,则继续沿着原型链往上一级查 找,直至最顶级的原型对象 Object.prototype,如还是没找到,则输出 undefined;
属性修改机制: 只会修改实例对象本身的属性,如果不存在,则进行添加该属性, 如果需要修改原型的属性时,则可以用: b.prototype.x = 2;但是这样会造成 所有继承于该对象的实例的属性发生改变。
原型链
function A () {};
A.prototype.sayhi = () => {
// ...
}
function B () {};
B.prototype = new A();
var b = new B();
b.sayhi();
构造函数,通过call()或apply()来传递实例对象
function A () {
this.friends: ['liu', 'chen', 'lin']
}
function B () {
A.call(this);
}var b1 = new B();
var b2 = new B();b1.friends.push('zhang');
b2.friends; // "liu", "chen", "lin"ES6 class类的继承
class A {};
class B extends A {};
同一操作作用于不同的对象上,可以产生不同的解释和不同的执行结果。
class Person{
// 定义一个Person类,属性有:姓名,年龄。方法有:介绍自己
constructor(name,age){
this.name = name;
this.age = age;
}
introduce = function(){
return(`My name is `+this.name+`. I am `+this.age+` years old.`)
}
}class Student extends Person{
// 定义一个Student类继承Person类,属性有:姓名,年龄,班级。方法有:介绍自己
constructor(name,age,klass){
super(name,age);
this.klass = klass;
}
introduce = function(){
return(`My name is `+this.name+`. I am `+this.age+` years old. I am a Student. I am at Class `+this.klass+`.`)
}
}
(1)创建一个空对象。(var obj = new Object())
(2)将空对象的原型赋值给构造函数的原型。(Person.prototype = obj.proto)
(3)执行构造函数的代码,为空对象添加属性(Person.call(obj)),也可以理解为将构造函数内部的this指针指向新建的空对象。
(4)返回添加后的对象
箭头函数与普通函数的区别在于:
1、箭头函数没有 this,所以需要通过查找作用域链来确定 this 的值,这就意味着如果箭头函数被非箭头函数包含,this 绑定的就是最近一层非箭头函数的 this,
2、不能通过 new 关键字调用,同样也没有 new.target 值和原型
1) this 总是指向函数的直接调用者(而非间接调用者)
2) 如果有 new 关键字,this 指向 new 出来的那个对象
3) 全局环境中,this 默认绑定到 window。
当我们使用一个函数需要改变this指向的时候才会用到call,apply,bind
立即执行,传递的参数不多,则可以使用fn.call(thisObj, arg1, arg2 ...)
立即执行,传递的参数很多,则可以将参数放在数组里使用fn.apply(thisObj, [arg1, arg2 ...])
生成一个新的函数长期绑定某个函数给某个对象使用,则可以使用bind(thisObj, arg1, arg2 ...)// 全局添加一个_call方法
Function.prototype._Call = function(obj) {
// 参数是否存在,如果存在则转为 Object 类型,否则直接取 window 对象为默认对象
let _obj = obj? Object(obj) : window;
_obj.fn = this;
// 保存参数的数组
var argArr = []
// 遍历参数,因为首项是 obj,所以要从次项开始遍历才是参数
for (let i = 1; i < arguments.length; i++) {
argArr.push('arguments['+ i + ']');
}
// 或者用一下两种方法得到 argArr:
// 1. let argArr = [...arguments].slice(1)
// 2. let argArr = Array.from(arguments).slice(1)// 执行 _context 的 fn 方法,把 argArr 拆分
_obj.fn(...argArr); // eval("_context.fn(" + argArr + ")");
// 移除 fn 方法
delete _context.fn;
}Function.prototype._apply = function(obj, argArr) {
// 如果obj不存在则默认window对象
var _obj = obj ? obj : window
// 给_obj添加fn方法
_obj.fn = this
// 获取第二个数组参数
var arg = []
// 当这个参数数组不存在或者为空时,直接执行函数,否则把数组拆分后传递给函数并执行
if (!argArr || argArr.length == 0) {
_obj.fn()
} else {
for (var i = 0; i < argArr.length; i++) {
arg.push('argArr['+ i + ']')
}
// 执行obj的fn方法,把arg拆分
eval("_obj.fn(" + arg + ")")
}
// 移除这个方法
delete _obj.fn
}Function.prototype._bind = function(obj) {
// 判断调用_bind方法的是否为函数
if (typeof(this) !== "function") {
throw Error("调用_bind方法的必须为函数")
}
// 截取传给函数的参数
var args = Array.prototype.slice.call(arguments, 1)
// 保存这个函数,以便后续使用
var _fn = this
// 创建一个待会儿返回出去的函数,这个函数会赋到外部变量中
var bindFn = function() {
// 获取_bind方法返回的函数的参数
var newArgs = Array.prototype.slice.call(arguments)
// 通过apply去改变this指向,实现函数柯里化
var _obj = this.constructor === _fn ? this : obj
_fn.apply(_obj, newArgs.concat(args))
}
// 创建一个中介函数,以便实现原型继承
var ProtoFn = function(){}
ProtoFn.prototype = _fn.prototype
bindFn.prototype = new ProtoFn()
// 返回bindFn的函数给外部
return bindFn
}
默认绑定:全局环境中,this 默认绑定到 window。
隐式绑定:一般地,被直接对象所包含的函数调用时,也称为方法调用,this 隐式绑定到该直接对象。
隐式丢失:隐式丢失是指被隐式绑定的函数丢失绑定对象,从而默认绑定到 window。
显式绑定:通过 call()、apply()、bind()方法把对象绑定到 this 上, 叫做显式绑定。 new 绑定:如果函数或者方法调用之前带有关键字 new,它就构成构造函数调用。 对于 this 绑定来说,称为 new 绑定。
【1】构造函数通常不使用 return 关键字,它们通常初始化新对象,当构造函数 的函数体执行完毕时,它会显式返回。在这种情况下,构造函数调用表达式的计 算结果就是这个新对象的值。
【2】如果构造函数使用 return 语句但没有指定返回值,或者返回一个原始值, 那么这时将忽略返回值,同时使用这个新对象作为调用结果。
【3】如果构造函数显式地使用 return 语句返回一个对象,那么调用表达式的值 就是这个对象。
设计模式总共有 23 种,总体来说可以分为三大类:创建型模式( Creational Patterns )、结构型模式( Structural Patterns )和行为型模式( Behavioral Patterns )。
什么是观察者模式?
观察者模式是定义对象间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式又叫做发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
优点:
观察者模式可以实现表示层和数据逻辑层的分离,并定义了稳定的消息更新传递机制,抽象了更新接口,使得可以有各种各样不同的表示层作为具体观察者角色;
观察者模式在观察目标和观察者之间建立一个抽象的耦合;
观察者模式支持广播通信;
观察者模式符合开闭原则(对拓展开放,对修改关闭)的要求。
缺点:
如果一个观察目标对象有很多直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间;
如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃;
观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
在观察者模式中有如下角色:
Subject:抽象主题(抽象被观察者),抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个接口,可以增加和删除观察者对象;
ConcreteSubject:具体主题(具体被观察者),该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知;
Observer:抽象观察者,是观察者者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己;
ConcrereObserver:具体观察者,实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。
答:单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例。
优点:不会频繁地创建和销毁对象,浪费系统资源。
使用场景:IO 、数据库连接、Redis 连接等。
答:简单工厂模式又叫静态工厂方法模式,就是建立一个工厂类,对实现了同一接口的一些类进行实例的创建。比如,一台咖啡机就可以理解为一个工厂模式,你只需要按下想喝的咖啡品类的按钮(摩卡或拿铁),它就会给你生产一杯相应的咖啡,你不需要管它内部的具体实现,只要告诉它你的需求即可。
优点:
工厂类含有必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的责任,而仅仅“消费”产品;简单工厂模式通过这种做法实现了对责任的分割,它提供了专门的工厂类用于创建对象;
客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以减少使用者的记忆量;
通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。
缺点:
不易拓展,一旦添加新的产品类型,就不得不修改工厂的创建逻辑;
产品类型较多时,工厂的创建逻辑可能过于复杂,一旦出错可能造成所有产品的创建失败,不利于系统的维护。
Put(增)
Delete(删)
Post(改)
Get(查)get 和 post 在缓存方面的区别:
get 请求类似于查找的过程,用户获取数据,可以不用每次都与数据库连接,所以可以使用缓存。
post 不同,post 做的一般是修改和删除的工作,所以必须与数据库交互,所以 不能使用缓存。因此 get 请求适合于请求缓存。
JS的单线程:是指一个浏览器进程中只有一个JS的执行线程,同一时刻内只会有一段代码在执行
例如:打开的多个页面使用的都是同一个JS执行线程,如果其中一个页面在执行一个运算量较大的function时,其他窗口的JS就会停止工作。异步机制:是浏览器的两个或以上常驻线程共同完成的。
例如:异步请求是由两个常驻线程:JS执行线程和事件触发线程共同完成的,JS的执行线程发起异步请求(这时浏览器会开一条新的HTTP请求线程来执行请求,这时JS的任务已完成,继续执行线程队列中剩下的其他任务),然后在未来的某一时刻事件触发线程监视到之前的发起的HTTP请求已完成,它就会把完成事件插入到JS执行队列的尾部等待JS处理。
例如:定时触发(settimeout和setinterval)是由浏览器的定时器线程执行的定时计数,然后在定时时间把定时处理函数的执行请求插入到JS执行队列的尾端(所以用这两个函数的时候,实际的执行时间是大于或等于指定时间的,不保证能准确定时的)。所谓的JS的单线程和异步更多的应该是属于浏览器的行为,他们之间没有冲突,更不是同一种事物,没有什么区别不区别的。
同步:一般指的是在代码运行的过程中,从上到下逐步运行代码,每一部分代码运行完成之后,下面的代码才能开始运行。
异步:指的是当我们需要一些代码在执行的时候不会影响其他代码的执行,也就是在执行代码的同时,可以进行其他的代码的执行,不用等待代码执行完成之后才执行之后的代码,就像我们人一样,可以一边看电视剧一边吃东西一样,互不干扰,这种方式就是异步。
promises是异步编程的一种解决方法:从语法上讲,promise是一个对象,他可以获取异步操作的消息;本意讲,他是承诺,承诺他过一段时间会给你一个结果。
解决了什么问题:
解决可读性的问题
解决信任问题:promise 只有成功和失败 2 个状态,让一个函数无论成功还是失败都能被调用。原理:
Promise.all()方法用于将多个 Promise 实例,包装成一个新的 Promise 实例。该方法提供了并行执行异步操作的能力,并且在所有异步操作执行完并且执行结果都是成功的时候才执行回调。没有promise用async,实际开发中,我觉得async更好用。
同步和异步的区别?
同步:一般指的是在代码运行的过程中,从上到下逐步运行代码,每一部分代码运行完成之后,下面的代码才能开始运行。
eg:浏览器访问服务器,用户看到页面刷新,重新发请求,等请求完,页面刷新,新内容出现,用户看到新内容之后进行下一步操作。
异步:浏览器访问服务器请求,用户正常操作,浏览器在后端进行请求。等请求完,页面不刷新,新内容也会出现,用户看到新内容。JS 的延迟加载有助与提高页面的加载速度。
defer 和 async、动态创建 DOM 方式(用得最多)、按需异步载入 JS
defer:延迟脚本。立即下载,但延迟执行(延迟到整个页面都解析完毕后再运行),按照脚本出现的先后顺序执行。
async:异步脚本。下载完立即执行,但不保证按照脚本出现的先后顺序执行。
promise、generator、async/await
回调函数的定义:函数A作为参数(函数引用)传递到另一个函数B中,并且这个函数B执行函数A。我们就说函数A叫做回调函数。如果没有名称(函数表达式),就叫做匿名回调函数。
回调地狱:嵌套的回调函数
defer属性和async属性都只适用于外部脚本文件:表示立即下载,延迟执行。
defer与async的区别是:
defer要等到整个页面正常渲染结束,才会执行;
async一旦下载完,渲染引擎就会中断渲染,执行这个脚本以后,再继续渲染。
一句话,defer是“渲染完再执行”,async是“下载完就执行”。如果有多个defer脚本,会按照它们在页面出现的顺序加载,而多个async脚本是不能保证加载顺序的。
async function testResult() {
let result = await after2seconds(30);
console.log(result);
}
Promise
Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大,简单地说,Promise好比容器,里面存放着一些未来才会执行完毕(异步)的事件的结果,而这些结果一旦生成是无法改变的async await
async await也是异步编程的一种解决方案,他遵循的是Generator 函数的语法糖,他拥有内置执行器,不需要额外的调用直接会自动执行并输出结果,它返回的是一个Promise对象。两者的区别:
Promise的出现解决了传统callback函数导致的“地狱回调”问题,但它的语法导致了它向纵向发展行成了一个回调链,遇到复杂的业务场景,这样的语法显然也是不美观的。而async await代码看起来会简洁些,使得异步代码看起来像同步代码,await的本质是可以提供等同于”同步效果“的等待异步返回能力的语法糖,只有这一句代码执行完,才会执行下一句。async await与Promise一样,是非阻塞的(异步)。
async await是基于Promise实现的,可以说是改良版的Promise,它不能用于普通的回调函数。简单来看,这两者除了语法糖不一样外,他们解决的问题、达到的效果是大同小异的,我们可以在不同的应用场景,根据自己的喜好来选择使用。
// 要7秒才能执行完
function ajax1() {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('7秒后我才完成')
}, 7000)
})
}// 5秒后,抛出一个错误
function timeout() {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('超过5秒了')), 5000)
})
}Promise.race([
ajax1(),
timeout()
]).then(res => {
console.log(res)
})
ajax 是使用js中XMLHttpRequest对象发起异步请求,
axios 是使用XMLHttpRequest基于Promise 的实现版本
Fetch是基于promise设计的。一定记住fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象。
原理
requireJS核心原理是通过动态创建 script 脚本来异步引入模块,然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数。
通过正则匹配模块以及模块的依赖关系,保证文件加载的先后顺序。通过指定script的src属性来异步加载模块的。然后对每个脚本的 load 事件进行监听,如果每个脚本都加载完成了,再调用回调函数。根据文件的路径对加载过的文件做了缓存。
必要性:JavaScript 引擎每次创建字符串、数组或对象时,解释器都会动态地分配了内存,为了避免消耗完系统中所有可用的内存,造成系统崩溃。所以要释放这些内存以便他们能够被再用。
JavaScript 的解释器可以检测到何时程序 不再使用一个对象了,当他确定了一个对象是无用的时候,他就知道不再需要这 个对象,可以把它所占用的内存释放掉了。
垃圾回收的方法:
(1)标记清除:最常见的垃圾回收方式,当变量进入环境时,就标记这个变量为”进入环境 “,从逻辑上讲,永远不能释放进入环境的变量所占的内存,永远不能释放进入环境变量所占用的内存,只要执行流程进入相应的环境,就可能用到他们。当离开环境时,就标记为离开环境。 垃圾回收器在运行的时候会给存储在内存中的变量都加上标记(所有都加),然后去掉环境变量中的变量,以及被环境变量中的变量所引用的变量(条件性去除 标记),删除所有被标记的变量,删除的变量无法在环境变量中被访问所以会被删除,最后垃圾回收器,完成了内存的清除工作,并回收他们所占用的内存。
(2)计数引用。(不太常见),引用计数法的意思就是标记每个值引用的次数,当声明了一个变量,并用一个引用类型的值赋值给改变量,则这个值的引用次数为 1,;相反的,如果包含了对这个值引用的变量又取得了另外一个值, 则原先的引用值引用次数就减 1,当这个值的引用次数为 0 的时候,说明没有办 法再访问这个值了,因此就把所占的内存给回收进来,这样垃圾收集器再次运行 的时候,就会释放引用次数为 0 的这些值。
用引用计数法会存在内存泄露:objA 和 objB 通过各自的属性相互引用,这样的话,两个对象 的引用次数都为 2,在采用引用计数的策略中,由于函数执行之后,这两个对象 都离开了作用域,函数执行完成之后,因为计数不为 0,这样的相互引用由于不会触发垃圾回收的过程,如果大量存在就会导致内存泄露。
五种基本数据类型: Undefined、Null、Boolean、Number和String。
一种复杂数据类型:Object三大引用类型(存储在堆内存中): Object、Array和Function
类型判断:首先会想到typeof。单单用 typeof 并无法完全满足,这其实并不是 bug,本质原因是 JS 的万物皆对象的理论。因此要真正完美判断时,我们需要区分对待:
基本类型(包括null)直接使用 typeof 即可。
引用类型的类型判断:
(1)可以使用原型链,使用instanceof,但是不能识别出基本数据类型。
(2)Object.prototype.toString.call(obj)可以相对较全的判断js的数据类型。
原理:Object原型上的toString方法作用在传入的obj的上下文中。
为什么要用Object的toString方法?
其实各数据类型使用toString()后的结果表现不一的原因在于:所有类在继承Object的时候,改写了toString()方法。 Object原型上的方法是可以输出数据类型的。因此我们想判断数据类型时,也只能使用原始方法。在项目中使用哪个判断,还是要看使用场景,具体的选择,一般基本的类型可以选择typeof,引用类型可以使用instanceof。
===比较类型和值,==只比较值。
1
隐式转换(var a=?;console.log(a= =1&&a= =2&&a= =3);//返回true
答案一
var a = {
i:1,
valueOf:function(){
return a.i++;
}
}
解释:
如果原始类型和对象比较,对象会转为原始类型的值在进行比较。
对象转换为原始类型的值,先调用对象的 valueOf 方法,如果返回的还是对象,再接着调用 toString 方法为什么[]==![]
!的优先级要大于==的,所以先运算右边,![]---->结果为false,
所以实际上比较的是 [] == false
数组与布尔值进行比较,两个运算子都会先转成数值,然后再进行比较。
Number([])为0,Number(false)为0,
过程:[]==![] ----> []==false -----> 0==0 --->//true
浏览器的渲染流程
渲染流程主要有4个步骤:
解析 HTML 生成DOM 树
解析 CSS 样式生成 CSSOM 树,CSSOM 树与 DOM 树结合生成 Render tree
布局 Render Tree 对每个节点进行布局处理,确定在屏幕上的位置(重排/回流)
绘制 Render Tree,遍历渲染树将每个节点绘制出来(重绘)CSS3 动画也被称为补间动画,原因是只需要添加关键帧的位置,其他的未定义的帧会被自动生成。
JS 动画是逐帧动画,在时间帧上绘制内容,一帧一帧的,所以他的可再造性很高,几乎可以完成任何你想要的动画形式。但是由于逐帧动画的内容不一样,会增加制作的负担,占用比较大的资源空间。如果我们采用 JS 来实现动画,每一帧画面的改变都会导致回流(重排),而 CSS 的动画是运行在合成线程中的,不会阻塞主线程,并且在合成线程中完成的动作不会触发回流和重绘。JS 动画运行在 CPU,而 CSS 动画运行在 GPU
DOM0级事件绑定
curEle.οnclick=function(){};DOM2级事件绑定,只有2级DOM包含3个事件:事件捕获阶段、处于目标阶段和事件冒泡阶段
标准浏览器:curEle.addEventListener('click',function(){},false)
IE6-8:curEle.attachEvent('onclick',function(){})
ES5:require:对于基本数据类型,属于复制(即会被模块缓存),对于复杂数据类型,属于浅拷贝.
ES6:import:只读引用// 导入
import "/app";
import React from “react”;
import { Component } from “react”;
// 导出
export function multiply() {...};
export var year = 2018;
export default ...
防抖与节流函数是一种最常用的 高频触发优化方式,能对性能有较大的帮助。
防抖 (debounce): 将多次高频操作优化为只在最后一次执行,通常使用的场景是:用户输入,只需再输入完成后做一次输入校验即可。
节流(throttle): 每隔一段时间后执行一次,也就是降低频率,将高频操作优化成低频操作,通常使用场景: 滚动条事件,通常每隔 100~500 ms 执行一次即可。
浅拷贝:以赋值的形式拷贝引用对象,仍指向同一个地址,此时修改新对象时原对象也会改变。
深拷贝: 完全拷贝一个新对象,修改时原对象不再受到任何影响
(1)JSON.parse(JSON.stringify(obj)): 性能最快 具有循环引用的对象时,缺点:当被拷贝的对象含有函数、undefined、或 symbol 时,无法拷贝 。
(2)递归进行逐一赋值。
function deepClone(obj={}){
if(typrof obj !=="object" || obj == null){
retuen obj;
}
let result
if(obj instanceof Array){
result = [];
}else{
result = {};
}
for(let key in obj){
if(obj.hasOwnProperty(key)){
result[key]=deepClone(obj[key])
}
}
return result;
}
在HTML 中,与 javascript 交互是通过事件驱动来实现的,例如鼠标点击事件 onclick、页面的滚动事件 onscroll 等等,可以向文档或者文档中的元素添加事件侦听器来预订事件。想要知道这些事件是在什么时候进行调用的,就需要了解一下“事件流”的概念。
什么是事件流:事件流描述的是从页面中接收事件的顺序,DOM2 级事件流包括下面几个阶段。
事件捕获阶段:事件对象从Window对象开始沿传播路径向下,依次经过各元素传播至目标元素的父元素;
处于目标阶段:事件对象到达目标元素;
事件冒泡阶段:事件对象从目标元素的父元素开始沿传播路径向上,依次经过各元素传播至Window对象。
事件捕获:先由最上一级的节点先接收事件,然后向下传播到具体的节点。当用户点击了<div>元素,采用事件捕获,则click事件将按照document—><html>—><body>—><div>的顺序进行传播。
事件冒泡:是由IE开发团队提出来的,即事件开始时由最具体的元素(文档中嵌套层次最深的那个节点)接收,然后逐级向上传播
addEventListener:addEventListener 是 DOM事件新增的指定事件处理程 序的操作,这个方法接收 3 个参数:要处理的事件名、作为事件处理程序的函数 和一个布尔值。最后这个布尔值参数如果是 true,表示在捕获阶段调用事件处理程序;如果是 false,表示在冒泡阶段调用事件处理程序。
IE 只支持事件冒泡。
方法一:
使用stopPropagation()方法,使用event对象调用即可。代码:event.stopPropagation();如何阻止默认事件?
可以使用preventDefault()方法,直接使用event对象调用即可。代码:event.preventDefault()
事件委托是利用事件的冒泡原理来实现的,就是事件从最深的节点开始,然后逐步向上传播事件。不在事件的发生地上设置监听函数,而是 在其父元素上设置监听函数,通过事件冒泡,父元素可以监听到子元素上事件的 触发,通过判断事件发生元素 DOM 的类型,来做出不同的响应。
举例:todoList就是 ul 和 li 标签的事件监听,比如我们在添加事件时候,采用 事件委托机制,不会在 li 标签上直接添加,而是在 ul 父元素上添加。
好处:比较合适动态元素的绑定,新添加的子元素也会有监听函数,也可以有事件触发机制.
解析器根据词语构建节点node,形成DOM树。
构建CSS规则树
CSS和DOM树生成Render树
生成布局(Generating the Layout)
绘制(Painting)
回流(位置)与重绘(颜色)
generator(生成器)是ES6标准引入的新的数据类型。一个generator看上去像一个函数,但可以返回多次。
定义:generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
用AJAX时可以这么写:
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}
(1)原型链:Array.prototype.isPrototypeOf(obj); // 判定Array是不是在obj的原型链中,如果是,则返回true,否则false。
(2)构造函数:obj instanceof Array
(3)改变this跨原型链调用toString():Object.prototype.toString.call(obj)
(4)ES5新增的方法:Array.isArray(arr1)
定义:
Array.prototype.remove = function(val) {
var index = this.indexOf(val);
if (index > -1) {
this.splice(index, 1);
}
};
使用:
arr.remove(arr2)
法一:indexOf 循环去重
法二:ES6 Set 去重;Array.from(new Set(array))
arr.join(',').split(',');
(1)最简单的思路:循环中判断,如果子元素是数组则递归。
function flatten(origin) {
var result = [];
for(var i = 0; i< origin.length; i++) {
var item = origin[i];
if(Array.isArray(item)) {
result = result.concat(flatten(item))
} else {
result.push(item);
}
}
return result;
}
var arr = ["a", "b", ["c", "d"], [["d"],"e"], "f"];
flatten(arr); // ["a", "b", "c", "d", "d", "e", "f"]
(2)toString()
数组的原型对象上有一个方法,toString, 它能把数组的所以元素转化成用逗号隔开的字符串。
origin.toString().split(','); // ["1", "2", "3", "4", "a", "b", "c", "d", "d", "e", "f"]
由于 toString 转化为字符串的时候,不会区分字符串和数字类型,在进行区分数据类型的时候要注意。(3)split
上面的方法,我们用 toString() 将数组转化为字符串,那么我们也可以用 split 来做:
arr.join(',').split(',');
同样,这种字符串和数组互转的过程,不适合多种数据类型同时处理。(4)reduce
我们注意到其实数组扁平化其实就是“迭代 + 拼接(累加) + 递归”的过程,数组 reduce 方法既可以迭代又可以累加,适合做数组扁平化。
function flatten(origin){
return origin.reduce(function(init, item){
return init.concat(Array.isArray(item) ? flatten(item) : item)
}, [])
}
var arr = [1, [2, 3, [4]], "a", "b", ["c", "d"], [["d"],"e"], "f"];
console.log(flatten(arr)) // [1, 2, 3, 4, "a", "b", "c", "d", "d", "e", "f"](5)ES6 扩展运算符...可以将两重数组转换为单层数组:
[].concat(...[1, [2, 3, [4]], "a", "b", ["c", "d"], [["d"],"e"], "f"]);
typeof:
1.返回值是一个字符串, 用来说明变量的数据类型。
2.typeof 一般只能返回如下几个结果: number, boolean, string, function, object, undefined。
instanceof:
1.返回值为布尔值;
2. instanceof 用于判断一个变量是否属于某个对象的实例。
计算机是二进制来表示浮点数的,无法准确表示一个浮点数,只能逼近。
1
用symbol.iterator实现对象遍历(清晰的记得是b站的问题,难搞哦)Symbol.iterator 为每一个对象定义了默认的迭代器。该迭代器可以被 for...of 循环使用。
1
URL和URI有什么区别?URI 是统一资源标识符,相当于一个人身份证号码
Web 上可用的每种资源如 HTML 文档、图像、视频片段、程序等都是一个来 URI 来定位的 URI
一般由三部组成
①访问资源的命名机制
②存放资源的主机名
③资源自身的名称,由路径表示,着重强调于资源。URL:是统一资源定位符,相当于一个人的家庭住址
URL 是 Internet 上用来描述信息资源的字符串,主要用在各种 WWW 客户程序和 服务器程序上,特别是著名的 Mosaic。采用 URL 可以用一种统一的格式来描述 各种信息资源,包括文件、服务器的地址和目录等。URL 一般由三部组成
①协议(或称为服务方式)
②存有该资源的主机 IP 地址(有时也包括端口号)
③主机资源的具体地址。如目录和文件名等。
webpack是一个模块打包工具,他要做的事是分析你的项目,找到js模块以及其他一些浏览器不能直接运行的拓展语言,并将其转换和打包为合适的格式给浏览器使用。
尤玉溪在知乎上推荐过vite,但是我没有用过。
一、是什么
HMR全称 Hot Module Replacement,可以理解为模块热替换,指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个应用例如,我们在应用运行过程中修改了某个模块,通过自动刷新会导致整个应用的整体刷新,那页面中的状态信息都会丢失。如果使用的是 HMR,就可以实现只将修改的模块实时替换至应用中,不必完全刷新整个应用
在webpack中配置开启热模块也非常的简单,如下代码:
const webpack = require('webpack')
module.exports = {
// ...
devServer: {
// 开启 HMR 特性
hot: true
// hotOnly: true
}
}
通过上述这种配置,如果我们修改并保存css文件,确实能够以不刷新的形式更新到页面中
但是,当我们修改并保存js文件之后,页面依旧自动刷新了,这里并没有触发热模块
所以,HMR并不像 Webpack 的其他特性一样可以开箱即用,需要有一些额外的操作
我们需要去指定哪些模块发生更新时进行HRM,如下代码:
if(module.hot){
module.hot.accept('./util.js',()=>{
console.log("util.js更新了")
})
}
二、实现原理
Webpack Compile:将 JS 源代码编译成 bundle.js(构建输出的文件)
HMR Server:用来将热更新的文件输出给 HMR Runtime(socket服务器),会被注入到浏览器,更新文件的变化
Bundle Server:静态资源文件服务器,提供文件访问路径
在HMR Runtime 和 HMR Server之间建立 websocket,用于实时更新文件变化当某一个文件或者模块发生变化时,webpack监听到文件变化对文件重新编译打包,编译生成唯一的hash值,这个hash值用来作为下一次热更新的标识
根据变化的内容生成两个补丁文件:manifest(包含了 hash 和 chundId,用来说明变化的内容)和chunk.js 模块
由于socket服务器在HMR Runtime 和 HMR Server之间建立 websocket链接,当文件发生改动的时候,服务端会向浏览器推送一条消息,消息包含文件改动后生成的hash值,作为下一次热更新的标识
在浏览器接受到这条消息之前,浏览器已经在上一次socket 消息中已经记住了此时的hash 标识,这时候我们会创建一个 ajax 去服务端请求获取到变化内容的 manifest 文件
mainfest文件包含重新build生成的hash值,以及变化的模块,对应上图的c属性
浏览器根据 manifest 文件获取模块变化的内容,从而触发render流程,实现局部模块更新三、总结
关于webpack热模块更新的总结如下:
通过webpack-dev-server创建两个服务器:提供静态资源的服务(express)和Socket服务
express server 负责直接提供静态资源的服务(打包后的资源直接被浏览器请求和解析)
socket server 是一个 websocket 的长连接,双方可以通信
当 socket server 监听到对应的模块发生变化时,会生成两个文件.json(manifest文件)和.js文件(update chunk)
通过长连接,socket server 可以直接将这两个文件主动发送给客户端(浏览器)
浏览器拿到两个新的文件后,通过HMR runtime机制,加载这两个文件,并且针对修改的模块进行更新
【Loader】:用于对模块源码的转换,loader描述了webpack如何处理非javascript模块,并且在buld中引入这些依赖。loader可以将文件从不同的语言(如TypeScript)转换为JavaScript,或者将内联图像转换为data URL。比如说:CSS-Loader,Style-Loader等。
loader的使用很简单:
在webpack.config.js中指定loader。module.rules可以指定多个loader,对项目中的各个loader有个全局概览。
loader是运行在NodeJS中,可以用options对象进行配置。plugin可以为loader带来更多特性。loader可以进行压缩,打包,语言翻译等等。
loader从模板路径解析,npm install node_modules。也可以自定义loader,命名XXX-loader。
语言类的处理器loader:CoffeeScript,TypeScript,ESNext(Bable),Sass,Less,Stylus。任何开发技术栈都可以使用webpack。
【Plugin】:目的在于解决loader无法实现的其他事,从打包优化和压缩,到重新定义环境变量,功能强大到可以用来处理各种各样的任务。plugins在webpack运行的生命周期中会广播出许多事件,plugin可以监听这些事件,在合适的时机通过webpack提供的API改变输出结果。webpack提供了很多开箱即用的插件:CommonChunkPlugin主要用于提取第三方库和公共模块,避免首屏加载的bundle文件,或者按需加载的bundle文件体积过大,导致加载时间过长,是一把优化的利器。而在多页面应用中,更是能够为每个页面间的应用程序共享代码创建bundle。
webpack功能强大,难点在于它的配置文件,webpack4默认不需要配置文件,可以通过mode选项为webpack指定了一些默认的配置,mode分为:development/production,默认是production。
插件可以携带参数,所以在plugins属性传入new实例。
vue3新特性,proxy和defineProperty的区别
mvvm和mvc差异(和项目开发一个道理,小公司接小项目,boss从客户那接了单给程序员做,程序员做了给客户看,客户看了不满意找领导,领导找程序员,这么个循环就是mvc;公司大了,项目大了领导不管了,那就是客户需求和产品经理说,产品经理和程序员对接,程序员做完给产品经理看,产品再去把东西给客户,这就是mvvm)
父子通信
非父子通信
生命周期
key的作用
v-if和
vue-router原理,路由如何跳转
vuex数据流向,mutation和action
他们的设计思路的不同:
react组件是函数式的思想。组件的状态和逻辑通过参数传入,尤其在hook中体现的非常明显。
vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom。
react单向数据流,vue双向绑定。
我们平时会使用的主要是两种路由模式:
HashRouter:这种模式基于浏览器location的hash片段来实现,实现比较简单,不需要服务器的支持,缺点是url样式不够优雅,而且hash参数容易丢失。
BrowserRouter:这种模式基于浏览器的history API,可以让我们创建一个像 `http://example.com/home/files` 这样真实的URL,而且切换url不会引起页面的刷新,用户体验比较好,是我们比较推荐的路由方式,不过这种模式需要服务器比如Nginx的支持,因为路径 `/home/files` 只是一个前端定义的路由,当用户刷新页面的时候浏览器会去向服务器请求这个资源,服务器因为没有对应的这个资源,就会返回404,导致页面无法显示,所以需要Nginx将所有404的请求返回入口文件 `/index.html`
数据流动方向可以跟踪,流动单一,追查问题的时候可以跟快捷。缺点就是写起来不太方便。要使UI发生变更就必须创建各种action来维护对应的state。
(1)react整体是函数式的思想,把组件设计成纯组件,状态和逻辑通过参数传入,所以在react中,是单向数据流,推崇结合immutable来实现数据不可变。react在setState之后会重新走渲染的流程。
vue的思想是响应式的,也就是基于是数据可变的,通过对每一个属性建立Watcher来监听,当属性变化的时候,响应式的更新对应的虚拟dom
(2)react的性能优化需要手动去做,而vue的性能优化是自动的,但是vue的响应式机制也有问题,就是当state特别多的时候,Watcher也会很多,会导致卡顿,所以大型应用(状态特别多的)一般用react,更加可控
(3)react的思路是all in js,通过js来生成html,所以设计了jsx,还有通过js来操作css,社区的styled-component、jss等。
vue是把html,css,js组合到一起,用各自的处理方式,vue有单文件组件,可以把html、css、js写到一个文件中,html提供了模板引擎来处理。
虚拟DOM可以看作是一个使用javascript模拟了DOM结构的树形结构,然后用这个树构建一个真正的DOM 树,插到文档当中。当状态变更的时候,重新构造一棵新的对象树。然后用 新的树和旧的树进行比较,记录两棵树差异 把所记录的差异应用到所构建的真 正的 DOM 树上,视图就更新了。
Virtual DOM 本质上就是在 JS 和 DOM 之间做 了一个缓存。当状态改变时,重新构建新的DOM树。为什么要使用虚拟DOM?
浏览器里一遍又一遍的渲染DOM是非常非常消耗性能的,常常会出现页面卡死的情况;所以尽量减少对DOM的操作成为了优化前端性能的必要手段。
虚拟DOM是怎么工作的?
vdom就是将DOM的对比放在了js层,通过对比不同之处来选择新渲染DOM节点,从而提高渲染效率。
为什么操作 DOM 慢?
DOM 本身是一个 js 对象, 操作这个对象本身不慢, 但是操作后触发了浏览器的行为, 如 repaint 和 reflow 等浏览器行为, 使其变慢。
diff算法的作用:计算出Virtual DOM中真正变化的部分,并只针对该部分进行原生DOM操作,而非重新渲染整个页面。
传统diff算法:通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3) ,n是树的节点数,这个有多可怕呢?——如果要展示1000个节点,得执行上亿次比较。。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。React的diff算法
(1)什么是调和?
将Virtual DOM树转换成actual DOM树的最少操作的过程 称为 调和 。
(2)什么是React diff算法?
diff算法是调和的具体实现。diff策略
React用 三大策略 将O(n^3)复杂度 转化为 O(n)复杂度。
策略一(tree diff):
Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。
策略二(component diff):
拥有相同类的两个组件 生成相似的树形结构,
拥有不同类的两个组件 生成不同的树形结构。
策略三(element diff):
对于同一层级的一组子节点,通过唯一id区分。
执行过程代码是同步的,只是合成事件和钩子函数的调用顺序在更新之前,导致在合成事件和钩子函数中没法立马拿到更新后的值,形成了所谓的“异步”,所以表现出来有时是同步,有时是“异步”。一句话来说就是:“只在合成事件和钩子函数中是“异步”的,在原生事件和 setTimeout/setInterval等原生 API 中都是同步的”
那为什么会出现异步的情况呢?
为了做性能优化,将 state 的更新延缓到最后批量合并再去渲染对于应用的性能优化是有极大好处的,如果每次的状态改变都去重新渲染真实 dom,那么它将带来巨大的性能消耗。那如何在表现出异步的函数里可以准确拿到更新后的 state 呢?
通过第二个参数 setState(partialState, callback) 中的 callback 拿到更新后的结果。或者可以通过给 setState 传递函数来表现出同步的情况:
this.setState((state) => {
return { val: newVal }
})那表现出异步的原理是怎么样的呢?
源码可参考<React-setState原理>
这里还是用最简单的语言理解:在 React 的 setState 函数实现中,会根据 isBatchingUpdates(默认是 false) 变量判断是否直接更新 this.state 还是放到队列中稍后更新。然后有一个 batchedUpdate 函数,可以修改 isBatchingUpdates 为 true,当 React 调用事件处理函数之前,或者生命周期函数之前就会调用 batchedUpdate 函数,这样的话,setState 就不会同步更新 this.state,而是放到更新队列里面后续更新。
这样就可以理解为什么原生事件和 setTimeout/setinterval 里面调用 this.state 会同步更新了吧,因为通过这些函数调用的 React 没办法去调用 batchedUpdate 函数将 isBatchingUpdates 设置为 true,那么这个时候 setState 的时候默认就是 false,那么就会同步更新。
在React中,数据在组件中是单向流动的,数据从一个方向父组件流向子组件(通过props),所以,两个非父子组件之间通信就相对麻烦,redux的出现就是为了解决state里面的数据问题。Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。
我们首先要创建一个状态类,来管理我们的全局状态,而且当这些状态发生变化的时候可以引起界面的刷新,比如我们可以把当前登录的用户信息放到全局状态中,其他组件可以直接引用。
如果想要实现全局状态的管理,我们可以借助于React提供的 `Context` 或者社区的 `Redux` ,`MobX` 也是一个非常流行的状态管理库,它使用非常简单,上手容易。
为什么使用Hooks?
1.React Hooks 就是让你不必写class组件就可以用state和其他的React特性;
2.你也可以编写自己的hooks在不同的组件之间复用;
3.由于业务变动,函数组件不得不改为类组件等等。React hooks优势:
1.没有破坏性改动
完全可选的。 你无需重写任何已有代码就可以在一些组件中尝试 Hook。
100% 向后兼容的。 Hook 不包含任何破坏性改动。
2.更容易复用代码:它通过自定义hooks来复用状态,从而解决了类组件有些时候难以复用逻辑的问题
3.函数式编程风格:函数式组件、状态保存在运行环境、每个功能都包裹在函数中,整体风格更清爽,更优雅
4.代码量更少
5.更容易拆分组件React hooks缺点:
hooks 是 React 16.8 的新增特性、以前版本的就别想了
1.部分代码从主动式变成响应式
2.状态不同步:函数的运行是独立的,每个函数都有一份独立的作用域。当我们处理复杂逻辑的时候,经常会碰到“引用不是最新”的问题
挂载:componentDidMount()
更新:componentDidUpdate()
卸载:componentWillUnmount()
为什么要用?
在使用React中,会出现过一个文件的代码很多,既存在应用数据的读取和处理,又存在数据的显示。要实现组件的复用,我们就需要将展示组件和容器组件分离出来。展示组件(Presentational Component)
关注页面的展示效果(外观)
内部可以包含展示组件和容器组件,通常会包含一些自己的DOM标记和样式(style)
通常允许通过this.props.children方式来包含其他组件。
对应用程序的其他部分没有依赖关系,例如Flux操作或store。
不用关心数据是怎么加载和变动的。
只能通过props的方式接收数据和进行回调(callback)操作。
很少拥有自己的状态,即使有也是用于展示UI状态的。
会被写成函数式组件除非该组件需要自己的状态,生命周期或者做一些性能优化。
Example:Page,Header,Sidebar,UserInfo,List容器组件(Container Component)
关注应用的是如何工作的
内部可以包含容器组件和展示组件,但通常没有任何自己的DOM标记,除了一些包装divs,并且从不具有任何样式。
提供数据和行为给其他的展示组件或容器组件。
调用Flux操作并将它们作为回调函数提供给展示组件。
往往是有状态的,因为它们倾向于作为数据源
通常使用高阶组件生成,例如React Redux的connect(),Relay的createContainer()或Flux Utils的Container.create(),而不是手工编写。
父子组件props
自定义事件
redux
1.状态提升 -- 中间人模式(通过父组件来通信,一级一级的分发)
2.发布订阅者模式 -- 不需要借助任何人的力量,纯原生js的方式(设计模式中的一种)
3.mobx
高阶组件是一个函数,接收一个组件参数,然后返回一个新组件。比如说redux的connect连接器。
高阶组件并不会改变传入的组件,也不会使用继承来拷贝他的行为,而是将传入组件放进了这个容器组件中。一个高阶组件是0副作用的纯函数。
shouldComponentUpdate:确定是否将渲染更新组件。默认情况下,它返回true。如果您确定组件在状态或道具更新后不需要渲染,则可以返回false值。这是提高性能的好地方,因为如果组件收到新的props,它可以防止重新渲染。
http:超文本传输协议,https:超文本传输安全协议
协议:为了实现网络通信而达成的一种“约定”,这种“规则”不同厂商的生产设备,以及不同操作系统组成的计算机之间,就可以实现通信。
基于TCP/IP通信协议,从WEB服务器传输超文本标记语言(HTML)到本地浏览器的传送协议。
简单快速:(1)客户向服务器请求服务时,只需传送请求方法和路径(没有验证对方身份,存在冒充危险)。(2)HTTP允许传输任意类型的数据对象(数据的完整性未校验,容易被篡改。请求信息明文传输,容易被窃听截取。)。(3)每次连接只处理一个请求。服务器处理完请求,并收到客户的应答后,即断开连接,但是却不利于客户端与服务器保持会话连接,为了弥补这种不足,产生了两项记录http状态的技术,一个叫做Cookie,一个叫做Session。
绝大的网站现在都采用的是https协议,这也是未来互联网发展的趋势
一般理解为HTTP+SSL/TLS,SSL 协议位于 TCP/IP 协议与各种应用层协议之间。通过 SSL证书来验证服务器的身份,并为浏览器和服务器之间的通信进行加密。
HTTPS协议多次握手,(非对称加密的加解密效率是非常低的);
HTTPS连接缓存不如HTTP高效,会增加数据开销和功耗;
申请SSL证书需要钱,功能越强大的证书费用越高。
SSL涉及到的安全算法会消耗 CPU 资源,对服务器资源消耗较大。
因为http的内容是明文传输的,明文数据会经过中间代理服务器、路由器、wifi热点、通信服务运营商等多个物理节点,如果信息在传输过程中被劫持,传输的内容就完全暴露了。劫持者还可以篡改传输的信息且不被双方察觉,这就是中间人攻击。所以我们才需要对信息进行加密。
1. 对称加密
简单说就是有一个密钥,通信双方都持有同一个密钥。
Window btoa() // 创建一个 base-64 编码的字符串
window.atob(// 解码使用 base-64 编码的字符串。
2. 非对称加密?
简单说就是有两把密钥,通常一把叫做公钥、一把叫私钥,用公钥加密的内容必须用私钥才能解开,同样,私钥加密的内容只有公钥能解开。3. https使用了那种加密方式?
HTTPS 在内容传输的加密上使用的是对称加密,非对称加密只作用在证书验证阶段(浏览器端通过公钥加密随机数,并把加密后的随机数传输到服务端;服务端通过私钥对随机数进行解密;)。
HTTP 1.0 :浏览器每次请求都需要与服务器建立一个TCP连接,服务器处理完成以后立即断开TCP连接(无连接),服务器不跟踪每个客户单,也不记录过去的请求(无状态)。
HTTP 1.1
(1)长连接:增加Connection字段,通过设置Keep-Alive保持HTTP连接不中断,如果客户端想关闭HTTP连接,可以在请求头中携带Connection:false来告知服务器关闭请求。
(2)管道化:服务器必须按照客户端请求的先后顺序依次回送相应的结果。一旦有某个请求超时,后续请求只能被阻塞,也就是常说的 线头阻塞
(3)缓存处理,
缓存处理 — 强缓存、协商缓存,启发式缓存(新增)
缓存处理(强缓存和协商缓存),新的字段如cache-control,支持断点传输,以及增加了Host字段(使得一个服务器能够用来创建多个Web站点),
HTTP 2.0
(1)二进制分帧:HTTP2.0通过在应用层和传输层之间增加一个二进制分层帧,改进传输性能。
(2)多路复用(链接共享)— 真并行传输。所有HTTP2.0通信都在一个TCP链接上完成,这个链接可以撑在任意流量的双向数据流。每个数据流以消息的形式发送,而消息由一或多个帧组成。这些帧可以乱序发送,然后再根据每个帧头部的流标识符(Stream_id)重新封装。
(3)头部压缩:使用encoder来减少需要传输的header大小,通讯双方各自cache一份header_files表,既避免重复header的传输,又减少了需要传输的大小。高效的压缩算法可以很大的压缩header,减少发送包的数量从而降低延迟。
(4)服务器推送:服务器除了最初请求的响应外,服务器还可以额外向客户端推送资源,而无需客户端明确的需求。
HTTP 3.0:基于UDP协议的QUIC协议。
(1)0-RTT 是 QUIC协议相比HTTP2.0的最大优势。缓存当前会话的上下文,下次恢复会话的时候,只需要将之前的缓存传递给服务器,验证通过,就可以进行传输了。
什么是0-RTT建连?
传输层0-RTT就能建立连接
加密层0-RTT就能建立加密连接
(2)多路复用
QUIC基于UDP,一个连接上的多个stream之间没有依赖,即使丢包,只需要重发丢失的包即可,不需要重传整个连接。
(3)更好的移动端表现
QUIC在移动端的表现比TCP好,因为TCP是基于IP识别连接,而QUIC是通过ID识别链接。
无论网络环境如何变化,只要ID不便,就能迅速重新连上。
(4)加密认证的根文
TCP协议头没有经过任何加密和认证,在传输过程中很容易被中间网络设备篡改、注入和窃听。QUIC的packet可以说武装到了牙齿,除了个别报文,比如PUBLIC_RESET和CHLO,所有报文头部都是经过认证的,报文Body都是经过加密的。所以只要对 QUIC 做任何更改,接收端都能及时发现,有效地降低了安全风险。
(5)向前纠错机制
QUIC协议有一个非常独特的特性,成为向前纠错(Foward Error Connec,FEC),每个数据包除了它本身的内容之外还包括了其他数据包的数据,因此少量的丢包可以通过其他包的冗余数据直接组装而无需重传。
1**: 服务器收到请求, 需请求者进一步操作
2**: 请求成功
3**: 重定向, 资源被转移到其他 URL 了
4**: 客户端错误, 请求语法错误或没有找到相应资源
5**: 服务端错误, server error301: 资源(网页等)被永久转移到其他 URL, 返回值中包含新的 URL, 浏览器会自动定向到新 URL
302: 临时转移. 客户端应访问原有 URL
304: Not Modified. 指定日期后未修改, 不返回资源
403: 服务器拒绝执行请求
404: 请求的资源(网页等)不存在
500: 内部服务器错误
8 种请求方法
OPTIONS:返回服务器针对特定资源所支持的 HTTP 请求方法,也可以利用其向 web 服务器发送 * 的请求来测试服务器的功能性。
HEAD:向服务器索与 GET 请求相一致的响应,只不过响应体将不会被返回。这一方法可以在不必传输整个响应内容的情况下,就可以获取包含在响应小消息头中的元信息。
GET:向特定的资源发出请求,它的本质就是发送一个请求来取得服务器上的某一资源。
POST:向指定资源提交数据进行处理请求(例如提交表单或者上传文件等)。数据被包含在请求体中。
PUT:向指定资源位置上传最新内容。
DELETE:请求服务器删除 Request-URL 所表示的资源。
TRACE:回显服务器收到的请求,主要用于测试或诊断。
CONNECT:协议中预留给能够将连接改为管道方式的代理服务器。
首先, GET 和 POST 方法都是基于 TCP/IP 协议,也就是说,两者的数据传输都是建立在 TCP 的连接,所以,如果从两者的本质来讲并没有多大的区别,你非要给 GET 方法加上 request body,给 POST 方法加上 URL 参数都是行得通的,HTTP 协议对于 GET 和 POST 其实并没有长度限制。因而,两者的区别更多地体现在使用规范上,从使用规范上来说:
GET 浏览器回退是无害的,因为有本地缓存。而 POST 会再次提交请求,post没有本地缓存。
GET 只能进行 URL 编码,而POST 支持多种编码方式。
GET 参数通过 URL 传递,并且长度有限制,而 POST 放在 request body 并且长度没有限制。并且,正因为这个原因, GET 比 POST 更不安全,因为参数暴露在 URL 中。最后一个小细节:
GET 产生一个 TCP 数据包,而 POST 产生两个 TCP 数据包。
对于 GET 请求,浏览器会把 http header 和 data 一起发送出去,服务器响应 200。
而 POST 请求,浏览器会先把 http header 发送出去,服务器响应 100,然后浏览器再发送 data,服务器响应 200。(当然了,并不是所有浏览器,Firefox只发一次)幂等性
什么是幂等性?
幂等性指的是一次请求和多次请求某一资源具有同样的副作用。比如,你采用 GET 请求银行账户的余额,虽然结果不一定相同,可是并无副作用,因此是幂等的。而如果采用 POST 请求提交扣款,多次提交产生不同的副作用,因此不满足幂等性。HTTP 的 GET、DELETE、PUT、POST 的幂等性
GET 请求用于获取资源,无副作用,所以是幂等的。
DELETE 请求用于删除资源,有副作用,但是,DELETE 同一资源,无论调用一次还是调用多次,其副作用是相同的,因此也满足幂等性。
PUT 方法用于创建或更新操作,有副作用,与 DELETE 相同,对同一资源无论调用一次还是多次,其副作用是相同的,因此也满足幂等性。
POST 方法与 PUT 方法的区别主要在于幂等性,POST 不具备幂等性,因为 POST 请求每次都会创建一个文件,而 PUT 方法会在服务器验证是否有 ENTITY,若有则更新该 ENTITY 而不是重新创建。
TCP:传输控制协议。是一种面向连接的、可靠的、基于字节流的运输层的全双工通信协议。
TCP 对应的协议
(1) HTTP:是从 Web 服务器传输超文本到本地浏览器的传送协议UDP :用户数据报协议。是一种面向无连接,不可靠的,支持多种交互的运输层通讯协议。
UDP 对应的协议
(1) DNS:用于域名解析服务,将域名地址转换为 IP 地址。DNS 用的是 53 号端口。
TCP 三次握手
第一步让服务器知道客户端能够发送,
第二步让客户端知道服务器能够接收并且能够发送,
第三步让服务器知道了客户端能够发送。
至此双方都能确认了对方能够收和发, 接下来就是愉快的互相收发数据的过程啦。四次挥手:
第一步: 客户端做为主动发起方想要与服务器断开连接,
第二步: 服务器告诉客户端说我收到你的关闭请求了
第三步: 服务器向客户端请求关闭
第四步: 客户端告诉服务器说我也收到了你的关闭请求了. 此时服务器就关闭了, 而客户端还要等待2MSL的时间之后才能关闭
MSL:最长报文段寿命。服务器的超时重发机制。
在TIME_WAIT (也被叫做2MSL等待状态)状态下,客户端向服务器发出ACK以后需要等待2个MSL的时间。假如ACK在传输中丢失,超时后服务端会重新发送FIN, 客户端收到以后会重新发ACK。假如在2MSL时间中客户端都没有收到服务端重发的FIN,那么客户端认为服务端已经收到了客户端发送的ACK, 此时客户端才可以放心的断开连接。
防止过多的数据注入到网络中.
TCP的四种拥塞控制算法
1.慢开始
2.拥塞控制
3.快重传
4.快恢复
怎么理解大前端方向
LRU原理
常用数据结构和算法
线程和进程
图片资源压缩显示
前端性能检测
伪随机算法
最有成就感的事
你最有成就感的事与某次成长困难有关,且这件事的解决让你明白一个道理。
最失败的经历
最近读过什么书
写过多少代码
怎么和人合作编程,遇到矛盾怎么处理
平时怎么学习的
学习目的:
考试=>做历年真题,刷错题
实际应用=>官网,练习小demo,边学边用
休闲=>科幻小说=>可以跳出自己的惯性思维。
为什么选择学习这方面知识
1. 最初始的两年中,踏实的做好自己的本职工作,会向高手请教,研究些新技术,提高自己的技术水平。当前面这些完成,我已经是可以成为一位独挡一面的技术工程师了。
2. 在接下来的几年中,熟悉公司使用的这套框架,搞懂底层原理。想努力成为一名架构师了,“不想当架构师的程序员不是好程序员。”,自己在以后也会朝着这个方向去努力。
3. 一个产品进行分析的程序员,如果能够创造出一款自己设计的产品被大众使用,
兴趣爱好
平时作息时间
更想从事什么样的行业?游戏,动漫,生活(外卖,买房等)(贝壳的问题,我选择动漫,用爱发电,他选择挂我
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。