前端面试题(汇总)

前端面试题(较全面):https://blog.csdn.net/weixin_43606158/article/details/89811189

1. 网页从输入网址(URL)到渲染完成经历了哪些过程?

大致可以分为如下7步:

输入网址;
发送到DNS服务器,并获取域名对应的web服务器对应的ip地址;
与web服务器建立TCP连接;
浏览器向web服务器发送http请求;
web服务器响应请求,并返回指定url的数据(或错误信息,或重定向的新的url地址);
浏览器下载web服务器返回的数据及解析html源文件;
生成DOM树,解析css和js,渲染页面,直至显示完成;

2.前端四大存储方式

  • h5之前,存储主要是用cookies。cookies缺点有在请求头上带着数据,大小是4k之内

  • HTML5 提供了两种在客户端存储数据的新方法:
    localStorage - 没有时间限制的数据存储
    sessionStorage - 针对一个 session 的数据存储,在关闭页面后即被清空

  • 离线缓存(application cache)
    本地缓存应用所需的文件
    使用方法:
    ①配置manifest文件
    页面上:

     
     
     ...
     
    

Manifest 文件:
manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。

manifest 文件可分为三个部分:

①CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
②NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
③FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)

完整demo:

CACHE MANIFEST
# 2016-07-24 v1.0.0
/theme.css
/main.js
 
NETWORK:
login.jsp
 
FALLBACK:
/html/ /offline.html

服务器上:manifest文件需要配置正确的MIME-type,即 “text/cache-manifest”。

如Tomcat:


     manifest
     text/cache-manifest

3.GET/POST 和 TCP/UDP 区别

Get和Post方法的区别

1.给服务器传输数据的方法:
GET:通过网址字符串,把请求的数据附在URL后
POST:通过data

2.传输数据的大小:
GET:网址字符串最多255字节
POST:使用NSData.容量达到4G

3.安全性:
GET:所有传输给服务器的数据,显示在网址里,类似于密码的明文输入,直接可见.
POST:数据被转成NSData(二进制数据),类似于密码的密文输入,无法直接读取.

TCP与UDP区别

TCP:是基于连接的协议,也就是说,在正式收发数据前,必须和对方建立可靠的连接.一个TCP连接必须要经过三次”对话”(也称握手)才能建立起来.

UDP:是面向非连接的协议,它不与对方建立连接,而是直接就把数据包发送过去.UDP适用于一次只传送少量数据,对可靠性要求不高的应用环境.比如,我们经常使用”ping”命令来测试两台主机之间TCP/IP通信是否正常,例如:QQ语音,视频等

比较:
1)TCP面向连接,UDP面向非连接
2)TCP对系统资源的要求较多,UDP对系统资源的要求较少
3)TCP程序结构相对复杂,UDP程序结构较简单
4)TCP是流模式,UDP是数据报模式
5)TCP保证数据正确性,UDP可能丢包
6)TCP保证数据顺序,UDP不保证数据顺序
7)UDP Server不需要调用listen和accept
UDP 收发数据用sendto/recvfrom函数
8)TCP:地址信息在connect/accept时确定UDP:在sendto/recvfrom函数中每次均需指定地址信息.

注意:为什么需要三次握手,而非两次
防止已失效的连接请求又传送到服务器端,因而产生错误。
为了实现可靠传输,发送方和接收方始终需要同步( SYNchronize )序号。 需要注意的是, 序号并不是从 0 开始的, 而是由发送方随机选择的初始序列号 ( Initial Sequence Number, ISN )开始 。 由于 TCP 是一个双向通信协议, 通信双方都有能力发送信息, 并接收响应。 因此, 通信双方都需要随机产生一个初始的序列号, 并且把这个起始值告诉对方。
三次握手的过程即是通信双方相互告知序列号起始值, 并确认对方已经收到了序列号起始值的必经步骤,如果只是两次握手, 至多只有连接发起方的起始序列号能被确认, 另一方选择的序列号则得不到确认。

4.懒加载

1、懒加载原理
图片先用占位符表示,不要将图片地址放到src属性中,而是放到其它属性(data-original)中 页面加载完成后,监听窗口滚动,当图片出现在视窗中时再给它赋予真实的图片地址,也就是将data-original中的属性拿出来放到src属性中 在滚动页面的过程中,通过给scroll事件绑定lazyload函数,不断的加载出需要的图片

注意:请对lazyload函数使用防抖与节流
引入概念:防抖 节流
区别:防抖动是将多次执行变为最后一次执行,节流是将多次执行变成每隔一段时间执行。

  • 防抖(debounce)定义:
    对于短时间内连续触发的事件(上面的滚动事件),防抖的含义就是让某个时间期限(如500毫秒)内,事件处理函数只执行一次。如果 n 秒内高频事件再次被触发,则重新计算时间

     function debounce(fn,delay){
         let timer = null //借助闭包
         return function() {
             if(timer){
                 clearTimeout(timer) ;
             }
             timer = setTimeout(fn,delay) ;
         }
     }
     // 然后
     function showTop  () {
         var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
       console.log('滚动条位置:' + scrollTop);
     }
     window.onscroll = debounce(showTop,500); // 实际时间根据需要来配置
    
  • 节流(throttle)定义:所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。如果短时间内大量触发同一事件,那么在函数执行一次之后,该函数在指定的时间期限内不再工作,直至过了这段时间才重新生效。

     function throttle(fn,delay){
         let valid = true
         return function() {
            if(!valid){
                //休息时间 暂不接客
                return false 
            }
            // 工作时间,执行函数并且在间隔期内把状态位设为无效
             valid = false
             setTimeout(() => {
                 fn()
                 valid = true;
             }, delay)
         }
     }
     /* 请注意,节流函数并不止上面这种实现方案,
        例如可以完全不借助setTimeout,可以把状态位换成时间戳,然后利用时间戳差值是否大于指定间隔时间来做判定。
        也可以直接将setTimeout的返回的标记当做判断条件-判断当前定时器是否存在,如果存在表示还在冷却,并且在执行fn之后消除定时器表示激活,原理都一样
         */
     
     // 以下照旧
     function showTop  () {
         var scrollTop = document.body.scrollTop || document.documentElement.scrollTop;
       console.log('滚动条位置:' + scrollTop);
     }
     window.onscroll = throttle(showTop,500) 
    

2、懒加载方式
1)纯粹的延迟加载,使用setTimeOut或setInterval
这种方式,本质上不算懒加载 加载完首屏内容后,隔一段时间,去加载全部内容 但这个时间差已经完成了用户对首屏加载速度的期待

2)条件加载
用户点击或者执行其他操作再加载 其实也包括的滚动可视区域,但大部分情况下,大家说的懒加载都是只可视区域的图片懒加载,所以就拿出来说了

3)可视区加载
这里也分为两种情况:

1、页面滚动的时候计算图片的位置与滚动的位置

2、通过新的API: IntersectionObserver API(可以自动"观察"元素是否可见)
3、懒加载代码实现
1、核心原理
将非首屏的图片的src属性设置一个默认值,监听事件scroll、resize、orientationchange,判断元素进入视口viewport时则把真实地址赋予到src上

2、img标签自定义属性相关

I'm an image!

如上,data-属于自定义属性, ele.dataset. 可以读取自定义属性集合 img.srcset 属性用于设置不同屏幕密度下,image自动加载不同的图片,比如


3、判断元素进入视口viewport
常用的方式有两种

1)、图片距离顶部距离 < 视窗高度 + 页面滚动高度(不推荐)

imgEle.offsetTop < window.innerHeight + document.body.scrollTop
复制代码
2)getBoundingClientRect (很舒服的一个API)

Element.getBoundingClientRect()方法返回元素的大小及其相对于视口的位置

  function isInViewport(ele) {
    // 元素顶部 距离 视口左上角 的距离top <= 窗口高度 (反例:元素在屏幕下方的情况)
    // 元素底部 距离 视口左上角 的距离bottom > 0 (反例:元素在屏幕上方的情况)
    // 元素display样式不为none
    const notBelow = ele.getBoundingClientRect().top <= window.innerHeight ? true : false;
    const notAbove = ele.getBoundingClientRect().bottom >= 0 ? true : false;
    const visable = getComputedStyle(ele).display !== "none" ? true : false;
    return notBelow && notAbove && visable ? true : false;
  }

3)Intersection Observer(存在兼容性问题,但帅啊)

5.跨域

跨域有三个条件,满足任何一个条件就是跨域
1:服务器端口不一致
2:协议不一致
3:域名不一致

(1)JSONP
大多数跨域的解决方案一样都是JSONP,但是只支持GET。

	客户端:get是后面加上www.bbb.com/11?callback=xxx
	服务端:返回的数据:jsonpcallback({"Email":"[email protected]","Remark":"备注111"})

就是合法的js语句

jsonp的一个要点就是允许用户传递一个callback参数给服务端, 然后服务端返回数据时会将这个callback参数作为函数名来包裹住JSON数据, 这样客户端就可以随意定制自己的函数来自动处理返回数据了。
  发现凡是拥有"src"这个属性的标签都拥有跨域的能力,比如script、img、iframe; src 的能力就是把远程的数据资源加载到本地(图片、JS代码等);
  
