开放性的题目
自我介绍
突出学习能力
我想换工作的主要原因是
介绍项目
平时是如何学习前端开发的
主要就是两个途径,一个是查阅官方文档,然后就是在网上查找技术资料或者视频去学习。平时没事的时候也会看看github,同时关注一些社区和IT网站,我经常看的就是掘金,CSDN,开源中国这些。通过一些大牛们的分享接触到新的技术和知识,然后就自己感兴趣或者好用的前沿技术,就会深入的去学习。
未来三到五年的规划是怎样的
在未来的三到五年之内,我希望自己能够在这个前端这个领域能有自己的技术沉淀,可能产出的是技术文档,或者开源框架或者组件库,并且能够得到稳定的提升,现在的在前端岗位上我拥有丰富的前端项目研发经验和一些大公司的项目开发经验,但还有管理方面,例如管理代码,管理项目成员上是我稍有欠缺的,这将是后期我需要去学习和提升的地方,最后可以在岗位上做到独挡一面,更好地为公司服务。
网络协议和浏览器相关
讲一下cookie?
我的理解是 cookie 是服务器提供的一种用于维护会话状态信息的数据,通过服务器发送到浏览器,浏览器保存在本地的一种纯文本文件,当下一次有同源的请求时,将保存的 cookie 值添加到请求头部,发送给服务端。这可以用来实现记录用户登录状态等功能。cookie 一般可以存储 4k 大小的数据,并且只能够被同源的网页所共享访问。
服务器端可以使用 Set-Cookie 的响应头部来配置 cookie 信息。一条cookie 包括了5个属性值 expires、domain、path、secure、HttpOnly。其中 expires 指定了 cookie 失效的时间,domain 是域名、path是路径,domain 和 path 一起限制了 cookie 能够被哪些 url 访问。secure 规定了 cookie 只能在确保安全的情况下传输,HttpOnly 规定了这个 cookie 只能被服务器访问,不能在客户端使用js 脚本访问。
客户端可以通过JS脚本,例如document.cookie="key=value"形式设置cookie
在发生 xhr 的跨域请求的时候,即使是同源下的 cookie,也不会被自动添加到请求头部,除非显示地规定。
session是什么?
session是服务器为了保存用户状态而创建的一个特殊的对象
在浏览器第一次访问服务器时,服务器会创建一个session对象,该对象有一个唯一的id,即sessionid,服务器会把sessionid以cookie的形式发送给浏览器,当浏览器再次访问服务器时,会携带cookie在请求头,可以通过cookie中的sessionid来访问session对象
可以实现在http无状态基础上实现用户状态管理(即两个页面之间的用户状态,我可以保存在session中)
sessionstorage和localstorage
sessionstorage:是一种会话存储,当关闭浏览器页面之后,相应的数据会被删除
localstorage:本地存储,存储持久数据,没有时间限制,保存之后会永久存在,除非手动清除,解决cookie读写困难,存储容量有限的问题
可以通过window. sessionstorage ,window. localstorage在js中操作这两个对象
localstorage.setItem()/.getItem()/removeItem()
sessionstorage.setItem()/getItem()/removeItem()来实现对存储的操作,
完全存储在客户端,大小有5M ,localstorage有XSS注入的风险
重绘和回流:重排是什么,什么情况下会触发,区别在哪里,怎么优化?(回流)
在HTML中,每个元素都可以理解成一个盒子,在浏览器解析过程中,会涉及到回流与重绘:
- 回流:布局或者几何属性需要改变就称为回流。当render树的一部分或全部的元素因改变了自身的宽高,布局,显示或隐藏,或者元素内部的文字结构发生变化 导致需要重新构建页面的时候,回流就产生了
- 重绘:重绘是当节点需要更改外观而不会影响布局的,比如改变 color 就叫称为重绘
回流一定是重绘,重绘不一定是回流
回流的成本比重绘的成本高得多的多。DOM树里的每个结点都会有reflow方法,一个节点的reflow很有可能导致子结点,甚至父点以及同级结点的reflow。
造成影响
浏览器渲染一个网页的时候会启用两条线程: 一条渲染javascript 脚本,另一条渲染 ui 即css 样式的渲染。 但这两条线程是互斥的。当javascript 线程运行的时候 ui 线程则会中止暂停,反之亦然。 因为当ui 线程运行对页面进行渲染的时候 js 脚本难免会涉及到页面视图上的一些样式的改变,为了使这个改变更加准确, js 脚本只好等待ui 线程渲染完成的时候才去执行。 所以当一个页面的元素样式改动频繁的时候ui 线程就会持续渲染,造成js 代码反应慢半拍,卡顿的情况。
回流触发时机
- 添加或删除可见的 DOM 元素
- 元素的位置发生变化
- 元素的尺寸发生变化(包括外边距、内边框、边框大小、高度和宽度等)
- 内容发生变化,比如文本变化或图片被另一个不同尺寸的图片所替代
- 页面一开始渲染的时候(这避免不了)
- 浏览器的窗口尺寸变化(因为回流是根据视口的大小来计算元素的位置和大小的)
重绘触发时机
- 颜色的修改
- 文本方向的修改
- 阴影的修改
如何减少
- 如果想设定元素的样式,通过改变元素的 class 类名 (尽可能在 DOM 树的最里层)
- 避免设置多项内联样式
- 应用元素的动画,使用 position 属性的 fixed 值或 absolute 值(如前文示例所提)
- 避免使用 table 布局,table 中每个元素的大小以及内容的改动,都会导致整个 table 的重新计算
- 对于那些复杂的动画,对其设置 position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响
- 使用 css3 硬件加速,可以让transform、opacity、filters这些动画不会引起回流重绘
- 避免使用 CSS 的 JavaScript 表达式
http和https的区别(对称加密,非对称加密,数字签名,数字证书,都必须懂!)
1.安全性上,HTTP 是超文本传输协议,信息是明文传输,存在安全风险的问题。HTTPS 则解决 HTTP 不安全的缺陷,在 TCP 和 HTTP 网络层之间加入了 SSL/TLS 安全协议,使得报文能够加密传输
2.申请证书上,HTTPS 协议需要向 CA(证书权威机构)申请数字证书,来保证服务器的身份是可信的
3.传输协议上, HTTP是超文本传输协议,明文传输;HTTPS是具有安全性的 SSL 加密传输协议
4.连接方式与端口上,http的连接简单,是无状态的,端口是 80; https 在http的基础上使用了ssl协议进行加密传输,端口是 443
浏览器输入url后发生了什么?
- 浏览器的地址栏输入URL并按下回车。
- 浏览器查找当前URL是否存在缓存,并比较缓存是否过期。
- DNS解析URL对应的IP。
- 根据IP建立TCP连接(三次握手)。
- HTTP发起请求。
- 服务器处理请求,浏览器接收HTTP响应。
- 渲染页面,构建DOM树。
-
- 解析HTML,构建DOM树
- 解析CSS,生成CSS规则树
- 合并DOM树和CSS规则,生成render树
- 布局render树(重绘/回流),负责各元素尺寸、位置的计算
- 绘制render树(paint),绘制页面像素信息
- 浏览器会将各层的信息发送给GPU,GPU会将各层合成(composite),显示在屏幕上
- 关闭TCP连接(四次挥手)。
https三次握手四次挥手
(1)三次握手:
1.客户端TCP向服务端TCP发送一个请求报文SYN=1(SYN就表示这是一个连接请求),seq=x(seq为随机数)
2.服务端的TCP接受到请求报文后,如果同意建立连接,就向客户端发回确认,并为该TCP的连接分配TCP缓存和变量。这个确认报文中SYN和ACK都被置为1,确认号ack的值为x+1,然后服务器产生起始序列号seq=y
3.客户端收到服务端的确认报文后,也向服务端给出确认,并给连接分配缓存和变量。ACK置为1,seq=x+1,ack=y+1
(2)四次挥手:
1.客户机打算关闭连接,就会向服务端TCP发送FIN=1终止位,以及seq序列号
2.服务器收到关闭请求之后,返回ACK=1 确认关闭,以及seq序列号v 确认号u+1。
3.向客户端发起自己这一方的终止请求FIN=1,ACK=1,seq=w,ack=u+1
4.客户机收到请求之后返回ACK=1,seq=u+1,ack=w+1
TIME_WAIT:如果客户机收到服务器的终止请求后,返回确认断开的请求后,里面关闭连接的话,如果这个确认 请求没有被传达,那么服务器可能要重新请求,而这个时候客户机已经关闭了 收不到请求了。那么服务器可能会响应RST(表示目标服务器崩溃了,必须释放再重新连接)。
(3)TCP如何保证可靠连接:
比如当发送方向接收方发送序号为1,2,3,4,5的报文段,二号报文在传输过程中丢失了,接受收到了1,3,4,5接收方期望收到的是2号报文段,那么345就是冗余报文,这个时候接收方就会向发送方发三个对1号报文的冗余ACK。这时发送方就知道了,2号报文没有传达,立马重传
(4)两次握手不行吗?为什么TCP客户端最后还要发送一次确认呢?
主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。
经典场景:客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了
- 由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。
- 此时此前滞留的那一次请求连接,网络通畅了到达服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。
- 如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。
(5)为什么要四次挥手?
- 由于 TCP 的半关闭(half-close)特性,TCP 提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。
- 任何一方都可以在数据传送结束后发出连接释放的通知,待对方确认后进入半关闭状态。当另一方也没有数据再发送的时候,则发出连接释放通知,对方确认后就完全关闭了TCP连接。
- 通俗的来说,两次握手就可以释放一端到另一端的 TCP 连接,完全释放连接一共需要四次握手。
浏览器的同源策略,不做限制会造成什么影响
同源是指域名,协议,端口三个都相同。
不同源,同源策略对于js脚本有限制。主要表现在3点
(1) 无法用js读取非同源的Cookie、LocalStorage 和 IndexDB 无法读取。【为了防止恶意网站通过js获取用户其他网站的cookie】
(2) 无法用js获取非同源的DOM 。【如果没有这一条,恶意网站可以通过iframe打开银行页面,可以获取dom就相当于可以获取整个银行页面的信息。】
(3) 无法用js发送非同源的AJAX请求 。更准确的说,js可以向非同源的服务器发请求,但是服务器返回的数据会被浏览器拦截。
跨域的方式(重点 cors跨域!)
跨域问题来自于浏览器的同源策略,即当协议、域名、端口号任意一个不同时,都会引发跨域问题。
jsonp、CORS可以用来解决跨域问题
CORS是一个W3C标准,全称是"跨域资源共享"
CORS分简单请求和非简单请求,同时满足下面两个条件的就是简单请求,否则就是非简单请求
(1) 请求方法是以下三种方法之一:
HEAD
GET
POST
(2) HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
简单请求:请求头通过Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
后端设置Access-Control-Allow-Origin字段为*(表示接受任意Origin的请求)就可以了。
前端代码:简单的ajax即可
非简单请求:的CORS请求,会在正式通信之前,增加一次HTTP查询请求,称为"预检"请求(preflight)。
CORS与JSONP比较:
CORS与JSONP的使用目的相同,但是比JSONP更强大。
JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。
状态码有哪些?
- 1xx 类状态码属于提示信息,是协议处理中的一种中间状态,实际用到的比较少。
- 2xx 类状态码表示服务器成功处理了客户端的请求,也是我们最愿意看到的状态。
「200 OK」是最常见的成功状态码,表示一切正常。如果是非 HEAD 请求,服务器返回的响应头都会有 body 数据。
「204 No Content」也是常见的成功状态码,与 200 OK 基本相同,但响应头没有 body 数据。
「206 Partial Content」是应用于 HTTP 分块下载或断电续传,表示响应返回的 body 数据并不是资源的全部,而是其中的一部分,也是服务器处理成功的状态。
- 3xx 类状态码表示客户端请求的资源发送了变动,需要客户端用新的 URL 重新发送请求获取资源,也就是重定向。
「301 Moved Permanently」表示永久重定向,说明请求的资源已经不存在了,需改用新的 URL 再次访问。
「302 Moved Permanently」表示临时重定向,说明请求的资源还在,但暂时需要用另一个 URL 来访问。
「304 Not Modified」不具有跳转的含义,表示资源未修改,重定向已存在的缓冲文件,也称缓存重定向,用于缓存控制。
- 4xx 类状态码表示客户端发送的报文有误,服务器无法处理,也就是错误码的含义。
「400 Bad Request」表示客户端请求的报文有错误,但只是个笼统的错误。
「401」请求要求用户认证
「403 Forbidden」表示服务器禁止访问资源,并不是客户端的请求出错。
「404 Not Found」表示请求的资源在服务器上不存在或未找到,所以无法提供给客户端。
「426 Not Found」表示http版本不匹配,在nigix需要指定版本,proxy_http_version 1.1,默认1.0
- 5xx 类状态码表示客户端请求报文正确,但是服务器处理时内部发生了错误,属于服务器端的错误码。
「500 Internal Server Error」与 400 类型,是个笼统通用的错误码,服务器发生了什么错误,我们并不知道。
「501 Not Implemented」表示客户端请求的功能还不支持,类似“即将开业,敬请期待”的意思。
「502 Bad Gateway」通常是服务器作为网关或代理时返回的错误码,表示服务器自身工作正常,访问后端服务器发生了错误。
「503 Service Unavailable」表示服务器当前很忙,暂时无法响应服务器,类似“网络服务正忙,请稍后重试”的意思。
HTTP缓存
HTTP缓存机制是根据HTTP报文的缓存标识进行的。HTTP缓存分为强缓存和协商缓存。优先级最高的是强缓存,在命中强缓存失败的情况下,才会走协商缓存。
问:什么时候命中强缓存?
判断该资源是否命中强缓存,就看 response 中 Cache-Control 的值,如果有max-age=xxx秒,则命中强缓存。如果Cache-Control的值是no-cache,说明没命中强缓存,走协商缓存。
强缓存
强缓存是利用http头中的Expires和Cache-Control两个字段来控制的。强缓存中,当请求再次发出时,浏览器会根据其中的Expires和Cache-Control判断目标资源是否“命中”强缓存,如果命中则直接从缓存中获取资源,不会再与服务端发生通信。
当浏览器去请求某个文件的时候,服务端就在respone header里面对该文件做了缓存配置。缓存的时间、缓存类型都由服务端控制,具体表现为:respone header 的cache-control,常见的设置是max-age public private no-cache no-store等
(1)Expires(基本淘汰)
(2)Cache-control
Cache-control里面有如下字段:
- s-maxage仅在代理服务器中生效,客户端只需要考虑max-age。
- public 表示任何能被任何对象进行缓存
- private 仅表示能被客服端缓存
- no-cache 表示直接进入协商缓存
- no-store 表示不进行缓存
- max-age 设置缓存的最大周期,单位为s,超过这个时间会被认为过期
- s-maxage 覆盖max-age或者Expires头。如果s-maxage未过期,则向代理服务器请求其缓存内容。
- s-maxage仅在代理服务器中生效,客户端只需要考虑max-age。
强缓存总结
- cache-control: max-age=xxxx,public
客户端和代理服务器都可以缓存该资源;
客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,如果用户做了刷新操作,就向服务器发起http请求 - cache-control: max-age=xxxx,private
只让客户端可以缓存该资源;代理服务器不缓存
客户端在xxx秒内直接读取缓存,statu code:200 - cache-control: max-age=xxxx,immutable
客户端在xxx秒的有效期内,如果有请求该资源的需求的话就直接读取缓存,statu code:200 ,即使用户做了刷新操作,也不向服务器发起http请求 - cache-control: no-cache
跳过设置强缓存,但是不妨碍设置协商缓存;一般如果你做了强缓存,只有在强缓存失效了才走协商缓存的,设置了no-cache就不会走强缓存了,每次请求都回询问服务端。 - cache-control: no-store
不缓存,这个会让客户端、服务器都不缓存,也就没有所谓的强缓存、协商缓存了。
协商缓存
强缓存就是为了给客户端自给自足用的。而当某天,客户端请求该资源时发现其过期了,这是就会去请求服务器了,而这时候去请求服务器的这过程就可以设置协商缓存。这时候,协商缓存就是需要客户端和服务器两端进行交互的。协商缓存机制下,浏览器需要向服务器去询问缓存的相关信息,进而判断是重新发起请求、下载完整的响应,还是从本地获取缓存的资源。如果服务端提示缓存资源未改动(Not Modified),资源会被重定向到浏览器缓存,这种情况下网络请求对应的状态码是 304。同样,协商缓存的标识也是在响应报文的HTTP头中和请求结果一起返回给浏览器的,控制协商缓存的字段分别有:Last-Modified和Etag,其中Etag的优先级比Last-Modified高。
触发条件:
- Cache-Control 的值为 no-cache (不强缓存)
- 或者 max-age 过期了 (强缓存,但总有过期的时候)
协商缓存步骤总结:
- 请求资源时,把用户本地该资源的 ETag 同时带到服务端,服务端和最新资源做对比。
- 如果资源没更改,返回304,浏览器读取本地缓存。
- 如果资源有更改,返回200,返回最新的资源。
Etag: '5c20abbd-e2e8'
Last-Modified: Mon, 24 Dec 2018 09:49:49 GMT
ETag:每个文件有一个,改动文件了就变了,可以看似md5
Last-Modified:文件的修改时间
问:为什么要有etag?
HTTP1.1中etag的出现(也就是说,etag是新增的,为了解决之前只有If-Modified的缺点)主要是为了解决几个last-modified比较难解决的问题:
- 一些文件也许会周期性的更改,但是他的内容并不改变(仅仅改变的修改时间),这个时候我们并不希望客户端认为这个文件被修改了,而重新get;
- 某些文件修改非常频繁,比如在秒以下的时间内进行修改,(比方说1s内修改了N次),if-modified-since能检查到的粒度是秒级的,这种修改无法判断(或者说UNIX记录MTIME只能精确到秒);
- 某些服务器不能精确的得到文件的最后修改时间。
如何设置一个可靠的缓存规则?
- 如果资源不可复用,直接为Cache-Control设置no-store,拒绝一切形式的缓存;
- 如果资源可复用,考虑是否每次都需要向服务器进行缓存确认,如果是,设置Cache-Control的值为no-cache;
- 如果不需要每次都向服务器确认,考虑资源是否可以被代理服务器缓存,根据其结果决定是设置为private还是public;
- 接下来考虑资源的过期时间,设置对应的max-age;
- 最后,配置协商缓存需要用到的Etag、Last-Modified等参数。
怎么设置缓存?
后端服务器nodejs:
res.setHeader('max-age': '3600 public')
res.setHeader(etag: '5c20abbd-e2e8')
res.setHeader('last-modified': Mon, 24 Dec 2018 09:49:49 GMT)
nginx配置:
JS相关
数据类型相关
数据类型
基本类型
1、undefined 类型表示不存在定义,声明变量但没有初始化,这个变量的值就是undefined; 注意:在任何一个引用变量值设置为undefined都是错误的
2、null 类型,表示一个值被定义了,定义为空值; 使用场景为 定义变量准备在将来用于保存对象;所以引用值可以是null而不会是undefined;
3、Boolean类型,字面值为true和false
4、number类型,字面量格式可以是十进制、八进制(八进制第一位必须是0)、十六进制(前两位必须是0x)
5、String类型 由零个或多个16位Unicode字符组成的字符序列
6、symbol类型,ES5 的对象属性名都是字符串,这容易造成属性名的冲突。比如,你使用了一个他人提供的对象,但又想为这个对象添加新的方法(mixin 模式),新方法的名字就有可能与现有方法产生冲突。如果有一种机制,保证每个属性的名字都是独一无二的就好了,这样就从根本上防止属性名的冲突。这就是ES6 引入Symbol的原因
引用类型
引用类型统称为object类型,细分的话有:Object 类型、Array 类型、Date 类型、RegExp 类型、Function 类型 等。
null,undefined 的区别?
null 表示一个对象是“没有值”的值,也就是值为“空”;
undefined 表示一个变量声明了没有初始化(赋值);
undefined不是一个有效的JSON,而null是;
undefined的类型(typeof)是undefined;
null的类型(typeof)是object;
Javascript将未赋值的变量默认值设为undefined;
Javascript从来不会将变量设为null。它是用来让程序员表明某个用var声明的变量时没有值的。
堆和栈的概念
栈(stack): 由操作系统自动分配内存空间,自动释放,存储的是基础变量以及一些对象的引用变量,占据固定大小的空间。
堆(heap): 由操作系统动态分配的内存,大小不定也不会自动释放,一般由程序员分配释放,也可由垃圾回收机制回收。
区别:
1、栈:基础变量的值是存储在栈中,而引用变量存储在栈中的是指向堆中的数组或者对象的地址,这就是为何修改引用类型总会影响到其他指向这个地址的引用变量。(线性结构,后进先出)
优点:
(1)栈中的内容是操作系统自动创建、自动回收,占据固定大小的空间,因此内存可以及时得到回收,相对于堆来说,更加容易管理内存空间。
(2)相比于堆来说存取速度会快,并且栈内存中的数据是可以共享的,例如同时声明了var a = 1和var b =1,会先处理a,然后在栈中查找有没有值为1的地址,如果没有就开辟一个值为1的地址,然后a指向这个地址,当处理b时,因为值为1的地址已经开辟好了,所以b也会同样指向同一个地址。
缺点:
相比于堆来说的缺点是,存在栈中的数据大小与生存期必须是确定的,缺乏灵活性。
2、堆:堆内存中的对象不会随方法的结束而销毁,就算方法结束了,这个对象也可能会被其他引用变量所引用(参数传递)。创建对象是为了反复利用(因为对象的创建成本通常较大),这个对象将被保存到运行时数据区(也就是堆内存)。只有当一个对象没有任何引用变量引用它时,系统的垃圾回收机制才会在核实的时候回收它。
优点:
(1) 堆是操作系统动态分配的大小不定的内存,因此方便存储和开辟内存空间。
(2)堆中保存的对象不会自动释放,一般由程序员分配释放,也可由垃圾回收机制回收,因此生存周期比较灵活。
缺点:
相比于栈来说的缺点是,存在堆中的数据大小与生存期是不确定的,比较混乱,杂乱无章。
怎么判断数据类型?各有什么区别
instanceof
typeof
constructor
Object.prototype.toString.call()
1、typeof
检测不出null 和 数组,结果都为object,所以typeof常用于检测基本类型
NaN Not a Number,表示非数字,但是 typeof NaN === 'number' 返回为true
2、instanceof
不能检测出number、boolean、string、undefined、null、symbol类型,所以instancof常用于检测复杂类型以及级成关系
3、constructor
null、undefined没有construstor方法,因此constructor不能判断undefined和null。
但是contructor的指向是可以被改变,所以不安全
4.Object.prototype.toString.call()
对于 Object.prototype.toString() 方法,会返回一个形如 "[object XXX]" 的字符串,能判断所有类型
This指向
this总是指向函数的直接调用者(而非间接调用者);
如果有new关键字,this指向new出来的那个对象;
在事件中,this指向触发这个事件的对象,特殊的是,IE中的attachEvent中的this总是指向全局对象Window;
call和bind有什么区别
call、apply、bind都是改变this指向的方法
apply:和call基本上一致,唯一区别在于传参方式
apply把需要传递给fnction的参数放到一个数组(或者类数组)中传递进去,虽然写的是一个数组,但是也相当于给fnction一个个的传递
bind:语法和call一模一样,区别在于立即执行还是等待执行,bind不兼容IE6~8
两者的区别就在于,apply方法的参数只有两个,第一个是替换后的对象,而第一个参数是一个数组,用于存放调用方式所引用的若干个参数;而call方法的参数按照顺序是:替换后的对象,调用方法的参数1,以及如果有的话还有参数2……也就是将所要传的参数排列在第一个参数之后,总的参数的数目是不一定的。如果call的传递这个函数处于非严格模式下,则指定为null和undefined的this值会自动指向全局对象(浏览器中就是window对象),严格模式下指向nul,undefined
变量提升
在JavaScript中变量声明与函数声明都会被提升到作用域顶部,优先级依次为:变量声明 函数声明 变量赋值。
变量提升需要注意两点:
1.提升的部分只是变量声明,赋值语句和可执行的代码逻辑还保持在原地不动
2.提升只是将变量声明提升到变量所在的变量范围的顶端,并不是提升到全局范围
函数作用域和作用域链
作用域就是变量与函数的可访问范围,即作用域控制着变量与函数的可见性和生命周期。
1. 全局作用域
任何地方都能访问到的对象拥有全局作用域。
2. 局部作用域
局部作用域一般只在固定的代码片段内可访问到,最常见的例如函数内部,所以在一些地方会把这种作用域称为函数作用域。局部作用域的特性,外部无法访问
3. ES6的块级作用域
ES6引入了块级作用域,明确允许在块级作用域中声明函数,let和const命令都涉及块级作用域。
作用域链
通俗地讲,当声明一个函数时,局部作用域一级一级向上包起来,就是作用域链。
1.当执行函数时,总是先从函数内部找寻局部变量
2.如果内部找不到(函数的局部作用域没有),则会向创建函数的作用域(声明函数的作用域)寻找,依次向上
闭包
提到作用域就不得不提到闭包,简单来讲,闭包外部函数能够读取内部函数的变量。
优点:闭包可以形成独立的空间,永久的保存局部变量。
缺点:保存中间值的状态缺点是容易造成内存泄漏,因为闭包中的局部变量永远不会被回收
谈谈闭包
定义:当一个嵌套的内部函数引用了外部函数的变量或者函数时,外部函数在执行时就产生了闭包
产生一个闭包:创建闭包最常见方式,就是在一个函数内部创建另一个函数。
闭包特点:函数嵌套函数,内部函数引用外部函数的变量
闭包的作用:
1.闭包可以延长外部函数的局部变量的生命周期,可以实现计数器,累加器这类
2.可以形成变量的局部作用域,实现函数封装
缺点:函数定义的变量和数据会一直存在内部函数中,不会被及时释放,这样会导致内存泄漏
解决:尽量不使用闭包;用完后及时释放
Array数组
1. 判断数组的方式
instanceof
Object.prototype.tostring.call
Array.isArray
arr.__proto__ === Array.prototype
arr.constructor === Array
2. 数组常用的API
数组操作方法(push、pop、shift、unshift、reverse(倒置)、sort、toString、join、concat、slice(数组截取)、splice(删除 / 插入))
数组位置方法(indexOf、lastIndexOf)
数组迭代方法(forEach、map、filter、some、every)
其它方法(toString、join、indexOf、lastIndexOf、includesflat (降维))
js 数组操作 会修改原数组的 方法
1.pop() 删除 数组 最后一个元素;
2.shift() 删除 数组 第一个元素;
3.push() 数组 最后 添加 一个 元素;
4.unshift() 数组 开头 添加 一个 元素;
5.sort() 数组 排序
6.splice() 数组 删除 替换 插入 元素
7.reverse() 数组 反转
slice和splice的区别
splice改变原数组,slice不改变原数组。
splice除了可以删除之外,还可以插入。
reduce和map的区别
map函数的第一个参数是函数,函数的参数可以是1个或者多个,而reduce只能接受2个参数。
map()是将传入的函数依次作用到序列的每个元素,每个元素都是独自被函数“作用”一次 。
reduce()是将传人的函数作用在序列的第一个元素得到结果后,把这个结果继续与下一个元素作用(累积计算)
3.数组去重
let arr = [1, 2, 3, 1, 2, 1, 2, 3, 2, 1, 2, 3];
// ES6方法去重
// console.log([...new Set(arr)]);
// @1 迭代数组当前项,拿其后面的每一项和当前项做对比,如果和当项一样,则把这一项从数组中干掉
// O(n^2)
for (let i = 0; i < arr.length - 1; i++) {
let item = arr[i];
for (let j = i + 1; j < arr.length; j++) {
let compare = arr[j];
if (compare === item) {
// 这一项重复了,我们从原始数组中把其干掉即可
arr.splice(j, 1);
j--;
}
}
}
console.log(arr);
Object对象
1.对象的API
valueOf // 返回当前对象对应的值。
toString // 返回当前对象对应的字符串形式。
toLocaleString // 返回当前对象对应的本地字符串形式。
hasOwnProperty // 判断某个属性是否为当前对象自身的属性,还是继承自原型对象的属性。
isPrototypeOf // 判断当前对象是否为另一个对象的原型。
propertyIsEnumerable // 判断某个属性是否可枚举。
Object.keys(o) //遍历对象的可枚举属性
Object.getOwnPropertyName(o) //遍历对象不可枚举的属性
2.描述new一个对象过程
- 创建一个空对象,作为将要返回的对象实例。
- 将这个空对象的原型,指向构造函数的
prototype
属性。 - 将这个空对象赋值给函数内部的
this
关键字。 - 开始执行构造函数内部的代码。
也就是说,构造函数内部,this
指的是一个新生成的空对象,所有针对this
的操作,都会发生在这个空对象上。
构造函数:构造函数之所以叫“构造函数”,就是说这个函数的目的,就是操作一个空对象(即this
对象),将其“构造”为需要的样子。
如果忘了使用new
命令,直接调用构造函数会发生什么事?
这种情况下,构造函数就变成了普通函数,并不会生成实例对象。
var Vehicle = function (){
this.price = 1000;
};
var v = Vehicle();
v // undefined
price // 1000
// 变量v变成了undefined,而price属性变成了全局变量
(1)为了保证构造函数必须与new
命令一起使用,一个解决办法是,构造函数内部使用严格模式,即第一行加上use strict
。这样的话,一旦忘了使用new
命令,直接调用构造函数就会报错。
由于严格模式中,函数内部的this
不能指向全局对象,默认等于undefined
,导致不加new
调用会报错(JavaScript 不允许对undefined
添加属性)
(2)另一个解决办法,构造函数内部判断是否使用new
命令,如果发现没有使用,则直接返回一个实例对象
function Fubar(foo, bar) {
if (!(this instanceof Fubar)) {
return new Fubar(foo, bar);
}
this._foo = foo;
this._bar = bar;
}
Fubar(1, 2)._foo // 1
(new Fubar(1, 2))._foo // 1
(3) new.target 函数内部可以使用new.target属性。如果当前函数是new命令调用,new.target指向当前函数,否则为undefined。
function f() {
console.log(new.target === f);
}
f() // false
new f() // true
//使用这个属性,可以判断函数调用的时候,是否使用new命令。
function f() {
if (!new.target) {
throw new Error('请使用 new 命令调用!');
}
// ...
}
f() // Uncaught Error: 请使用 new 命令调用!
3.自己实现一个new
function _new(/* 构造函数 */ constructor, /* 构造函数参数 */ params) {
// 将 arguments 对象转为数组
var args = [].slice.call(arguments);
// 取出构造函数
var constructor = args.shift();
// 创建一个空对象,继承构造函数的 prototype 属性
var context = Object.create(constructor.prototype);
// 执行构造函数
var result = constructor.apply(context, args);
// 如果返回结果是对象,就直接返回,否则返回 context 对象
return (typeof result === 'object' && result != null) ? result : context;
}
// 实例
var actor = _new(Person, '张三', 28);
其它
字符串幂运算
Math.pow(x,y) // x的y次幂 x——底数,y——幂数 x,y必须为数字
原型链以及会产生的问题
每个对象都拥有一个原型对象,类是以函数的形式来定义的。prototype表示该函数的原型,
当我们访问一个对象的属性时,如果这个对象内部不存在这个属性,那么他就会去prototype里找这个属性,这个prototype又会有自己的prototype,于是就这样一
直找下去,也就是我们平时所说的"原型链"的概念。
关系:instance.constructor.prototype = instance.__proto__
特点:
JavaScript对象是通过引用来传递的,我们创建的每个新对象实体中并没有一份属于自己的原型副本。当我们修改原型时,与之相关的对象也会继承这一改变。
每个对象拥有一个原型对象,通过 __proto__ 指针指向上一个原型 ,并从中继承方法和属性,同时原型对象也可能拥有原型,这样一层一层,最终指向 null。
这种关系被称为原型链 (prototype chain)
JSON序列化和反序列化
JSON.stringfy()把一个JavaScript对象序列成一个JSON字符串,JSON.parse()把JSON字符串解析为原生Javascript对象
JSON.stringfy()除了要序列化js对象外,还可以接收两个参数。第一个参数是过滤器,可以是一个数组也可是一个函数;第二个蚕食是一个选项,表示是否在JSON字符串中保留缩进
1.过滤器:比如我要将某个属性过滤掉
JSON.stringify(book,function(key,value){
if(key=="editor") return undefined;//通过undefine删除该属性
return value
})
2.字符串缩进 :如果是一个树枝,那么表示的是每个级别缩进的空个数(最大为10)。如果是一个字符串而非数值,则这个字符串将在JSON字符串中被用做缩进字符(替换空格)
深浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的,浅拷贝只复制指向某个对象的指针而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象
深拷贝方法:
JSON.parse(JSON.stringify(obj))//深拷贝对象
原理: 用JSON.stringify将对象转成JSON字符串,再用JSON.parse()把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。
递归方法实现深度克隆原理:遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
防抖和节流
(1)为什么要防抖?
- 高频的函数操作可能产生不好的影响
- 如:resize、scroll、mousedown、mousemove、keyup、keydown……
这种在一瞬间(短时间内)对浏览器或服务器造成了过多压力的交互就需要进行优化了,为了解决这个问题,一般有两种解决方案:
- debounce 防抖
- throttle 节流
(2)目的:降低一个函数的触发频率,以提高性能或避免资源浪费
(3)防抖的原理就是:你尽管触发事件,但是我一定在事件触发n秒无操作后
才执行。
function debounce(func, wait) {
var timer;
return function () {
clearTimeout(timer)
timer = setTimeout(func, wait);
}
}
(4)节流的原理很简单:如果你持续触发某个事件,特定的时间间隔内,只执行一次。
// 创建定时器timer,记录当前是否在周期内;
// 判断定时器是否存在,若存在则直接结束,否则执行事件;
// wait时间之后再次执行,并清掉定时器;
//当触发事件的时候,我们设置一个定时器,再触发事件的时候,如果定时器存在,就不执行,直到定时器执行,然后执行函数,清空定时器,这样就可以设置下个定时器。
function throttle(func, wait) {
var _this, arg;
var timer; // 初始化
return function () {
_this = this; // 记录this
arg = arguments; // 记录参数数组
if (timer) return; // 时候未到
timer = setTimeout(() => {
func.apply(_this, arg); // 允许传入参数,并修正this
timer = null;
}, wait);
}
}
继承的方式有哪些?优缺?
原型链继承,构造继承,组合继承,原型式继承,寄生式继承,寄生组合式继承
1、原型链继承
将父类的实例作为子类的原型
function Cat(){ }
Cat.prototype = new Animal();
(1)非常纯粹的继承关系,实例是子类的实例,也是父类的实例。
(2)父类新增原型方法/原型属性,子类都能访问到
(3)简单,易于实现
2、构造继承
使用父类的构造函数来增强子类实例,等于是复制父类的实例属性给子类(没用到原型)
function Cat(name){
Animal.call(this);
this.name = name || 'Tom';
}
(1)解决了原型链继承,子类实例共享父类引用属性的问题
(2)创建子类实例时,可以向父类传递参数
(3)可以实现多继承多个父类对象
3.组合继承
原型链继承和经典继承双剑合璧。
function Parent (name) {
this.name = name;
this.colors = ['red', 'blue', 'green'];
}
Parent.prototype.getName = function () {
console.log(this.name)
}
function Child (name, age) {
Parent.call(this, name);
this.age = age;
}
Child.prototype = new Parent();
var child1 = new Child('kevin', '18');
child1.colors.push('black');
console.log(child1.name); // kevin
console.log(child1.age); // 18
console.log(child1.colors); // ["red", "blue", "green", "black"]
var child2 = new Child('daisy', '20');
console.log(child2.name); // daisy
console.log(child2.age); // 20
console.log(child2.colors); // ["red", "blue", "green"]
优点:融合原型链继承和构造函数的优点,是 JavaScript 中最常用的继承模式。
4.原型式继承
function createObj(o) {
function F(){}
F.prototype = o;
return new F();
}
就是 ES5 Object.create 的模拟实现,将传入的对象作为创建的对象的原型。
缺点:
包含引用类型的属性值始终都会共享相应的值,这点跟原型链继承一样
5. 寄生式继承
创建一个仅用于封装继承过程的函数,该函数在内部以某种形式来做增强对象,最后返回对象。
function createObj (o) {
var clone = object.create(o);
clone.sayName = function () {
console.log('hi');
}
return clone;
}
缺点:跟借用构造函数模式一样,每次创建对象都会创建一遍方法。
6. 寄生组合式继承
这种方式的高效率体现它只调用了一次Parent构造函数,并且因此避免了在 Parent.prototype 上面创建不必要的、多余的属性。与此同时,原型链还能保持不变;因此,还能够正常使用 instanceof 和 isPrototypeOf。开发人员普遍认为寄生组合式继承是引用类型最理想的继承范式。
Promise解决的问题和存在的问题?async和await
1.promise 解决了回调函数的回调地域的问题,有时候我们的请求需要上一个请求返回的结果,会造成相互间回调函数的嵌套,使得代码的可读性和可维护性很低;
- promise让代码变得扁平,可读性更好,then返回一个promise,我们可以把then串起来,then返回的promise装载了由调用返回的值。
- 在异步回调中,函数的执行栈与原函数分离开,导致外部无法抓住异常。在promise中我们可以使用reject捕获是失败情况,和catch捕获执行异常。
//Promise 只不过是一种更良好的编程风格
2.存在的缺点,无法取消,一旦开始执行,中途无法取消;
不设置回调函数,promise内部抛出的错误,无法返回到外部
处于pendding状态时,无法得知进展到哪一个阶段。
3.async 和 await
async 函数返回的是一个 Promise 对象,在没有 await 的情况下执行 async 函数,他会立即返回一个promise对象,并且,绝对不会注意后面语句的执行,await关键字只能用在aync定义的函数内;
await 可以用于等待一个 async 函数的返回值,如果它等到的是一个 Promise 对象,await 就忙起来了,它会阻塞后面的代码,等着 Promise 对象 resolve,然后得到 resolve 的值,作为 await 表达式的运算结果。
async/await使得异步代码看起来像同步代码,使代码简洁,可读性更好,避免嵌套。
宏任务微任务(事件循环)
宏任务是由宿主发起的,而微任务由JavaScript自身发起。
在ES3以及以前的版本中,JavaScript本身没有发起异步请求的能力,也就没有微任务的存在。在ES5之后,JavaScript引入了Promise
,这样,不需要浏览器,JavaScript引擎自身也能够发起异步任务了。
所以,总结一下,两者区别为:
宏任务(macrotask) | 微任务(microtask) | |
谁发 起的 | 宿主(Node、浏览器) | JS引擎 |
具体事件 | 1. script (可以理解为外层同步代码) 主js 2. setTimeout/setInterval 3. UI rendering/UI事件 UI渲染 4. postMessage,MessageChannel 5. setImmediate,I/O(Node.js) | 1. promise.then()(new Promise不算!) 2. MutaionObserver 3. Object.observe(已废弃; 4. process.nextTick(Node.js) |
谁先运行 | 后运行 | 先运行 |
会触发新一轮Tick吗 | 会 | 不会 |
其中比较注意的是promise的内部既包含宏任务也包含微任务,promise内部执行为宏任务,then执行为微任务
事件冒泡
IE的事件流叫做事件冒泡,即事件开始时有最具体的元素接收,然后逐级向上传播到较为不具体的节点,直至传播到document对象。
事件对象
DOM中事件对象btn.addEventListener("click", function(event){ event.preventDefault() })
属性方法 | 说明 |
preventDefault( ) | 阻止事件冒泡(cancelable为true可以使用) |
cancelable | 表明是否可以取消事件的默认行为 |
stopImmediatePropagation( ) | 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(DOM3新增) |
stopPropagation( ) | 取消事件的进一步捕获或冒泡。如果bunbbles为true,则可以使用这个方法 |
bubbles | 表名事件是否冒泡 |
type | 被触发的事件类型 |
target | 事件的目标 |
事件类型
(1)UI事件:当用户与页面上的元素交互式触发。常用:load(页面完全加载后在window上触发)、unload(页面完全卸载后触发)、select(当用户选择文本框inputh或texterea中的一个或多个字符触发)、resize(窗口发生变化时)、scroll(页面带滚动条的元素发生滚动时)
(2)焦点事件:在页面获得或失去焦点时触发。常用:blur(不冒,失去焦点)、focus(不冒,获得焦点)、focusin(冒泡,获得焦点)、focusout(冒泡,失去焦点)
(3)鼠标事件:click、mouseDown、mouseEnter、mouseLeave、mouseMove、mouseOut、mouseOver、mouseUp
(4)键盘事件:keydown(按下键盘人意键触发)、keypress(回车键会触发)、keyup(键盘弹起)
(5)触摸事件:touchstart、touchmove、touchend、thouchcancle
事件委托(又称为事件代理)
对"事件处理程序过多"问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如在一个复杂的web应用中,对所有的元素添加单击事件,那门结果就会有数不清的添加事件处理程序,利用事件委托可以有效解决,只需要在DOM树的最高层次上天际一个事件处理程序。就可以冒泡到下面各个子元素
这样做优点:1.document对象很快就可以访问,而且可以在页面生命周期任何时间点上为他添加事件处理。换句话说,只要可以单击的元素呈现在页面上门就可以立即具备相应功能
2.在页面中设置事件处理的所需的时间更少。只添加一个事件处理程序所需的DOM引用更少,所花的时间也更少
3.整个页面占用的内存空间更少,能够提升整体性能
ES新特性相关
ES6的新特性
1.let和const
问:const申明的变量一定不能改变吗?
const实际上保证的,并不是变量的值不得改动,而是变量指向的那个内存地址所保存的数据不得改动。
对于简单类型的数据(数值、字符串、布尔值),值就保存在变量指向的那个内存地址,因此等同于常量。
但对于复合类型的数据(主要是对象和数组),变量指向的内存地址,保存的只是一个指向实际数据的指针,const只能保证这个指针是固定的(即总是指向另一个固定的地址),至于它指向的数据结构是不是可变的,就完全不能控制了。因此,将一个对象声明为常量必须非常小心。
2.箭头函数
问:箭头函数和普通函数的区别
1.this
2.箭头函数是匿名函数,不能作为构造函数,不能使用new
3.箭头函数不绑定arguments参数,虽然有name属性但是是空字符串,取而代之用...扩展运算符
4.箭头函数通过 call() 或 apply() 方法调用一个函数时,只传入了一个参数,对 this 并没有影响
5.箭头函数没有原型属性 prototype
3.函数参数默认值
4.模版字符串
5.解构复值
6.扩展操作符
[..., arr] {...,obj}
7.promise
8.Array.prototype.includes() (ES7)
9.async/await (ES8)
HTML和CSS相关
css3有哪些新特性?
1)CSS3 实现圆角(border-radius:8px;),
2)阴影(box-shadow:10px),
3)对文字加特效(text-shadow),
4)线性渐变(gradient),
5)transform:rotate(9deg) scale(0.85,0.90) translate(0px,-30px) skew(-9deg,0deg);//旋转,缩放,定位,倾斜 ,
6)增加了更多的 css 选择器 ,
7)多背景 rgba
css选择符有哪些?哪些属性可以继承
1、!important,加在样式属性值后,权重值为 10000
2、内联样式,如:style=””,权重值为1000
3、ID选择器,如:#content,权重值为100
4、类,伪类和属性选择器,如: content、:hover 权重值为10
5、标签选择器和伪元素选择器,如:div、p、:before 权重值为1
6、通用选择器(*)、子选择器(>)、相邻选择器(+)、同胞选择器(~)、权重值为0
1.id选择器( # myid)
2.类选择器(.myclassname)
3.标签选择器(div, h1, p)
4.相邻选择器(h1 + p)
5.子选择器(ul > li)
6.后代选择器(li a)
7.通配符选择器( * )
8.属性选择器(a[rel = “external”])
9.伪类选择器(a:hover, li:nth-child)
低等级的选择器,个数再多也不会越等级超过高等级的选择器的优先级的;
介绍一下 CSS 的盒子模型?
1)有两种,IE 盒子模型(box-sizing:border-box)、标准 W3C 盒子模型(box-sizing:content-box);
IE 的 content 部分包含了 border 和 padding;
2)盒模型:内容(content)、填充(padding)、边界(margin)、边框(border)
3)flex弹性伸缩盒模型
4)多列布局(column)
CSS盒模型和IE盒模型的区别:
-
- 在 标准盒子模型中,width 和 height 指的是内容区域的宽度和高度。增加内边距、边框和外边框会影响盒子的总尺寸。
- IE盒子模型中,width 和 height 指的是内容区域+border+padding的宽度和高度。
标准回答:其实我们常用的盒模型就是标准盒模型,他写的宽高是指内容的宽高,并不是我们盒子的宽高,其实最终盒子的宽高是加上了我们的padding和margin还有border最终组成的,但是这种东西在我们真实项目可能就会遇到一个问题,假设我想构建一个100*100的的盒子,我发现只要我给他加border,我不改width和height的话,他就会变大了,这样就和我们的UI不符,所以每改一次border和padding,都要手动改width和height值,还要去做一系列计算,我认为这种方式特别的麻烦,后来CSS3里提供了box-sizing,就可以通修改box-sizing:border-box来修改盒模型为IE盒模型,IE盒模型宽高是指内容+border-padding的宽和高,不管我怎么调padding和border它都会自动缩放内容来实现宽高不变,这样我写样式比较方便,所以现在真实项目中大部分都是用box-sizing:border-box,包括我在看了bootstap以及各大UI组件它的源码里,他们公共样式里面大部分都是让盒子默认采用box-sizing:border-box,所以我认为这是我们开发中的一种规范和方式。
box-sizing有那些值,有什么区别?
box-sizing是CSS3新增属性,可调整盒子模型的样式。
box-sizing: border-box 表示盒模型基于IE的盒模型,width和height决定盒模型的content区、padding区和border区。
box-sizing: content-box 表示盒模型基于标准盒模型,width和height只决定盒模型的content区
box-sizing: inherit 表示继承自父元素。
两列布局的实现方式
1.浮动
2.flex布局
3.绝对定位
/*1.采用浮动*/
.outer{
height:100px;
}
.left{
float:left;
height:100px;
width:200px;
background:yellow;
}
.right{
margin left:200px;
height:100px;
width:auto;//撑满
background:red;
}
/*2.flex布局*/
.outer{
display:flex;
height:100px;
}
.left{
height:100px;
flex shrink:0;
flex grow:0;
flex basic:200px
}
.right{
height:100px;
flex:1
}
/*3.绝对定位(左边的绝对定位或者右边的绝对定位)
.....*/
水平垂直居中的方式(掌握三种)
/*(1)已知容器的宽高 设置层的外边距*/
div {
position: relative; /* 相对定位或绝对定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
margin-top: -150px;
margin-left: -250px; /* 外边距为自身宽高的一半 */
}
/*(2)让绝对定位的div居中 不需要考虑宽高,!!!但是一定要有宽高*/
div {
position: absolute;
width: 300px;
height: 300px;
margin: auto;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
/*(3)不需要知道具体宽高-水平垂直居中 利用 `transform` 属性,但是有个问题就是兼容性不好*/
div {
position: absolute; /* 相对定位或绝对定位均可 */
width:500px;
height:300px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
/*(1)(2)(3)这三个方案都是基于定位*/
/*利用flex布局,控制主轴和交叉轴 不兼容 移动端都用这种方式*/
.container {
display: flex;
align-items: center; /* 垂直居中 */
justify-content: center; /* 水平居中 */
}
/*table-cell 缺点:父元素必须有固定宽高,不可以是百分比高度*/
.father{
display: table-cell;
vertical-align: middle;
text-align: center
//=>宽高不能是百分比
}
清除浮动
清除浮动是为了清除使用浮动元素产生的影响。浮动的元素,高度会塌陷,而高度的塌陷使我们页面后面的布局不能正常显示
1、父级div定义height;
2、父级div 也一起浮动;
3、浮动元素的父级div定义伪类:after , clear:both用来闭合浮动的
&::after,&::before{
content: " ";
visibility: hidden;
display: block;
height: 0;
clear: both;
}
Flex三个属性
flex:0 0 1这是flex
的缩写: flex-grow
、flex-shrink
、flex-basis
- flex-grow 表示当有剩余空间的时候,分配给项目的比例
- flex-shrink 表示空间不足的时候,项目缩小的比例
- flex-basis 表示分配空间之前,项目占据主轴的空间
(1)flex-grow(默认值 0)
假设有一个宽度为 800 的容器,里面有 3 个项目,宽度分别是 100,200,300。
这时候就出现了多余的 200 的空间(灰色部分)。这时候如果我们对左中右分别设置flex-grow
为 2,1,1,各个项目的计算逻辑如下:
-
- 首先将多余空间 200 除以 4(2 + 1 + 1),等于 50
- left = 100 + 2 x 50 = 200
- middle = 200 + 1 x 50 = 250
- right = 300 + 1 x 50 = 350
(1)flex-shrink(默认值 1)
假设父容器宽度调整为 550,里面依然是 3 个项目,宽度分别是 100,200,300,这时候空间就不够用溢出了。首先要理解清楚,当我们定义一个固定宽度容器为flex
的时候,flex
会尽其所能不去改变容器的宽度,而是压缩项目的宽度。这时我们对左中右分别设置flex-shrink
为 1,2,3,计算逻辑如下:
-
- 溢出空间 = 100 + 200 + 300 - 550 = 50
- 总权重 = 1 x 100 + 2 x 200 + 3 x 300 = 1400
- left = 100 - (50 x 1 x 100 / 1400) = 96.42
- middle = 200 - (50 x 2 x 200 / 1400) = 185.72
- right = 300 - (50 x 3 x 300 / 1400) = 267.86
sass和less的区别
1.编译环境不同
less是通过js编译 是在客户端处理
sass同通过ruby 是在服务器端处理
2.变量符不一样
less是用@,sass是用$
3.sass支持条件语句,可以使用if{}else{},for{}循环等等。而less不支持。
4.less没有输出设置,sass提供4中输出选项:nested, compact, compressed 和 expanded。
输出样式的风格可以有四种选择,默认为nested
nested:嵌套缩进的css代码
expanded:展开的多行css代码
compact:简洁格式的css代码
compressed:压缩后的css代码
5.Sass和Less的工具库不同
Sass有工具库Compass, 简单说,Sass和Compass的关系类似于像Javascript和jQuery的关系,Compass在Sass的基础上,封装了一系列有用的模块和模板,补充强化了Sass的功能。
Less有UI组件库Bootstrap,Bootstrap是web前端开发中一个比较有名的前端UI组件库,Bootstrap的样式文件部分源码就是采用Less语法编写
1
VUE相关
数据请求在哪个生命周期函数?
- 越早越好(能放created就放created里)
- 不要在 updated 里更新数据
data为啥是一个函数
组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份data,就会造成一个变了全都会变的结果。
组件中的 data 为什么是一个函数
1.vue中组件是用来复用的,为了防止data复用,将其定义为函数。
2.vue组件中的data数据都应该是相互隔离,互不影响的,组件每复用一次,data数据就应该被复制一次,之后,当某一处复用的地方组件内data数据被改变时,其他复用地方组件的data数据不受影响,就需要通过data函数返回一个对象作为组件的状态。
3.当我们将组件中的data写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的data,拥有自己的作用域,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。
4.当我们组件的date单纯的写成对象形式,这些实例用的是同一个构造函数,由于JavaScript的特性所导致,所有的组件实例共用了一个data,就会造成一个变了全都会变的结果。
MVC和MVVM的区别
MVC全名是Model View Controller
MVVM与MVC最大的区别就是:MVVM实现了View和Model的自动同步,也就是当Model的属性改变时,我们不用再自己手动操作Dom元素,来改变View的显示,而是改变属性后该属性对应View层显示会自动改变
vue生命周期函数
beforeCreate( 创建前 )
created(创建后)
beforeMount(挂在前)
mounted(挂载后)
beforeUpdate(数据更新前调用)
updated(更新后)
beforeDestroy(实例销毁前)beforeUnmount
destroyed(实例销毁后) unmounted
activated(被 keep-alive 缓存的组件激活时调用)
decactived(被 keep-alive 缓存的组件停用时调用)
vue父子组件的生命周期
一、加载渲染过程
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
二、子组件更新过程
父beforeUpdate->子beforeUpdate->子updated->父updated
三、父组件更新过程
父beforeUpdate->父updated
四、销毁过程
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
vue的组件通信
1.方法一:props和$emit
父组件通过props向下传递数据给子组件,子组件通过event给父组件发送消息,实际上就是子组件把自己的数据发送给父组件。
2.方法二:$attrs和$listeners
第一种方式处理父子组件之间的数据传输有一个如果父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢? 如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。Vue 2.4开始提供了
listeners来解决这个问题,能够让组件A之间传递消息给组件C。
3.方法三:provide和 inject
在 Vue.js 的 2.2.0+
版本中添加加了 provide 和 inject 选项。他们成对出现,用于父级组件向下传递数据。
父组件中通过provide来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provide中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。
4.方法四:vuex处理组件之间的数据交互
如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的。
5.方法五:中央事件总线
如果两个组件不是父子关系呢?这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.$emit触发事件,bus.$on监听触发的事件。
公共事件总线eventBus的实质就是创建一个vue实例,通过一个空的vue实例作为桥梁实现vue组件间的通信。它是实现非父子组件通信的一种解决方案。
6.方法六:$parent和$children
Vue.component('child',{
props:{
value:String, //v-model会自动传递一个字段为value的prop属性
},
data(){
return {
mymessage:this.value
}
},
methods:{
changeValue(){
this.$parent.message = this.mymessage;//通过如此调用可以改变父组件的值
}
},
template:`
<div>
<input type="text" v-model="mymessage" @change="changeValue">
</div>`
})
Vue.component('parent',{
template:`
<div>
<p>this is parent compoent!</p>
<button @click="changeChildValue">test</button >
<child></child>
</div>
`,
methods:{
changeChildValue(){
this.$children[0].mymessage = 'hello';
}
},
data(){
return {
message:'hello'
}
}
})
var app=new Vue({
el:'#app',
template:`
<div>
<parent></parent>
</div>
`
})
hash和hostory
1、hash模式
这里的hash就是指url尾巴后的#号以及后面的字符。这里的#和css里的#是一个意思。hash也称作锚点,本身是用来做页面定位的,他可以使对应的id元素显示在可视区域内。
由于hash值变化不会导致浏览器向服务器发出请求,而且hash改变会触发hashchange事件,浏览器的进后退也能对其进行控制,所以人们在html5的history出现前,基本都是使用hash来实现前端路由的。他的特点在于:hash虽然出现url中,但不会被包含在HTTP请求中,对后端完全没有影响,因此改变hash不会重新加载页面。hash 本来是拿来做页面定位的,如果拿来做路由的话,原来的锚点功能就不能用了。其次,hash的而传参是基于url的,如果要传递复杂的数据,会有体积的限制
2、history模式
history模式不仅可以在url里放参数,还可以将数据存放在一个特定的对象中。
history———利用了HTML5 History Interface 中新增的pushState()和replaceState()方法。(需要特定浏览器的支持)history不能运用与IE8一下
vue响应式原理
vue2.0实现mvvm的双向绑定,是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
使用Object.defineProperty()存在几个
(1)需要对原始数据克隆(不能监听数组的变化)
(2)需要分别给对象中的每一个属性设置监听(必须遍历对象的每个属性)
(3)如果在实例创建之后添加新的属性到实例上,它不会触发视图更新($set强制更新)
【数组的这些方法是无法触发set的:push, pop, shift, unshift,splice, sort, reverse.】
所以在vue3的时候为了解决这些问题,用的是ES6中新提供的拦截器Proxy实现的。
<!--vue2.0-->
<body>
姓名:<span id="spanName"></span>
<br>
<input type="text" id="inpName">
<!-- IMPORT JS -->
<script>
let obj = {
name: ''
};
let newObj = {
...obj
};
Object.defineProperty(obj, 'name', {
get() {
return newObj.name;
},
set(val) {
newObj.name = val;
observe();
}
});
function observe() {
spanName.innerHTML = newObj.name;
}
inpName.oninput = function () {
obj.name = this.value;
};
</script>
</body>
<!--vue3.0-->
<body>
姓名:<span id="spanName"></span>
<br>
<input type="text" id="inpName">
<!-- IMPORT JS -->
<script>
let obj = {
name: ''
};
obj = new Proxy(obj, {
get(target, prop) {
return target[prop];
},
set(target, prop, value) {
target[prop] = value;
observe();
}
});
function observe() {
spanName.innerHTML = obj.name;
}
inpName.oninput = function () {
obj.name = this.value;
};
</script>
</body>
Proxy的优势:
可以直接监听对象而非属性
可以直接监听数组的变化
Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的
Proxy返回一个新对象,可以只操作新对象达到目的,而Object.defineProperty只能遍历对象属性直接修改
Proxy作为新标准将受到浏览器厂商重点持续的性能优化
Object.defineProperty的优势如下: 兼容性好,支持IE9
vue3和vue2区别(重点)
1.为什么要有vue3
我们使用vue2常常会遇到一些体验不太好的地方,比如:
- 随着功能的增长,需求的增加,复杂组件的代码越来越难以维护,逻辑混乱,虽然vue2也有一些复用的方法,但是都存在一定的弊端,比如我们常常用的Mixin,特别容易发生命名冲突,暴露出来的变量意图不是很明显,重用到其他组件容易冲突。
- vue2对于typeScript的支持非常有限,没有考虑到ts的集成。
vue3的出现就是为了解决vue2的弊端,其composition API很好的解决了逻辑复用的问题,而且vue3源码就是用ts写的,对ts的支持非常好。我们在开发项目过程中可以使用ts的加持,使代码更加健壮。
2.vue3优点
- vue3支持vue2的大多数特性,实现对vue2的兼容
- vue3对比vue2具有明显的性能提升
-
- 打包大小减少41%
- 初次渲染快55%,更新快133%
- 内存使用减少54%
- vue3具有的composition API实现逻辑模块化和重用
- 增加了新特性,如Teleport组件,全局API的修改和优化等
- 编译优化
vue2 通过标记静态根节点,优化 diff 算法
vue3 标记和提升所有静态根节点,diff 的时候只比较动态节点内容
优化编译和重写虚拟dom,让首次渲染和更新dom性能有更大的提升
3.响应式原理不同
Vue2.x实现双向数据绑定原理,是通过es5的 Object.defineProperty,根据具体的key去读取和修改。其中的setter方法来实现数据劫持的,getter实现数据的修改。但是必须先知道想要拦截和修改的key是什么,所以vue2对于新增的属性无能为力,比如无法监听属性的添加和删除、数组索引和长度的变更,vue2的解决方法是使用Vue.set(object, propertyName, value) 等方法向嵌套对象添加响应式。
Vue3.x使用了ES2015的更快的原生proxy 替代 Object.defineProperty。Proxy可以理解成,在对象之前架设一层“拦截”,外界对该对象的访问,都必须先通过这层拦截,因此提供了一种机制,可以对外界的访问进行过滤和改写。Proxy可以直接监听对象而非属性,并返回一个新对象,具有更好的响应式支持
4.生命周期不同
beforeCreate -> 请使用 setup()
created -> 请使用 setup()
beforeMount -> onBeforeMount
mounted -> onMounted
beforeUpdate -> onBeforeUpdate
updated -> onUpdated
beforeDestroy -> onBeforeUnmount
destroyed -> onUnmounted
errorCaptured -> onErrorCaptured
如果要想在页面中使用生命周期函数,以往vue2的操作是直接在页面中写入生命周期,而vue3是需要去引用的,这就是为什么3能够将代码压缩到更低的原因
5.默认项目目录结构不同
vue3移除了配置文件目录,config 和 build 文件夹,移除了 static 文件夹,新增 public 文件夹,并且 index.html 移动到 public 中,在 src 文件夹中新增了 views 文件夹,用于分类视图组件和公共组件
6.默认项目目录结构不同
在 Vue3 中,全局和内部 API 都经过了重构,并考虑到了 tree-shaking 的支持。因此,全局 API现在只能作为 ES 模块构建的命名导出进行访问。
6.使用Proxy代替了defineProperty
proxy 相比于 defineProperty 的优势
Object.defineProperty() 的问题主要有三个:
- 不能监听数组的变化
- 必须遍历对象的每个属性
- 必须深层遍历嵌套的对象
Proxy 在 ES2015 规范中被正式加入,它有以下几个特点:
- 针对对象:针对整个对象,而不是对象的某个属性,所以也就不需要对 keys 进行遍历。这解决了上述 Object.defineProperty() 第二个问题
- 支持数组:Proxy 不需要对数组的方法进行重载,省去了众多 hack,减少代码量等于减少了维护成本,而且标准的就是最好的。
除了上述两点之外,Proxy 还拥有以下优势:
- Proxy 的第二个参数可以有 13 种拦截方法,这比起 Object.defineProperty() 要更加丰富
- Proxy 作为新标准受到浏览器厂商的重点关注和性能优化,相比之下 Object.defineProperty() 是一个已有的老方法。
vue 的初始化过程(new Vue())都做了什么?
- 处理组件配置项
- 初始化根组件时进行了选项合并操作,将全局配置合并到根组件的局部配置上
- 初始化每个子组件时做了一些性能优化,将组件配置对象上的一些深层次属性放到 vm.$options 选项中,以提高代码的执行效率
- 初始化组件实例的关系属性,比如 parent、children、root、refs 等
- 处理自定义事件
- 调用 beforeCreate 钩子函数
- 初始化组件的 inject 配置项,得到 ret[key] = val 形式的配置对象,然后对该配置对象进行响应式处理,并代理每个 key 到 vm 实例上
- 数据响应式,处理 props、methods、data、computed、watch 等选项
- 解析组件配置项上的 provide 对象,将其挂载到 vm._provided 属性上
- 调用 created 钩子函数
- 如果发现配置项上有 el 选项,则自动调用 $mount 方法,也就是说有了el选项,就不需要手动调用$mount方法。反之,没提供 el 选项则必须调用 $mount
- 接下来则进入挂载阶段
vue里的name作用
(1)递归组件运用(指组件自身组件调用自身组件)
(2)keep-alive包裹动态组件时,会缓存不活动的组件实例,会出现include和exclude属性,包含或者排除指定name组件
<div id="app">
<keep-alive exclude="compA">
<router-view/>
</keep-alive>
</div>
exclude="compA" keep-alive属性对compA组件不生效
(3)vue-tools插件调试
组件未定义name属性
组件将显示成,这很没有语义。
通过提供name选项,可以获得更有语义信息的组件树。
$nextTick原理
由于Vue DOM更新是异步执行的,即修改数据时,视图不会立即更新,而是会监听数据变化,并缓存在同一事件循环中,等同一数据循环中的所有数据变化完成之后,再统一进行视图更新。为了确保得到更新后的DOM,所以设置了 Vue.nextTick()方法。
Vue.nextTick()官方说明:在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的DOM。
使用场景:
- 在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因:是created()钩子函数执行时DOM其实并未进行渲染。
- 在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作应该放在Vue.nextTick()的回调函数中。原因:Vue异步执行DOM更新,只要观察到数据变化,Vue将开启一个队列,并缓冲在同一事件循环中发生的所有数据改变,如果同一个watcher被多次触发,只会被推入到队列中一次。
vue自定义指令
Vue中内置了很多的指令,如v-model、v-show、v-html等,但是有时候这些指令并不能满足我们,或者说我们想为元素附加一些特别的功能。自定义指令解决的问题或者说使用场景是对普通 DOM 元素进行底层操作,所以我们不能盲目的胡乱的使用自定义指令
如何声明自定义指令?
对于全局自定义指令的创建,我们需要使用Vue.directive
接口
Vue.directive('demo', Opt)
对于局部组件,我们需要在组件的钩子函数directives中进行声明
Directives: {
Demo: Opt
}
一个指令定义对象可以提供如下几个钩子函数 (均为可选):
bind
:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。inserted
:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。update
:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。componentUpdated
:指令所在组件的 VNode 及其子 VNode 全部更新后调用。unbind
:只调用一次,指令与元素解绑时调用。
指令钩子函数会被传入以下参数:
el
:指令所绑定的元素,可以用来直接操作 DOM。- binding:一个对象,包含以下 property:
-
name
:指令名,不包括v-
前缀。value
:指令的绑定值,例如:v-my-directive="1 + 1"
中,绑定值为2
。oldValue
:指令绑定的前一个值,仅在update
和componentUpdated
钩子中可用。无论值是否改变都可用。expression
:字符串形式的指令表达式。例如v-my-directive="1 + 1"
中,表达式为"1 + 1"
。arg
:传给指令的参数,可选。例如v-my-directive:foo
中,参数为"foo"
。modifiers
:一个包含修饰符的对象。例如:v-my-directive.foo.bar
中,修饰符对象为{ foo: true, bar: true }
。
vnode
:Vue 编译生成的虚拟节点。移步 VNode API 来了解更多详情。oldVnode
:上一个虚拟节点,仅在update
和componentUpdated
钩子中可用。
vue中 keep-alive 组件的作用
keep-alive 是 Vue 的内置组件,当它包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 transition 相似,keep-alive 是一个抽象组件:它自身不会渲染成一个 DOM 元素,也不会出现在父组件链中。
作用:在组件切换过程中将状态保留在内存中,防止重复渲染DOM,减少加载时间及性能消耗,提高用户体验性
原理:在 created 函数调用时将需要缓存的 VNode 节点保存在 this.cache 中/在 render(页面渲染) 时,如果 VNode 的 name 符合缓存条件(可以用 include 以及 exclude 控制),则会从 this.cache 中取出之前缓存的 VNode 实例进行渲染。
VNode:虚拟DOM,其实就是一个JS对象
props:
- include - 字符串或正则表达式。只有名称匹配的组件会被缓存。
- exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。
- max - 数字。最多可以缓存多少组件实例。
v-model 语法糖
v-model 的本质就是语法糖,即:
<input type="text" v-model="name">
相当于:
<input type="text" :value="name" @input="name = $event.target.value">
实现v-modeld的两种方式:
只要在一个自定义组件内通过设置一个名为 value 的 prop,并且在数据发生变化时 $emit 一个带新值的 input 事件,就可以在该自定义组件中使用 v-model 进行双向绑定。非常简单,就像这种
<template>
<input type="text" :value="value" @input="handleInput" :placeholder="placehodler" />
</template>
<script>
export default {
name: 'kInput',
props: {
value: ['String', 'Number'],
placeholder: String
},
methods: {
handleInput ($event) {
// 通过input标签的原生事件input将值emit出去,以达到值得改变实现双向绑定
this.$emit('input', $event.target.value)
}
}
}
</script>
一个组件上的v-model
默认会利用名为value
的 prop 和名为input
的事件,但是像单选框、复选框等类型的输入控件可能会将value
attribute 用于不同的目的。model
选项可以用来避免这样的冲突
Vue.component('base-checkbox', {
model: {
prop: 'checked',
event: 'change'
},
props: {
checked: Boolean
},
template: `
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
`
})
vue项目打包优化策略
(1)组件按需加载
(2)路由懒加载:
resolve => require(['@/components/DefaultIndex'],resolve),(异步加载 )
webpack提供的require.ensure()
const 组件名=() => import('组件路径');
(3)优化构建速度 [ 缩小文件搜索范围]
在配置 `Loader` 时通过 `include` 去缩小命中范围
(4)使用 UglifyJsPlugin
插件来压缩代码,drop_console除掉代码中的 console
vue-router 有哪几种导航钩子?
答:三种,
第一种:是全局导航钩子:router.beforeEach(to,from,next),作用:跳转前进行判断拦截。
第二种:组件内的钩子(beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave)
第三种:单独路由独享组件(路由定义里的钩子beforeEnter)
webpack
1.webpack的作用
①、依赖管理:方便引用第三方模块、让模块更容易复用,避免全局注入导致的冲突、避免重复加载或者加载不需要的模块。会一层一层的读取依赖的模块,添加不同的入口;同时,不会重复打包依赖的模块。
②、合并代码:把各个分散的模块集中打包成大文件,减少HTTP的请求链接数,配合UglifyJS(压缩代码)可以减少、优化代码的体积。
③、各路插件:统一处理引入的插件,babel编译ES6文件,tslint,eslint 可以检查编译期的错误。
一句话总结:webpack 的作用就是处理依赖,模块化,打包压缩文件,管理插件。
一切皆为模块,由于webpack只支持js文件,所以需要用loader 转换为webpack支持的模块,其中plugin 用于扩张webpack 的功能,在webpack构建生命周期的过程中,在合适的时机做了合适的事情。
2.webpack怎么工作的过程
①解析配置参数,合并从shell(npm install 类似的命令)和webpack.config.js文件的配置信息,输出最终的配置信息;
②注册配置中的插件,让插件监听webpack构建生命周期中的事件节点,做出对应的反应;
③解析配置文件中的entry入口文件,并找出每个文件依赖的文件,递归下去;
④在递归每个文件的过程中,根据文件类型和配置文件中的loader找出对应的loader对文件进行转换;
⑤递归结束后得到每个文件最终的结果,根据entry 配置生成代码chunk(打包之后的名字);
⑥输出所以chunk 到文件系统。
3.请你介绍一下webpack?
Webpack 是一个前端资源加载/打包工具。它将根据模块的依赖关系进行静态分析,然后将这些模块按照指定的规则生成对应的静态资源。
entry: 入口,定义整个编译过程的起点
entry可以是个字符串或数组或者是对象。
1、当entry是个字符串的时候,用来定义入口文件:
2、当entry是个数组的时候,里面同样包含入口js文件,另外一个参数可以是用来配置webpack提供的一个静态资源服务器,webpack-dev-server。webpack-dev-server会监控项目中每一个文件的变化,实时的进行构建,并且自动刷新页面:
3、当entry是个对象的时候,我们可以将不同的文件构建成不同的文件,按需使用,比如在我的hello页面中只要\引入hello.js即可:
output: 输出,定义整个编译过程的终点,output参数是个对象,用于定义构建后的文件的输出。其中包含path和filename
当我们在entry中定义构建多个文件时,filename可以对应的更改为[name].js用于定义不同文件构建后的名字。
module: 定义模块module的处理方式
plugins: 插件,对编译完成后的内容进行二度加工
插件(Plugins)是用来拓展webpack功能的,它们会在整个构建过程中生效,执行相关的任务。
Loaders和Plugins常常被弄混,但是他们其实是完全不同的东西:Loaders是在打包构建过程中用来处理源文件的(JSX,Scss,Less..),一次处理一个;插件并不直接操作单个文件,它直接对整个构建过程其作用。webpack有很多内置插件,同时也有很多第三方插件,可以让我们完成更加丰富的功能。
前端安全问题
1.CSRF
跨站请求伪造,利用cookie
CSRF攻击攻击原理及过程如下:
1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
2.在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
csrf攻击解决方法:
目前,防御CSRF攻击主要有三种策略:
1、验证HTTP Referer 字段;
2、在请求地址中添加token并验证;
3、HTTP头中自定义属性并验证。
2.XSS
跨站脚本攻击,盗取cookie
指的是恶意攻击者往Web页面里插入恶意html代码,当用户浏览该页之时,嵌入其中Web里面的html代码会被执行,从而达到恶意的特殊目的。
解决办法:
请记住两条原则:过滤输入和转义输出。
第一、在输入方面对所有用户提交内容进行可靠的输入验证,提交内容包括URL、查询关键字、http头、post数据等
第二、在输出方面,在用户输内容中使用标签。标签内的内容不会解释,直接显示。
第三、严格执行字符输入字数控制。
性能优化
请说出三种减少网页加载时间的方法
1.服务器角度
采取CDN加速
开启gzip压缩
允许使用强缓存或协商缓存
增加服务器带宽
2.客户端角度
合理组织CSS、JavaScript代码位置
减少DOM操作、添加事件委托
部分操作可设置防抖和节流
对于可预见的操作采取preload或prerender的预加载
对于图片可以懒加载
合并CSS图片(精灵图/雪碧图)
减少使用iframe
资源优化打包角度
3.资源优化打包角度
使用打包工具将Js文件、CSS文件和静态文件进行恰当打包处理。
浅谈SPA、SEO、SSR
1. SPA
【单页面应用程序】传统的Web网站中,进入了新的页面,会从服务器请求完整的一个整个页面,而在SPA中,当切换到新的页面,只需要重写页面发生了变化的部分。
目前常见的几个SPA框架
- AngularJS
- React
- Vue.js
SPA的优点
- 页面之间的切换非常快
- 一定程度上减少了后端服务器的压力(不用管页面逻辑和渲染)
- 后端程序只需要提供API,完全不用管客户端到底是Web界面还是手机等
SPA的缺点
- 首屏打开速度很慢,因为用户首次加载需要先下载SPA框架及应用程序的代码,然后再渲染页面。
- 不利于SEO
2. SEO
搜索引擎优化。SEO是一种通过了解搜索引擎的运作规则(如何抓取网站页面,如何索引以及如何根据特定的关键字展现搜索结果排序等)来调整网站,以提高该网站在搜索引擎中某些关键词的搜索结果排名。
常见SEO优化:HTML<title>标签、 <meta>标签的description、<meta>标签的keywords
Google的相关文档中也没有提到过使用meta keywords, Quora也讨论过Google是否还在使用meta keywords这个问题,大部分的回答都是谷歌已经不再使用它了,但是其它的一些搜索引擎比如百度等还在使用meta keywords。
SPA与SEO的冲突
前面我们谈到的SPA不利于SEO,因为就目前而言,部分搜索引擎如Google、bing等,它们的爬虫虽然已经支持执行JS甚至是通过AJAX获取数据了,但是对于异步数据的支持也还不足(也可能是搜索引擎提供商觉得没必要),Vue SSR中是这样说的如果你的应用程序初始展示 loading 菊花图,然后通过 Ajax 获取内容,抓取工具并不会等待异步完成后再行抓取页面内容。
前·面也谈到过SPA应用中,通常通过AJAX获取数据,而这里就难以保证我们的页面能被搜索引擎正常收录到。并且有一些搜索引擎不支持执行JS和通过AJAX获取数据,那就更不用提SEO了。
3. SSR
SSR是 Server-Side Rendering(服务器端渲染)的缩写,在普通的SPA中,一般是将框架及网站页面代码发送到浏览器,然后在浏览器中生成和操作DOM,但其实也可以将SPA应用打包到服务器上,在服务器上渲染出HTML,发送到浏览器,这样的HTML页面还不具备交互能力,所以还需要与SPA框架配合,在浏览器上“混合”成可交互的应用程序。所以,只要能合理地运用SSR技术,不仅能一定程度上解决首屏慢的问题,还能获得更好的SEO。
SSR的优点
更快的响应时间,不用等待所有的JS都下载完成,浏览器便能显示比较完整的页面了。这个个人深有体会,我的博客最开始仅仅使用了Vue.js,而没有做服务端渲染,加之服务器不在大陆,第一次输入地址到看到完整的页面几乎是过了4、5秒,有时候还更长。
更好的SSR,我们可以将SEO的关键信息直接在后台就渲染成HTML,而保证搜索引擎的爬虫都能爬取到关键数据。
SSR的缺点
- 相对于仅仅需要提供静态文件的服务器,SSR中使用的渲染程序自然会占用更多的CPU和内存资源
- 一些常用的浏览器API可能无法正常使用,比如window、docment和alert等,如果使用的话需要对运行的环境加以判断
- 开发调试会有一些麻烦,因为涉及了浏览器及服务器,对于SPA的一些组件的生命周期的管理会变得复杂
- 可能会由于某些因素导致服务器端渲染的结果与浏览器端的结果不一致。
SSR常用框架
- React 的 Next
- Vue.js 的 Nuxt
移动端适配方案
1. 百分比方案
使用 百分比% 定义 宽度,高度 用px固定,根据可视区域实时尺寸进行调整,尽可能适应各种分辨率,通常使用max-width/min-width控制尺寸范围过大或者过小。下表是子元素不同属性设置百分比的依据
优势
原理简单,不存在兼容性问题
不足
- 如果屏幕尺度跨度太大,相对设计稿过大或者过小的屏幕不能正常显示,在大屏手机或横竖屏切换场景下可能会导致页面元素被拉伸变形,字体大小无法随屏幕大小发生变化。
- 设置盒模型的不同属性时,其百分比设置的参考元素不唯一,容易使布局问题变得复杂
vw/vh方案
视口是浏览器中用于呈现网页的区域,移动端的视口通常指的是 布局视口
- vw : 1vw 等于 视口宽度 的 1%
- vh : 1vh 等于 视口高度 的 **1% **
- vmin : 选取 vw 和 vh 中 最小 的那个
- vmax : 选取 vw 和 vh 中 最大 的那个
2. rem方案
原理
rem是相对长度单位,rem方案中的样式设计为相对于根元素font-size计算值的倍数。根据 屏幕宽度 设置html标签的font-size,在布局时使用 rem 单位布局,达到自适应的目的,是 弹性布局 的一种实现方式。
实现过程: 首先获取文档根元素和设备dpr,设置 rem,在html文档加载和解析完成后调整body字体大小; 在页面缩放 / 回退 / 前进的时候, 获取元素的内部宽度 (不包括垂直滚动条,边框和外边距),重新调整 rem 大小。
实现方法:用 css 处理器或 npm 包将页面 css 样式中的px自动转换成 rem。在整个 flexible 适配方案中,文本使用px作为单位,使用[data-dpr]属性来区分不同dpr下的文本字号。由于手机浏览器对字体显示最小是8px,因此对于小尺寸文字需要采用px为单位,防止通过 rem 转化后出现显示问题。手机淘宝 中的字体使用px为单位,腾讯新闻中的字体使用rem为单位。
优势
兼容性好
- ios: 6.1系统以上都支持
- android: 2.1系统以上都支持
- 大部分主流浏览器都支持
不足
- 不是纯css移动适配方案,需要引入js脚本 在头部内嵌一段 js脚本 监听分辨率的变化来动态改变根元素的字体大小,css样式和 js 代码有一定 耦合性,并且必须将改变font-size的代码放在 css 样式之前。
- 小数像素问题,浏览器渲染最小的单位是像素,元素根据屏幕宽度自适应,通过 rem 计算后可能会出现小数像素,浏览器会对这部分小数四舍五入,按照整数渲染。浏览器在渲染时所做的摄入处理只是应用在元素的尺寸渲染上,其真实占据的空间依旧是原始大小。也就是说如果一个元素尺寸是 0.625px,那么其渲染尺寸应该是 1px,空出的 0.375px 空间由其临近的元素填充;同样道理,如果一个元素尺寸是 0.375px,其渲染尺寸就应该是0,但是其会占据临近元素 0.375px 的空间。会导致:缩放到低于1px的元素时隐时现(解决办法:指定最小转换像素,对于比较小的像素,不转换为 rem 或 vw);两个同样宽度的元素因为各自周围的元素宽度不同,导致两元素相差1px;宽高相同的正方形,长宽不等了;border-radius: 50% 画的圆不圆。
- Android 浏览器下 line-height 垂直居中偏离的问题。常用的垂直居中方式就是使用line-height,这种方法在Android设备下并不能完全居中。
- cursor: pointer 元素点击背景变色的问题,对添加了cursor:pointer属性的元素,在移动端点击时,背景会高亮。为元素添加tag-highlight-color:transparent 属性可以隐藏背景高亮。
3. 基于媒体查询的响应式设计
响应式设计 使得一个网站同时适配 多种设备 和 多个屏幕,让网站的布局和功能随用户的使用环境(屏幕大小、输出方式、设备/浏览器能力而变化),使其视觉合理,交互方式符合习惯。如使得内容区块可伸缩与自由排布,边距适应页面尺寸,图片适应比例变化,能够自动隐藏/部分显示内容,能自动折叠导航和菜单。
原理
主要实现是通过 媒体查询,通过给不同分辨率的设备编写不同的样式实现响应式布局,用于解决不同设备不同分辨率之间兼容问题,一般是指PC、平板、手机设备之间较大的分辨率差异。实现上不局限于具体的方案,通常结合了 流式布局+弹性布局 方案。比如给小屏幕手机设置@2x图,为大屏手机设置@3x图
优势
能够使网页在不同设备、不同分辨率屏幕上呈现合理布局,不仅仅是样式伸缩变换
不足
- 要匹配足够多的设备与屏幕,一个web页面需要多个设计方案,工作量比较大
- 通过媒体查询技术需要设置一定量的断点,到达某个断点前后的页面发生显著变化,用户体验不太友好
4.ios安全区域的适配
而我们需要将viewport设置为cover,env和constant才能生效。设置代码如下:
<meta name="viewport" content="width=device-width,initial-scale=1.0,viewport-fit=cover">
同时设置env和constant代码,同样env()和constant()需要同时存在,而且顺序不能换。
body {
/*兼容 IOS<11.2*/
padding:
constant(safe-area-inset-top)
constant(safe-area-inset-right)
constant(safe-area-inset-bottom)
constant(safe-area-inset-left); /* 兼容 iOS < 11.2 */
/* 可以通过增加padding-bottom来适配 */
padding:
env(safe-area-inset-top)
env(safe-area-inset-right)
env(safe-area-inset-bottom)
env(safe-area-inset-left); /* 兼容 iOS >= 11.2 */
}
5.10px字体
/*font-size: 10px;*/
.font10px {
font-size: 12px;
transform : scale(0.83,0.83);
}
/*还有一些方式可以制作小字体:
图片
自制字体
SVG*/
6.0.5px的线
/*伪类 + transform 实现*/
.centent{
position: relative;
border:none;
}
.centent:after{
content: '';
position: absolute;
bottom: 0;
background: #000;
width: 100%;
height: 1px;
-webkit-transform: scaleY(0.5);
transform: scaleY(0.5);
-webkit-transform-origin: 0 0;
transform-origin: 0 0;
}
7.在高清显示屏中的位图被放大,图片会变得模糊?
移动端的视觉稿通常会设计为传统PC的2倍。
前端的应对方案是:设计稿切出来的图片长宽保证为偶数,并使用backgroud-size把图片缩小为原来的1/2;
例如图片宽高为:200px*200px,那么写法如下:.css{width:100px;height:100px;background-size:100px 100px;}其它元素的取值为原来的1/2,例如视觉稿40px的字体,使用样式的写法为20px:.css{font-size:20px}