记录和汇总看到的和碰到的前端面试题
主要来源:
https://github.com/Advanced-Frontend/Daily-Interview-Question
HTML + CSS 篇
Q1、 常用的行内元素、块级元素、空元素分别有哪些?
1. 块级元素:div ul ol li h1~h6 p table 等
2. 行内元素: a span img input select 等
3. 空元素: br hr link等
Q2、HTML语义化的理解
简单来说,就是合适的标签做合适的事情
具体的好处有:
- 有助于构建良好的HTML结构,有利于搜索引擎的建立索引、抓取,利于SEO
- 有利于不同设备的解析
- 有利于团队的开发、维护
Q3、 常见的浏览器内核及理解
- Trident 内核: IE
- Gecko内核: NETSCAPE6及以上版本,火狐
- Presto内核:Opera7及以上 [ Opera内核原为:Presto, 现为:Blink ]
- Webkit内核:Safari、Chrome等 [ Chrome:Blink(Webkit的分支) ]
浏览器内核又可以分为两部分:渲染引擎和JS引擎。<br />
渲染引擎主要负责取得页面的内容、整理讯息、计算网页的显示方式等;<br />
JS引擎则是解析JavaScript语言,执行Javascript语言来实现网页的动态效果。
Q4、src 与 href 的区别
区别在于 src用于替代这个元素,而href用于建立这个标签与外部资源之间的关系。<br />
<link href="style.css" rel="stylesheet" />浏览器加载到这里的时候,html的渲染和解析不会暂停,css文件的加载是同时进行的 <br />
<script src="script.js"></script>当浏览器解析到这句时,页面的加载和解析会暂停直到浏览器拿到和执行完这个JS文件。
Q5、CSS中link 和 import 的区别
link属于XHTML标签,而@import完全是CSS提供的另一种方式,只能加载CSS<br />
加载顺序不同:当一个页面被加载时,link引用的css会被同时加载,而import引用的css会等到页面全部被下载完再被加载<br />
兼容性的区别:由于import时CSS2.1提出的所以老的浏览器不支持,而link标签无此问题<br />
当使用JavaScript控制DOM去改变样式时,只能用link标签,因为@import不是dom可以控制的
Q6、CSS部分优化、提高性能的方法有哪些?
- 移除空的css规则
- 正确使用display属性
- 不滥用浮动、web字体
- 不声明过多的font-size
- 不在选择器中使用ID标识符
- 遵守盒模型规则
- 尽量减少页面重排、重绘
- 提取公共样式,减少代码量
Q7、清除浮动的方式
浮动的元素是脱离文档标准流的,如果不清除浮动,就会造成父元素高度塌陷,影响页面布局
清除浮动的方式有:
- 为父元素设置高度
- 为父元素设置
overflow: hidden - 伪元素
.father::after {
content: "";
display: block;
clear: both;
}
注:
- 使用伪元素的好处:不增加冗余的DOM节点,符合语义化
-
overflow: hidden;可以触发BFC机制
Q8、介绍BFC及其应用
BFC就是块级格式上下文,是页面盒模型布局中的一种CSS渲染模式,创建了BFC的元素就相当于一个独立的盒子,它规定了内部如何布局,里面的元素和外面的元素互不影响。 计算BFC的高度时,浮动元素也参与计算。
BFC的特性:
- 内部box会在垂直方向上,一个接一个的放置
- Box垂直方向的距离由margin决定,在一个BFC中,两个相邻的块级元素的垂直外边距会产生重叠
- 在BFC中,每个盒子的左外边缘(margin-left)会触碰到容器的左边缘(border-left)(对于从右到左的格式来说,则触碰到右边缘)
- 形成了BFC的区域不会与float box 重叠
- 计算BFC的高度时,浮动元素也参与计算
创建BFC的方式有:
- html根元素
- float
- 绝对定位
- overflow 不为visiable
- display为表格布局或弹性布局
BFC主要的作用:
- 清除浮动
- 防止同一BFC容器中的相邻元素之间的外边距重叠的问题
Q9、BFC、IFC、GFC 和 FFC
BFC( Block Formatting Contexts ):块级格式上下文
页面上的一个隔离的渲染区域,可触发BFC的元素有:float、position、overflow、display: table-cell;/inline-block; 作用有实现多栏布局等;
IFC(Inline Formatting Contexts):内联格式上下文
IFC的line box(线框)高度由其包含行内元素中最高的实际高度计算而来(不受到竖直方向的padding和margin影响), IFC中的line box 一般左右都贴紧整个IFC,但是会因为float元素而打乱。float元素会位于IFC 与 line box 之间,使得line box 宽度缩小。同时,IFC下的多个line box 高度会不同,IFC中是不可能有块级元素的,当插入块级元素时,会产生两个匿名块与div隔开(即产生两个IFC,每个IFC对外表现为块级元素,与div垂直排列)。作用一般有:水平居中:当一个块要在环境中水平居中时,设置其为inline-block 则会在外层产生IFC,通过text-align则可以使其水平居中。垂直居中:创建一个IFC,用其中一个元素撑开父元素的高度,然后设置其vertical-align: middle; 其他行内元素就可以在此父元素下垂直居中。
GFC (GridLayout Formatting Contexts):网格化布局格式上下文
当为一个元素设置display: grid; 的时候,此元素将会获得一个独立的渲染区域,我们可以通过在网格容器(grid container)上定义网格定义行(grid definition rows)和网格定义列(grid definition columns)属性和在网格项目(grid item)上定义网格行(grid rows)和网格列(grid columns)为每一个网格项目定义位置和空间。与table同为二维的表格,GridLayout会有更加丰富的属性来控制行和列,控制对齐以及更为精细的渲染语义和控制。、
FFC (Flex Formatting Contexts):自适应格式上下文
diaplay为 flex 或 inline-flex 的元素将会生成自适应容器(flex container),可惜这个属性只有谷歌和火狐支持(移动端够用了)。Flex Box 由伸缩容器和伸缩项目组成。通过设置元素的display为flex 或 inline-flex 就可以得到一个伸缩容器。设置为flex的容器被渲染成一个块级元素,而设置inline-box的元素被渲染成一个行内元素。伸缩容器中的每一个子元素都是一个伸缩项目。伸缩项目可以是任意数量的。伸缩容器内外元素互不影响。简单的说就是,Flex Box 定义了伸缩容器里的伸缩项目怎么布局。
Q10、怎么让不定宽高的DIV水平垂直居中
<div class="parent">
<div class="child"></div>
</div>
- 定位的方式
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
margin: auto;
}
- CSS3属性
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
- flex 布局
.parent {
display: flex;
align-items: center;
justify-content: center;
}
Q11、分析比较opacity: 0; visibility: hidden; 和 display: none; 的优劣
- 结构
display: none; 会让元素完全从渲染树中消失,渲染的时候不占据任何空间,不能点击;
visibility: hidden; 不会让元素从渲染树中消失,渲染元素继续占据空间,只是内容看不见,不能点击;
opacity: 0; 不会让元素从渲染树消失,渲染元素继续占据空间,只是内容看不见,可以点击; - 继承性
display: none;和 opacity: 0; 是非继承属性,子孙节点消失是由于元素从渲染树消失造成的,通过修改子孙节点的属性无法将其显示;
visibility: hidden; 是继承属性,子孙节点消失是由于继承了hidden属性,通过给子孙节点设置visibility: visible;可以将子孙节点显示出来; - 性能
display: none; 修改元素会造成文档回流,性能消耗较大;
visibility: hidden; 修改元素只会造成重绘,性能消耗较少;
opacity: 0; 修改元素会造成重绘,性能消耗较少; - 联系
三者都可以让元素不可见
Q12、用CSS或JS实现多行文本溢出省略效果(考虑兼容性)
- CSS
*单行*
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
*多行*
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3; // 行数
overflow: hidden;
text-overflow: ellipsis;
- JS
实现思路:
- 使用split + 正则表达式将单词与单个文字切割出来存入words
- 加上 '...'
- 判断scrollHeight与clientHeight,超出的话就从words中pop一个出来
<p>测试文字,test words 1231231121</p>
const p = document.querySelector('p');
let words = p.innerHTML.split(/(?<=[\u4e00-\u9fa5])|(?<=\w*?\b)/g);
while(p.scrollHeight > p.clientHeight) {
words.pop()
p.innerHTML = words.join('') + '...'
}
JavaScript 篇
Q1、数组对象有哪些常用的方法
修改器方法
-pop(): 删除数组的最后一个元素,并返回这个元素
-push(): 在数组的末尾增加一个或多个元素,并返回数组的新长度
-reverse(): 颠倒数组中元素的排列顺序
-shift(): 删除数组的第一个元素,并返回这个元素
-unshift(): 在数组的开头增加一个或多个元素,并返回数组的新长度
-sort(): 对数组元素进行排序,并返回当前数组
-splice(): 在任意的位置给数组添加或删除任意个元素
访问方法
-concat(): 返回一个由当前数组和其它若干个数组或非数组值组合而成的新数组
-join(): 连接所有数组元素组成一个字符串
-slice(): 抽取当前数组中的一段元素组合成一个新数组
-indexOf(): 返回数组中第一个与指定值相等的元素的索引,如果找不到这样的元素则返回-1
-lastIndexOf(): 返回数组中最后一个(从右边数第一个)与指定值相等的元素的索引,如果找不到这样的元素则返回-1
迭代方法
-
forEach(): 为数组中的每个元素执行一次回调函数,无返回值或默认为undefined -
map(): 返回一个由回调函数的返回值组成的新数组 -
filter(): 将所有在过滤函数中返回true的数组元素放进一个新数组中并返回 -
every(): 如果数组中的每个元素都满足测试函数,则返回true,否则返回false -
some(): 如果数组中至少由一个元素满足测试函数,则返回true,否则返回false
Q2. JS有哪几种创建对象的方式
- 对象字面量
var obj = {} - Object 构造函数
var obj = new Object - 工厂模式
缺点: 每次通过function Person(name, age) { var o = new Object() o.name = name; o.age = age; o.say = function() { console.log(name) } return 0 }Person创建对象的时候,所有的say方法都是一样的,但是却存储了多次,浪费资源。 - 构造函数模式
构造函数模式隐式在最后返回function Person(name, age) { this.name = name this.age = age this.say = function() { console.log(name) } } var person = new Person('hello', 16)return this所以在缺少new的情况下,会将属性和方法添加给全局对象,浏览器端就会添加给window对象,可以根据return this的特性调用call或者apply指定this - 原型模式
实现了方法和属性的共享,可以动态添加对象的属性和方法。但是没有办法创建实例自己的属性和方法,也没有办法传递参数。function Person() {} Person.prototype.name = 'Bob' Person.prototype.say = function() { alert(this.name); } Person.prototype.friends = ['Tom']; var person = new Person(); - 构造函数和原型组合
function Person(name, age) { this.name = name this.age = age } Person.prototype.say = function() { console.log(this.name) } var person = new Person('hello')
Q3. JavaScript 如何实现继承
-
原型链继承
function Animal() {} Animal.prototype.name = 'cat' Animal.prototype.age = 2 Animal.prototype.say = function() { console.log('hello') } var cat = new Animal() cat.name // cat cat.age // 2 cat.say() // hello最简单的实现继承的方式,但是也有缺点
1. 来自原型对象的所有属性都被所有实例共享
2. 创建子类实例时,无法向父类构造函数传参
3. 要想为子类新增属性和方法,必须要在new语句之后执行,不能放到构造器中 -
构造继承
function Animal() { this.species = '动物' } function Cat(name, age) { Animal.call(this) this.name = name this.age = age } var cat = new Cat('coco', 2) cat.name // coco cat.age // 2 cat.species // 动物使用
call或apply方法,将父对象的构造函数绑定在子对象上 -
组合继承
function Animal() { this.species = 'animal' } function Cat(name) { Animal.call(this) this.name = name } Cat.prototype = new Animal() // 重写原型 Cat.prototype.constructor = Cat如果没有
Cat.prototype = new Animal()这一行,Cat.prototype.constructor是指向Cat的,加了这一行以后,Cat.prototype.constructor指向Animal. 这显然会导致继承链的紊乱(cat1明明是用构造函数Cat生成的),因此必须手动纠正,将Cat.prototype对象的constructor值改为Cat -
extends继承ES6新增继承方式,class可以通过extends关键字实现继承class Animal {} class Cat extends Animal { constructor() { super(); } }使用
extends实现继承,必须添加super关键字定义子类的constructor,这里的super()就相当于Animal.prototype.constructor.call(this)
Q4、同步和异步的区别,怎么异步加载JavaScript
同步模式
同步模式,又称为阻塞模式,JavaScript在默认情况下是会阻塞加载的。当前面的JavaScript请求没有处理和执行完时,会阻止浏览器的后续处理
异步模式
异步加载又叫非阻塞,浏览器在下载执行JavaScript同时,还会继续进行后续页面的处理
异步加载JavaScript
- 动态添加
script标签 deferasync
defer属性和async都是属于script标签上面的属性,两者都能实现JavaScript的异步加载。不同之处在于:async在异步加载完成的时候就马上开始执行了,而defer会等到html加载完毕之后再执行
VUE篇
Q1. 写 React / Vue 项目时为什么要在列表组件中写 key,其作用是什么?
key是给每一个vnode的唯一id,可以依靠key,更准确,更快的拿到oldVnode中对应的vnode节点
1. 更准确
带key就不是就地复用了,在samenode函数a.key === b.key对比中可以避免就地复用的情况,所以更加准确。
2. 更快
利用key的唯一性生成map对象来获取对应节点,比遍历方式更快。
vue和React都是采用diff算法来对比新旧虚拟节点,从而更新节点。vue的diff算法在交叉对比中,当新节点跟旧节点头尾交叉对比没有结果时,会根据新节点的key去对比旧节点数组中的key,从而找到相应旧节点(这里对应的是一个key => index的map映射)。如果没找到就认为是一个新增节点。而如果没有key,那么就会采用遍历查找的方式去找到对应的旧节点。一种是map映射,另一种是遍历查找。相对比而言,map映射的速度更快。
vue 部分源码如下:
// oldCh 是一个旧虚拟节点数组
if (isUndef(oldKeyToIdx)) {
oldKeyToIdx = createKeyToOldIdx(oldCh, oldStartIdx, oldEndIdx)
}
if(isDef(newStartVnode.key)) {
// map 方式获取
idxInOld = oldKeyToIdx[newStartVnode.key]
} esle {
// 遍历方式获取
idxInOld = findIdxInOld(newStartVnode, oldCh, oldStartIdx, oldEndIdx)
}
创建map函数
function createKeyToOldIdx(children, beginIdx, endIdx) {
let i, key
const map = {}
for(i = beginIdx; i <= endIdx; ++i) {
key = children[i].key
if(isDef(key)) map[key] = i
}
return map
}
遍历寻找
// sameVnode 是对比新旧节点是否相同的函数
function findIdxInOld(node, oldCh, start, end) {
for(let i = start; i < end; i++) {
const c = oldCh[i]
if(isDef(c) && sameVnode(node, c)) return i
}
}