(2)CORS
如果需要改成支持POST,因为传输的数据量比较大,GET形式搞不定。这就需要CORS(跨域资源共享,Cross-Origin Resource Sharing)。与jsonp不同的是,它不是直接发请求给要进行跨域的网站,而是先经过服务器,通过服务器的处理后,才能实现的跨域

	给客户端设置一个响应头,浏览器响应时就会知道允许跨域了
	header( 'Access-Control-Allow-Origin:*' )//允许所有网站跨域访问该网站
	header( 'Access-Control-Allow-Origin:http://baidu.com' )//只允许百度跨域访问该网站

在CORS中,所有的跨域请求被分为了两种类型,一种是简单请求,一种是复杂请求 (严格来说应该叫‘需预检请求’)。如果请求方式为get和post简单请求,则只需要设置响应头:res.setHeader(‘Access-Control-Allow-Origin’,‘*’);来允许某一个域 或者 所有域进行数据共享;
若是其他方式的请求,会在发送真正的请求之前发送一个options请求,通过options请求里设置:res.setHeader(‘Access-Control-Allow-Methods’, ‘DELETE’),

告知服务器正式请求会使用哪一种 HTTP 请求方法。

总结:jsonp方式和cors方式的区别
1.jsonp是jquery提供的跨域方式,cors是w3c提供的一个跨域标准
2.jsonp只支持get方式的跨域,cors支持get和post方式的跨域
3.jsonp支持所有的浏览器(因为所有浏览器都可以使用script标签发送请求),cors不支持IE10以下的浏览器

(3)ngnix反向代理

6.axios和ajax的区别

传统 Ajax 指的是 XMLHttpRequest(XHR), 最早出现的发送后端请求技术,隶属于原始js中,核心使用XMLHttpRequest对象,多个请求之间如果有先后关系的话,就会出现回调地狱。
JQuery ajax 是对原生XHR的封装,除此以外还增添了对JSONP的支持。
缺点:
1.本身是针对MVC的编程,不符合现在前端MVVM的浪潮
2.基于原生的XHR开发,XHR本身的架构不清晰。
3.JQuery整个项目太大,单纯使用ajax却要引入整个JQuery非常的不合理
4.不符合关注分离(Separation of Concerns)的原则
5.配置和调用方式非常混乱,而且基于事件的异步模型不友好。

axios 是一个基于Promise 用于浏览器和 nodejs 的 HTTP 客户端,本质上也是对原生XHR的封装,是通过Promise的来实现的,符合最新的ES规范,它本身具有以下特征:
1.从浏览器中创建 XMLHttpRequest
2.支持 Promise API
3.客户端支持防止CSRF
4.提供了一些并发请求的接口(重要,方便了很多的操作)
5.从 node.js 创建 http 请求
6.拦截请求和响应
7.转换请求和响应数据
8.取消请求
9.自动转换JSON数据
*注意:防止CSRF:就是让每个请求都带一个从cookie中拿到的key, 根据浏览器同源策略,假冒的网站是拿不到cookie中的key,这样,后台就可以轻松辨别出这个请求是否来自假冒网站,从而采取正确的策略。

*并发请求:同时进行多个请求,同时处理返回值
axios.all(
[
  axios.get(‘/data.json’),
  axios.get(‘/city.json’)
]
).then(
  axios.spread((dataRes,cityRes) =>{
  console.log(dataRes,cityRes)
}))
流程说明:现进行all里面的两个请求,请求完成后输入两个返回值dataRes和cityRes,
spread在继续对返回值进行请求或者处理得到两个返回值输出。
当需要同时请求多个接口时,并同时处理返回值时就可以使用并发请求。

7.JavaScript和jQuery的区别

(1)本质上的区别:
js是网页脚本语言,而jQuery是基于js语言封装出来的一个前端框架。也就是说js是一种语言,而jQuery是基于该语言的一种框架。

(2)用法上的区别:
1.外观上jQuery对象比js对象多了"$()",最直观的区别;
2.操作内容

非表单元素(如果是文本就用text方法,如果是html代码就用html方法):

例如:
div.text();——无参数的情况下是取值
div.text("aaaa");——有参数的情况下是赋值
div.html();——无参数的情况下是取值
div.html("aaaa");——有参数的情况下是赋值

表单元素:
JavaScript:

div.value;——取值;
div.value = xxx;——赋值;

jQuery:

div.val();——无参数是取值,有参数是赋值。

3.操作属性
JavaScript里面用来操作属性的方法:

div.setAttribute("","");——设置属性、修改属性
div.removeAttribute("");——移除属性,引号里面写一个属性名
div.getAttribute();——获取属性

jQuery里面用来操作属性的方法:

添加属性:div.attr("test","aa"); ——给这个attr方法加入参数,属性名叫做test,属性的值是aa
移除属性:div.removeAttr("test"); ——移除test这条属性
获取属性:div.attr("test"); —— 在attr方法里面直接写入一个属性的名就可以了

4.操作样式
JavaScript里面操作样式的关键字是style

例:div.style.backgroundColor = "red";

jQuery里面操作样式的关键字是css

例:div.css("background-color","yellow");

*注意:JS操作样式的方法只能获取内联样式,不能取内嵌的和外部的。
JQUERY操作样式的方法可以是内联的也可以是内嵌的。

8.java和javascript的区别

首先,这两个家伙没有任何的血缘关系,java是是由Sun 公司于1995年5月推出的;
javascript是于1995年由Netscape公司设计实现而成的,由于Netscape公司与Sun公司合作,Netscape高层希望它看上去能够像Java,因此取名为JavaScript。

1、Java是面向对象的语言,JavaScript是脚本语言,是基于对象和事件驱动的语言。

2、Java的源代码在执行之前必须经过编译,而JavaScript的代码不需要,可以由浏览器直接解释执行。

3、java主要在服务端运行;javascript主要运行在客户端浏览器中。

4、JavaScript是动态类型语言;而Java是静态类型语言。java在定义了一个数组的长度以后就不能再改变了,但是javascript却可以。

5、JavaScript是弱类型的,即在使用前不需要声明,而是浏览器解释器在运行时检查数据类型;Java属于强类型,即所有变量在编译前必须作声明;

6、JavaScript 的面向对象是基于原型的(prototype-based)实现的,Java 是基于类(class-based)的;

7、Java的语法规则比JavaScript要严格的多,功能要强大的多。

9.框架与库的区别

简单地来分析,我们把框架(Framework)和库(Library,简写Lib)可以这样理解:

假如我们要买一台电脑,框架为我们提供了已经装好的电脑,我们只要买回来就能用,但前提是你必须把整个电脑要买回来。另外,我们还必须根据框架设定的使用规则来使用电脑。虽然这样用户可能轻松许多,但会导致很多人用一样的电脑,或你想自定义某个部件将需要修改这个框架。而库就如自己组装的电脑。库为我们提供了很多部件,我们需要自己组装,如果某个部件库未提供,我们也可以自己做。
库的使用非常灵活,但没有框架方便,这就是框架和库本质的区别。

:库是更多是一个封装好的特定的集合,提供给开发者使用,(控制权在开发者手上)
框架:是一套架构,会基于自身的特点向用户提供一套相当于叫完整的解决方案,而且(控制权的在框架本身),使用者要找框架所规定的某种规范进行开发。

像angular、backbone、vue就属于框架,而jQuery、react、underscore就是库,在前者中我们完全可以自由的使用后者,同时也可以没有前者的基础之上使用后者,都是很自由,控制权始终在我们的手中,但是使用框架时候就必须按照它的规范来进行模块化的开发

*因为框架是有一套解决方案的,React就是纯粹写UI组件的 没有什么异步处理机制、模块化、表单验证这些。React和react-router, react-redux结合起来才叫框架,而React本身只是充当一个前端渲染的库而已。

10.angular 双向数据绑定与vue数据的双向数据绑定

1.二者都是 MVVM 模式开发的典型代表
2.angular 是通过脏检测实现,angular 会将 UI 事件,请求事件,settimeout 这类延迟的对象放入到事件监测的脏队列,当数据变化的时候,触发 $digest 方法进行数据的更新,视图的渲染
3.vue 通过数据属性的数据劫持和发布订阅的模式实现,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。大致可以理解成由3个模块组成,observer 完成对数据的劫持,compile 完成对模板片段的渲染,watcher 作为桥梁连接二者,订阅数据变化及更新视图

11.事件委托/事件代理

事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。比如我们有100个li,每个li都有相同的click点击事件,可能我们会用for循环的方法,来遍历所有的li,然后给它们添加事件,这样会导致性能降低。(在JavaScript中,添加到页面上的事件处理程序数量将直接关系到页面的整体运行性能,因为需要不断的与dom节点进行交互,访问dom的次数越多,引起浏览器重绘与重排的次数也就越多,就会延长整个页面的交互就绪时间)

