大家好,我是梅巴哥er
。
从今天开始,开一个《每天进步一点点:日期》的系列内容。
该系列会以问答的形式,整理一些前端问题。
每天会对博客进行更新。
如果你有问题在本系列中没提到,可以评论下方留言,我会在该篇结尾处加上你提出的问题,并给出自己的解答。
注:博文内写的回答,并不一定正确,也不一定全面,只是自己的理解。回答后,还会查资料,不断进行修正。切勿模仿里面的回答,最多只可作为参考,谢谢!
1,问:怎么做前端优化
答:
- 图片
- 使用精灵图,减少http请求次数
- 使用favicon.ico
- 使用懒加载技术
- 合理利用缓存
- 使用WebP、svg格式的图片
- css方面
- css样式从外部引入,放入head标签里
- 避免使用css表达式
- 减少选择器的嵌套
- 压缩css代码
- 使用link,少使用@Import
- js方面
- 最大化合并JS、CSS模块
- js代码从外部引入,并放入body的最下面
- 启用事件委托
- 减少DOM操作
- 清除废弃的事件监听
- 减少iframe的使用,因为会阻塞页面onload事件加载
- 使用defer,async异步加载
- http方面
- 减少http请求次数
- 避免重定向,因为要多一步去别的地方找资源
- 减少iframe的使用,因为会增加请求
- 避免404错误
2,问:说说css盒模型
答:
-
所有html元素都可以看作盒子。盒模型的本质就是盒子,由内容,边距和边框组成的区域。
-
盒模型分为标准模型和怪异模型(IE盒子或非标准盒子)。
-
标准盒子是的宽度就是content的宽度。符合W3C规范。html盒子默认是标准盒子。也可以用用属性box-sizing: content-box来规定。
-
怪异模型的宽度是content的宽度 + padding宽度 + border宽度。可以用属性box-sizing: border-box来规定。
IE8及以下版本存在浏览器的兼容性,可以在HTML页面声明 <!DOCTYPE html>
即可。
3,问:BFC是什么?讲讲BFC
答:
-
BFC是块级格式化上下文。是一块独立的区域,在区域内,有自己独立的布局规则,不受外界环境影响。
-
BFC的布局规则:
- 区域内的盒子会在垂直方向上一个接一个的放置
- 相邻的盒子的距离由margin绝对,会发生边距重叠。
- BFC区域不会被浮动盒子覆盖
- 计算BFC区域的高度时,里面的浮动元素也会参与计算
-
触发BFC区域的属性:
- float值为left/ right
- position的值为absolute/ fixed
- display的值为inline-block/ flex/ inline-flex/ table-cell/ table-caption
- overflow的值为hidden/ scroll/ auto
-
根据BFC的特性和触发条件,可以轻松解决或解释很多常见问题:
- 比如:利用规则4,可以清除浮动。把父盒子变为BFC后,就可以计算高度。
- 比如一个盒子浮动,另一个盒子不浮动,怎么解决不浮动盒子被覆盖的问题?那就是把不浮动盒子变成BFC即可。
4,问:flex布局
答:
- flex布局就是弹性布局,通过设置display: flex属性和值,让盒子变成一个可伸缩的弹性盒子。
- flex在布局方向上,有两个轴。一个是沿X方向的水平轴(横轴),一个是沿Y轴方向的交叉轴(竖轴)
- 设置盒子内的项目排列方向,通过属性flex-direction设置。箭头代表方向。
- 值raw:→(默认)
- 值raw-reverse: ←
- 值column: ↓
- 值column-reverse: ↑
- 盒子内的项目在一行或一列排满后,是否换行,由属性flax-wrap决定。
- 值nowrap:不换行(默认)
- 值wrap: 换行。第一行排满,再去第二行排列。竖轴也同理。
- 值wrap-reverse:换行。但是项目是先从最下面一行开始排的,排满之后往上面一行切换,继续排。
- 属性flex-flow,用户把排列方向和是否换行两个属性合并在一起写。
- 横轴方向的对齐方式,由属性justify-content决定。
- 值flex-star: 靠左对齐
- 值flex-end:靠右对齐
- 值center:居中对齐
- 值space-between:左右两端对齐,项目之间的间隔都相等
- 值space-around: 每个项目两侧的间隔相等。
- 竖轴方向的对齐方式,由属性align-items决定。
- 值flex-start: 上对齐
- flex-end: 下对齐
- center: 居中(垂直方向的居中)
- baseline: 项目的第一行文字的基线对齐
- stretch: 默认值。项目被拉伸。如果项目不设置高度,则在高度上顶满容器。
- 以上的属性,都是添加到flex盒子上的,用来控制整体项目的。flex还有一些控制单个项目的属性。
- 项目的排列顺序,由order属性决定。order的值是数字,默认是0.使用方法是,先给父盒子设置flex属性,然后规定项目的排列方向,再给项目设置order,数值越小,位置越靠前。假如横轴上排列有3个子盒子,如果给第一个盒子设置order: 1;,那么这个盒子就会跑去最后面,因为另外两个盒子的默认值是0,小于1,所以这个盒子要跑去最后面。
- 项目是否要放大,由属性flex-grow决定。值是数字,默认为0,就是不放大。当横轴上排列了3个盒子,其中一个盒子设置了flex-grow: 1;,则这个盒子会把整行除了其他两个盒子外的区域都填满。如果3个盒子都设置flex-grow: 1; ,则这3个盒子会在同一行上平分宽度。如果其中一个盒子的值是2,其他两个的值是1,那么这个盒子占据的区域,要比另外俩盒子占据的区域大一些,但是并不是2倍哦。我做了一个demo,父盒子宽度是200px,3个子盒子宽度原始值是50px。现在给盒子1设置放大值为2,盒子2和3都设置放大1倍,最后的结果是,盒子1的宽度是75px,盒子2和3的宽度是62.5px。
- 项目是否缩小,由属性flex-shrink决定。默认是1,即同一行的空间不足时,项目盒子会缩小自己的宽度。举例一个demo。仍然设置父盒子200px,3个子盒子中,盒子1和2的宽度为50px,盒子3的宽度设置为150px,3个盒子在横轴上的宽度之和明显超过了父盒子的200px。这时候,查看各个盒子的宽度,盒子1和2的宽度变为40px,盒子3的宽度实际为120px。各自都缩小了20%, flex-shrink的值也可以设置为0,表示当宽度不足时,该盒子不缩小。它会挤占其他盒子的空间,让其他盒子缩小。
- 项目的初始宽度,由属性flex-basis决定。因为盒子都会放大或缩小,所以flex-basis的值一般都是用auto的。
- flex属性,是用来合并放大、缩小和初始值的写法的。常用的值有两个,即auto (1 1 auto) 和 none (0 0 auto)。auto表示这个项目会自动放大缩小来填满空间,none表示这个项目不放大也不缩小。
- flex常用来做弹性布局,实际开发中,用的也特别多。缺点是还有点兼容性问题。要IE11才支持。(IE不愧是开发界的毒瘤)
5,问: js有哪些数据类型?怎么判断数据类型? typeof null的结果是什么?
答:
- js的基本数据类型有:string, number, boolean, null, undefined, symbol(ES6加的)
- js的引用类型有:array, function, object 。(Date, Math之类的就不单独说了,这些都是一种可以调用的函数)
// 外面先来看一组demo
// 可以看出,typeof可以判断string , number,boolean,undefined,symbol,这5种基本数据类型的直接值,但是不能判断它们实例的类型
// 但是可以判断function, object的new实例
// typeof不能判断null和array
console.log(typeof 'a') // string
console.log(typeof 1) // number
console.log(typeof true) // boolean
console.log(typeof null) // object
console.log(typeof undefined) // undefined
console.log(typeof Symbol()) // symbol
console.log('-----继续')
console.log(typeof new String('a')) // object
console.log(typeof new Number(1)) // object
console.log(typeof new Boolean(true)) // object
console.log('-----继续')
console.log(typeof [1]) // object
console.log(typeof function(){}) // function
console.log(typeof {}) // object
// 我们再来看一组demo
// 可以知晓,instanceof可以判断实例,可以判断引用类型的直接值,
// 但是不能判断基本数据类型的直接值,
// 也不能判断null和undefined
console.log( 'a' instanceof String) // false
console.log(new String('a') instanceof String) // true
console.log(null instanceof Null) // 报错
console.log(1 instanceof Number) // false
console.log(new Number(1) instanceof Number) // true
console.log([] instanceof Array) // true
console.log({} instanceof Object) // true
console.log(function(){} instanceof Function) // true
// 最后,我们再来看一组demo
// 可以知晓,Object.protorype.toString.call()可以判断所有数据类型
// 不管是直接值,还是实例
console.log(Object.prototype.toString.call('a'))
console.log(Object.prototype.toString.call(new String('a')))
console.log(Object.prototype.toString.call(1))
console.log(Object.prototype.toString.call(new Number(1)))
console.log(Object.prototype.toString.call(true))
console.log(Object.prototype.toString.call(new Boolean(true)))
console.log(Object.prototype.toString.call(null))
console.log(Object.prototype.toString.call(undefined))
console.log(Object.prototype.toString.call([]))
console.log(Object.prototype.toString.call({}))
console.log(Object.prototype.toString.call(function(){}))
console.log(Object.prototype.toString.call(new Function()))
console.log(Object.prototype.toString.call(new Array([])))
6,问:js原型链 讲解一下
答:
- js的构造函数,例如function Star() { },他的原型是Star.protorype。假设构造函数Star上有两个实例,分别是star1和star2,当通过star1添加属性和方法时,star2是没办法调用的。为了解决这个问题,就创造了原型Star.protorype。
// 代码举例
function Star() {
this.name = 'dilireba'
}
var star1 = new Star()
var star2 = new Star()
star1.age = 18
console.log(star1.age) // 18
console.log(star2.age) // undefined
- 为了方便查找对应的原型,创造了__proto__属性。每个对象都有一个__proto__属性。
- Star的原型是Star.protorype,Star.protorype的constructor又指回了Star本身。Star通过new创建了自己的实例,实例的__proto__又指向了Star的原型Star.protorype。Star.protorype的__proto__又指向了Object的原型Object.prototype。Object.prototype的__proto__最终指向null。这就是原型链。原型链提供了一种查询原型对应关系的途径。
- 用图表示就是:
7,问:手写instanceof
答:
a instanceof B
表示a可以通过原型链查找到B的原型。即a.__proto__ = B.prototype
或a.__proto__.__proto__ = B.prototype
。一直查找,直到a.__proto__ = null
。
function useInstance(a, obj) {
// 判断a的__proto__是否为空
if(a.__proto__ === null) {
return false
// 判断a的第一步查找有没有找到
} else if(a.__proto__ === obj.prototype) {
return true
} else {
// 如果没找到,就继续沿着原型链查找
return useInstance(a.__proto__, obj)
}
}
8,问:谈谈call, apply, bind的区别
答:
- 三兄弟都是改变this指向的
- call,语法是A.call(thisArg, arg1, arg2…)。里面的参数都不是必须的,也可以有多个参数。调用的时候会理解执行。返回的是this和参数调用A方法得到的值。
- apply,语法是A.apply(thisArg, array)。第一个参数是必须的,第二个参数是数组形式。而且参数最多只能有俩。调用的时候会立即执行。返回的是this和参数调用A方法得到的值。
- bind,也可以有多个参数,不会立即执行,而是需要的时候才执行。返回的是一个带有新的this的函数。
9,手写bind
答:
Function.prototype.useBind = function (thisArg) {
if(typeof this !== 'function') {
return
}
var _self = this
var args = Array.prototype.slice.call(arguments, 1)
return function() {
return _self.apply(thisArg, args.concat(Array.prototype.slice.call(arguments)))
}
}
10,问:箭头函数和普通函数的区别
答:
- 箭头函数没有自己的this,它的this来自于父级作用域
- 箭头函数没有自己的arguments
- 箭头函数不能使用new
// 举个demo
// 在谷歌浏览器下输出结果(和编辑器下的结果会稍微不一样)
var a = 1
var obj = {
a: 2,
fn1: function() {
var a = 3
console.log(this.a) // 2
console.log(this) // obj
},
fn2: () => {
var a = 4
console.log(this.a) // 1
console.log(this) // window
}
}
obj.fn1()
obj.fn2()
// 可以得知,普通函数在对象里,指向的是自己的调用者
// 箭头函数没有自己的this,在对象里也不是指向自己的调用者
// 而是指向了他的父级作用域window,并获取了window下的值
11,问:函数的this指向
答:
- 普通函数的this指向window,在严格模式下,指向undefined
- 定时器的this指向window
- 箭头函数没有自己的this,他会去找父级作用域里找值,this就指向父级作用域
- 对象中的函数,指向调用者
- 事件中的this指向绑定事件的节点
- 构造函数中的this指向构造函数本身
12,问:手撕一个调度器,第一秒输出1,第二秒输出2,以此类推,第10秒后输出10,然后停止输出。
答:
// 方法一:for循环
function scheduler(n) {
for(let i = 1; i < n+1; i++) {
setTimeout(function() {
console.log(i)
}, 1000*i)
}
}
scheduler(10)
// 方法二:递归
var i = 0
function scheduler(n) {
i++
setTimeout(function() {
console.log(i)
i === n || scheduler(n)
}, 1000)
}
scheduler(10)
// 方法三:promise
// 一直.then下去,可能写的不好
function scheduler(n) {
return new Promise((resolve, reject) => {
setTimeout(function() {
console.log(n)
resolve()
}, 1000)
})
}
scheduler(1)
.then(() => scheduler(2))
.then(() => scheduler(3))
...
.then(() => scheduler(10))
13,问:TCP(三次握手,四次挥手,流量控制,拥塞控制)
答:
- TCP三次握手用于客户端和服务器的连接
- 第一次:客户端连接服务器,向服务器发送连接请求(
服务器大哥,我想和你说话,嘤嘤嘤~
) - 第二次:服务器接收客户端的连接请求,向客户端返回应答(
好的,我知道了,我准备好了
) - 第三次:客户端确认收到服务器的应答,并告诉服务器自己收到了应答。连接完成,可以发送数据了。(
服务器大哥,我知道你也想和我说话了,那我们开始说话吧,嘻嘻嘻~
)
- 第一次:客户端连接服务器,向服务器发送连接请求(
- TCP四次挥手用于断开客户端和服务器的连接
- 第一次:客户端向服务器发送断开连接的请求(
我说完了,不想说了
) - 第二次:服务器接收到客户端的断开连接请求,向客户端返回应答,并确认客户端的状态(
好了,我知道你不想说了。你真的不想和我说话了吗?
此时服务器能向客户端发送应答,但客户端不能向服务器发送请求了) - 第三次:客户端接收到服务器的应答,进入等待,然后断开连接。
- 第四次:服务器确认客户端的状态,也关闭连接。(
他确实不想和我说话了,电话都挂了,那我也挂吧
)
- 第一次:客户端向服务器发送断开连接的请求(
- TCP的流量控制
- 如果发送方把数据发送得过快,接收方可能会来不及接收,这就会造成数据的丢失。所谓流量控制就是让发送方的发送速率不要太快,要让接收方来得及接收。
- 利用滑动窗口实现流量控制
- TCP的拥塞控制
- 拥塞:即对资源的需求超过了可用的资源。若网络中许多资源同时供应不足,网络的性能就要明显变坏,整个网络的吞吐量随之负荷的增大而下降。
- 拥塞控制:防止过多的数据注入到网络中,这样可以使网络中的路由器或链路不致过载。拥塞控制所要做的都有一个前提:网络能够承受现有的网络负荷。拥塞控制是一个全局性的过程,涉及到所有的主机、路由器,以及与降低网络传输性能有关的所有因素。
- 慢启动(slow-start)、拥塞避免(congestion avoidance)、快速重传(fast retransmission)和快速恢复(fastrecover)解决拥塞。
14,问:React的生命周期
答:
- react生命周期分为3个阶段
- 挂载,渲染,卸载
以上。时间晚了,明天继续。