
本文共 33293 字,大约阅读时间需要 110 分钟。
目录
vue相关
0/ v-if和 v-show的区别, display:none和visible:hidden的区别
display:none和visibility:hidden的区别是:
1.display:none是彻底消失,不在文档流中占位,浏览器也不会解析该元素;visibility:hidden是视觉上消失了,可以理解为透明度为0的效果,在文档流中占位,浏览器会解析该元素;
2.使用visibility:hidden比display:none性能上要好,display:none切换显示时visibility,页面产生回流(当页面中的一部分元素需要改变规模尺寸、布局、显示隐藏等,页面重新构建,此时就是回流。所有页面第一次加载时需要产生一次回流),而visibility切换是否显示时则不会引起回流。
1/ vue的生命周期及各个周期的特点
vue每个组件都是独立的,每个组件都有一个属于它的生命周期,从一个组件创建、数据初始化、挂载、更新、销毁,这就是一个组件所谓的生命周期。
在组件中具体的方法有:
beforeCreate created beforeMount mounted ( beforeUpdate updated ) beforeDestroy destroyed
2/ vue通信方法, bus通信原理
方法一、props / $emit
父组件A通过props的方式向子组件B传递,B to A 通过在 B 组件中 $emit, A 组件中 v-on 的方式实现。
方法二、$emit / $on
这种方法通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级。当我们的项目比较大时,可以选择更好的状态管理解决方案vuex。
var Event=new Vue();Event.$emit(事件名,数据);Event.$on(事件名,data => { });
3/ vue的nextTick实现原理, js事件循环,微任务与宏任务
javascript
是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
js的一大特点是非阻塞,实现这一点的关键在于一项机制——事件队列(Task Queue)。
js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。
被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop
)”的原因。
以上的事件循环过程是一个宏观的表述,实际上因为异步任务之间并不相同,因此他们的执行优先级也有区别。不同的异步任务被分为两类:微任务(micro task)和宏任务(macro task)。
宏任务(macrotask):setTimeout、setInterval、setImmediate、I/O、UI rendering
微任务(microtask):promise.then、process.nextTick、MutationObserver、queneMicrotask(开启一个微任务)
前面我们介绍过,在一个事件循环中,异步事件返回结果后会被放到一个任务队列(队列-先进先出)中。
然而,根据这个异步事件的类型,这个事件实际上会被对应的宏任务队列或者微任务队列中去。并且在当前执行栈为空的时候,主线程会查看微任务队列是否有事件存在。如果不存在,那么再去宏任务队列中取出一个事件并把对应的回到加入当前执行栈;如果存在,则会依次执行队列中事件对应的回调,直到微任务队列为空,然后去宏任务队列中取出最前面的一个事件,把对应的回调加入当前执行栈…如此反复,进入循环。
我们只需记住在当前执行栈执行完毕时会立刻先处理所有微任务队列中的事件,然后再去宏任务队列中取出一个事件。同一次事件循环中,微任务永远在宏任务之前执行。
举例如图所示,先Promise后setTimeout。
console.log('script start');setTimeout(function() { console.log('timeout1');}, 10);new Promise(resolve => { console.log('promise1'); resolve(); setTimeout(() => console.log('timeout2'), 10);}).then(function() { console.log('then1')})console.log('script end');
4/ vue的优点,与jquery对比
在开发方面的优势
与jquery对比:
jQuery是MVC模式即model,view,control,vue是MVVM模式,即model, view, ViewModel,
在性能方面的优势
数据驱动, 不会影响渲染, 整体更加流程
虚拟dom节点, 可以追踪依赖关系
5/ vue2如何实现数据双向绑定
vue源文档的解释
1. Object.defineProperty()
vue.js 采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调
2. Vue 不能检测数组和对象的变化
3. 声明响应式property
4. 异步更新队列
6/ vue3如何实现数据绑定- proxy代理
Proxy是 ES6 中新增的一个特性,翻译过来意思是"代理",用在这里表示由它来“代理”某些操作。 Proxy 让我们能够以简洁易懂的方式控制外部对对象的访问。其功能非常类似于设计模式中的代理模式。
Proxy 可以理解成,在目标对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。
使用 Proxy 的核心优点是可以交由它来处理一些非核心逻辑(如:读取或设置对象的某些属性前记录日志;设置对象的某些属性值前,需要验证;某些属性的访问控制等)。 从而可以让对象只需关注于核心逻辑,达到关注点分离,降低对象复杂度等目的。
7/ vue虚拟dom, diff算法-比较那些部分找出差异
8/ vue循环中的key的作用, 与index对比
作用:用唯一标识标记每一个节点,可以高效渲染虚拟DOM树, 默认用“就地复用”策略,方便了diff算法更高效的比对dom元素
使用v-for更新已渲染的元素列表时,默认用就地复用策略;列表数据修改的时候,他会根据key值去判断某个值是否修改,如果修改,则重新渲染这一项,否则复用之前的元素.
为什么设置key值
Vue 和 React 都实现了一套虚拟DOM,使我们可以不直接操作DOM元素,只操作数据便可以重新渲染页面, 其原理是Diff算法, 总体来说是指只会同级比较,不会跨级比较。
Vue 和 React 的虚拟DOM的Diff算法大致相同,其核心是基于两个简单的假设:
1.两个相同的组件产生类似的DOM结构,不同的组件产生不同的DOM结构。
2.同一层级的一组节点,他们可以通过唯一的id进行区分。
基于以上这两点假设,使得虚拟DOM的Diff算法的复杂度从O(n^3)降到了O(n)。
用一张图简单说明一下:
当页面的数据发生变化时,Diff算法只会比较同一层级的节点:
如果节点类型不同,直接干掉前面的节点,再创建并插入新的节点。
如果节点类型相同,则会重新设置该节点的属性,从而实现节点的更新。
举例:
如上图:在同级A,B,C,D四个节点中,在B和C之间插入E,没有key的时候:C更新成了E,D更新成了C,最后插入D,这样效率很低。
加上key之后,就能准确的找到节点的位置:发现ABCD都没变化,直接插入E就可以了。
所以key的作用主要是为了唯一标识每个节点, 以能够高效的更新虚拟DOM
为什么不能用index作为key
例如一组数:
list:[ { id: 1, name: 'a', //index: 0 }, { id: 2, name: 'b', //index: 1 }, { id: 3, name: 'c', //index: 2 }, { id: 4, name: 'd', //index: 3 },]
这样的页面显示的是:a,b,c,d(默认选中c)
如果key绑定的是index, 删掉index=1的第二组数(b),除了b之外,c和d的index变化了(c变为1了,d的变为2了),所以c和d也都要重新渲染一遍,影响了性能。
刚开始默认选中c(index原来为2),删除b后,d的index变为2了,这时候页面上默认就选中d了,这就产生了bug
9/ vue2.0到vue3.0的变化以及项目中转换
(项目aizoo中用的是vue2.0)
vue2 和 vue3 思路的设计变化
网络相关
1/ 七层网络模型
http DNS DHCP FTP等协议属于应用层
TCP UDP属于传输层
2/ TCP如何实现可靠传输
TCP是有连接,可靠的,面向字节流的传输协议
主要从下列三个方面来保证TCP可靠性:
- 保证数据能够到达对方:通过应答确认、超时重传保证数据的无差错到达,滑动窗口保证发送方发送数据和接收方速率匹配,拥塞控制保证发送方发送数据的速率与当前网络情况相匹配来保证。
- 保证数据不重复、不乱序:通过32位seq序列号保证,因为seq的初始值是随机生成的,在三次握手结束后,保证了通信的双方知道彼此的初始化seq,这个序列号要作为以后的数据通信的序号,以保证应用层接收到的数据不会因为网络上的传输问题而乱序。TCP会用这个序号来拼接数据,这样就可以保证数据的不重复,有序。
- 保证数据不失真:通过报文首部的16位校验和保证,添加12字节的伪首部进行检验,一般采取CRC冗余检验实现。
2/ UDP
2/ 两次握手三次握手四次挥手
【问题1】为什么连接的时候是三次握手,关闭的时候却是四次握手?
答:因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,“你发的FIN报文我收到了”。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
【问题2】为什么TIME_WAIT状态需要经过2MSL(最大报文段生存时间)才能返回到CLOSE状态?
答:虽然按道理,四个报文都发送完毕,我们可以直接进入CLOSE状态了,但是我们必须假象网络是不可靠的,有可以最后一个ACK丢失。所以TIME_WAIT状态就是用来重发可能丢失的ACK报文。在Client发送出最后的ACK回复,但该ACK可能丢失。Server如果没有收到ACK,将不断重复发送FIN片段。所以Client不能立即关闭,它必须确认Server接收到了该ACK。Client会在发送出ACK之后进入到TIME_WAIT状态。Client会设置一个计时器,等待2MSL的时间。如果在该时间内再次收到FIN,那么Client会重发ACK并再次等待2MSL。所谓的2MSL是两倍的MSL(Maximum Segment Lifetime)。MSL指一个片段在网络中最大的存活时间,2MSL就是一个发送和一个回复所需的最大时间。如果直到2MSL,Client都没有再次收到FIN,那么Client推断ACK已经被成功接收,则结束TCP连接。
【问题3】为什么不能用两次握手进行连接?
答:3次握手完成两个重要的功能,既要双方做好发送数据的准备工作(双方都知道彼此已准备好),也要允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
现在把三次握手改成仅需要两次握手,死锁是可能发生的。作为例子,考虑计算机S和C之间的通信,假定C给S发送一个连接请求分组,S收到了这个分组,并发 送了确认应答分组。按照两次握手的协定,S认为连接已经成功地建立了,可以开始发送数据分组。可是,C在S的应答分组在传输中被丢失的情况下,将不知道S 是否已准备好,不知道S建立什么样的序列号,C甚至怀疑S是否收到自己的连接请求分组。在这种情况下,C认为连接还未建立成功,将忽略S发来的任何数据分 组,只等待连接确认应答分组。而S在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
【问题4】如果已经建立了连接,但是客户端突然出现故障了怎么办?
TCP还设有一个保活计时器,显然,客户端如果出现故障,服务器不能一直等下去,白白浪费资源。服务器每收到一次客户端的请求后都会重新复位这个计时器,时间通常是设置为2小时,若两小时还没有收到客户端的任何数据,服务器就会发送一个探测报文段,以后每隔75秒钟发送一次。若一连发送10个探测报文仍然没反应,服务器就认为客户端出了故障,接着就关闭连接。
3/ http请求报文和响应报文 状态码
一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成。
<request-line><headers><blank line>[<request-body>
HTTP响应也由三个部分组成,分别是:状态行、消息报头、响应正文。
如下所示,HTTP响应的格式与请求的格式十分类似:
<status-line><headers><blank line>[<response-body>]
在响应中唯一真正的区别在于第一行中用状态信息代替了请求信息。状态行(status line)通过提供一个状态码来说明所请求的资源情况。
3/ 项目中对状态码的拦截请求
import axios from 'axios'import ViewUI from 'view-design'// 通用错误码提示信息function responseError (error, msg) { switch (error) { case 404: ViewUI.Message.error({ content: '404 Not Found', duration: 6 }) // gotoUrl('404.html') break case 500: ViewUI.Message.error({ content: msg, duration: 6 }) // gotoUrl('500.html') break case 401: ViewUI.Message.error({ content: msg, duration: 6 }) gotoUrl('login.html') break case 403: ViewUI.Message.error({ content: msg, duration: 6 }) gotoUrl('403.html') break default: ViewUI.Notice.error({ title: 'Error', desc: msg, duration: 6 }) }}function gotoUrl (goLink) { // 跳转到错误码页面,jobs页面详情页(jobs/jobId), AI工作台打开图页design/graphId点击到其他页面时,页面替换(截取url中jobs/和design/的前半部分),跳转到错误码页面 let url = window.location.href // 当前页面的url地址 http://localhost:8081/design/125 let arrUrlHttp = url.split('//') // ["http:", "localhost:8081/design/125"] let arrUrl = arrUrlHttp[arrUrlHttp.length - 1].split('/') // ["localhost:8081", "design", "125"] switch (arrUrl[1]) { case 'jobs': let link1 = url.split('jobs')[0] location.href = link1 + goLink break case 'design': let link2 = url.split('design')[0] location.href = link2 + goLink break default: location.href = goLink }}// 请求拦截axios.interceptors.request.use((config) => { ViewUI.LoadingBar.start() // 自动模拟进度条 return config}, function (error) { ViewUI.Notice.error({ title: 'Error', desc: 'Something wrong when requesting data!' }) ViewUI.LoadingBar.error() // 补全失败进度 return Promise.reject(error)})// 响应拦截axios.interceptors.response.use((config) => { ViewUI.LoadingBar.finish() // 补全成功进度 // 后台自定义错误码提示信息,后台返回{code:xxx,msg:xxx},前端展示msg if (config.data.code && config.data.code !== 200) { // 非200状态码则均为后台自定义的错误码 responseError(config.data.code, config.data.msg) return Promise.reject(config) } return config.data}, function (error) { console.log('error=', error) ViewUI.LoadingBar.error() // 补全失败进度 if (error && error.response) { responseError(error.response.status) } return Promise.reject(error)})axios.defaults.headers['Content-Type'] = 'application/json'export default axios
4/ http 1.0/1.1/2.0 区别
4.1/ HTTP1.0与HTTP1.1的主要区别
长连接 / 节约带宽 / HOST域 / 缓存处理 / 错误通知管理
4.1.1 默认开启keep-alive长连接(Persistent Connection)
HTTP1.0
需要使用keep-alive
参数来告知服务器端要建立一个长连接。
HTTP1.1
默认开启长连接, 支持长连接和请求的流水线处理,在一个TCP连接上可以传送多个HTTP请求和响应,减少了建立和关闭连接的消耗和延迟,一定程度上弥补了HTTP1.0
每次请求都要创建连接的缺点。
HTTP
是基于TCP/IP
协议的,创建一个TCP
连接是需要经过三次握手的,有一定的开销,如果每次通讯都要重新建立连接的话,对性能有影响。因此最好能维持一个长连接,可以用一个长连接来发多个请求。
4.1.2 只发送header信息以节约带宽
HTTP1.0
中存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能。
HTTP1.1
支持只发送header
信息(不带任何body
信息),如果服务器认为客户端有权限请求服务器,则返回100,否则返回401。客户端接收到100才开始把请求body
发送到服务器;如果返回401,客户端就可以不用发送请求body
了,节约了带宽。
另外HTTP1.1
还支持传送内容的一部分。这样当客户端已经有一部分的资源后,只需要跟服务器请求另外的部分资源即可。这是支持文件断点续传的基础
4.1.3 HOST域
在HTTP1.0
中认为每台服务器都绑定一个唯一的IP
地址,因此,请求消息中的URL
并没有传递主机名(hostname),HTTP1.0
没有host
域。
随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机(Multi-homed Web Servers),并且它们共享一个IP
地址。HTTP1.1
的请求消息和响应消息都支持host
域,且请求消息中如果没有host
域会报告一个错误(400 Bad Request)。
4.1.4 缓存处理
在HTTP1.0中主要使用header里的If-Modified-Since,Expires来做为缓存判断的标准,HTTP1.1则引入了更多的缓存控制策略例如Entity tag,If-Unmodified-Since, If-Match, If-None-Match等更多可供选择的缓存头来控制缓存策略。
4.1.5 错误通知的管理
在HTTP1.1中新增了24个错误状态响应码,如409(Conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上的某个资源被永久性的删除。
4.2/ HTTP1.1与HTTP 2.0的主要区别
HTTP 2.0
的出现,相比于HTTP 1.x
,大幅度的提升了web
性能。在与HTTP/1.1
完全语义兼容的基础上,进一步减少了网络延迟。对于前端开发人员来说,减少了在前端方面的优化工作。
多路复用 / 二进制分帧 / 首部压缩 / 服务器推送
4.2.1 多路复用
允许同时通过单一的 HTTP/2
连接发起多重的请求-响应消息。
在HTTP/1.1
协议中,浏览器客户端在同一时间针对同一域名的请求有一定数据限制。超过限制数目的请求会被阻塞。
HTTP2.0
使用了多路复用的技术,做到同一个连接并发处理多个请求,而且并发请求的数量比HTTP1.1
大了好几个数量级。
当然HTTP1.1
也可以多建立几个TCP
连接,来支持处理更多并发的请求,但是创建TCP
连接本身也是有开销的。
TCP
连接有一个预热和保护的过程,先检查数据是否传送成功,一旦成功过,则慢慢加大传输速度。因此对应瞬时并发的连接,服务器的响应就会变慢。所以最好能使用一个建立好的连接,并且这个连接可以支持瞬时并发的请求。
在过去,HTTP
性能优化的关键并不在于高带宽,而是低延迟。
通过单连接多资源的方式,减少服务端的链接压力,内存占用更少,连接吞吐量更大。
由于TCP
连接的减少而使网络拥塞状况得以改善,同时慢启动时间的减少,使拥塞和丢包恢复速度更快。
4.2.2 头部数据压缩
在HTTP1.1
中,HTTP
请求和响应都是由状态行、请求/响应头部、消息主体三部分组成。一般而言,消息主体都会经过gzip压缩,或者本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着Web功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输UserAgent
、Cookie
这类不会频繁变动的内容,完全是一种浪费。
HTTP1.1不支持header数据的压缩,HTTP2.0使用HPACK算法对header的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
4.2.3 服务器推送
服务端推送是一种在客户端请求之前发送数据的机制。
网页使用了许多资源:HTML、样式表、脚本、图片等等。在HTTP1.1
中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取HTML开始,然后在它解析和评估页面的时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲的和未充分使用的。
为了改善延迟,HTTP2.0
引入了server push
,它允许服务端推送资源给浏览器,在浏览器明确地请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。
在
HTTP/2
中,服务器可以对客户端的一个请求发送多个响应。
Server Push
让HTTP1.x
时代使用内嵌资源的优化手段变得没有意义;
意思是说,当我们对支持HTTP2.0
的web server
请求数据的时候,服务器会顺便把一些客户端需要的资源一起推送到客户端,免得客户端再次创建连接发送请求到服务器端获取。这种方式非常合适加载静态资源。
服务器推送可以缓存,并且在遵循同源的情况下,不同页面之间可以共享缓存。
因此当客户端需要的数据已缓存时,客户端直接从本地加载这些资源就可以了,不用走网络,速度自然是快很多的。
5/ http-80端口与https-443端口的区别
HTTP
协议传输的数据都是未加密的,也就是明文的,因此使用HTTP
协议传输隐私信息非常不安全,为了保证这些隐私数据能加密传输,于是网景公司设计了SSL
(Secure Sockets Layer)协议用于对HTTP
协议传输的数据进行加密,从而就诞生了HTTPS
。
简单来说,HTTPS
协议是由SSL
+HTTP
协议构建的可进行加密传输、身份认证的网络协议,要比http
协议安全。
HTTPS
和HTTP
的区别主要如下:
1、https
协议需要到ca
申请证书,一般免费证书较少,因而需要一定费用。
2、http
是超文本传输协议,信息是明文传输,https
则是具有安全性的ssl
加密传输协议。
3、http
和https
使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http
的连接很简单,是无状态的;HTTPS
协议是由SSL+HTTP
协议构建的可进行加密传输、身份认证的网络协议,比http
协议安全。
6/ websocket
7/ websocket和轮询等的区别
项目中选择使用websocket的原因, 与轮训,http2.0的服务器推送的对比选择.
7/ websocket断连应该如何处理?
8/ 跨域请求
项目中用过websocket
浏览器相关
1/ 浏览器输入url到展示的整个过程
2/ 浏览器渲染机制, 即渲染页面的一般过程, 渲染树和DOM树的区别
浏览器的渲染机制:
- 浏览器采用的是流式布局模型(Flow Based Layout)
- 浏览器会把HTML解析成 DOM Tree,把CSS解析成CSSOM Tree,把这两个合并成 Render Tree。
- 有了Render Tree 我们就知道了所有节点的位置和样式,浏览器就开始计算他们在页面中的位置,然后开始绘制。
- 由于浏览器是流式布局,对于Render Tree的计算通常只需要遍历一遍就可以完成。但是table及其内部的元素除外,他们可能要计算多次,需要花费等同的元素3倍的时间,这也是不推荐使用table的原因。
3/ 重绘和回流
回流一定会导致重绘,但是重绘不一定会导致回流
3.1 回流(Reflow)
当Render Tree 中的部分或全部元素的尺寸,结构或者触发某些属性时,浏览器会重新计算并渲染页面,称为回流。此时浏览器需要重新进行计算,计算后还需要重新页面布局,因此是较重的操作。
会导致回流的操作有:
-
页面初次渲染
-
浏览器窗口发生改变
-
元素尺寸,位置,内容发生变化
-
元素字体大小变化
-
添加或者删除的可见dom元素
-
激活CSS伪类,例如 :hover
-
查询某些属性或调用某些方法
-
一些常用的会导致回流的属性或方法
- clientWidth, clientHeight, clientTop, clientLeft
- offsetWidth, offsetHeight, offsetTop, offsetLeft
- scrollWidth, scrollHeight, scrollTop, scrollLeft
- scrollIntorView(), scrollInToViewIfNeeded()
- getComputedStyle()
- getBoundingClientRect()
- scrollTop
…
3.2 重绘(Repaint)
当元素的样式改变不影响布局时(例如:color, background-color, visibility等),浏览器将使用重绘对元素进行更新,此时由于只需要对UI层面重新绘制,因此损耗较少。
如何避免:
-
CSS
避免使用table布局
尽可能在DOM树的最末端改变class
避免设置多层内联样式
避免CSS表达式 -
JavaScript
避免频繁的操作样式,最好一次性写好style属性,或者将样式定义为calss,并一次性更改class属性
避免频繁操作DOM,创建一个 documentFragment ,在这个上面应用所有的DOM操作,最后再把它添加到文档中
也可以先设置为display:none; 操作结束后再把它显示出来,因为在display属性为none的元素上进行DOM操作不会引起回流和重绘
避免频繁读取引发回流、重绘的属性,如果要多次使用,建议先把它缓存起来
对具有复杂动画的元素使用绝对定位,使它脱离文档流,否则会引起父元素及后续元素频繁回流。
4/ 浏览器缓存机制
一个网站打开网页的速度直接关系到用户体验,用户粘度,而提高网页的打开速度有很多方面需要优化,其中比较重要的一点就是利用好缓存,缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
4.1/ (思否) http缓存, 强缓存200/协商缓存304, 缓存机制
4.2/ (掘金) http缓存机制(http报文+缓存过程分析(强制缓存+协商缓存))
强制缓存优先于协商缓存进行,若强制缓存(Expires和Cache-Control)生效则直接使用缓存,若不生效则进行协商缓存(Last-Modified / If-Modified-Since和Etag / If-None-Match),协商缓存由服务器决定是否使用缓存,若协商缓存失效,那么代表该请求的缓存失效,返回200,重新获取请求结果,再存入浏览器缓存中;生效则返回304,继续使用缓存
4.3/本地存储 cookie,session和webStorage(localStorage,sessionStorage)
5/ IndexDB浏览器数据库
随着浏览器的功能不断增强,越来越多的网站开始考虑,将大量数据储存在客户端,这样可以减少从服务器获取数据,直接从本地获取数据。
现有的浏览器数据储存方案,都不适合储存大量数据:Cookie 的大小不超过4KB,且每次请求都会发送回服务器;LocalStorage 在 2.5MB 到 10MB 之间(各家浏览器不同),而且不提供搜索功能,不能建立自定义的索引。所以,需要一种新的解决方案,这就是 IndexedDB 诞生的背景。
通俗地说,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。
CSS相关
1/ css垂直居中
1.1 flex布局(元素已知宽度)
子元素设置宽高
父元素设置display: flex; display: -webkit-flex; justify-content: center; align-items: center;
<style> .box{ width: 300px; height: 300px; background-color: #ccc; display: flex; display: -webkit-flex; justify-content: center; align-items: center; } .box .a{ width: 100px; height: 100px; background-color: blue; } </style>HTML 代码:<div class="box"> <div class="a"></div> </div>
1.2 position (元素已知宽度)
父元素设置为:position: relative;
子元素设置为:position: absolute;
距上50%,据左50%,然后减去元素自身宽度的一半距离就可以实现
1.3 position transform (元素未知宽度)
如果元素未知宽度,只需将上面例子中的 margin: -50px 0 0 -50px;替换为:transform: translate(-50%,-50%);
2/ css的position定位
【1】static
静态定位。默认值,即没有定位,遵循正常的文档流对象,不受到 top、bottom、left、right影响。
【2】relative
相对定位。相对于离它最近的使用static定位的父元素进行定位,不脱离文档流(不影响其他元素的布局)。
【3】absolute
绝对定位。相对于离它最近的使用relative定位的父元素进行定位,如没有找到使用relative定位的父元素,将相对于浏览器视口进行定位,脱离文档流(将影响其他元素的布局)。
【4】fixed
固定定位。相对于浏览器视口进行定位,脱离文档流(于其他元素的布局无关)。相对容易理解。
3/ 常见图片文件格式的优缺点,以及不同的文件格式对Web应用程序性能的影响
像素图-放大失真 vs 矢量图-放大不失真
BMP/GIF/JPEG/PNG-8/PNG-24/SVG
4/ box-sizing
css盒模型 IE宽度=padding+border+content, 标准浏览器宽度=content, 可以通过box-sizing设置
5/ BFC 块级格式上下文
6/ rem适配, 移动端如何进行rem的适配
6.1 第一种方法:viewport适配
原理:通过设置 initial-scale
, 将所有设备布局视口的宽度调整为设计图的宽度.
//获取meta节点var metaNode = document.querySelector('meta[name=viewport]');//定义设计稿宽度为375var designWidth = 375;//计算当前屏幕的宽度与设计稿比例var scale = document.documentElement.clientWidth/designWidth;//通过设置meta元素中content的initial-scale值达到移动端适配meta.content="initial-scale="+scale+",minimum-scale="+scale+",maximum-scale="+scale+",user-scalable=no";
6.2 第二种方法:借助media实现rem适配
rem
:CSS
的长度单位,根元素字体大小的倍数,只有根元素字体大小有关; html
中的根元素即 html
元素。
大部分浏览器的默认字体大小都是16px,所以1rem = 16px
;
rem适配原理:
- 长度单位都是用 rem 设置
- 当屏幕尺寸改变时,只需要修改
html
元素的font-size
即可实现等比适配 - 我们在制作页面的时候,只考虑跟设计稿相同的屏幕尺寸即可,其他尺寸屏幕自动适配
//对屏幕大小划分了html不同的font-size@media screen and (min-width: 320px) { html{ font-size:50px;}}@media screen and (min-width: 360px) { html{ font-size:56.25px;}}@media screen and (min-width: 375px) { html{ font-size:58.59375px;}}@media screen and (min-width: 400px) { html{ font-size:62.5px;}}@media screen and (min-width: 414px) { html{ font-size:64.6875px;}}@media screen and (min-width: 440px) { html{ font-size:68.75px;}}@media screen and (min-width: 480px) { html{ font-size:75px;}}@media screen and (min-width: 520px) { html{ font-size:81.25px;}}@media screen and (min-width: 560px) { html{ font-size:87.5px;}}@media screen and (min-width: 600px) { html{ font-size:93.75px;}}@media screen and (min-width: 640px) { html{ font-size:100px;}}@media screen and (min-width: 680px) { html{ font-size:106.25px;}}@media screen and (min-width: 720px) { html{ font-size:112.5px;}}@media screen and (min-width: 760px) { html{ font-size:118.75px;}}@media screen and (min-width: 800px) { html{ font-size:125px;}}@media screen and (min-width: 960px) { html{ font-size:150px;}}
js相关
0/js实现小写字母加下划线形式的字符串命名转为驼峰式命名
function camelCase(string){ // Support: IE9-11+ return string.replace( /_([a-z])/g, function( all, letter ) { return letter.toUpperCase(); });}camelCase('hello_vue_text') // "helloVueText"
// 下划线转换驼峰function toHump(name) { return name.replace(/\_(\w)/g, function(all, letter){ return letter.toUpperCase(); });}// 驼峰转换下划线function toLine(name) { return name.replace(/([A-Z])/g,"_$1").toLowerCase();} // 测试let a = 'a_b2_345_c2345';console.log(toHump(a)); let b = 'aBdaNf';console.log(toLine(b));
1/js实现数组去重, 统计数组中重复出现的数字的各自计数
方法1,通过indexOf 判断数组去重, 新建数组存储不重复的元素,
新建数组存储重复元素的次数
var arr = [1, 2, 3, 1, 2, 4];function arrayCnt(arr) { var newArr = []; for(var i = 0; i < arr.length; i++) { if(newArr.indexOf(arr[i]) == -1) { newArr.push(arr[i]) } } var newarr2 = new Array(newArr.length); for(var t = 0; t < newarr2.length; t++) { newarr2[t] = 0; } for(var p = 0; p < newArr.length; p++) { for(var j = 0; j < arr.length; j++) { if(newArr[p] == arr[j]) { newarr2[p]++; } } } for(var m = 0; m < newArr.length; m++) { console.log(newArr[m] + "重复的次数为:" + newarr2[m]); }}arrayCnt(arr);
方法二, 通过set数组去重
var arr = [1, 2, 3, 1, 2, 4]; function arrayCnt(arr) { var newArr = []; //使用set进行数组去重 newArr = [...new Set(arr)]; console.log(newArr); var newarr2 = new Array(newArr.length); for(var t = 0; t < newarr2.length; t++) { newarr2[t] = 0; } for(var p = 0; p < newArr.length; p++) { for(var j = 0; j < arr.length; j++) { if(newArr[p] == arr[j]) { newarr2[p]++; } } } console.log(newarr2); for(var m = 0; m < newArr.length; m++) { console.log(newArr[m] + "重复的次数为:" + newarr2[m]); } } arrayCnt(arr);
2/手写bind和trigger事件
function Emitter() { this._listener = []; //_listener[自定义的事件名] = [所用执行的匿名函数1, 所用执行的匿名函数2]} //注册事件Emitter.prototype.bind = function(eventName, callback) { var listener = this._listener[eventName] || []; // this._listener[eventName], 没有值则将listener定义为[](数组)。 listener.push(callback); this._listener[eventName] = listener;} //触发事件Emitter.prototype.trigger = function(eventName) { var args = Array.prototype.slice.apply(arguments).slice(1); // atgs为获得除了eventName后面的参数(注册事件的参数) var listener = this._listener[eventName]; if(!Array.isArray(listener)) return; // 自定义事件名不存在 listener.forEach(function(callback) { try { callback.apply(this, args); }catch(e) { console.error(e); } })}//实例var emitter = new Emitter();emitter.bind("myevent", function(arg1, arg2) { console.log(arg1, arg2);});emitter.bind("myevent", function(arg1, arg2) { console.log(arg2, arg1);});emitter.trigger('myevent', "a", "b");// a b// b a
3/ js事件机制
js红宝书第17章, 参见博客总结(事件流 + 事件处理程序 + 事件对象 + 事件类型 + 内存和性能(事件委托与删除事件处理程序) + 模拟事件)
3.1 事件流
事件流描述的是从页面中接收事件的顺序。IE的事件流是事件冒泡流,而Netscape Communicator 的事件流是事件捕获流。
1、事件冒泡
IE 的事件流叫做事件冒泡,即事件开始时由最具体的元素 (文档中嵌套层次最深的那个节点, 如div
) 接收,然后逐级向上传播到较为不具体的节点(文档对象document
)。
注意:所有现代浏览器都支持事件冒泡,但在具体实现上还是有一些差别。IE5.5及更早版本中的事件冒泡会跳过 <html>
元素 (从 <body>
直接跳到 document
) 。Firefox、Chrome 和 Safari 则将事件一直冒泡到 window
对象。
2、事件捕获
事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。
3、DOM事件流
“DOM2级事件”规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段和事件冒泡阶段。同样前面的例子触发事件的顺序为:
IE9、Opera、Firefox、Chrome和Safari都支持DOM事件流;IE8及更早版本不支持DOM事件流。
3.2 事件委托
事件委托可以解决页面中事件处理程序过多的问题。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以处理某一类型的所有事件。
只要可行,就应该考虑只给 document 添加一个事件处理程序,通过它处理页面中所有某种类型的事件。相对于之前的技术,事件委托具有如下优点。
document
对象随时可用,任何时候都可以给它添加事件处理程序(不用等待 DOMContentLoaded
或load
事件)。这意味着只要页面渲染出可点击的元素,就可以无延迟地起作用。
节省花在设置页面事件处理程序上的时间。只指定一个事件处理程序既可以节省DOM
引用,也可以节省时间。
减少整个页面所需的内存,提升整体性能。
最适合使用事件委托的事件包括:click、mousedown、mouseup、keydown 和 keypress。
mouseover 和 mouseout 事件冒泡,但很难适当处理,且经常需要计算元素位置(因为 mouseout 会在光标从一个元素移动到它的一个后代节点以及移出元素之外时触发)。
EventUtil.addHandler(document.body, 'click', function (event) { event = EventUtil.getEvent(event); var target = EventUtil.getTarget(event); switch (target.id) { case 'site_nav_top': alert('口号'); break; case 'nav_menu': alert('点击菜单'); EventUtil.preventDefault(event); break; case 'editor_pick_lnk': alert('推荐区'); EventUtil.preventDefault(event); break; }});
4/js设计模式 单例模式(什么时候用单例模式) 发布订阅者模式
一、单例模式
二、策略模式
三、代理模式
四、迭代器模式
五、发布—订阅模式
六、命令模式
七、组合模式
八、模板方法模式
九、享元模式
十、职责链模式
十一、中介者模式
十二、装饰者模式
十三、状态模式
十四、适配器模式
十五、外观模式
5/原型链 作用域链
在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用 arguments
和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止。
函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。
6/闭包的理解
闭包指的是那些引用了另一个函数作用域中变量的函数,通常是在嵌套函数中实现的。
理解作用域链创建和使用的细节对理解闭包非常重要。
在调用一个函数时,会为这个函数调用创建一个执行上下文,并创建一个作用域链。然后用 arguments和其他命名参数来初始化这个函数的活动对象。外部函数的活动对象是内部函数作用域链上的第二个对象。这个作用域链一直向外串起了所有包含函数的活动对象,直到全局执行上下文才终止。
函数执行时,每个执行上下文中都会有一个包含其中变量的对象。全局上下文中的叫变量对象,它会在代码执行期间始终存在。而函数局部上下文中的叫活动对象,只在函数执行期间存在。
闭包只是定义嵌套函数时的外在表现, 闭包的本质是包含函数的活动对象加长了嵌套函数的作用域链
函数内部的代码在访问变量时,就会使用给定的名称从作用域链中查找变量。函数执行完毕后,局部活动对象会被销毁,内存中就只剩下全局作用域。
不过,闭包就不一样了。在一个函数内部定义的函数会把其包含函数的活动对象添加到自己的作用域链中。
7/js类的继承
7.1 原型链继承
ECMA-262 把原型链定义为 ECMAScript 的主要继承方式。其基本思想就是通过原型继承多个引用类型的属性和方法。
构造函数、原型和实例的关系:每个构造函数(A)都有一个原型对象(A Prototype),原型有一个属性(constructor)指回构造函数,而实例(instance)有一个内部指针[[Prototype]]指向原型。
如果原型是另一个类型的实例,则意味着这个原型本身有一个内部指针指向另一个原型,相应地另一个原型也有一个指针指向另一个构造函数。
这样就在实例和原型之间构造了一条原型链。这就是原型链的基本构想。
7.2 盗用构造函数
为了解决原型包含引用值导致的继承问题,一种叫作“盗用构造函数”(constructor stealing)的技术在开发社区流行起来(这种技术有时也称作“对象伪装”或“经典继承”)。
基本思路很简单:在子类构造函数中调用父类构造函数。因为毕竟函数就是在特定上下文中执行代码的简单对象,所以可以使用apply()和 call()方法以新创建的对象为上下文来执行构造函数。
7.3 组合继承/伪经典继承
组合继承(有时候也叫伪经典继承)综合了原型链和盗用构造函数,将两者的优点集中了起来。
基本的思路是使用原型链继承原型上的属性和方法,而通过盗用构造函数继承实例属性。这样既可以把方法定义在原型上以实现重用,又可以让每个实例都有自己的属性。
组合继承弥补了原型链和盗用构造函数的不足,是 JavaScript 中使用最多的继承模式。而且组合继承也保留了 instanceof 操作符和 isPrototypeOf()方法识别合成对象的能力。
8/javascript中apply、call和bind的区别及什么时候用
Function.call(obj,[param1[,param2[,…[,paramN]]]])Function.apply(obj[,argArray])Function.bind(thisArg[, arg1[, arg2[, ...]]])
call
和apply
的主要作用,是改变对象的执行上下文,并且是立即执行的。它们在参数上的写法略有区别。
第一个参数都是一个对象, Function
的调用者,将会指向这个对象。如果不传,则默认为全局对象window
。
call
的第二个参数开始可以接收任意个参数, 但apply
只接收两个参数且第二个参数必须是数组或者类数组对象.
bind()
方法会创建一个新函数。当这个新函数被调用时,bind()
的第一个参数将作为它运行时的this
,之后的一序列参数将会在传递的实参前传入作为它的参数。
bind 也能改变对象的执行上下文,它与 call 和 apply 不同的是,返回值是一个函数,并且需要稍后再调用一下,才会执行。
9/null 和 NaN 通过Number()转型函数变换以后的值
console.log(Number(null));//输出0;console.log(Number(undefined));//输出NaN;
10/ js深拷贝和浅拷贝,深拷贝中有循环引用该如何处理
10.1 js的基本数据类型和引用数据类型
10.2 js的浅拷贝: Array.concat(), Array.from(object, mapFunction, thisValue), slice(), Object.assign()等
10.3 js的深拷贝: JSON.parse(JSON.stringify(xxxx)), 递归进行深拷贝
10.3.1 JSON.parse()和JSON.stringify()
const obj1 = { x: 1, y: { m: 1 }};const obj2 = JSON.parse(JSON.stringify(obj1));console.log(obj1) //{x: 1, y: {m: 1}}console.log(obj2) //{x: 1, y: {m: 1}}obj2.y.m = 2; //修改obj2.y.mconsole.log(obj1) //{x: 1, y: {m: 1}} 原对象未改变console.log(obj2) //{x: 2, y: {m: 2}}
这种方法使用较为简单,可以满足基本日常的深拷贝需求,而且能够处理JSON格式能表示的所有数据类型,但是有以下几个缺点:
undefined
、任意的函数、正则表达式类型以及symbol
值,在JSON.stringfy()
序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成null
(出现在数组中时);- 它会抛弃对象的
constructor
。也就是深拷贝之后,不管这个对象原来的构造函数是什么,在深拷贝之后都会变成Object
; - 如果对象中存在循环引用的情况无法正确处理。
10.3.2 递归,如果子属性为引用数据类型则递归进行深拷贝
1.其他参考的递归方法-deepClone
function deepCopy(obj1) { var obj2 = Array.isArray(obj1) ? [] : { }; if (obj1 && typeof obj1 === "object") { for (var i in obj1) { //hasOwnPropert返回值是一个布尔值,指示对象自身属性中是否具有指定的属性,会忽略从原型链上继承到的属性 if (obj1.hasOwnProperty(i)) { // 如果子属性为引用数据类型,递归复制 if (obj1[i] && typeof obj1[i] === "object") { obj2[i] = deepCopy(obj1[i]); } else { // 如果是基本数据类型,只是简单的复制 obj2[i] = obj1[i]; } } } } return obj2;}var obj1 = { a: 1, b: 2, c: { d: 3 }}var obj2 = deepCopy(obj1);obj2.a = 3;obj2.c.d = 4;alert(obj1.a); // 1alert(obj2.a); // 3alert(obj1.c.d); // 3alert(obj2.c.d); // 4
2.当前参考文章的递归方法deepCopy1
function deepCopy1(obj) { // 创建一个新对象 let result = { } let keys = Object.keys(obj), // 获取当前obj对象的所有key值的数组 key = null, temp = null; for (let i = 0; i < keys.length; i++) { key = keys[i]; temp = obj[key]; // 如果字段的值也是一个对象则递归操作 if (temp && typeof temp === 'object') { result[key] = deepCopy1(temp); } else { // 否则直接赋值给新对象 result[key] = temp; } } return result;}const obj1 = { x: { m: 1 }, y: undefined, z: function add(z1, z2) { return z1 + z2 }, a: Symbol("foo")};const obj2 = deepCopy1(obj1);obj2.x.m = 2;console.log(obj1); //{x: {m: 1}, y: undefined, z: ƒ, a: Symbol(foo)}console.log(obj2); //{x: {m: 2}, y: undefined, z: ƒ, a: Symbol(foo)}
10.3.3 循环引用的深拷贝
1.父级引用-deepCpoy2
这里的父级引用指的是,当对象的某个属性,正是这个对象本身,此时我们如果进行深拷贝,可能会在子元素->父对象->子元素…这个循环中一直进行,导致栈溢出。
比如下面这个例子:
const obj1 = { x: 1, y: 2};obj1.z = obj1;const obj2 = deepCopy1(obj1); // 栈溢出, 10.3.2中的第二个方法的deepCopy1
解决办法是:只需要判断一个对象的字段是否引用了这个对象或这个对象的任意父级即可
可以修改上面的deepCopy1函数:
function deepCopy2(obj, parent=null) { //创建一个新对象 let result = { }; let keys = Object.keys(obj), key = null, temp = null, _parent = parent; //该字段有父级则需要追溯该字段的父级 while(_parent) { //如果该字段引用了它的父级,则为循环引用 if(_parent.originParent === obj) { //循环引用返回同级的新对象 return _parent.currentParent; } _parent = _parent.parent } for(let i=0,len=keys.length;i<len;i++) { key = keys[i] temp = obj[key] // 如果字段的值也是一个新对象 if(temp && typeof temp === 'object') { result[key] = deepCopy2(temp, { //递归执行深拷贝,将同级的待拷贝对象与新对象传递给parent,方便追溯循环引用 originParent: obj, currentParent: result, parent: parent }); } else { result[key] = temp; } } return result;}const obj1 = { x:1}obj1.z = obj1;const obj2 = deepCopy2(obj1);
2.同级引用-deepClone3
假设对象obj有a,b,c三个子对象,其中对象obj的属性d引用了对象obj下面的子对象a。
const obj= { a: { name: 'a' }, b: { name: 'b' }, c: { }};obj.d = obj.a;
此时obj.d
和obj.a
是相等的,因为它们引用的是同一个对象
console.log(obj.d === obj.a); //true
如果我们调用上面的deepCopy2
函数
const copy = deepCopy2(obj);console.log(copy.a); // 输出: {name: "a"}console.log(copy.d);// 输出: {name: "a"}console.log(copy.a === copy.d); // 输出: false
以上表现我们就可以看出,虽然opj.a
和obj.d
在字面意义上是相等的,但二者并不是引用的同一个对象,这点上来看对象copy
和原对象obj
还是有差异的。(因为上面运行显示obj.a === obj.d
为true, 而copy.a === copy.d
为false)
这种情况是因为obj.a
并不在obj.d
的父级对象链上,所以deepCopy2
函数就无法检测到obj.d
对obj.a
也是一种引用关系,所以deepCopy2
函数就将obj.a
深拷贝的结果赋值给了copy.d
。
解决方案:父级的引用是一种引用,非父级的引用也是一种引用,那么只要记录下对象A中的所有对象,并与新创建的对象一一对应即可。
function deepCopy3(obj) { // hash表,记录所有的对象的引用关系 let map = new WeakMap(); function dp(obj) { let result = null; let keys = Object.keys(obj); let key = null, temp = null, existobj = null; existobj = map.get(obj); //如果这个对象已经被记录则直接返回 if(existobj) { return existobj; } result = { } map.set(obj, result); for(let i =0,len=keys.length;i<len;i++) { key = keys[i]; temp = obj[key]; if(temp && typeof temp === 'object') { result[key] = dp(temp); }else { result[key] = temp; } } return result; } return dp(obj);}const obj= { a: { name: 'a' }, b: { name: 'b' }, c: { }};obj.d = obj.a;const copy = deepCopy3(obj);
11/ js判断变量的数据类型
1.typeof
typeof 是一个操作符,其右侧跟一个一元表达式,并返回这个表达式的数据类型。返回的结果用该类型的字符串(全小写字母)形式表示,包括以下 7 种:number、boolean、symbol、string、object、undefined、function
2.instanceof
instanceof 是用来判断 A 是否为 B 的实例,表达式为:A instanceof B,如果 A 是 B 的实例,则返回 true,否则返回 false。 instanceof 检测的是原型。
instanceof 主要的作用就是判断一个实例是否属于某种类型
let person = function () { }let nicole = new person()nicole instanceof person // true
instanceof 也可以判断一个实例是否是其父类型或者祖先类型的实例。
let person = function () { }let programmer = function () { }programmer.prototype = new person()let nicole = new programmer()nicole instanceof person // truenicole instanceof programmer // true
3.constructor
当一个函数 F被定义时,JS引擎会为F添加 prototype 原型,然后再在 prototype上添加一个 constructor 属性,并让其指向 F 的引用。
4.toString 即Object.prototype.toString.call
比较准确的判断对象实例的类型时,可以采取 Object.prototype.toString.call
方法
toString() 是 Object 的原型方法,调用该方法,默认返回当前对象的 [[Class]] 。这是一个内部属性,其格式为 [object Xxx] ,其中 Xxx 就是对象的类型。
对于 Object 对象,直接调用 toString() 就能返回 [object Object] 。而对于其他对象,则需要通过 call / apply 来调用才能返回正确的类型信息。
Object.prototype.toString.call(1) // "[object Number]"Object.prototype.toString.call('hi') // "[object String]"Object.prototype.toString.call({ a:'hi'}) // "[object Object]"Object.prototype.toString.call([1,'a']) // "[object Array]"Object.prototype.toString.call(true) // "[object Boolean]"Object.prototype.toString.call(() => { }) // "[object Function]"Object.prototype.toString.call(null) // "[object Null]"Object.prototype.toString.call(undefined) // "[object Undefined]"Object.prototype.toString.call(Symbol(1)) // "[object Symbol]"
12/ 箭头函数和普通函数的区别
13/ callback,promise,async&await三者的区别?
1.callback
经过深层次的嵌套,会产生回调地狱,需手动检查err参数。
2.promise
通过链式调用,直接在 then
中返回一个 promise
来进行成功之后的回调函数,用 catch
来做错误处理。
3.async/await
是基于Promise
实现的,它不能用于普通的回调函数,直接将其变成了同步的写法,使得异步代码看起来像同步代码,既可以用.catch
又可以用try-catch
捕捉,简洁,可读性强,写法更加优雅 。
原文链接:https://blog.csdn.net/m0_48076809/article/details/106439990
13/ promise.all() 同时发两个请求, promise.race()两个请求中有一个就可以回复
前端工程化-webpack相关
1/ webpack配置, loader和plugin的区别
webpack配置一般是在config文件夹中添加webpack.config.js文件
本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
其中包含四个核心概念
入口(entry)
:指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的
输出(output)
:告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
loader
:让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)
插件(plugins)
:插件则可以用于执行范围更广的任务。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。
2/ webpack打包优化
1:优化图片
使用 url-loader 优化, 将小图片转化成base64压缩,防止小图片太多请求次数太多。
2:分离第三方包
打包后的bundle.js文件夹较大,所以每次加载的时候,请求比较慢,所以有必要在打包时将第三方包分离出来。使用CommonsChunkPlugin 插件进行配置。
3:分离 css 文件并压缩 css 文件
使用 extract-text-webpack-plugin 插件将css文件分离出来。为了使项目加载时候尽早优先加载css样式,也为了解决js文件体积过大的问题
4: 压缩 js 文件
使用 uglifyjs-webpack-plugin 将js压缩,减少打包后的 vendor.js , bundle.js 等js的文件大小
5:压缩Html
为了减少打包后的文件体积,使性能更好,效率更高,提高加载速度,打包时有必要进行压缩。
使用html-webpack-plugin 进行压缩
2/ webpack输出output文件的path和public-path的区别, 有哪些loader?
3/ 前后端分离?
4/ 前端模块化
web安全?
目前常见的web攻击方式主要分为:XSS攻击、CSRF攻击、点击劫持。
1/XSS攻击
2/CSRF攻击
3/点击劫持
4/ 常见三种加密(MD5、非对称加密,对称加密)
前端可以实现对表单的验证判断处理
主要是服务器端进行安全防御
关于性能优化
0/ http请求性能优化?
保证发出的http请求的性能是优秀的
1/ 页面性能优化
简书-PC端优化策略
主要包括网络加载类、页面渲染类、CSS优化类、JavaScript执行类、缓存类、图片类、架构协议类等几类;
一、资源压缩合并,减少http请求
合并图片(css sprites)、CSS和JS文件合并、CSS和JS文件压缩,图片较多的页面也可以使用 lazyLoad 等技术进行优化。
二、非核心代码异步加载
异步加载的方式:
(1)动态脚本加载
(2)defer:在HTML解析完之后才会执行。如果是多个,则按照加载的顺序依次执行。
(3)async:在加载完之后立即执行。如果是多个,执行顺序和加载顺序无关。
三、利用浏览器缓存
缓存:资源文件(比如图片)在本地存有副本,浏览器下次请求的时候,可能直接从本地磁盘里读取,而不会重新请求图片的url。
缓存分为:强缓存和协商缓存
强缓存:不用请求服务器,直接使用本地的缓存。
利用 http 响应头中的Expires或Cache-Control实现,这两个属性可以只启用一个,也可以同时启用。当response header中,Expires和Cache-Control同时存在时,Cache-Control的优先级高于Expires。
协商缓存:浏览器发现本地有资源的副本,但是不太确定要不要使用,于是去问问服务器。
当浏览器对某个资源的请求没有命中强缓存(也就是说超出时间了),就会发一个请求到服务器,验证协商缓存是否命中。
协商缓存是利用的是两对Header:
第一对:Last-Modified、If-Modified-Since
第二对:ETag、If-None-Match
四、使用CDN
怎么最快地让用户请求资源。一方面是让资源在传输的过程中变小,另外就是CDN。
要注意,浏览器第一次打开页面的时候,浏览器缓存是起不了作任何用的,使用CDN,效果就很明显。
五、DNS预解析(dns-prefetch)
通过 DNS 预解析来告诉浏览器未来我们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就可以尽快地完成 DNS 解析。
2/ 防抖和节流
对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如上面的1000毫秒)内,事件处理函数只执行一次。
debounce,去抖动。策略是当事件被触发时,设定一个周期延迟执行动作,若期间又被触发,则重新设定周期,直到周期结束,执行动作。 这是debounce的基本思想,在后期又扩展了前缘debounce,即执行动作在前,然后设定周期,周期内有事件被触发,不执行动作,且周期重新设定。
debounce的特点是当事件快速连续不断触发时,动作只会执行一次。 延迟debounce,是在周期结束时执行,前缘debounce,是在周期开始时执行。但当触发有间断,且间断大于我们设定的时间间隔时,动作就会有多次执行。
throttling,节流的策略是,固定周期内,只执行一次动作,若有新事件触发,不执行。周期结束后,又有事件触发,开始新的周期。 节流策略也分前缘和延迟两种。与debounce类似,延迟是指 周期结束后执行动作,前缘是指执行动作后再开始周期。
版本管理
一次版本更新的过程:
git pull
git add
git commit
git push
学习资源记录
发表评论
最新留言
关于作者