添加到页面上的事件处理程序的数量和页面的整体性能是直接相关的。

  • 每个函数都是对象,会占用内存,对象越多性能越差。
  • 必须事先指定事件处理程序会导致访问DOM的次数增多,会延迟整个页面的交互就绪时间。

Vue 事件委托
使用场景:如果数据量在几百上千考虑用事件委托来实现,如果数据量较小就没必要了
首先要搞清 e.target和e.currentTarget的区别:

e.target指向触发事件监听的对象。(在事件委托中 需要通过e.target拿到真正触发事件的元素 而不是绑定事件的父元素)
e.currentTarget指向添加监听事件的对象.(在事件委托中,指向的是绑定了事件处理函数的父元素)

12.安全XSS, CSRF

一、XSS
定义:XSS, 即为(Cross Site Scripting), 中文名为跨站脚本, 是发生在目标用户的浏览器层面上的,当渲染DOM树的过程成发生了不在预期内执行的JS代码时,就发生了XSS攻击。

跨站脚本的重点不在‘跨站’上,而在于‘脚本’上。大多数XSS攻击的主要方式是嵌入一段远程或者第三方域上的JS代码。实际上是在目标网站的作用域下执行了这段js代码

XSS攻击方式
(1)反射型 XSS
反射型XSS,也叫非持久型XSS,是指发生请求时,XSS代码出现在请求URL中,作为参数提交到服务器,服务器解析并响应。响应结果中包含XSS代码,最后浏览器解析并执行。
(2)存储型 XSS
存储型XSS,也叫持久型XSS,主要是将XSS代码发送到服务器(不管是数据库、内存还是文件系统等。),然后在下次请求页面的时候就不用带上XSS代码了。

最典型的就是留言板XSS。用户提交了一条包含XSS代码的留言到数据库。当目标用户查询留言时,那些留言的内容会从服务器解析之后加载出来。浏览器发现有XSS代码,就当做正常的HTML和JS解析执行。XSS攻击就发生了。
(3)DOM XSS
DOM XSS攻击不同于反射型XSS和存储型XSS,DOM XSS代码不需要服务器端的解析响应的直接参与,而是通过浏览器端的DOM解析。这完全是客户端的事情。

DOM XSS代码的攻击发生的可能在于我们编写JS代码造成的。我们知道eval语句有一个作用是将一段字符串转换为真正的JS语句,因此在JS中使用eval是很危险的事情,容易造成XSS攻击。避免使用eval语句。

危害:
XSS危害有盗取用户cookie,通过JS或CSS改变样式,DDos造成正常用户无法得到服务器响应。
XSS代码的预防主要通过对数据解码,再过滤掉危险标签、属性和事件等。

防御:
(1)对cookie的保护
对重要的cookie设置httpOnly, 防止客户端通过document.cookie读取cookie。服务端可以设置此字段。

对用户输入数据的处理
编码:不能对用户输入的内容都保持原样,对用户输入的数据进行字符实体编码。对于字符实体的概念可以参考文章底部给出的参考链接。
解码:原样显示内容的时候必须解码,不然显示不到内容了。
过滤:把输入的一些不合法的东西都过滤掉,从而保证安全性。如移除用户上传的DOM属性,如onerror,移除用户上传的Style节点,iframe, script节点等。

二、CSRF攻击:Cross-site request forgery),中文名称:跨站请求伪造
CSRF能够做的事情包括:以你名义发送邮件,发消息,盗取你的账号,甚至于购买商品,虚拟货币转账…造成的问题包括:个人隐私泄露以及财产安全。

要完成一次CSRF攻击,受害者必须依次完成两个步骤:
  1.登录受信任网站A,并在本地生成Cookie。
  2.在不登出A的情况下,访问危险网站B。
CSRF的防御:CSRF的防御可以从服务端和客户端两方面着手,防御效果是从服务端着手效果比较好,现在一般的CSRF防御也都在服务端进行
1.服务端进行CSRF防御:服务端的CSRF方式方法很多样,但总的思想都是一致的,就是在客户端页面增加伪随机数。
(1).Cookie Hashing(所有表单都包含同一个伪随机值)
(2).验证码
(3).One-Time Tokens(不同的表单包含一个不同的伪随机值)

13.前端优化

前端面试题(汇总)_第1张图片

14.webpack

1.)开发环境和生产环境的配置
2.)针对某个文件打包
CopyWebpackPlugin前端面试题(汇总)_第2张图片

3.)打包文件过大怎么优化

  • 去除不必要的插件

  • 提取第三方库 — vendor

  • 代码压缩
    webpack 自带了一个压缩插件 UglifyJsPlugin,只需要在配置文件中引入即可。

     {
       plugins: [
         new webpack.optimize.UglifyJsPlugin({
           compress: {
             warnings: false
           }
         })
       ]
     }
    

加入了这个插件之后,编译的速度会明显变慢,所以一般只在生产环境启用。

另外,服务器端还可以开启 gzip 压缩,优化的效果更明显。

  • 代码分割
  • 设置缓存

15.深拷贝 浅拷贝

16.路由缓存,路由周期

keep-alive

beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
beforeEnter: 路由独享守卫
beforeRouteEnter: 路由组件的组件进入路由前钩子。
beforeResolve:路由全局解析守卫
afterEach:路由全局后置钩子
beforeCreate:组件生命周期,不能访问this。
created:组件生命周期,可以访问this,不能访问dom。
beforeMount:组件生命周期
deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
mounted:访问/操作dom。
activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
执行beforeRouteEnter回调函数next

17.Promise.all、race和any方法都是什么意思?

Promise.all()中的Promise序列会全部执行通过才认为是成功,否则认为是失败;
Promise.race()中的Promise序列中第一个执行完毕的是通过,则认为成功,如果第一个执行完毕的Promise是拒绝,则认为失败;
Promise.any()中的Promise序列只要有一个执行通过,则认为成功,如果全部拒绝,则认为失败;
https://www.zhangxinxu.com/wordpress/2021/05/promise-all-race-any/

18.什么叫优雅降级和渐进增强?

渐进增强 :
针对低版本浏览器进行构建页面,保证最基本的功能,然后再针对高级浏览器进行效果、交互等改进和追加功能达到更好的用户体验。
优雅降级 :
一开始就构建完整的功能,然后再针对低版本浏览器进行兼容。

19.浏览器页面有哪三层构成,分别是什么,作用是什么?

构成:结构层、表示层、行为层
分别是:HTML、CSS、JavajScript

20.简述一下src与href的区别

  • href 是指向网络资源所在位置,建立和当前元素(锚点)或当前文档(链接)之间的链接,用于超链接
  • src是指向外部资源的位置,指向的内容将会嵌入到文档中当前标签所在位置;在请求src资源时会将其指向的资源下载并应用到文档内,例如js脚本,img图片和frame等元素。当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执行完毕,图片和框架等元素也如此,类似于将所指向资源嵌入当前标签内。这也是为什么将js脚本放在底部而不是头部。

21.Web 缓存

大致可以分为:数据库缓存、服务器端缓存(代理服务器缓存、CDN 缓存)、浏览器缓存。
浏览器缓存也包含很多内容: HTTP 缓存、indexDB、cookie、localstorage 等等。

22.http缓存

  • 强缓存
    强缓存不会向服务器发送请求,直接从缓存中读取资源,在 chrome 控制台的 network 选项中可以看到该请求返回 200 的状态码,并且size显示from disk cache或from memory cache;
  • 协商缓存
    协商缓存会先向服务器发送一个请求,服务器会根据这个请求的 request header 的一些参数来判断是否命中协商缓存,如果命中,则返回 304 状态码并带上新的 response header 通知浏览器从缓存中读取资源。

HTTP 缓存控制
在 HTTP 中,我们可以通过设置响应头以及请求头来控制缓存策略。
强缓存可以通过设置Expires和Cache-Control 两种响应头实现。如果同时存在,Cache-Control优先级高于Expires。

  • Expires 指缓存过期的时间,超过了这个时间点就代表资源过期。并且 Expires 是 HTTP/1.0 的标准,现在更倾向于用 HTTP/1.1 中定义的 Cache-Control。两个同时存在时也是 Cache-Control 的优先级更高。
  • Cache-Control 可以由多个字段组合而成,主要有以下几个取值:
  1. max-age 指定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置 Cache-Control:max-age=31536000,也就是说缓存有效期为(31536000 / 24 / 60 * 60)天,第一次访问这个资源的时候,服务器端也返回了 Expires 字段,并且过期时间是一年后。
  2. s-maxage 同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。
  3. public 表明响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。
  4. private 表明响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。
  5. no-cache 强制所有缓存了该响应的用户,在使用已缓存的数据前,发送带验证器的请求到服务器。不是字面意思上的不缓存。(浏览器需要向服务器发请求校验资源是否新鲜进行判断)
  6. no-store 禁止缓存,每次请求都要向服务器重新获取数据。

考虑缓存的内容(不经常改变的文件):
css样式文件
js文件
logo、图标
html文件
可以下载的内容
一些不应该被缓存的内容:
业务敏感的 GET 请求

VUE

1. 谈谈你对MVVM开发模式的理解

