相信大家也和我一样,2020年的春节过得非常特别。新型冠状病毒不仅对国家还是对社会以及对我们的个人都有很大影响!
很多小伙伴心里肯定想着由于种种原因,心里开始蠢蠢欲动了...😀
笔者通过平时面试总结以及面试别人常提的问题,结合自己认为非常重要的前端各技术栈的知识点,总结了这篇中高级前端面试。让需要的小伙伴所阅读,让不在大厂的小伙伴提前了解大厂前端面试官常问的各种常见前端问题。
文章有点长,请各位小伙伴耐心阅读完。相信认真阅读者必定或多或少有点感悟!
注释
本文来自于:
作者:tmx
https://juejin.im/post/5e4c0b856fb9a07ccb7e8eca
Html5
对WEB标准和W3C的理解认识
个人理解:
- html - 表示人的光身体 ---结构
- css - 表示给人穿的衣服 ---表现
- js - 表示人的行为,走路等 ---行为
什么是DOCTYPE及作用
DTD(document type definition,文档类型定义)是一系列的语法规则,用来定义XML或(X)HTML的文件类型。浏览器会使用它来判断文档类型,决定使用何种协议来解析以及切换浏览器模式。(DTD告诉浏览器我是什么文档类型,浏览器会根据这个来判断用什么引擎来解析和渲染他们)
DOCTYPE是用来声明文档类型和DTD规范的,一个主要的用途便是文件的合法性验证。如果文件代码不合法,那么浏览器解析时会出一些错误。(DOCTYPE告诉浏览器当前是哪个文档类型)
Html5语义化与新特性
1、什么是HTML语义化?
- 表示选择合适的标签(语义化标签)便于开发者阅读和写出更优雅的代码
2、为什么要使用语义化标签?
-
在没有CSS样式的情况下,页面整体也会呈现很好的结构效果
- 更有利于用户体验
- 更有利于搜索引擎优化
- 代码结构清晰,方便团队开发与维护
3、 HTML5新特性有哪些?
- 语义化标签
- 音视频处理
- canvas / webGL
- history API
- requestAnimationFrame
- 地理位置
- webSocket
行内元素与块级元素
1、行内元素的特点?
- 元素排在一行
- 只能包含文本或者其他内联元素
- 宽高就是内容宽高、设置宽高无效
2、块级元素的特点?
- 元素单独占一行
- 元素的宽高都可以设置
- 可以包含内联元素和其他块元素
- 为设置宽度时,默认宽度是它容器的100%
常见行内元素标签:
a、br、code、em、img、input...
常见块级元素标签:
div、p、dl、dt、form、h1~h6...
渐进增强与优雅降级的理解及区别
渐进增强(Progressive Enhancement):
一开始就针对低版本浏览器进行构建页面,完成基本的功能,然后再针对高级浏览器进行效果、交互、追加功能达到更好的体验。
优雅降级(Graceful Degradation):
一开始就构建站点的完整功能,然后针对浏览器测试和修复。比如一开始使用 CSS3 的特性构建了一个应用,然后逐步针对各大浏览器进行 hack 使其可以在低版本浏览器上正常浏览。
两者区别?
1、广义:
其实要定义一个基准线,在此之上的增强叫做渐进增强,在此之下的兼容叫优雅降级
2、狭义:
渐进增强一般说的是使用CSS3技术,在不影响老浏览器的正常显示与使用情形下来增强体验,而优雅降级则是体现html标签的语义,以便在js/css的加载失败/被禁用时,也不影响用户的相应功能。
例子:
.transition { /*渐进增强写法*/
-webkit-transition: all .5s;
-moz-transition: all .5s;
-o-transition: all .5s;
transition: all .5s;
}
.transition { /*优雅降级写法*/
transition: all .5s;
-o-transition: all .5s;
-moz-transition: all .5s;
-webkit-transition: all .5s;
}
cookie、sessionStorage、localStorage区别
相同点:
- 存储在客户端
不同点:
- cookie数据大小不能超过4k;sessionStorage和localStorage的存储比cookie大得多,可以达到5M+
- cookie设置的过期时间之前一直有效;localStorage永久存储,浏览器关闭后数据不丢失除非主动删除数据;sessionStorage数据在当前浏览器窗口关闭后自动删除
- cookie的数据会自动的传递到服务器;sessionStorage和localStorage数据保存在本地
CSS3
CSS盒模型
- 盒子由margin(外边距)、border(边框)、padding(内边距)、content(内容)组成
-
盒子的宽度 = 内容宽度 + 左填充 + 右填充 + 左边框 + 右边框 + 左边距 + 右边距
- 盒子的高度 = 内容高度 + 上填充 + 下填充 + 上边框 + 下边框 + 上边距 + 下边距
-
box-sizing: content-box(默认)----指的是标准模型(本身内容宽高度+边框和内边距)
- box-sizing: border-sizing-----指的是IE模型(本身内容的宽高度)
获取盒子宽高的几种方式及区别
dom.style.width/height
这种方式只能取到dom元素内联样式所设置的宽高,也就是说如果该节点的样式是在style标签中或外联的CSS文件中设置的话,通过这种方法是获取不到dom的宽高的
dom.currentStyle.width/height
获取渲染后的宽高。但是仅IE支持
window.getComputedStyle(dom).width/height
与2原理相似,但是兼容性,通用性更好一些
dom.getBoundingClientRect().width/height
计算元素绝对位置,获取到四个元素left,top,width,height
扩展:
获取浏览器高度和宽度的兼容性写法:
var w = window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth
var h = window.innerHeight || document.documentElement.clientHeight || document.body.clientHeight
Flex布局
想详细了解Flex布局知识点的小伙伴,请查看 Flex布局详解
让页面里的字体变清晰,变细用CSS怎么做
-webkit-font-smoothing在window系统下没有起作用,
但是在IOS设备上起作用-webkit-font-smoothing:antialiased是最佳的,灰度平滑
CSS常见选择器
1、CSS选择器:
- id选择器(#myid)
- 类选择器(.myclass)
- 标签选择器(div, h1,p)
- 相邻选择器(h1 + p)
- 子选择器(ul > li)
- 后代选择器(li a)
- 通配符选择器(*)
- 属性选择器(a[rel="external"])
- 伪类选择器(a:hover, li:nth-child)
2、CSS3属性选择器:
- a[href$='.pdf']:选择href属性中以.pdf结尾的元素
- a[href^='www']:选择href属性中以www开头的元素
- a[href*='tmc']:选择href属性中包含tmc的元素
3、CSS3常见伪类选择器:
:nth-of-type():
- 可以通过参数来选择表格中的奇数行和偶数行,odd表示奇数行,even表示偶数行
:nth-child():
- 参数n时选中所有行
- 参数为n+i时表示从第i行开始下面的都被选中,如n+3,从第3行开始下面全部选中
- 2n表示2的倍数行被选中,选中偶数行
- 2n+1表示选中奇数行
- 3n表示每个3行选中一次
可继承的属性:font-size, font-family, color
不可继承的样式:border, padding, margin, width, height
优先级(就近原则):!important > [id > class > tag]
!important比内联优先级高
display有哪些值?说明他们的作用?
- inline(默认)–内联
-
none–隐藏
- block–块显示
- table–表格显示
- list-item–项目列表
- inline-block-内联块
BFC
一、BFC的概念?
BFC(块级格式上下文):它是页面中的一块渲染区域,有自己的渲染规则,决定了其子元素如何布局,以及和其他元素之间的关系和作用
二、BFC的原理?
- 内部的Box会在垂直方向,一个接一个地放置
- Box垂直方向的距离由margin决定。属于同一个BFC的两个相邻Box的margin会发生重叠
- 每个元素的margin box的左边, 与包含块border box的左边相接触(对于从左往右的格式化,否则相反
- BFC的区域不会与float box重叠
- BFC就是页面上的一个隔离的独立容器,容器里面的子元素不会影响到外面的元素
- 计算BFC的高度时,浮动元素也参与计算
三、如何创建BFC?
- 根元素,即HTML元素
- float的值不为none
- overflow的值不为visible
- display的值为inline-block、table-cell、table-caption
- position的值为absolute或fixed
四、BFC的使用场景?
- 去除边距重叠现象
- 清除浮动(让父元素的高度包含子浮动元素)
- 避免某元素被浮动元素覆盖
- 避免多列布局由于宽度计算四舍五入而自动换行
浮动与定位
一、清除浮动的方法
方式一、使用overflow 属性来清除浮动
.parent {
overflow: hidden;
}
缺点:离开这个元素所在区域的会被隐藏(overflow: hidden将超出的分布隐藏起来)
方式二、使用额外标签法
.clear {
clear: both;
}
缺点:会增加页面的标签,造成结构的紊乱
方式三、使用伪元素来清除浮动【推荐使用】
.clearfix:after {
content: ""; // 设置内容为空
height: 0; // 高度为0
line-height: 0; // 行高为0
display: block; // 将文本转为块级元素
visibility: hidden; // 将元素隐藏
clear: both; //清除浮动
}
.clearfix {
zoom: 1; // 为了兼容IE
}
二、定位
posiiton的值:
- static(默认):按照正常文档流进行排列;
- relative(相对定位):不脱离文档流,参考自身静态位置通过 top, bottom, left, right 定位;
- absolute(绝对定位):参考距其最近一个不为static的父级元素通过top, bottom, left, right 定位;
- fixed(固定定位):所固定的参照对像是可视窗口。
常见的页面布局
- 水平居中布局
- 左侧固定 右侧自适应
- 流式布局
- 弹性布局
- 圣杯布局
- 双飞翼布局
CSS3新特性
- 过渡
- 动画
-
形状转换
- 选择器
- 阴影
文字阴影: text-shadow: 2px 2px 2px #000;(水平阴影,垂直阴影,模糊距离,阴影颜色)
盒子阴影: box-shadow: 10px 10px 5px #999 - 边框
- 背景
- 文字
- 渐变
- 弹性布局、栅格布局、多列布局
- 媒体查询
常见单位
1. px:绝对单位,页面按精确像素展示
2. em:相对单位,基准点为父节点字体的大小,如果自身定义了font-size按自身来计算(浏览器默认字体是16px),整个页面内1em不是一个固定的值
3. rem:相对单位,可理解为”root em”, 相对根节点html的字体大小来计算,CSS3新加属性,chrome/firefox/IE9+支持
4. vw:viewpoint width,视窗宽度,1vw等于视窗宽度的1%
5. vh:viewpoint height,视窗高度,1vh等于视窗高度的1%
6. vmin:vw和vh中较小的那个
7. vmax:vw和vh中较大的那个
8. %:百分比
移动端视口配置
<meta name="viewport" content="width=device-width, initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no">
initial-scale:初始的缩放比例
minimum-scale:允许用户缩放到的最小比例
maximum-scale:允许用户缩放到的最大比例
user-scalable:用户是否可以手动缩放
文字、盒子水平垂直居中
一、行内元素水平垂直居中方法:
方式1:
text-align:center /*水平居中*/
height = 100px; /*垂直居中 */
line-height = 100px;
方式2:
text-align:center 水平居中
display:table-cell; 垂直居中
vertical-align:middle;
二、块级元素水平居中方法:
margin:0 auto;只能设置水平居中,
而margin:auto 0 不能设置垂直居中
,因为margin垂直塌陷问题
方法1:定位+margin
父级元素设置position:relative;
儿子元素设置
width: 100px;
height: 100px;
position:absolute;
top:50%;
left:50%;
margin-top:-50px;
margin-right:-50px;
方式2:定位方法
父级元素设置position:relative;
儿子元素设置
position:absolute;
top:0;
bottom:0;
left:0;
right:0;
margin:auto;
方式3:单元格方法
父级元素
display:table-cell;
text-align:center;
vertical-align:middle;
子元素
display:inline-table
Sass、Less、Stylus区别
- 什么事CSS预处理器?
CSS预处理器是一种语言用来为CSS增加一些变成的特性,无需考虑浏览器兼容问题,例如你可以在CSS中使用变量,简单的程序逻辑、函数等在编程语言中的一些基本技巧,可以让CSS更加简洁,适应性更强,代码更直观等诸多好处
- 基本语法区别
Sass是以.sass为扩展名,Less是以.less为扩展名,Stylus是以.styl为扩展名
- 变量的区别
Sass 变量必须是以$开头的,然后变量和值之间使用冒号(:)隔开,和css属性是一样的。
Less 变量是以@开头的,其余sass都是一样的。
Stylus 对变量是没有任何设定的,可以是以$开头或者任意字符,而且变量之间可以冒号,空格隔开,但是在stylus中不能用@开头
- 三种预处理器都有:嵌套、运算符、颜色函数、导入、继承、混入。Stylus还有一些高级特性。例如循环、判断等
浅谈CSS响应式布局
- 使用@media查询可以针对不同的媒体类型定义不同的样式
- @media 可以针对不同的屏幕尺寸设置不同的样式,特别是如果需要设置设计响应式的页面。
-
重置浏览器大小的过程中,页面也会根据浏览器的宽度和高度重新渲染页面。
语法:@media 媒介类型 and | not | only (媒介特性) { css 代码 } 媒介类型: print: 用于打印机和打印预览 screen: 用于电脑屏幕、平板电脑、只能手机等 all: 用于所有媒体设备类型 媒介特性: device-height: 定义输出设备的屏幕可见高度 device-width: 定义输出设备的屏幕可见宽度 height:定义输出设备中的页面可见区域高度。 width:定义输出设备中的页面可见区域宽度。 max-device-height:定义输出设备的屏幕可见的最大高度。 max-device-width:定义输出设备的屏幕可见的最大宽度。 max-height:定义输出设备中的页面可见的最大高度。 max-width:定义输出设备中的页面可见的最大宽度。 min-device-height:定义输出设备的屏幕可见的最小高度。 min-device-width:定义输出设备的屏幕可见的最小宽度。 min-height:定义输出设备中的页面可见的最小高度。 min-width:定义输出设备中的页面可见的最小宽度。
link和@import有什么区别
1. link属于html标签,除了引入css样式以外还可以定义RSS等其他事物,@import是css提供的,只能引入css
2. lilnk在页面加载的时候会同时加载,@import引用的css要等页面加载结束后再加载
3. link是html标签,没有兼容性,@import只有ie5以上才能识别
display: none与visibility: hidden的区别
display:none 不显示对应的元素,在文档布局中不再分配空间(回流+重绘)
visibility:hidden 隐藏对应元素,在文档布局中仍保留原来的空间(重绘)
jQuery
jQuery选择器与css选择器的区别
- 两者的作用不同,CSS选择器找到元素后为设置该元素的样式,jQuery选择器找到元素后添加行为
- jQuery选择器拥有更好的跨浏览器的兼容性
- 两者选择器的效率不同
CSS选择器的效率:id选择器(#myid)、类选择器(.myclassname)、标签选择器(div,h1,p)、相邻选择器(h1+p)、子选择器(ul > li)、后代选择器(li a)、通配符选择器(*)、属性选择器(a[rel="external"])、伪类选择器(a:hover,li:nth-child)从高到低依次排列jQuery选择器的效率:id选择器$('#id')和元素标签选择器$('form')、类选择器$('.className')、属性选择器$('[attribute=value]')和伪类选择器$(':hidden')
jQuery对象与Dom对象
$("#aijquery"): //这种方式获取得到的就是jquery对象
document.getElementById("aijquery")://这种方法获取到的就是dom对象
dom对象转换为jquery对象:一般情况下,dom对象直接用$()就可以转换成jquery对象,如:
$(document.getElementById("aijquery"))
jquery对象转换成dom对象,有两种方法,一种是用jquery的内置函数get,来获取dom对象,如:
$("#aijquery").get(0)
还有一种方法更简单,因为jquery对象的属性是一个集合,所以我们可以像数组那样,取出其中一项就行:
$("#aijquery")[0];
$("div")[5];
//上面这两种返回的都是dom对象,可以直接使用js里的方法
window.onload 事件和 jQuery ready 函数有何不同
- 可以在页面中使用多个document.ready()函数,但只能用一次window.onload
- document.ready()函数在页面DOM元素加载完就会执行,而window.onload()函数则是要所有都加载
JS
js基本数据类型
5个简单数据类型(基本数据类型)+ 1个复杂数据类型
undefined、number、string、null、boolean+object
ES6新增Symbol
以下代码的执行结果
async function async1() {
console.log('async1 start');
await async2();
console.log('async1 end');
}
async function async2() {
console.log('async2');
}
console.log('script start');
setTimeout(function() {
console.log('setTimeout');
}, 0)
async1();
new Promise(function(resolve) {
console.log('promise1');
resolve();
}).then(function() {
console.log('promise2');
});
console.log('script end');
1. 第一步,输出script start;
虽然有两个函数声明,有async关键字,但是没有调用,就不看,直接打印同步代码console.log(‘script start’)
2. 第二步,输出async1 start; 因为执行async1这个函数的时候,async表达式定义的函数也是立即执行
3. 第三步,输出async2;
因为async2是async定义的函数,输出async2并返回promise对象
4. 第四步,输出promise1;
因为执行到new promise,promise是立即执行,就先打印promise1
5. 第五步,输出script end;
因为上一步先打印了promise1,然后执行到resolve的时候,然后跳出promise继续向下执行,输出script end
6. 第六步,输出promise2;
因为前面的nue promise放进resolve回调,所以resolve被放到调用栈执行
7. 第七步,输出async1 end;
因为await定义的这个promise已经执行完,并且返回结果,所以继续执行async1函数后的任务,就是console.log(‘async1 end’)
8. 第八步,输出setTimerout; setTimeout被放在最后被调用
如何统计网⻚上出现了多少种标签
- 获取所有的DOM节点
document.querySelectorAll('*')
- NodeList集合转化为数组
[...document.querySelectorAll('*')]
-
获取数组每个元素的标签名
[...document.querySelectorAll('*')}.map(ele => ele.tagName)
-
去重
new Set([...document.querySelectorAll('*').map(ele=>ele.tagName)).size
几种判断数据类型的优缺点
一、typeof
console.log(typeof 1); // number
console.log(typeof true); // boolean
console.log(typeof 'mc'); // string
console.log(typeof function(){}); // function
console.log(typeof []); // object
console.log(typeof {}); // object
console.log(typeof null); // object
console.log(typeof undefined); // undefined
优点:能够快速区分基本数据类型 缺点:不能将Object、Array和Null区分,都返回object
二、instanceof
console.log(1 instanceof Number); // false
console.log(true instanceof Boolean); // false
console.log('str' instanceof String); // false
console.log([] instanceof Array); // true
console.log(function(){} instanceof Function); // true
console.log({} instanceof Object); // true
优点:能够区分Array、Object和Function,适合用于判断自定义的类实例对象 缺点:Number,Boolean,String基本数据类型不能判断
三、Object.prototype.toString.call()
var toString = Object.prototype.toString;
console.log(toString.call(1)); //[object Number]
console.log(toString.call(true)); //[object Boolean]
console.log(toString.call('mc')); //[object String]
console.log(toString.call([])); //[object Array]
console.log(toString.call({})); //[object Object]
console.log(toString.call(function(){})); //[object Function]
console.log(toString.call(undefined)); //[object Undefined]
console.log(toString.call(null)); //[object Null]
优点:精准判断数据类型 缺点:写法繁琐不容易记,推荐进行封装后使用
原生ajax
如何创建Ajax
XMLHttpRequest对象的工作流程
==========兼容性写法===========
var xmlHttp = null;
if(window.XMLHttpRequset) {
// IE7+,Firefox,Chrome,Safari,Opera
xmlHttp = new XMLHttpRequset();
}
else {
// IE5,IE6
xmlHttp = new ActiveXObject("Microsoft.XMLHTTP")
}
兼容性处理
事件的触发条件
xmlHttp.onreadystatechange = function() {
if(xmlHttp.readyState == 4 && xmlHttp.status == 200) {
responseText、responseXML
}
}
事件的触发顺序
======================注意=================
如果是POST请求则需要添加头
xmlHttp.setRequestHeader("Content-type": "application/x-www-form-urlencoded")
null和undefined的区别
undefined是访问一个未初始化的变量时返回的值,而null是访问一个尚未存在的对象时所返回的值。
undefined看作是空的变量,而null看作是空的对象
-
实现 (5).add(3).minus(2),使其输出结果为6
有题目分析可得,该表达式属于链式调用方法。以数组为例:
arr.push() 为什么arr有push方法?
因为arr是Array的实例,push是Array原型上的方法,所有实例arr可以调用原型上的方法;故,我们只需要在数字Number的原型上添加add和minus方法即可。
~ function () { function add(number) { if (typeof number !== 'number' || Number.isNaN(number)) { throw new Error('请输入数字~'); } return this + n } function minus(number) { if (typeof number !== 'number' || Number.isNaN(number)) { throw new Error('请输入数字~'); } return this - n } Number.prototype.add = add Number.prototype.minus = minus }() console.log((5).add(3).minus(2)) // 6
注意:this的指向始终指向最后谁调用了this所在的函数,this就指向谁!
DOM事件类
基本概念:DOM事件的级别(事件的本质在于信息的传递)
DOM0:element.onlick = function(){}
一是在标签内写onclick事件
二是在JS写onclick=function(){}函数
DOM2:element.addEventListener('click', function(){},false)
三个参数分别表示为:事件名、事件处理的函数、为true则表示在捕获阶段调用,为false则表示在冒泡阶段调用
DOM3:element.addEventListener('keyup',function(){},false)
DOM事件模型:(冒泡、捕获...)
冒泡事件是从内向外;捕获是从外向内======document->html->body->div
DOM事件流
一个完整的事件流分为三个阶段:捕获-》目标阶段-》冒泡=========事件通过捕获到达目标元素再从目标元素通过冒泡上传到元素
描述DOM事件捕获的具体流程
捕获:window->document->html->body->......->目标元素
冒泡:相反
Event事件的用
event.preventDefault() // 阻止默认行为
event.stopPropagation() // 阻止冒泡行为
event.stopImmediatePropagation() // 在调用完stopPropagation函数之后,就会立即停止对后续节点的访问,但是会执行完绑定到当前节点上的所有事件处理程序;而调用stopImmediatePropagation函数之后,除了所有后续节点,绑定到当前元素上的、当前事件处理程序之后的事件处理程序就不会再执行了
event.currentTarget // 指的是绑定了事件监听的元素(可以理解为触发事件元素的父级元素)
event.target // 表示当前被点击的元素
自定义事件(CustomEvent)
var eve = new Event('custom');
eve.addEventListener('custom',function(){
console.log('custom')
})
eve.dispatchEvent(eve)
对象深浅拷贝
一、深拷贝
1.1 最简单的方法就是JSON.parse(JSON.stringify())
但是这种拷贝方法不可以拷贝一些特殊的属性(例如正则表达式,undefine,function)
1.2 用递归去复制所有层级属性
function deepCopyTwo(obj) {
let objClone = Array.isArray(obj) ? [] : {};
if (obj && typeof obj == 'object') {
for (const key in obj) {
//判断obj子元素是否为对象,如果是,递归复制
if (obj[key] && typeof obj[key] === "object") {
objClone[key] = deepCopyTwo(obj[key]);
} else {
//如果不是,简单复制
objClone[key] = obj[key];
}
}
}
return objClone;
}
二、浅拷贝
Object.assign(target, ...sources)
function simpleClone(obj) {
var result = {};
for (var i in obj) {
result[i] = obj[i];
}
return result;
}
想详细了解,请点击查看 对象深浅拷贝
谈谈你对Promise的理解
想详细了解,请点击查看 一步步教你实现Promise/A+ 规范 完整版
this的理解
this表示当前对象,this的指向是根据调用的上下文来决定的,默认指向window对象,指向window对象时可以省略不写
全局环境: this始终指向的是window对象
局部环境: 在全局作用域下直接调用函数,this指向window 对象函数调用,哪个对象调用就指向哪个对象 使用new实例化对象,在构造函数中的this指向实例化对象 使用call或apply改变this的指向
总结:this始终指向最后一个调用它的函数的对象
Class与JS构造函数的区别
1. 箭头函数不能被直接命名,不过允许它们赋值给一个变量
2. 箭头函数不能用做构造函数,你不能对箭头函数使用new关键字
3. 箭头函数也没有prototype属性箭头函数绑定了词法作用域,不会修改this的指向(**最大特点**)
4. 箭头函数的作用域不能通过.call、.apply、.bind等语法来改变,这使得箭头函数的上下文将永久不变
ES6其他常用功能
1. let/const
2. 多行字符串/模板变量
3. 解构赋值
4. 块级作用域
5. 函数默认参数
6. 箭头函数
箭头函数带来的一些问题
当你的简写箭头函数返回值为一个对象时,你需要用小括号括起你想返回的对象。否则,浏览器会把对象的{}解析为箭头函数函数体的开始和结束标记。
// 正确的使用形式 **var** objectFactory = () => ({ modular: 'es6' })
函数柯里化
/**
* 函数柯里化
* 柯里化(Currying)是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术。
*/
// 通用版
function curry(fn, args) {
var length = fn.length;
var args = args || [];
return function() {
newArgs = args.concat(Array.prototype.slice.call(arguments))
console.log(newArgs)
if(newArgs.length < length) {
return curry.call(this, fn, newArgs);
} else {
return fn.apply(this, newArgs);
}
}
}
function multiFn(a, b, c) {
console.log(a * b * c)
return a * b * c;
}
var multi = curry(multiFn);
multi(2)(3)(4)
// multi(2, 3, 4)
// multi(2)(3, 4)
// multi(2, 3)(4)
你了解js的执行机制吗
什么是单线程,和异步有何不同
单线程,只有一个线程,只能做一件事
原因:避免DOM渲染的冲突
解决方案:异步
实现方式:event-loop
什么是event-loop
事件轮询-----JS异步的解决方案
JS实现异步的具体解决方案
1、同步代码,直接执行
2、异步代码先放在 异步队列 中
3、待同步函数执行完毕,轮询执行异步队列 中的函数
自己可以实现一个new吗
一、具体实现步骤:
1. 首先函数接受不定量的参数,第一个参数为构造函数,接下来的参数被构造函数使用
2. 然后内部创建一个空对象 obj
3. 因为 obj 对象需要访问到构造函数原型链上的属性,所以我们通过 setPrototypeOf 将两者联系起来。这段代码等同于 obj.__proto__ = Con.prototype
4. 将 obj 绑定到构造函数上,并且传入剩余的参数
5. 判断构造函数返回值是否为对象,如果为对象就使用构造函数返回的值,否则使用 obj,这样就实现了忽略构造函数返回的原始值
二、 new操作符的特点
1. new通过构造函数Test创建处理的实例可以访问构造函数中的属性也可以访问构造函数原型链上的属性,所以:通过new操作符,实例与构造函数通过原型链连接了起来
2. 构造函数如果返回原始值,那么这个返回值毫无意义
2. 构造函数如果返回对象,那么这个返回值会被正常的使用,导致new操作符没有作用
三、new操作符的几个作用:
1. new操作符返回一个对象,所以我们需要在内部创建一个对象
2. 这个对象,也就是构造函数中的this,可以访问到挂载在this上的任意属性
3. 这个对象可以访问到构造函数原型链上的属性,所以需要将对象与构造函数链接起来
4. 返回原始值需要忽略,返回对象需要正常处理
/**
* 创建一个new操作符
* @param {*} Con 构造函数
* @param {...any} args 忘构造函数中传的参数
*/
function createNew(Con, ...args) {
let obj = {} // 创建一个对象,因为new操作符会返回一个对象
Object.setPrototypeOf(obj, Con.prototype) // 将对象与构造函数原型链接起来
// obj.__proto__ = Con.prototype // 等价于上面的写法
let result = Con.apply(obj, args) // 将构造函数中的this指向这个对象,并传递参数
return result instanceof Object ? result : obj
}
bind、call、apply用法及区别
相同点:
三个函数的作用就是改变this的指向,将函数绑定到上下文中;
不同点:
三个函数的语法不同
fun.call(thisArg[, arg1[, arg2[, ...]]])
fun.apply(thisArg, [argsArray])
var bindFn = fun.bind(thisArg[, arg1[, arg2[, ...]]])
bindFn()
扩展 :手写三个函数的源码
-
手写apply源码
Function.prototype.apply = function(content = window) { content.fn = this; let result; // 判断是否有第二个参数 if(arguments[1]) { result = content.fn(...arguments[1]); } else { result = content.fn(); } delete content.fn; return result; }
-
手写call源码
/** * 完整版 步骤: * 将函数设为对象属性 * 执行&删除这个函数 * 指定this到函数并传入给定参数执行函数 * 如果不传参数,默认指向window
Function.prototype.call = function(content = window) {
// 判断是否是underfine和null
// if(typeof content === 'undefined' || typeof content === null){
// content = window
// }
content.fn = this;
let args = [...arguments].slice(1);
let result = content.fn(...args);
delete content.fn;
return result;
}
3. 手写bind源码
/**
* bind() 方法
* 会创建一个新函数。当这个新函数被调用时,bind() 的第一个参数将作为它运行时的 this,之后的一序列参数将会在传递的实参前传入作为它的参数。(来自于 MDN )
*/
Function.prototype.bind = function(content) {
if(typeof this != 'function') {
throw Error('not a function');
}
let _this = this;
let args = [...arguments].slice(1);
return function F() {
// 判断是否被当做构造函数使用
if(this instanceof F) {
return _this.apply(this, args.concat([...arguments]))
}
return _this.apply(content, args.concat([...arguments]))
}
}
### 目前JS解决异步的方案有哪些
* 回调函数
* 事件监听
* 发布-订阅
* Promise
* Generator
* Async/Await
### 创建对象有几种方法
// 第一种:字面量
var o1 = {name: "o1"}
var o2 = new Object({name: "o2"})
// 第二种:通过构造函数
var M = function(name){this.name = name}
var o3 = new M("o3")
// 第三种:Object.create()
var p = {name: "p"}
var o4 = Object.create(p)
### 介绍宏任务和微任务
![将来的你一定会感谢自己拼命努力的自己](https://user-gold-cdn.xitu.io/2020/2/17/17050f1267f186f6?w=1148&h=960&f=png&s=338549 '宏任务和微任务')
## Vue
### MVC & MVVM的理解
想详细了解的,请查看[MVVM详解](https://juejin.im/post/5e1b3144f265da3e4b5be2e3 '详解')
### Virtual DOM的理解
一、什么是vdom?
Virtual DOM 用JS模拟DOM结构
二、为何要用vdom?
1. DOM操作非常非常“rang贵”,将DOM对比操作放在JS层,提高效率
2. DOM结构的对比,放在JS层来做(图灵完备语言:能实现逻辑代码的语言)
三、vdom核心函数有哪些
核心函数:
h('标签名', {...属性名...}, [...子元素...])
h('标签名', {...属性名...}, '.........')
patch(container, vnode)
patch(vnode, newVnode)
### Vue 路由懒加载
Vue项目中实现路由按需加载(路由懒加载)的3中方式:
1. vue异步组件
2. es6提案的import()
3. webpack的require.ensure()
一、Vue异步组件技术:
{
path: '/home',
name: 'Home',
component: resolve => reqire(['path路径'], resolve)
}
二、es6提案的import()
const Home = () => import('path路径')
三、webpack提供的require.ensure()
{
path: '/home',
name: 'Home',
component: r => require.ensure([],() => r(require('path路径')), 'demo')
}
### 自己搭建过vue开发环境吗
想详细了解的小伙伴,请查看我的 [从零开发一套完整的vue项目开发环境](https://juejin.im/post/5e0cba76f265da5d4e27480c "从零开发一套完整的vue项目开发环境")
### Proxy与Object.defineProperty()的对比
Proxy的优点:
1. 可以直接监听对象而非属性,并返回一个新对象
2. 可以直接监听数值的变化
3. 可以劫持整个对象,并返回一个新对象
Proxy的缺点:
Proxy是es6提供的新特性,兼容性不好,所以导致Vue3一致没有正式发布让让广大开发者使用
Object.defineProperty的优点:
E9以下的版本不兼容
Object.defineProperty的缺点:
1. 只能劫持对象的属性,我们需要对每个对象的每个属性进行遍历,无法监控到数组下标的变化,导致直接通过数组的下标给数组设置值,不能实时响应
### 自己封装过组件吗
想详细了解的小伙伴,请查看我的 [从零实现一套属于自己的UI框架-发布到npm](https://juejin.im/post/5e200ee86fb9a02fdd38986d "从零实现一套属于自己的UI框架-发布到npm")
### Vue 组件间的通信
1. props 和 $emit
父组件向子组件传递数量通过props传递
子组件向父组件传递通过$emit派发事件
2. $children 和 $parent
3. 中央数据总线EventBus
4. ref 和 refs
5. Provide 和 inject
6. $attrs 和 $listeners
7. Vuex
### 谈谈你对生命周期的理解
生命周期:Vue实例从开始创建、初始化数据、编译模板、挂载DOM-》渲染、更新-》渲染、卸载等一系列过程,称为Vue的生命周期
vue.js的生命周期一共有10个:
beforeCreate:实例初始化之后,this指向创建实例,不能访问到data、computed、watch、method上订单方法和数据
created:实例创建完成,可访问data、computed、watch、method上的方法和数据,未挂载到DOM,不能访问到$el属性,$ref属性内容为空数组
beforeMount:在挂载开始之前被调用,beforeMount之前,会找到对应的template,并编译成render函数
mounted:实例挂载到DOM上,此时可以通过DOMAPi获取到DOM节点,$ref属性可以访问
beforeUpdate:响应式数据更新时调用,发生在虚拟DOM打补丁之前
updated:虚拟DOM重新渲染和打补丁之后调用,组件DOM已经更新,可执行依赖于DOM的操作
activated:keep-alive开启时调用
deactivated:keep-alive关闭时调用
beforeDestroy:实例销毁之前调用。实例仍然完全可用,this仍能获取到实例
destroy:实例销毁后调用,调用后,Vue实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁
![将来的你一定会感谢自己拼命努力的自己](https://user-gold-cdn.xitu.io/2019/8/1/16c498ca0e16ac26?imageView2/0/w/1280/h/960/format/webp/ignore-error/1 'Vue生命周期')
### vue的diff算法
问题:渲染真实的DOM开销是很大的,修改了某个数据,如果直接渲染到真实dom上会引起整个DOM树的重绘和重排。
Virtual Dom 根据真实DOM生成的一个Virtual DOM,当Virtual DOM某个节点的数据改变后生成一个新的Vnode,然后Vnode和oldVnode作对比,发现有不一样的地方就直接修改在真实的DOM上,然后使oldVnode的值为Vnode.
注意:在采取diff算法比较的时候,只会在同层级进行,不会跨层级比较。
当数据发生改变时,set方法会让调用Dep.notify()方法通知所有订阅者Watcher,订阅者就会调用patch函数给真实的DOM打补丁,更新响应的试图。
### 你有看过Element UI 的源码吗
想详细了解的小伙伴,请查看我的 [从零实现一套属于自己的UI框架-发布到npm](https://juejin.im/post/5e200ee86fb9a02fdd38986d "从零实现一套属于自己的UI框架-发布到npm")
### vue-router导航守卫
一、导航守卫大体分为三类:
1. 全局守卫钩子
2. 独享守卫钩子
3. 路由组件守卫钩子
二、全局守卫钩子(在路由切换全局执行)
全局守卫钩子函数有三种:
const router = new VueRouter({....})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// do something
})
// 全局解析守卫
router.beforeResolve((to, from, next) => {
// do something
})
// 全局后置守卫
router.afterEach((to, from) => {
// do something
})
注意:
to:route即将进入的路由
from:route即将离开的路由
next:function这个是必须要调用的
next()接受参数:
next():直接执行下一个钩子,如果执行完了导航状态为comfirmed状态
next(false):中断当前导航,回到from的位置
next('/hello')或next({path:'/hello'}):路由到任意地址,可以携带参数等
next(error):会回到router.onError(callback)
三、独享守卫钩子
独享守卫是定义在单独的某一个路由里的
const router = new VueRouter({
routes: [
{
path: '/foo',
component: Foo,
beforeEnter: (to, from, next) => {
// do something
},
beforeLeave: (to, from, next) => {
// do something
}
}
]
})
四、路由组件守卫钩子
路由组件即是在vue-router中注册的组件叫路由组件
beforeRouteEnter(to, from, next) {}
beforeRouteUpdate(to, from, next) {}
beforeRouteLeave(to,from, next){}
### Vuex的理解及使用场景
Vuex 是一个专为 Vue 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。
1. Vuex 的状态存储是响应式的;当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新
2. 改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation,这样使得我们可以方便地跟踪每一个状态的变化
Vuex主要包括以下几个核心模块:
1. State:定义了应用的状态数据
2. Getter:在 store 中定义“getter”(可以认为是 store 的计算属性),就像计算属性一样,getter 的返回值会根据它的依赖被缓存起来,且只有当它的依赖值发生了改变才会被重新计算
3. Mutation:是唯一更改 store 中状态的方法,且必须是同步函数
4. Action:用于提交 mutation,而不是直接变更状态,可以包含任意异步操作
5. Module:允许将单一的 Store 拆分为多个 store 且同时保存在单一的状态树中
![Vuex架构图](https://user-gold-cdn.xitu.io/2020/2/17/17050f12d4f0494e?w=701&h=551&f=png&s=8112 'Vuex架构图')
### Vue底层实现原理
vue.js是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来*劫持各个属性的setter和getter*,在数据变动时发布消息给订阅者,触发相应的监听回调
Vue是一个典型的MVVM框架,模型(Model)只是普通的javascript对象,修改它则试图(View)会自动更新。这种设计让状态管理变得非常简单而直观
Vue实现这种数据双向绑定的效果,需要三大模块:
1. Observer:能够对数据对象的所有属性进行监听,如有变动可拿到最新值并通知订阅者.
2. Compile:对每个元素节点的指令进行扫描和解析,根据指令模板替换数据,以及绑定相应的更新函数。
3. Watcher:链接Observer和Compile的桥梁,能够订阅并收到每个属性变动的通知,执行指令绑定的相应的回调函数,从而更新试图。
Observer(数据监听器)
Observer的核心是通过Object.defineProprtty()来监听数据的变动,这个函数内部可以定义setter和getter,每当数据发生变化,就会触发setter。这时候Observer就要通知订阅者,订阅者就是Watcher
Watcher(订阅者)
Watcher订阅者作为Observer和Compile之间通信的桥梁,主要做的事情是:
1.在自身实例化时往属性订阅器(dep)里面添加自己
2.自身必须有一个update()方法
3.待属性变动dep.notice()通知时,能调用自身的update()方法,并触发Compile中绑定的回调
Compile(指令解析器)
Compile主要做的事情是解析模板指令,将模板中变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加鉴定数据的订阅者,一旦数据有变动,收到通知,更新试图
若小伙伴想详细了解Vue的MVVM源码实现,请查看 [MVVM原理](https://juejin.im/post/5e1b3144f265da3e4b5be2e3 "MVVM原理")
## React
### React生命周期
若小伙伴想详细了解React生命周期,请查看 [React生命周期](https://juejin.im/post/5e37d8c7f265da3df245eb92 "React生命周期")
### 谈谈你对Redux的理解
使用Redux应该遵循原则:
1. 整个应用共享的state应该存储在store的状态数中,store是唯一的
2. state不能直接修改,只能通过action表达修改的意图,调用dispatch()修改state
3. state的修改规则reducers必须是一个纯函数,不能有副作用
Redux提供的API
1、createStore
createStore方法的作用就是创建一个Redux store来存放应用中所有的state
createStore(reducer, [preloadState], [enhancer])
createStore方法接受三个参数,后面两个是可选参数
reducer: 参数的类型必须是function
preloadState: 这个参数代表初始化的state(initialState), 可以是任意类型的参数
enhancer: 这个参数代表添加的各种中间件,参数的类型必须是function
createStore提供的方法:
1、getState()
返回当前的state
2、dispach(action)
参数action必须是一个对象,且必须含有type字段
3、subscribe(listener)
事件监听
2、combineReducers
combineReducers主要是把多个reducer合并成一个,并且返回一个新的reducer函数,该函数接收的参数也是两个state和action
3、compose
主要是在中间件时候使用,合成函数
compose(applyMiddleware(thunk),
window.devToolsExtension ?
window.devToolsExtension() : undefined
)
4、applyMiddleware
5、bindActionCreator
bindActionCreator的主要作用就是将aciton与dispatch函数绑定,生成直接可以出发action的函数
```
Http协议与数据请求
HTTP发展史:
一、HTTP/0.9
- 只有一个命令GET
- 没有HEADER等描述数据的信息
- 服务器发送完毕,就关闭TCP连接
二、HTTP/1.0
- 增加了很多命令
- 增加status code和header
- 多字符集支持、多部分发送、权限、缓存等
三、HTTP/1.1
- 持久连接
- pipeline
- 增加host和其他命令
四、HTTP2
- 所有数据以二进制传输(以前是以字符串)
- 同一个连接里面发送多个请求不再需要按照顺序来
- 头信息压缩以及推送等提高效率的功能
HTTP1.0定义了三种请求方法:GET(查)、POST(增)、HEAD
HTTP1.1新增了五种请求方法:OPTIONS、PUT(改)、DELETE(删)、TRACE、CONNECT
HTTP工作原理
HTTP协议定义Web客户端如何从Web服务器请求Web页面,以及服务器如何把Web页面传送给客户端。
客户端向服务器发送一个请求报文,请求报文:【请求方法、URL、协议版本、请求头部和请求数据】
服务器以一个状态行作为响应,响应的内容:【协议的版本、成功或失败代码、服务器信息、响应头部和响应数据】
HTTP请求/响应的步骤
- 客户端连接到Web服务器
- 发送HTTP请求
- 服务器接受请求并返回HTTP响应
- 释放连接TCP连接
- 客户端(浏览器)解析HTML内容
HTTP主要特点
- 简单快速
- 灵活
- 无连接
- 无状态
- 支持B/S、C/S模式
HTTP报文的组成成分
请求报文{
请求行、请求头、空行、请求体
}
请求行:{http方法、页面地址、http协议、http版本}
响应报文{
状态行、响应头、空行、响应体
}
HTTP方法
GET---获取资源
POST---传输资源
PUT---更新资源
DELETE---删除资源
HEAD---获取报文首部
POST和GET方法
GET在浏览器回退时时无害的,而POST会再次提交请求
GET请求会被浏览器主动缓存,而POST不会,除非手动设置
GET请求参数会被完整的保留在浏览器历史记录里,而POST中的参数不会被保留
GET请求在URL中传送的参数是有长度的限制,而POST没有限制
GET请求参数会暴露,不安全
GET参数通过URL传递,POST放在Requset Body中
HTTP状态码
1xx:指示信息类,表示请求已接受,继续处理
2xx:指示成功类,表示请求已成功接受
3xx:指示重定向,表示要完成请求必须进行更近一步的操作
4xx:指示客户端错误,请求有语法错误或请求无法实现
5xx:指示服务器错误,服务器未能实现合法的请求
常见状态码
200 OK:客户端请求成功
206 Partial Content:客户发送了一个带有Range头的GET请求,服务器完成了它(当时音频或视频文件很大时返回206)
301 Moved Permanently:所请求的页面已经转移至新的URL
302 Found:所请求的页面已经临时转移至新的URL
303 Not Modified:客户端有缓冲的文档并发出看一个条件性的请求,服务器告诉客户,原来缓冲的文档还可以继续使用
403 Forbidden:对请求页面的访问被禁止
404 Not Found:请求资源不存在
500 Internal Server Error:服务器发生不可预期的错误原来缓冲的文档还可以继续使用
503 Server Unavailable:请求未完成,服务器临时过载或宕机,一段时间后可恢复正常
什么是持久连接
HTTP协议采用”请求-应答“模式,当使用普通模式,既非Keep-Alive模式时,每个请求/应答客户和服务器都要新建一个连接,完成之后立即断开连接(HTTP协议为无连接的协议)
当使用Keep-Alive模式(又称持久连接、连接重用)时,Keep-Alive功能使客户端到服务器端的链接持续有效,当出现对服务器的后续请求时,Keep-Alive功能避免了建立或者重新建立连接
注意:keep-alive是1.1版本才有
什么是管线化
管线化技术:客户端可以发送多次请求到服务器,而不需要等待上一次请求得到响应的时候才能进行下一次请求。实现并行发送请求
管线化机制通过持久连接完成,仅HTTP/1.1支持此技术
只有GET和HEAD请求可以进行管线化,而POST则有所限制
浏览器
从输入URL到页面加载的全过程
从输入URL到页面加载的主干流程如下:
1. 首先,在浏览器地址中输入url
2. 浏览器先查看**浏览器缓存**-系统缓存-路由器缓存,如果缓存中有,会直接在屏幕中显示页面内容。若没有,则跳到第三步操作
3. 浏览器向DNS(Domain Name System)服务器请求解析该URL中的域名对应的IP地址
4. 解析出IP地址后,根据该IP地址和默认端口80,和服务器建立TCP连接
5. 浏览器发出读取文件(URL中域名后面部分对应的文件)的HTTP请求,该请求报文作为TCP三次握手的第三个报文的数据发送给服务器
6. 服务器对浏览器请求做出响应,并把对应的html文本发送给浏览器
7. 释放TCP连接
8. 浏览器将该html文本并显示内容
其中,步骤2的具体过程是:
1. **浏览器缓存**:浏览器会记录DNS一段时间,因此,只是第一个地方解析DNS请求;
2. **操作系统缓存:**如果在浏览器缓存中不包含这个记录,则会使系统调用操作系统,获取操作系统的记录(保存最近的DNS查询缓存);
3. **路由器缓存**:如果上述两个步骤均不能成功获取DNS记录,继续搜索路由器缓存;
4. **ISP缓存:**若上述均失败,继续向ISP搜索。
重绘 & 回流
- DOM结构中的各个元素都有自己的盒子(模型),这些都需要浏览器根据格式的样式来计算并根据计算结果将元素放到它该出现的位置,这个过程称为reflow
-
当各个盒子的位置、大小以及其他属性,如颜色、字体大小等都确定下来后,浏览器于是便把这些元素都按照各自的特性绘制了一遍,于是页面的内容出现了,这个过程称之为repaint
// 触发Reflow 当你增加、删除、修改DOM节点时,会导致Reflow或Repaint 当你移动DOM的位置,或是搞个动画的时候 当你修改CSS样式的时候 当你Resize窗口的时候(移动端没有这个问题),或是滚动的时候 当你修改网页的默认字体时 // 触发Repaint DOM改动 CSS改动 // 如何防止重排Reflow和重绘Repaint?
常见浏览器及其内核
1. -webkit-:webkit核心浏览器,Chrome
2. -moz-:Gecko核心浏览器,fixfox
3. -o-:Opera核心浏览器,Opera
4. -ms-:微软的IE浏览器
跨域通信的几种方式
1. JSONP(只支持GET请求)
2. window + hash
3. window + domain
4. window + name
5. postMessage
6. WebSocket
7. CORS(Cross-origin resource sharing) 跨域资源共享(所有的HTTP请求)
8. nginx反向代理
性能与安全
提升页面性能的方法有哪些
- 资源压缩合并、减少HTTP请求
- 非核心代码异步加载---------》异步加载的方式---------》异步加载的区别
- 利用浏览器缓存---------》缓存的分类-----------》缓存的原理【重点】
- 利用CDN
-
预解析DNS
<meta http-equiv="x-dns-prefetch-control" content="no"> <link rel="dns-prefetch" href="//host_name_to_prefetch.com">
前端错误类
1. 即时运行错误:代码错误;捕获方式:try...catch...、window.onerror
2. 资源加载错误;object.onerror(不会冒泡 )、performance.getEntries、Error事件捕获
安全类
1. CSRF:(称为跨站请求伪造)(Cross-site request forgery)缩写CSRF
2. CSRF预防措施:Token验证、Refer验证(页面来源)、隐藏令牌
3. XSS缩写(cross-site scripting)跨域脚本攻击
什么是同源策略及限制
同源策略限制从一个源加载的文档或脚本如何与来自另一个源的资源进行交互。这是一个用于隔离潜在恶意文件的关键的安全机制
源======协议+域名+端口(默认为80)源不一样就是跨域了
不同源之间不能执行以下情况,以下情况都是同源时执行:
1. Cookie、LocalStorage和indexDB无法读取
2. DOM无法获得
3. AJAX请求不能发送
常见的兼容性问题
1. 不同浏览器的标签默认的margin和padding不一样。*{margin:0;padding:0;}
2. IE6双边距bug:块属性标签float后,又有横行的margin情况下,在IE6显示margin比设置的大。hack:display:inline;将其转化为行内属性。
3. 设置较小高度标签(一般小于10px),在IE6,IE7中高度超出自己设置高度。hack:给超出高度的标签设置overflow:hidden;或者设置行高line-height 小于你设置的高度。
4. Chrome 中文界面下默认会将小于 12px 的文本强制按照 12px 显示,可通过加入 CSS 属性 -webkit-text-size-adjust: none; 解决。
5. 超链接访问过后hover样式就不出现了,被点击访问过的超链接样式不再具有hover和active了。解决方法是改变CSS属性的排列顺序:L-V-H-A ( love hate ): a:link {} a:visited {} a:hover {} a:active {}
后记
非常喜欢的一段话:在我们20岁的时候用30岁的心态来做事,那么当我们30岁的时候就可以享受别人40岁的成功!~💪💪💪
最后
如果本文对你有帮助得话,给本文点个赞❤️❤️❤️
欢迎大家加入,一起学习前端,共同进步!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。