MVVM分为Model、View、ViewModel三者。
Model 代表数据模型,数据和业务逻辑都在Model层中定义;
View 代表UI视图,负责数据的展示;
ViewModel 负责监听 Model 中数据的改变并且控制视图的更新,处理用户交互操作;
Model 和 View 并无直接关联,而是通过 ViewModel 来进行联系的,Model 和 ViewModel 之间有着双向数据绑定的联系。因此当 Model 中的数据改变时会触发 View 层的刷新,View 中由于用户交互操作而改变的数据也会在 Model 中同步。
这种模式实现了 Model 和 View 的数据自动同步,因此开发者只需要专注对数据的维护操作即可,而不需要自己操作 dom。

2. Vue 有哪些指令?

v-html、v-show、v-if、v-for等等

3. v-if 和 v-show 有什么区别?

v-show 仅仅控制元素的显示方式,将 display 属性在 block 和 none 来回切换;而v-if会控制这个 DOM 节点的存在与否。当我们需要经常切换某个元素的显示/隐藏时,使用v-show会更加节省性能上的开销;当只需要一次显示或隐藏时,使用v-if更加合理。

4.Vue 组件间通信六种方式

props/$emit
$emit/$on
vuex
$attrs/$listeners
provide/inject
$parent/$children 与 ref

5.Vue的生命周期

beforeCreate(创建前) 在数据观测和初始化事件还未开始
created(创建后) 完成数据观测,属性和方法的运算,初始化事件,$el属性还没有显示出来

beforeMount(载入前) 在挂载开始之前被调用,相关的render函数首次被调用。实例已完成以下的配置:编译模板,把data里面的数据和模板生成html。注意此时还没有挂载html到页面上。
mounted(载入后) 在el 被新创建的 vm.$el 替换,并挂载到实例上去之后调用。实例已完成以下的配置:用上面编译好的html内容替换el属性指向的DOM对象。完成模板中的html渲染到html页面中。此过程中进行ajax交互。
beforeUpdate(更新前) 在数据更新之前调用,发生在虚拟DOM重新渲染和打补丁之前。可以在该钩子中进一步地更改状态,不会触发附加的重渲染过程。
updated(更新后) 在由于数据更改导致的虚拟DOM重新渲染和打补丁之后调用。调用时,组件DOM已经更新,所以可以执行依赖于DOM的操作。然而在大多数情况下,应该避免在此期间更改状态,因为这可能会导致更新无限循环。该钩子在服务器端渲染期间不被调用。
beforeDestroy(销毁前) 在实例销毁之前调用。实例仍然完全可用。
destroyed(销毁后) 在实例销毁之后调用。调用后,所有的事件监听器会被移除,所有的子实例也会被销毁。该钩子在服务器端渲染期间不被调用。
1.什么是vue生命周期?
答: Vue 实例从创建到销毁的过程,就是生命周期。从开始创建、初始化数据、编译模板、挂载Dom→渲染、更新→渲染、销毁等一系列过程,称之为 Vue 的生命周期。

2.vue生命周期的作用是什么?
答:它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻辑。

3.vue生命周期总共有几个阶段?
答:它可以总共分为8个阶段:创建前/后, 载入前/后,更新前/后,销毁前/销毁后。

4.第一次页面加载会触发哪几个钩子?
答:会触发 下面这几个beforeCreate, created, beforeMount, mounted 。

5.DOM 渲染在 哪个周期中就已经完成?
答:DOM 渲染在 mounted 中就已经完成了。

6.分别简述computed和watch的使用场景

computed:
    当一个属性受多个属性影响的时候就需要用到computed
    最典型的栗子: 购物车商品结算的时候
watch:
    当一条数据影响多条数据的时候就需要用watch
    栗子:搜索数据

7.Vue中双向数据绑定是如何实现的?

vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。

8.vue的两个核心点

数据驱动、组件系统
数据驱动:ViewModel,保证数据和视图的一致性。
组件系统:应用类UI可以看作全部是由组件树构成的。

vuex常见面试题

9.vuex是什么?怎么使用?哪种功能场景使用它?

答:vue框架中状态管理。在main.js引入store,注入。
新建了一个目录store.js,…… export 。
场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
https://vuex.vuejs.org/zh/

10.vuex有哪几种属性?

有五种,分别是 State、 Getter、Mutation 、Action、 Module

state => 基本数据(数据源存放地)
getters => 从基本数据派生出来的数据
mutations => 提交更改数据的方法,同步!
actions => 像一个装饰器,包裹mutations,使之可以异步。
modules => 模块化Vuex

11.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?

如果请求来的数据是不是要被其他组件公用,仅仅在请求的组件内使用,就不需要放入vuex 的state里。
如果被其他地方复用,这个很大几率上是需要的,如果需要,请将请求放入action里,方便复用。

12.watch和computed的区别

①从属性名上,computed是计算属性,也就是依赖其它的属性计算所得出最后的值;
watch是去监听一个值的变化,然后执行相对应的函数。

②从实现上,computed的值在getter执行后是会缓存的,只有在它依赖的属性值改变之后,下一次获取computed的值时才会重新调用对应的getter来计算;
watch在每次监听的值变化时,都会执行回调。

*如果一个值依赖多个属性(多对一),用computed更加方便;
*如果一个值变化后会引起一系列操作,或者一个值变化会引起一系列值的变化(一对多),用watch更加方便一些。

③watch的回调里面会传入监听属性的新旧值,通过这两个值可以做一些特定的操作;
computed通常就是简单的计算,不支持异步,当computed内有异步操作时无效,无法监听数据的变化。

13.v-if和v-for的优先级

v-for优先级高于v-if,同时使用会出现v-if重复运行于每个v-for循环中,不推荐同时使用

14.nextTick原理

场景:需要在视图更新之后,基于新的视图进行操作。
在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM

15.Vue中使用key的作用

key的作用是为了在diff算法执行时更快的找到对应的节点,提高diff速度
key具有唯一性
vue中循环需加 :key=“唯一标识” ,唯一标识可以使item里面id index 等,因为vue组件高度复用增加key可以标识组件的唯一性,为了更好地区别各个组件key的作用主要是为了高效的更新虚拟DOM(diff算法)

16.Vue项目中的assets与static目录区别

在vue的目录结构中,static是静态资源目录,可以用于放置图片、字体等。
而在src目录(开发目录)下,同样提供了一个assets目录,可以用于存放图片,比如logo等。
访问assets目录下的资源文件使用的是相对路径,而访问static目录下的资源文件则需要使用绝对路径。

static :
static中的文件是不会经过编译的,打包后会生成dist文件夹,static中的文件只是复制复制一遍而已。
这是通过在 config.js 文件中的 build.assetsPublicPath 和 build.assetsSubDirectory 链接来确定的。
注意:任何放在 static 中的文件需要以绝对路径的形式引用:

yanggb

assets:
assets下面的资源,在js中使用的话,路径要经过webpack中的file-loader重新编译,使用时要用相对路径进行引用,不能使用绝对路径。
注意:
在动态绑定中,assets路径的图片会加载失败,因为webpack使用的是 commenJS 规范,必须使用require才可以

总结:
1.assets中的文件会经过webpack打包,重新编译,建议是存放一些只有组件自己使用到的静态资源。
2.static中的文件不会经过webpack编译,项目在经过打包后会生成dist文件夹,static中的文件只是复制一遍而已,
一般建议存放一些外部第三方(公用)的静态资源文件。

17.vue-router路由

前端路由核心原理:更新视图,但不重新请求页面
(1)hash(#)
vue-router默认使用hash模式,如浏览器不支持history也会回滚为hash模式

HashHistory有两个方法:HashHistory.push()将新路由添加到浏览器访问历史的栈顶 和 HashHistory.replace()替换掉当前栈顶的路由

onReady(),push()等方法只是代理,实际也是调用的history对象对应的方法

每次 hash 值的变化,会触发hashchange 这个事件,hash模式背后的原理是onhashchange事件,可以在window对象上监听这个事件

(2)history interface(h5新增)
浏览器通过 supportsPushState变量检查是否支持
pushState,replaceState()

popstate实现history路由拦截,监听页面返回事件

比较:
前面的hashchange,你只能改变#后面的url片段。而pushState设置的新URL可以是与当前URL同源的任意URL。
history模式则会将URL修改得就和正常请求后端的URL一样,如后端没有配置对应/user/id的路由处理,则会返回404错误

r o u t e r 与 router与 routerroute的区别

  1. r o u t e 从当前 r o u t e r 跳转对象里面可以获取 n a m e 、 p a t h 、 q u e r y 、 p a r a m s 等( < r o u t e r − l i n k > 传的参数由 t h i s . route从当前router跳转对象里面可以获取name、path、query、params等(传的参数由 this. route从当前router跳转对象里面可以获取namepathqueryparams等(<routerlink>传的参数由this.route.query或者 this.$route.params 接收)
  2. r o u t e r 为 V u e R o u t e r 实例。想要导航到不同 U R L ,则使用 router为VueRouter实例。想要导航到不同URL,则使用 routerVueRouter实例。想要导航到不同URL,则使用router.push方法;返回上一个history也是使用$router.go方法

18.slot原理

slot具有“占坑”的作用,在子组件占好了位置,那父组件使用该子组件标签时,新添加的DOM元素就会自动填到这个坑里面。
slot分为三类

  • 默认插槽(单个插槽,没有指定name,一个组件内只有一个匿名插槽)
  • 具名插槽(为插槽添加了name属性,可以添加多个聚名插槽)
    先在子组件的slot标签里,分别添加name=”name名” 属性:
    其次父组件在要分发的标签里添加 slot=”name名” 属性,然后就会将对应的标签放在对应的位置了:我是header
  • 作用域插槽
    可从子组件获取数据的可复用的插槽
    关键之处就在于,父组件能接收来自子组件的slot传递过来的参数
    作用域插槽必须放在template里面(父组件中)
    template标签中的属性slot-scope="props"声明从子组件传递的数据都放一个自定义属性内

插槽实现原理:
当子组件vm实例化时,获取到父组件传入slot标签的内容,存放在vm. s l o t 中,默认插槽为 v m . slot中,默认插槽为vm. slot中,默认插槽为vm.slot.default,具名插槽为vm. s l o t . x x x , x x x 为插槽名,当组件执行渲染函数时候,遇到 s l o t 标签,使用 slot.xxx,xxx 为插槽名,当组件执行渲染函数时候,遇到slot标签,使用 slot.xxxxxx为插槽名,当组件执行渲染函数时候,遇到slot标签,使用slot(父组件传入slot标签的内容)中的内容进行替换,此时可以为插槽传递数据,若存在数据,则可称该插槽为作用域插槽

19. vnode描述一个DOM结构

前端面试题(汇总)_第3张图片

20. 首页加载白屏问题

原因:由于html先加载,css,js加载解析,渲染时间太长
优化方案:
路由懒加载,减小打包体积,以及有一些首页用不到的js,css文件可以之后加载,去掉打包中配置的prefetch、preload预加载,ssr,loading提示提升用户体验

去掉打包中配置的prefetch、preload预加载:
前端面试题(汇总)_第4张图片
添加loading代码:

<head>
...
<style>
 /**loading样式代码忽略*/
</style>
</head>
<body>
    <noscript>
      <strong>We're sorry but yearlybill doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
    </noscript>
    <div id="app">
      <div id="loader-wrapper">
        <div id="loader"></div>
      </div>
    </div>
    <!-- built files will be auto injected -->
  </body>

JavaScript

1. JavaScript语言规定了7种语言类型,他们分别是:

Number(数字):是原始数值的包装对象,所有数字都采用64位浮点格式存储。
Boolean(布尔值)
String(字符串)
Null (空)
Undefined (未定义)
Symbol (es6新增,表示独一无二的值)
Object(对象)
其中,除了Object是对象类型,其他的都是原始(基本)类型。
并且基本数据类型的值不可改变,对象数据类型的值可以改变

JS分两种数据类型:

基本数据类型:Number、String、Boolean、Null、 Undefined、Symbol(ES6),这些类型可以直接操作保存在变量中的实际值。

引用数据类型:Object(在JS中除了基本数据类型以外的都是对象,数据是对象,函数是对象,正则表达式是对象):Object,Array,Date,RegExp

基本包装类型:Number、String、Boolean
    
引用类型与基本包装类型的主要区别就是对象的生存期:

使用new 操作符创建的引用类型的实例,在执行流离开当前作用域之前都一直保存在内存中。
自动创建的基本包装类型的对象,只存在于代码执行的一瞬间,然后被立即销毁。这意味着我们不能在运行时为基本类型值添加属性和方法。

2.typeof运算符(typeof与instanceof区别)

js中使用typeof能得到哪些类型
typeof只能判断五种基本数据类型:undefined、string、number、boolean、object
(Array利用typeof判断是object类型)

**延申 - - - 判断对象是不是数组Array类型:

  • Array.isArray()
  • 调用对象原型中的toString方法, Object.prototype.toString.call(obj)
  • constructor方法 arr.constructor = Array

typeof可以判断数据类型,但是无法判断array和object,如何解决—instanceof
(typeof在判断null、array、object以及函数实例(new + 函数)时,得到的都是object)
两者区别:

  • typeof判断所有变量的类型,返回值有number,boolean,string,function,object,undefined
  • typeof对于丰富的对象实例,只能返回"Object"字符串。
  • instanceof用来判断对象,代码形式为obj1 instanceof obj2(obj1是否是obj2的实例),obj2必须为对象,否则会报错!其返回值为布尔值。
  • instanceof可以对不同的对象实例进行判断,判断方法是根据对象的原型链依次向下查询,如果obj2的原型属性存在obj1的原型链上,(obj1 instanceof obj2)值为true。

3.变量提升(Hoisting)

变量提升:函数声明和变量声明总是会被解释器悄悄地被"提升"到方法体的最顶部。很简单,就是把变量提升提到函数的top的地方。需要说明的是,变量提升 只是提升变量的声明,并不会把赋值也提升上来
栗子:

	var v='Hello World';
	(function(){
	    alert(v);    // 由于变量提升,这里会输出undefined
	    var v='I love you';
	})()

相当于下面这种写法:

	var v='Hello World';
	(function(){
	    var v;
	    alert(v);
	    v='I love you';
	})()

注意:需要把变量放在函数级作用域的顶端,以防止出现意外。

4.函数提升

函数提升是把整个函数都提到前面去。

有两种写法,一种是函数表达式,另外一种是函数声明方式。我们需要重点注意的是,只有函数声明形式才能被提升

函数声明方式提升【成功】

function myTest(){
    foo();
    function foo(){
        alert("我来自 foo");
    }
}
myTest();

函数表达式方式提升【失败】--------与变量提升相似

function myTest(){
    foo();
   var foo =function foo(){
        alert("我来自 foo");
    }
}
myTest();

例子:

function myDemo(num){
	console.log('1,',  num);
	// 变量声明,变量赋值
	var num = 20;
	console.log('2,',  num);
	// 函数声明
	function num() {
		console.log('30');
	}
	console.log('3,',  num);
}
myDemo(10);
打印结果:
		1,  function test() {console.log('30');}
		2,  20
		3,  20

结论:
词法分析—函数开始执行一瞬间会生成Active Object(活动对象|容器),默认值是undefined。
执行顺序是:形参、变量、函数--------------
a.接收形参
b.分析变量声明,但 不执行赋值(所以不会覆盖接收到的形参)
c.分析函数声明,(如果之前已有赋值,会覆盖)

因此上面的代码可变形为:
	function myDemo(num){ 
	//形参时num为10
	// 变量声明提升,但不赋值
	var num;
	// 函数声明提升
	function num() {
		console.log('30');
	}
	console.log('1---',  num);
	// 变量赋值
	num = 20;
	console.log('2---',  num);
	console.log('3---',  num);
}
myDemo(10);

5.递归

函数内部调用自身,一般要设置一个临界函数,当达到条件时停止递归调用,防止无限循环。递归函数一般解决数学问题。
递归与循环的区别:
递归算法:
优点:代码简洁、清晰,并且容易验证正确性。
缺点:
1、它的运行需要较多次数的函数调用,如果调用层数比较深,每次都要创建新的变量,需要增加额外的堆栈处理,会对执行效率有一定影响,占用过多的内存资源。
2、递归算法解题的运行效率较低。在递归调用的过程中系统为每一层的返回点、局部变量等开辟了栈来储存。递归次数过多容易造成栈溢出等
循环算法:
优点:速度快,结构简单。
缺点:并不能解决所有的问题。有的问题适合使用递归而不是循环。如果使用循环并不困难的话,最好使用循环

递归例子:波非那切数列: 1, 1, 2,3,5,8,13,21,34.....
function test(num){
if(num === 1 || num === 2) {
	return 1;
}
return  test(num-1) + test(num-2);
}
console.log(1);
console.log(2);
console.log(3);
console.log(4);
console.log(5);
console.log(6);
console.log(7);
console.log(8);
console.log(9);

6.闭包

闭包是指有权访问另一个函数作用域中变量的函数。
例子:

function demo() {
	// 声明变量,并赋值
	var num = 10;
	// 声明内部函数,内部函数可以直接调用外部函数的变量
	function inner() {
		console.log(num);
	}
	// 将内部函数暴露出来
	return inner();
}
var outer = demo();
// 打印outer,得到的是inner函数
console.log(outer);
// 通过outer访问demo函数内部的变量
outer();

一个闭包就是当一个函数返回时,一个没有释放资源的栈区。
优点: 变量复用,且不会造成全局变量污染问题
闭包的缺点:
  闭包会携带包含它的函数的作用域,因此会比其他函数占用更多的内存
  解决:闭包不在使用时,要及时释放。
  将引用内层函数对象的变量赋值为null。

7.作用域,作用域链和原型链

(1)ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。
作用域就是代码的执行环境,全局执行环境就是全局作用域

  • 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域
  • 所有 window 对象的属性拥有全局作用域

函数的执行环境就是私有作用域,它们都是栈内存,为函数作用域: 函数作用域,是指声明在函数内部的变量

*作用域与执行上下文
我们知道 JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

解释阶段:
	词法分析
	语法分析
	作用域规则确定
执行阶段:
	创建执行上下文
	执行函数代码
	垃圾回收

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
(2)当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)

作用域链是针对变量的,一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
原型链是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链。

8.DOM和BOM (JavaScript组成)

js组成包括三部分:

  • ECMAScript(解释器 - - - 核心):描述了JS的语法和基本对象(语法、类型、语句、关键字、保留字、运算符、对象)

  • DOM(Document Object Model 文本对象):获取或设置文档中标签的属性

  • BOM(Browser Object Model浏览器对象):获取或设置浏览器的属性、行为

     Window对象包含属性:document、location、navigator、screen、history、frames;
     Document根节点包含子节点:forms、location、anchors、images、links;
    

*总结:从window.document可以看出,DOM的最根本的对象是BOM的window对象的子对象。

9.数组slice()和splice()的区别

  • slice()
        slice()定义:从已有的数组中返回你选择的某段数组元素
        slice()语法:arrayObject.slice(start,end)
  • splice()
        splice()定义:从数组中添加或删除元素,然后返回被删除的数组元素。
         splice()语法:arrayObject.splice(index,howmany,item1,…,itemX)

10.substr与substring的区别

js中substr和substring都是截取字符串中子串,非常相近,可以有一个或两个参数。

语法:substr(start [,length]) 第一个字符的索引是0,start必选 length可选
   substring(start [, end]) 第一个字符的索引是0,start必选 end可选

11.arguments

在函数调用的时候,浏览器每次都会传递进两个隐式参数:

  1. 函数的上下文对象this
  2. 封装实参的对象arguments

arguments 是一个对应于传递给函数的参数的类数组对象。
在javascript中,函数是没有重载这一项的,所谓的重载,一个函数可以有多个,就是参数的个数和形式不同所以引用的功能不同,而js不存在函数重载,不管传不传参数,函数里面是否引用,关系都不大,一个函数对应一个功能,但是函数可以模拟函数重载,所以有一个Arguments对象。
自带的属性:
(1)length
(2)callee 当前正在执行的函数(es5之后废弃)
(3)caller 指向调用当前函数的函数
(4)rguments[@@iterator] 返回一个新的Array迭代器对象,该对象包含参数中每个索引的值。
这个意思就是可以调用for-of循环 - -!
es6箭头函数的出现,arguments对象相对来说少用了,因为箭头函数没有arguments对象。再加上有一些属性都被遗弃。但是不能不学,所有的知识都是从底层创建出来的,了解底层知识是有好处的。

12.作用域,作用域链,原型,原型链

(1)ES6 之前 JavaScript 没有块级作用域,只有全局作用域和函数作用域。
作用域就是代码的执行环境,全局执行环境就是全局作用域

  • 最外层函数 和在最外层函数外面定义的变量拥有全局作用域
  • 所有末定义直接赋值的变量自动声明为拥有全局作用域
  • 所有 window 对象的属性拥有全局作用域

函数的执行环境就是私有作用域,它们都是栈内存,为函数作用域: 函数作用域,是指声明在函数内部的变量

*作用域与执行上下文
我们知道 JavaScript 属于解释型语言,JavaScript 的执行分为:解释和执行两个阶段,这两个阶段所做的事并不一样:

解释阶段:
	词法分析
	语法分析
	作用域规则确定
执行阶段:
	创建执行上下文
	执行函数代码
	垃圾回收

JavaScript 解释阶段便会确定作用域规则,因此作用域在函数定义时就已经确定了,而不是在函数调用时确定,但是执行上下文是函数执行之前创建的。执行上下文最明显的就是 this 的指向是执行时确定的。而作用域访问的变量是编写代码的结构确定的。

作用域和执行上下文之间最大的区别是:
执行上下文在运行时确定,随时可能改变;作用域在定义时就确定,并且不会改变。
(2)当代码在一个环境中执行时,会创建变量对象的一个作用域链(作用域形成的链条)

作用域链是针对变量的,一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。
原型链是针对构造函数的,比如我先创建了一个函数,然后通过一个变量new了这个函数,那么这个被new出来的函数就会继承创建出来的那个函数的属性,然后如果我访问new出来的这个函数的某个属性,但是我并没有在这个new出来的函数中定义这个变量,那么它就会往上(向创建出它的函数中)查找,这个查找的过程就叫做原型链。
原型:
分为隐式原型(proto),显示原型(prototype)

13.事件循环机制EventLoop

javascript是一门单线程的非阻塞的脚本语言。这是由最初的用途来决定的:与浏览器交互。
单线程意味着,javascript代码在执行的任何时候,都只有一个主线程来处理所有的任务,防止多个线程对dom进行一项操作。
非阻塞是当代码需要进行一项异步任务(无法立刻返回结果,需要花一定时间才能返回的任务,如I/O事件)的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调 ---- 通过event loop(事件循环)实现。

event loop最主要是分三部分:主线程、宏队列(macrotask)、微队列(microtask)
js的任务队列分为同步任务和异步任务,所有的同步任务都是在主线程里执行的,异步任务可能会在macrotask或者microtask里面
主线程
就是访问到的script标签里面包含的内容,或者是直接访问某一个js文件的时候,里面的可以在当前作用域直接执行的所有内容(执行的方法,new出来的对象等)
宏队列(macrotask)
setTimeout、setInterval、setImmediate、I/O、UI rendering
微队列(microtask)
promise.then、process.nextTick
执行顺序
1、先执行主线程
2、遇到宏队列(macrotask)放到宏队列(macrotask)
3、遇到微队列(microtask)放到微队列(microtask)
4、主线程执行完毕
5、执行微队列(microtask),微队列(microtask)执行完毕
6、执行一次宏队列(macrotask)中的一个任务,执行完毕
7、执行微队列(microtask),执行完毕
8、依次循环。。。

console.log(1)
process.nextTick(() => {
  console.log(8)
  setTimeout(() => {
    console.log(9)
  })
})
setTimeout(() => {
  console.log(2)
  new Promise(() => {
    console.log(11)
  })
})
requestIdleCallback(() => {
  console.log(7)
})
// 特殊说明: new Promise()属于主线程任务
let promise = new Promise((resolve,reject) => {
  setTimeout(() => {
    console.log(10)
  })
  resolve()
  // 这个console也属于主线程任务
  console.log(4)
})
fn()
console.log(3)
promise.then(() => {
  console.log(12)
})
function fn(){
  console.log(6)
}
结果是1、4、6、3、12、8、2、11、10、9、7

一、浏览器环境下的事件循环机制:

二、nodejs环境下的事件循环机制:

14. 函数柯里化, 函数反柯里化

定义:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术
作用:既能减少代码冗余,也能增加可读性
实现方式:需要依赖参数以及递归,通过拆分参数的方式,来调用一个多参数的函数方法

实现一个函数功能:sum(1,2,3,4…n)转化为 sum(1)(2)(3)(4)…(n)
 function curry(fn){
          let arr = [];
          return function(){
              if(arguments.length > 0){
                  [].push.apply(arr,arguments);
                  return arguments.callee;
              }
              else return fn(...arr);
          }
        }

//反柯里化 此处为笔记,不是答案
Function.prototype.unCurry=function(){
            var self=this;
            return function(thisArg,...arg){
                return self.apply(thisArg,arg);
            }
        }

15.js中对函数设置默认参数值的3种方法(三目、II、$.extend())

  1. 第一种 三目运算
    	let a = arguments[0] ? arguments[0] : 1
    
  2. 第二种 ||
    	let name=name||'貂蝉'
    
  3. 第三种 适合用于参数较多的情况,使用了Jquery的扩展$.extend()
    	function example(setting){ 
    	  var defaultSetting={ 
    	    name:'小红', 
    	    age:'30', 
    	    sex:'女', 
    	    phone:'100866', 
    	    QQ:'100866', 
    	    birthday:'1949.10.01'
    	  }; 
    	  $.extend(defaultSetting,settings); 
    	  var message='姓名:'+defaultSetting.name 
    	  +',性别:'+defaultSetting.sex 
    	  +',年龄:'+defaultSetting.age 
    	  +',电话:'+defaultSetting.phone 
    	  +',QQ:'+defaultSetting.QQ 
    	  +',生日:'+defaultSetting.birthday 
    	  +'。'; 
    	  alert(message); 
    	} 
    

16.异步加载js文件

同步加载 :js的加载默认是同步的,因为js是单线程执行,只能完成一件再执行下一件.
我们平时使用的最多的一种方式。

<script src="http://yourdomain.com/script.js"></script>
<script src="http://yourdomain.com/script.js"></script>

同步模式,又称阻塞模式,会阻止浏览器的后续处理,停止后续的解析,只有当当前加载完成,才能进行下一步操作。所以默认同步执行才是安全的。但这样如果js中有输出document内容、修改dom、重定向等行为,就会造成页面堵塞。所以一般建议把script标签放在body标签结尾处,这样尽可能减少页面阻塞。
异步加载

  • 一些外部引入的js文件可以因为文件太大,在加载资源的过程中会影响dom元素的加载,影响了用户体验,因此会使用异步加载技术加载文件.

  • 一般情况下给所有的script标签添加一个async异步属性,在加载script标签的同时加载dom元素.但会出现另外一个问题.加载的js资源,如jQuery,不能使用,因为在执行jQuery程序的时候,jQuery.js还没有加载完成.这时可以用到回调函数

1. 使用回调函数在加载完成资源后调用该资源的方法

<script async src="js/jquery-1.12.4.min.js" id="jq"></script>
<script async >
document.querySelector("#jq").onload = function () {
console.log($);
}
</script>

2.require.js模块化工具

  • 通过该模块化工具异步加载js文件后在执行该js文件的方法
// 
// 
<script src="./require.js"></script>
<script>
require(['./jquery','./templatet'],function(template){
console.log($);
console.log(template);
})
</script>

  • require()

参数1 : 是一个数组,里面的值是需要引用的js文件

参数2 : 回调函数,在异步加载完成js文件后执行的程序,如果引入的js文件的返回值是对象需要传参数,如果返回的是对象直接使用

3.由于JavaScript的动态性,还有很多异步加载方法

  • XHR Injection(XHR 注入):通过XMLHttpRequest来获取JavaScript,然后创建一个script元素插入到DOM结构中。ajax请求成功后设置script.text为请求成功后返回的responseText。
  • Script In Irame:在父窗口插入一个iframe元素,然后再iframe中执行加载JS的操作
  • HTML5新属性–defer属性:IE4.0就出现。defer属声明脚本中将不会有document.write和dom修改。浏览器会并行下载其他有defer属性的script。而不会阻塞页面后续处理。注:所有的defer脚本必须保证按顺序执行的。
<script type="text/javascript" defer>
	alert(document.getElementById("p1").firstChild.nodeValue)
</script>
  • HTML5新属性–async属性:Html5新属性。脚本将在下载后尽快执行,作用同defer,但是不能保证脚本按顺序执行。他们将在onload事件之前完成。
 <script type="text/javascript" src="demo_async.js" async="async"></script>

17.事件冒泡和事件捕获

注意: addEventListener中有三个属性,第三个属性是布尔值:默认属性是 false为事件冒泡,true 为事件捕获
注意:addEventListener()必须用removeEventListener()解除

<div class="classv">
        我是祖宗
        <div class="actva">我是老爸
            <div class="foo">我是孩子</div>
        </div>
    </div>
    <script type="text/javascript">
        var a = document.querySelector('.classv').addEventListener('click', function() {
            console.log('我是祖宗')
        }, false)
        var b = document.querySelector('.actva').addEventListener('click', function() {
            console.log('我是老爸')
        }, false)
        var c = document.querySelector('.foo').addEventListener('click', function() {
            console.log('我是孩子')
        }, false)
    </script>

1.事件冒泡(第三个属性为false时 – 如上代码):点击我是孩子,孩子后面会出现老爸和祖宗。点击老爸,后面会出现我是祖宗。
2.事件捕获(第三个属性为true时 — 如下):点击我是孩子,首先出现祖宗、老爸最后出现目标元素。点击我是老爸首先出现祖宗。

document.querySelector('').addEventListener('click', function() {}, true)

ES6

1.let 和 const 关键字的区别

在 ES6 中,提供了 let 关键字和 const 关键字。

使用 const 声明的是常量,其值一旦被设定便不可被更改。
let 声明的变量只在其声明的块或子块中可用,这一点,与 var 相似。二者之间最主要的区别在于 var 声明的变量的作用域是整个封闭函数。

let和const不存在变量提升

let 和 var 的区别代码实例:

function varTest() {
  var x = 1;
  if (true) {
    var x = 2;  // 同样的变量!
    console.log(x);  // 2
  }
  console.log(x);  // 2
}

function letTest() {
  let x = 1;
  if (true) {
    let x = 2;  // 不同的变量
    console.log(x);  // 2
  }
  console.log(x);  // 1
}

2.ES6箭头函数和普通函数的区别

  • 写法不同,箭头函数是 => ,普通函数是function
  • this指向不同,箭头函数的this指向的是父级,普通函数this指向window
  • 箭头函数不能使用new生成构造函数,因为箭头函数没有prototype,而construct在prototype里面,不能变量提升;普通函数有构造器,可以定义构造函数,且函数声明可以变量提升,函数表达式不可以变量提升
  • 普通函数的参数是arguments,而箭头函数是arg

3.ES6 异步编程Promise

支持多层异步嵌套

4. map,set

1. Set 对象类似于数组,且成员的值都是唯一的。

const arr = [1, 2, 3, 4, 5, 5, 4, 3, 2, 1];
const set = new Set();
arr.forEach(item => set.add(item));
console.log(set);  // 1, 2, 3, 4, 5
// 数组快速去重
console.log([...new Set(arr)]);  //[1, 2, 3, 4, 5]
  • array --> set (数组转set)

     let array = [1, 2, 3, 4];
     let set = new Set(array);
    
  • set --> array (set转数组)

     let demo = new Set([1,2,3,4]);
     Array.from(demo)
    

2. Map 对象是键值对集合,和 JSON 对象类似,但是 key 不仅可以是字符串还可以是对象

var map = new Map();
var obj = { name: '小缘', age: 14 };
map.set(obj, '小缘喵');
map.get(obj); // 小缘喵
map.has(obj); // true
map.delete(obj) ;// true
map.has(obj); // false

5. 类以及类继承

类的声明
传统写法,构造函数法

 function Animal1() {
        this.name = 'hyo'; //通过this,表明这是一个构造函数
    }

ES6写法,用class声明

  class Animal2 {
        constructor() {  //可以在构造函数里写属性
            this.name = name;
        }
    }

生成实例 ----- 直接new

var obj=new Animal1();
var obj2=new Animal2();

继承的方式有3种,每种形式的优缺点是?

1.借助构造函数

function Parent1() {
        this.name = 'parent1 的属性';
    }
 function Child1() {
        Parent1.call(this);         //【重要】此处用 call 或 apply 都行:改变 this 的指向,parent的实例 --> 改为指向child的实例
        this.type = 'child1 的属性';
    }
console.log(new Child1);

parent的实例的属性挂在到了child的实例上,这就实现了继承。
优点:改变this指向,实现了继承;
缺点:无法继承父类的原型。

2.通过原型链继承

 function Parent() {
        this.name = 'Parent 的属性';
    }
    function Child() {
        this.type = 'Child 的属性';
    }
  Child.prototype = new Parent(); //【重要】
 console.log(new Child());

优点:可以继承父类的原型;
缺点:如果修改子类一个实例的属性,则子类的另一个实例的相应属性也会跟着改变(原因:共用原型)

3.构造函数+原型链

 function Parent3() {
        this.name = 'Parent 的属性';
        this.arr = [1, 2, 3];
    }
    function Child3() {
        Parent3.call(this); //方法1
        this.type = 'Child 的属性';
    }
    Child3.prototype = new Parent3(); //方法2
    var child = new Child3();

优点:解决前两种方法的问题
缺点:让父类的构造方法执行了2次

6.for, forEach, for in 和 for of

  • 普通for循环在Array和Object中都可以使用
    在对象中不实用,对于属性的命名,key值有各种限制

  • forEach循环在Array、Set、Map中都可以使用(不能使用break、continue和return语句)

  • for of在Array、Object、Set、Map中都可以使用

     也可以使用 Object.entries(obj) 方法遍历属性和值
     for(let [key, value] of Object.entries(obj)) {
        console.log(key, value)
      }
      Object.keys()和Object.values()将对象的键名或属性生成一个数组,然后遍历这个数组,但是支持不太好,建议用Object.entries(obj) 
    
  • for in在Array和Object中都可以使用 – 循环数组索引、对象的属性
    (在对象中包含原型上的属性)
    解决方法:使用hasOwnProperty()
    hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定 属性存在于对象实例中时,才会返回true。

7.(bind,apply,call)改变函数内部 this指针的指向函数

  • call方法传入的参数数量不定,第一个参数为this指定对象,如果为null或者undefined,则指向window,其余参数则传入函数,根据需求调用
  • apply方法传入参数只有两个,第一个与call相同,为this指定对象,第二个为类数组对象,传入函数
  • bind则是改变this指向的同时,传入参数,返回一个新函数,该函数会在调用时才执行

就性能而言,call性能优于apply,传入参数3个以内时两者差距不大,传入参数超过三个,则call方法性能优于apply。

let a=0;
const obj={
	a:1,
	b:function () {
		console.log(this.a);
	},
};
const obj1={
	a:2
}
const fun=obj.b;
fun();
fun.apply(obj);
fun.bind(obj1).call(obj);
const fun1=fun.bind(obj1)
new fun() 
//求打印结果

打印结果为:fun() 打印undefined,fun.apply(obj)打印1,fun.bind(obj1).call(obj) 打印2,new fun() 打印undefined

apply,call取最大最小值
1.取最大值

var arr = [1,3,7,22,677,-1,2,70];
Math.max.apply(Math, arr);//677
Math.max.call(Math, 1,3,7,22,677,-1,2,70);//677

2.取最小值

var arr = [1,3,7,22,677,-1,2,70]
Math.min.apply(Math, arr);//-1
Math.min.call(Math, 1,3,7,22,677,-1,2,70);//-1

HTML/CSS

1. 行内元素与块级元素的区别

  • 行内元素与块级bai函数可以相互转换,通过修改display属性值来切换块级元素和行内元素,行内元素display:inline,块级元素display:block。
  • 行内元素和其他行内元素都会在一条水平线上排列,都是在同一行的;块级元素却总是会在新的一行开始排列,各个块级元素独占一行,垂直向下排列,若想使其水平方向排序,可使用左右浮动(float:left/right)让其水平方向排列。
  • 行内元素不可以设置宽高,宽度高度随文本内容的变化而变化,但是可以设置行高(line-height),同时在设置外边距margin上下无效,左右有效,内填充padding上下无效,左右有效;块级元素可以设置宽高,并且宽度高度以及外边距,内填充都可随意控制。
  • 块级元素可以包含行内元素和块级元素,还可以容纳内联元素和其他元素;行内元素不能包含块级元素,只能容纳文本或者其他行内元素

2.两种盒模型

W3C的标准盒模型:width指content部分的宽度
前端面试题(汇总)_第5张图片

IE盒模型width表示content+padding+border这三个部分的宽度
前端面试题(汇总)_第6张图片

box-sizing的使用
如果想要切换盒模型也很简单,这里需要借助css3的box-sizing属性

box-sizing: content-box 是W3C盒子模型(默认属性)
box-sizing: border-box 是IE盒子模型

3.实现水平垂直居中

方法(1):

html,body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
    }
    .content {
        width: 300px;
        height: 300px;
        background: orange;
        margin: 0 auto; /*水平居中*/
        position: relative;
        top: 50%; /*偏移*/
        margin-top: -150px;  /*上移    或者   transform: translateY(-50%)*/
    }
     *除了可以使用margin-top把div往上偏移之外,CSS3的transform属性也可以:transform: translateY(-50%)      意思是使得div向上平移(translate)自身高度的一半(50%)
     
    或者直接使用 top:calc(50% - 150px);

方法(2):CSS3的弹性布局(flex),设置父元素(body )的display的值为flex

 html,body {
        width: 100%;
        height: 100%;
        margin: 0;
        padding: 0;
    }
    body { /* 要居中的元素的父元素*/
        display: flex;
        align-items: center; /*定义body的元素垂直居中*/
        justify-content: center; /*定义body的里的元素水平居中*/
    }
    .content { /* 要居中的内容*/
        width: 300px;
        height: 300px;
        background: orange;        
    }

4.选择器

(1)元素选择器
(2)类选择器
(3)ID选择器
(4)属性选择器: eg:选择带有alt属性的所有img元素: img[alt] { …}
(5)派生选择器:后代选择器,子元素选择器,相邻兄弟选择器…

5.伪类,伪元素区别 – 通过css实现动态的交互

  • 伪类,更多的定义的是状态。常见的伪类有 :hover,:active,:focus,:visited,:link,:not,:first-child,:last-child等等。
  • 伪元素,不存在于DOM树中的虚拟元素,它们可以像正常的html元素一样定义css,但无法使用JavaScript获取。常见伪元素有 ::before,::after,::first-letter,::first-line等等。

CSS3明确规定了,伪类用一个冒号(:)来表示,而伪元素则用两个冒号(::)来表示。但目前因为兼容性的问题,它们的写法可以是一致的,都用一个冒号(:)就可以了,所以非常容易混淆。

*CSS3中的伪类

:root 选择文档的根元素,等同于 html 元素
:empty 选择没有子元素的元素
:target 选取当前活动的目标元素
:not(selector) 选择除 selector 元素意外的元素
:enabled 选择可用的表单元素
:disabled 选择禁用的表单元素
:checked 选择被选中的表单元素
:nth-child(n) 匹配父元素下指定子元素,在所有子元素中排序第n
:nth-last-child(n) 匹配父元素下指定子元素,在所有子元素中排序第n,从后向前数
:nth-child(odd) 、 :nth-child(even) 、 :nth-child(3n+1)
:first-child 、 :last-child 、 :only-child
:nth-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第n
:nth-last-of-type(n) 匹配父元素下指定子元素,在同类子元素中排序第n,从后向前数
:nth-of-type(odd) 、 :nth-of-type(even) 、 :nth-of-type(3n+1)
:first-of-type 、 :last-of-type 、 :only-of-type

*CSS3中的伪元素

::after 已选中元素的最后一个子元素
::before 已选中元素的第一个子元素
::first-letter 选中某个款级元素的第一行的第一个字母
::first-line 匹配某个块级元素的第一行
::selection 匹配用户划词时的高亮部分

6.attribute和property的区别?

1.含义区别:Attribute和Property分别为特性和属性
Attribute就是DOM节点自带属性,例如我们在HTML中常用的id,class,src,title,alt等
Property则是这个DOM元素作为对象,其附加的属性或者内容,例如childNodes,firstChild等
*有些常用的Attribute 例如id,class,title已经被作为Property附加在DOM对象上,也可以取值和赋值。但是自定义的Attribute就不能了

2.取值和赋值区别
Attribute取值和赋值

//attribute取值
  getAttribute()
  eg:var id = div1.getAttribute("id")
     var id = div1.getAttribute("title1")
//attribute赋值
  getAttribute(attribute,value)  //value只能是字符串形式
  eg:div1.setAttribute('class', 'a');
     div1.setAttribute('title1', 'asd');  //自定义属性也可

Property取值和赋值

//通过'.'号获取property
  var id = div1.id;
  var className = div1.className; //相当于div1.getAttribute('class')
//通过'='赋予property
  div1.className = 'a';
  div1.align = 'center';

拓展:attr( )和prop( )区别
1.针对属性对象不同:
prop( )是针对Dom元素属性,attr( )针对HTML元素属性,和attribute与property区别一样。

2.用于设置的属性值类型不同:
attr()函数操作的是文档节点的属性,因此设置的属性值只能是字符串类型,如果不是字符串类型,也会调用其toString()方法,将其转为字符串类型。
prop()函数操作的是JS对象的属性,因此设置的属性值可以为包括数组和对象在内的任意类型。

7.cookie的属性

一个Cookie包含以下信息:
1)name,Cookie名称必须使用只能用在URL中的字符,一般用字母及数字,不能包含特殊字符,如有特殊字符想要转码。如js操作cookie的时候可以使用escape()对名称转码。
2)value,Cookie值同理Cookie的名称,可以进行转码和加密。
3)Expires,过期日期,一个GMT格式的时间,当过了这个日期之后,浏览器就会将这个Cookie删除掉,当不设置这个的时候,Cookie在浏览器关闭后消失。
4)Path,一个路径,在这个路径下面的页面才可以访问该Cookie,一般设为“/”,以表示同一个站点的所有页面都可以访问这个Cookie。
5)Domain,子域,指定在该子域下才可以访问Cookie,例如要让Cookie在a.test.com下可以访问,但在b.test.com下不能访问,则可将domain设置成a.test.com。
6)Secure,安全性,指定Cookie是否只能通过https协议访问,一般的Cookie使用HTTP协议既可访问,如果设置了Secure(没有值),则只有当使用https协议连接时cookie才可以被页面访问。
7)HttpOnly,如果在Cookie中设置了"HttpOnly"属性,那么通过程序(JS脚本、Applet等)将无法读取到Cookie信息。


8.document.getElementById方法效率问题

1、$("#id")获取的对象其实是一个数组对象,jQuery内部封装的。
2、document.getElementById("id")获得的直接是DOM的操作对象

*他们二者之间还是原生的更加高效一点, jquery是二次封装,$(“#id”)[0] == document.getElementById(‘id’)

3. 原生Dom方法 querySelectorAll()   速度更快: document.querySelectorAll('#menu a')

9.清除浮动

原因:很多情况下,如果我们使用了float特效,出现margin,padding设置不能正确显示,浮动会导致父级子级之间设置了padding,导致了属性不能正常传达,导致margin不能正常显示;父元素的高度和宽度没有进行设置,而是由子元素支撑起来,则会导致父元素的高度塌陷(原本的height后来被置为0)

清除浮动的方式

  • 在父元素内添加冗余元素clear:both; 具体方法是,在父元素内添加一个div,然后对这个盒子添加样式
<div>
	<div>盒子1</div>
	<div>盒子2</div>
	<div>盒子3</div>
	<div style="clear:both"></div>
</div>
  • 在父元素中设置属性overflow:hidden||auto
  • 通过给父级元素添加伪类after
.cc:after {
        content: '';
        height: 0;
        line-height: 0;
        display: block;
        visibility: hidden;
        clear: both;
    }
  • 双伪类
.cc:after,
    .cc:before {
        content: "";
        display: block;
        clear: both;
    }

你可能感兴趣的:(javascript,VUE,vue.js,javascript,jquery,typescript)