本篇内容如下:
- 一、吐槽前端开发
- 前端是一个持续学习才能胜任的工作
- 前端卷的不行,面试造火箭,入职拧螺丝
- 二、吐槽成都前端JD
- 成都成都,内卷之都
- 想找26k+,基本不现实
- 有机会找到 22~25k 的
- 拿着白菜的钱,操着北上广的心
- 三、一面(基础知识篇)
- HTML基础知识
- 1.如何理解语义化?
- 2.哪些是块级元素,哪些是内联元素?
- CSS基础知识
- 3.盒模型宽度如何计算?
- 4.margin 纵向重叠问题?
- 5.margin 负值问题?
- 6.对 BFC 理解和应用?
- 7.清除浮动有几种方法?手写 clearfix?
- 8.实现一个圣杯布局?双飞翼布局?
- 9.使用 flex 布局画骰子?
- 10.relative 、absoulte、fixed 分别依据什么定位?
- 11.居中对齐有哪几种实现方式?
- 12.line-height 的继承问题?
- 13.如何实现响应式?
- 14.vw、vh、vmax、vmin 是什么?
- JS基础知识
- 变量的类型和计算
- 15.值类型、引用类型两者的区别?分别有哪些?
- 16.typeof 能判断哪些类型?
- 17.=== 和 == 的区别?
- 18.如何判断一个变量是不是数组?
- 原型和原型链
- 19.什么是原型?什么是原型链?
- 20.如何理解 instanceof,class?
- 作用域和闭包
- 21.如何理解作用域、自由变量?
- 22.如何理解闭包?其常见的表现形式有哪些?
- 23.this 在不同应用场景,如何取值?
- 24.apply、call、bind 区别?
- 异步和单线程
- 25.同步和异步的区别?
- 26.什么是 event loop(事件循环/事件轮询)?
- 27.描述下 event loop 的过程?
- 28.什么是宏任务和微任务,两者有什么区别?
- 29.为什么微任务比宏任务执行更早?
- 30.Promise 有哪三种状态?如何变化?
- 31.Promise的 then 和 catch 如何变化?
- 32.async/await 和 Promise 的关系是怎样的?
- 33.一道异步输出顺序的测试题?
- HTTP
- 34.http 常见错误码有哪些?
- 35.http 常见 header 有哪些?
- 36.什么是 http 缓存?为什么需要缓存?哪些可以缓存?
- 37.描述下 http 的缓存机制?
- 38.强制缓存 cache-control 常见有哪些值?分别代表什么意思?
- 39.如何清除 http 缓存?
- 40.http 和 https 区别?描述下 https 过程解析?
- 变量的类型和计算
- HTML基础知识
- 四、一面(手写篇)
- 1.手写一个深拷贝?
- 2.手写一个简易版 jQuery?
- 3.手写一个 bind 函数?
- 4.手写一个可缓存其他函数的高阶函数?
- 5.手写一个柯里化函数?
- 6.手写一个简易版 Promise?
- 7.手写一个事件代理,事件绑定函数?
- 8.手写一个简易版 ajax?
- 9.手写一个防抖 debounce 函数?
- 10.手写一个节流 throttle 函数?
- 11.手写一个获取最大值 max 函数?
一、吐槽前端职位
大家好,我是一名工作9年的前端技术负责人
,在上海工作7年。
2014年,正式进入互联网,到2024年,明年起,目前在前端领域整整十年。
可能有人认为,前端是一个简单易上手、掌握好HTML、CSS、JavaScript,就能够胜任的工作。不就画画页面嘛,so easy?
事实真的如此吗?我认为前端是一个持续学习才能胜任的工作。
前端仅仅一个 vue 或 react 框架及其对应技术栈,就足够你学很长时间。加上最近且一直火的 vite、ts,学个半年、一年就想上手?讲真,很多 ts 高级语法和 vite 高级配置,我都没仔细研究过,前端学无止境。
从前端面试来看第一个现象,前端卷的不行,面试造火箭,入职拧螺丝
。为什么?
大部分公司招前端人员,要求学历高,能英语交流、八股文厉害,基础知识厉害,会算法、会数据结构,这样的人才是我公司想要的。
但事实上,在真实做项目时,这些东西并没多大用处,然而并没什么卵用
,靠的是多年积累的项目经验
。大部分互联网公司都是以业务为导向,在做项目的时候,我要将算法、数据结构怎么用在公司的项目里面?复杂场景,顶多用到 Object 和 Array 相关的 API 去实现就行了。
一切的一切,都是面试官在面试候选人的时候,挑选出基础扎实、会算法数据结构的精英,淘汰基础差的前端人员
。
基础差的前端人员,他就一定不行?我倒不这样认为,相反,我会更加看重他曾经做的项目,与公司招前端岗位匹配度是否相符。
当然他的基础也不能太差。
二、吐槽成都前端岗位要求
再来看看第二个现象,按薪资来看看前端岗位,以成都举例: 技术能力一般 + 学历一般 = 10k以下; 技术能力强 + 学历一般 = 10-16k; 技术能力强 + 学历好 = 16-22k; 技术能力强 + 学历好 + 英语好 = 22-26k; 想要26k+?呵呵~ 好的,我明白了~ 按我目前的情况,在成都也就22k的水平。
再看第三个现象,成都80%的互联网公司都不是按工资的百分比来交五险一金
,换句话的意思是想找到全额缴纳五险一金的公司微乎其微。
说了这么多,请拿出证据?证据在哪儿呢? 我在 Boss 直聘,想找 26k 的前端开发
总结了下一般 HR 都会问的问题。
您好,请问您这边学历是全日制的还是非全日制?
您是否有学位证呢?总部会卡双证这块。
你这边英文怎么样呢?英语口语怎么样?
要是你英文好点也可以,但是你工资太高了,我们这边给不到的。英文也是个问题啊,不好意思。。。
你的工资要求超出我们的范围的。
你的工资是有些高,至少我们这个职位给不了。
成都 22k 对我来说是个什么水平呢? 7年前我在上海的水平
。
成都朋友一直跟我说,不要拿上海的薪资去和成都对比,这不公平
。尽管他说的对,但是反问一下自己,假如自己拿着7年前上海的工资在成都工作
,平时加班到无所谓,如果公司是外包、外派性质
,周末并非双休,是大小休,甚至单休
,性价比真的高吗?
假设一:
成都22k = 7年前上海工资 + 周末双休 + 全额社保公积金,
勉强能接受
,尽管不是很情愿。
假设二:
成都22k = 7年前上海工资 + 周末单休 + 外包/外派 + 最低基数社保公积金,
完全接受不了
。
假设三:
成都26k = 7年前上海工资 + 周末双休 + 全额社保公积金,
完美
,这是我在成都最最理想的公司。
假设四:
成都26k = 7年前上海工资 + 周末单休 + 外包/外派 + 最低基数社保公积金,,
当时能接受,但长期看,我应该也不会接受
。
理由:尽管自己快30岁,但到35岁找工作不好找,还是希望自己尽量找一个业务稳定的公司,长久干下去。
分析一波,那么问题就变得特别简单,假如自己在成都能够找到22k+、周末双休、全额社保公积金、业务稳定的公司,是可以回来的。
但目前成都的行情,能找到这样的公司,还是需要花费不少功夫的。
既然改变不了环境就改变自己
,那么后面开始系统回顾下前端基础知识、算法、数据结构、项目经验
来应对后面残酷的面试筛选
。
在写技术前,讲个题外话,
我在是否继续留在上海,或是重回故乡成都这一事情上
,自己其实纠结了很久。
既然成都环境不容乐观,为什么还要从上海回到成都?
理由1:自己在上海的目的基本都已实现,房车有了,学历提升了,能力认知有了,成都的房子装修好了
理由2:钱挣再多也买不了亲情,是时候回来多陪陪自己和老婆的父母
理由3:上海没房,始终成本太高,出于成本考虑
理由4:小孩读书问题,孩子教育挺重要的,家在成都,教育资源相比没落户上海的资源好
点到为止,对目前的自己来说,这个事情不单单是一个人的事情,而是一个家庭的事情,自己的决定会影响整个家庭的幸福
。
三、一面(基础知识篇)
1.如何理解语义化?
<div>标题</div>
<div>
<div>一段文字</div>
<div>
<div>列表一</div>
<div>列表二</div>
</div>
</div>
<h1>标题</h1>
<div>
<p>一段文字</p>
<ul>
<li>列表一</li>
<li>列表二</li>
</ul>
</div>
尽管内容完全一样,效果完全一样,好处:
增加可读性,方便SEO
2.哪些是块级元素,哪些是内联元素?
disply:block/table,有div、p、h1、h2、table、ul等
;
display:inline/inline-block,有span、img、input、button等
3.盒模型宽度如何计算?
<style>
#div {
width: 100px;
padding: 10px;
border: 1px solid #fff;
margin: 10px;
}
</style>
<div id="div"></div>
offsetWidth = width + padding + border
因此当前 dev 盒模型宽度 = 100 + 210 + 21 = 122px
如何让 dev 盒模型宽度弄成120px?
offsetWidth = width
box-sizing: border-box
4.margin 纵向重叠问题?
相邻的margin-top、marin-bottom会发生重叠
<style>
p {
margin-top:10px;
margin-bottom: 10px;
}
</style>
<p>AAA</p>
<p></p>
<p></p>
<p>BBB</p>
p 之间的间距是多少?10px
,不是20px
5.margin 负值问题?
margin-top,向上移动
;
margin-left,向左移动
;
margin-bottom,下方元素上移,自身不受影响
;
margin-right,右侧元素左移,自身不受影响
;
6.对 BFC 理解和应用?
BFC 是块级格式化上下文(Block format context)
,是一块独立渲染区域
,内部元素的渲染不会影响边界以外的元素
。
满足以下一个情况就会形成BFC:
float: left
;
position: absoulte/fixed
;
overflow: hidden
;
display: flex/inline-block
;
7.清除浮动有几种方法?手写 clearfix?
3种,clear: both
、overflow: hidden
、clearfix
.clearfix:after {
content: ''; /* 生成内容为空 */
display: table; /* 转为块级元素 使用display: block; 也可以 */
clear: both; /* 清除浮动 */
}
8.实现一个圣杯布局?双飞翼布局?
圣杯布局和双飞翼布局解决的问题:两边顶宽,中间自适应的三栏布局
。
圣杯布局:float + padding + margin-left + right + margin-right
<style>
.main {
padding-left: 100px; /* 重点 */
padding-right: 150px; /* 重点 */
}
.left {
float: left;
width: 100px;
margin-left: -100%; /* 重点 */
position: relative; /* 重点 */
right: 100px; /* 重点 */
}
.center {
float: left;
width: 100%;
}
.right {
float: left;
width: 150px;
margin-right: -150px; /* 重点 */
}
</style>
<div class="main">
<!-- 重点 -->
<div class="center">center</div>
<div class="left">left</div>
<div class="right">right</div>
</div>
双飞翼布局:float + margin + margin-left
<style>
.main {
margin-left: 100px; /* 重点 */
margin-right: 150px; /* 重点 */
}
.left {
float: left;
width: 100px;
margin-left: -100%; /* 重点 */
}
.center {
float: left;
width: 100%;
}
.right {
float: left;
width: 150px;
margin-left: -150px; /* 重点 */
}
</style>
<!-- 重点 -->
<div class="center">
<div class="main">center</div>
</div>
<div class="left">left</div>
<div class="right">right</div>
9.使用 flex 布局画骰子?
<style type="text/css">
.box {
width: 200px;
height: 200px;
border: 2px solid #ccc;
border-radius: 10px;
padding: 20px;
display: flex;
justify-content: space-between;
}
.item {
display: block;
width: 40px;
height: 40px;
border-radius: 50%;
background-color: #666;
}
.item:nth-child(2) {
align-self: center;
}
.item:nth-child(3) {
align-self: flex-end;
}
</style>
<div class="box">
<div class="item"></div>
<div class="item"></div>
<div class="item"></div>
</div>
flex-direction 主轴方向
;
justify-content 主轴对齐方式
;
align-items 主轴垂直对齐方式
;
flex-wrap 是否换行
;
align-self 子元素在交叉轴的对齐方式
10.relative 、absoulte、fixed 分别依据什么定位?
relative 依据自身定位
absoulte 依据最近一层的定位元素定位
fixed 依据浏览器窗口定位
定位元素有:relative、absoulte、fixed、body
11.居中对齐有哪几种实现方式?
居中对齐有水平居中、垂直居中
。
水平居中:2+ 3absoulte = 5种
inline 元素:text-align: center
block 元素:margin: auto
absoulte 元素:left: 50% + margin-left: 负值
absoulte 元素:left: 50% + left calc(50% - 值)
absoulte 元素:left: 50% + transform: translate(-50%, 0)
垂直居中:1+ 4absoulte = 5种
inline 元素:line-height = height
absoulte 元素:top: 50% + margin-top: 负值
absoulte 元素:top: 50% + top calc(50% - 值)
absoulte 元素:top: 50% + transform: translate(0, -50%,)
absoulte 元素:top/left/right/bottom 0 + margin:auto 0
水平垂直居中:3absolute + 7 = 10种
居中元素定宽高适用:3absolute = 3种
absolute + margin auto:absolute + top/left/right/bottom 0 + margin:auto
absolute + 负margin:absolute + top: 50% + left: 50% + margin-left: 负值 + margin-top: 负值
absolute + calc:absolute + top: 50% + left: 50% + top calc(50% - 值) + left calc(50% - 值)
居中元素不定宽高:1absolute + 6 = 7种
absolute + transform:absolute + top: 50% + left: 50% + transform: translate(-50%, -50%)
flex:flex + justify-content: center + align-items: center
grid:grid + justify-content: center + align-items: center
lineheight:line-height = height + text-align: center + vertical-align: middle
table:table td标签 + text-align: center
css-table:display: table-cell + text-align: center + vertical-align: middle
writing-mode:writing-mode: vertical-lr + writing-mode: horizontal-tb + text-align: center
12.line-height 的继承问题?
<style>
body {
font-size: 20px;
line-height: 200%;
}
p {
font-size: 16px;
}
</style>
<body>
<p>AAA</p>
</body>
如果 body line-height 为 200%,p 的 line-height 为多少?40px
(先算再继承该值)
如果 body line-height 为 20px,p 的 line-height 为多少?20px
(继承该值)
如果 body line-height 为 2, p 的 line-height 为多少?32px
(继承该比例再算)
13.如何实现响应式?
1.通过 media-query
媒体查询,根据不同的屏幕宽度
,用 rem
设置根元素font-size
。
<style>
@media only screen and (max-width: 374px) {
/** iphone5 或更小尺寸 **/
html {
font-size: 86px;
}
}
@media only screen and (min-width: 375px) and (max-width: 413px) {
/** iphone6/7/8/x **/
html {
font-size: 100px; // 100px = 1rem
}
}
@media only screen and (min-width: 414px) {
/** iphone6p 或更大尺寸 **/
html {
font-size: 110px;
}
}
body {
font-size: 0.16rem; // 在iphoen6 设备上相当于16px
}
</style>
这种方案就是著名的 手淘 lib-flexible + rem,解决方案。不过该方案已经被废弃了
2.通过vw + postcss 插件将 px 转换成vw
14.vw、vh、vmax、vmin 是什么?
window.screen.width // 屏幕宽度
window.screen.height // 屏幕高度
window.innerWidth // 网页视口宽度 100vw
window.innerHeight // 网页视口高度 100vh
document.body.clientWidth // body 宽度
document.body.clientHeight // body 高度
vw: 网页视口宽度的1/100
vh: 网页视口高度的1/100
vmax: 取两者最大值
vmin: 取两者最小值
可以理解成 vw 将手机屏幕网页视口宽度分成100份,1份 = 1vw; vh 将手机屏幕网页视口高度分成100份,1份 = 1vh。
15.值类型、引用类型两者的区别?分别有哪些?
值类型放在栈
中,栈中的 key 存变量名,value 存值
。
引用类型放在堆
中,栈中的 key 存变量名, value 存内存地址
;堆中的 key 存内存地址,value 存值
。
值类型有:undefined、string、number、boolean、symbol
引用类型有:function、object、array、null
const obj1 = {x: 100, y: 40}
const obj2 = obj1
let x1 = obj1.x
obj2.x = 101
x1 = 103
console.log(obj1.x) // 101
16.typeof 能判断哪些类型?如何再次细分引用类型?
typeof 能识别所有值类型
、识别函数
、判断是否是引用类型、但不可再细分
。
typeof undefined // undefined
typeof '' // string
typeof 0 // number
typeof true // boolean
typeof Symbol() // symbol
typeof (()=>{}) // function
typeof {} // object
typeof [] // object
typeof null // object
如何再次细分引用类型?
Object.prototype.toString.call()
Object.prototype.toString.call(()=>{}) // [object Function]
Object.prototype.toString.call({}) // [object Object]
Object.prototype.toString.call([]) // [object Array]
Object.prototype.toString.call(null) // [object Null]
17.=== 和 == 的区别?
=== 表示全等操作符
,== 表示相等操作符
。
除了 == null 之外,其他都一律用 ==
。
由于相等(==)和不相等(!==)操作符存在类型转换问题
,因此推荐使用全等(===)和不全等(!==)操作符。这样有助于在代码中保持数据类型的完整性。
const obj = { x: 100 }
if (obj.a == null) { }
// 相当于
if (obj.a === null || obj.a === undefined) { }
18.如何判断一个变量是不是数组?
共3种方式
Array.isArray([]) // true
Object.prototype.toString.call([]) // [object Array]
[] instanceof Array // true
19.什么是原型?什么是原型链?
原型:原型分为显式原型和隐式原型
1.每个类
都有显式原型 prototype
2.每个实例
都有隐式原型 __proto__
3.实例的__proto__指向类的prototype
(类与实例的关系)
Object.prototype // 显式原型
({}).__proto__ // 隐式原型
({}).__proto__ === Object.prototype // true
举例:
//父类
class People {
constructor(name) {
this.name = name
}
eat(){
console.log(`${this.name} eat something`)
}
}
//子类
class Student extends People {
constructor(name, number) {
super(name)
this.number = number
}
sayHi(){
console.log(`name: ${this.name} number: ${this.number}`)
}
}
class Teacher extends People{
constructor(name, major) {
super(name)
this.major = major
}
teach() {
console.log(`${this.name} teach: ${this.major}`)
}
}
const xialuo = new Student('夏洛', 100)
xialuo.name // 夏洛
xialuo.sayHi() // name: 夏洛 number: 100
// 隐式原型和显示原型
console.log(xialuo.__proto__) //实例的隐式原型
console.log(Student.prototype) //类的显示原型
console.log(xialuo.__proto__ === Student.prototype) // true
原型执行规则:获取属性xialuo.name或执行方法xialuo.sayHi()时,先在自身属性和方法寻找,如果找不到则自动去__proto__寻找
。
原型链:子类的prototype的__proto__指向其父类的prototype
(子类与父类的关系)
console.log(Student.prototype.__proto__) // 子类的prototype的__proto__
console.log(People.prototype) // 父类的prototype
console.log(Student.prototype.__proto__ === People.prototype)
console.log(People.prototype.__proto_) // 子类的prototype的__proto__
console.log(Object.prototype) // 父类的prototype
console.log(People.prototype.__proto__ === Object.prototype) // true
console.log(Object.prototype.__proto__) // null
总结一下:
原型说的是类与实例的关系
、原型链说的是子类与父类的关系
,Object没有父类
。
原型是实例的隐式原型指向类的显式原型
、原型链是子类的显示原型的隐式原型指向父类的显示原型
。
20.如何理解 instanceof,class?
instanceof 用法:实例 instanceof 类
instanceof原理:实例的隐式原型在原型链上找显式原型
,找到则true,找不到则false
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true
xialuo instanceof Array // false
class 用法:class T {}
class 原理:本质是函数function,ES6 语法糖
class T {}
typeof T // function
21.如何理解作用域、自由变量?
作用域:约束变量的区域
,分为:全局作用域、函数作用域、块级作用域
1.全局作用域:变量可全局使用
window // Window {0: Window, ...}
document // #document
localStorage // Storage {length: 0}
sessionStorage // Storage {length: 0}
history // History {length: 1, ...}
location // Location {ancestorOrigins: DOMStringList, ...}
navigator // Navigator {vendorSub: '', ...}
screen // Screen {availWidth: 1680, ...}
2.函数作用域:只能在定义的函数内使用
let a = 0
function fn1() {
let a1 = 100
function fn2() {
let a2 = 200
function fn3() {
let a3 = 300
return a + a1 + a2 + a3
}
return fn3()
}
return fn2()
}
// a3 变量只能在 fn3() 内使用,不能在 fn1()、fn2() 内使用
fn1() // 600
3.块级作用域:ES6 let、const
if(true) {
let x = 100
}
console.log(x) //Uncaught ReferenceError: x is not defined
自由变量:一个变量在当前作用域没定义但被使用
。
fn3() 内虽然没定义a、a1、a2
,但它们被使用
,可以把它们理解成自由变量
。
自由变量会向上级作用域,一层一层寻找,找到则取值,如果到全局作用域还是找不到,则报x is not defined
22.如何理解闭包?其常见的表现形式有哪些?
闭包:函数的定义和执行不在同一个作用域
常见表现形式:函数作为返回值
、函数作为参数
// 函数作为返回值
let a = 0
function fn1() {
let a1 = 100
function fn2() {
return a + a1
}
return fn2
}
// fn1 的返回值是 fn2 函数
const fn2 = fn1()
// 执行 fn2 函数
fn2() // 100
// 函数作为参数
let a = 0
function fn1(callback) {
let a1 = 100
return callback(a1)
}
function fn2(a1) {
return a + a1
}
// fn1 的参数是 fn2 函数
fn1(fn2) // 100
23.this 在不同应用场景,如何取值?
this 在不同应用场景,取值都是不一样的
。
有5种应用场景,如下:
1.作为普通函数
——返回window对象
2.使用 call apply bind
——传入什么绑定什么
3.在 对象方法中调用
——返回对象本身
4.在 class 方法中调用
——当前实例本身
5.箭头函数
——上级作用域
// 1.普通函数:返回window
function fn1() {
console.log(this)
}
fn1() // Window {0: Window, ...}
// 2.call apply bind:传入什么绑定什么
fn1.call({x:100}) // {x:100}
fn1.apply({x:100}) // {x:100}
const fn2 = fn1.bind({x:100})
fn2() // {x:100}
// 3.对象方法:返回对象本身
const order = {
name: 'JD001',
get() {
console.log(this)
}
}
order.get() // {name: 'JD001', get: ƒunciton}
// 4.class 方法:返回实例本身
class Order {
constructor(name) {
this.name = name
}
get () {
console.log(this)
}
}
const order = new Order('JD001')
order.get() // Order {name: 'JD001'}
// 注意:直接从隐式原型调用,this没有
order.__proto__.get() // undefined
order.__proto__.get.apply(order) // Order {name: 'JD001'}
// 5.箭头函数:上级作用域
const order = {
name: 'JD001',
asyncGet() {
setTimeout(function(){
console.log(this)
})
},
asyncGetAgain() {
setTimeout(() => {
console.log(this)
})
}
}
order.asyncGet() // Window {0: Window, ...}
order.asyncGetAgain() // Order {name: 'JD001'}
24.apply、call、bind 区别?
apply、call、bind都能改变this指向
,且它们都在Function类
中。
Function.prototype.apply // ƒ apply() { [native code] }
Function.prototype.call // ƒ call() { [native code] }
Function.prototype.bind // ƒ bind() { [native code] }
// 它们不在Object里
Object.prototype.apply // undefined
Object.prototype.hasOwnProperty // ƒ hasOwnProperty() { [native code] }
它们三者区别:
1.apply、call传参方式不同,一个[],一个,,,
2.bind、call使用方式一样,一个等待执行,一个立即执行
function fn1() {
console.log(this) // {x: 100}
console.log([...arguments]) // [1, 2, 3]
}
fn1.apply({x:100}, [1,2,3])
fn1.call({x:100}, 1,2,3)
fn1.bind({x:100},1,2,3)()
25.同步和异步的区别?
JS是单线程语言,只能同时做一件事
。
JS和 DOM渲染 共用同一个线程
,因此JS可修改DOM。
我们希望望遇到等待(网络请求,定时任务
)不能卡住,因此我们需要异步
。
同步会阻塞代码执行,异步不会阻塞代码执行
。
异步都是通过回调callback函数形式执行
。
26.什么是 event loop(事件循环/事件轮询)?
event loop 是异步回调的实现原理
。异步(ajax、setTimeout) 和 DOM事件 都是基于event loop实现。
27.描述下 event loop 的过程?
Call Stack:调用栈 Web APIs:调用setTimeout、setInterval Callback Queue:回调队列 Event Loop:事件循环
console.log('Hi')
setTimeout(function cb1(){
console.log('cb1')
}, 5000)
console.log('Bye')
1.同步代码,一行一行放在调用栈执行
;
2.异步代码,异步任务放入回调队列,等待时机
;
3.调用栈执行完同步代码,执行当前的微任务,尝试 DOM 渲染,触发 Event Loop开始工作
;
4.轮询查找回调队列的任务,有就放到调用栈去执行
;
5.然后继续轮询查找,执行
28.什么是宏任务和微任务,两者有什么区别?
宏任务(macroTask)、微任务(microTask)?
微任务:DOM渲染前触发的任务,如:Promise、async/await
;
宏任务:DOM渲染后触发的任务,如:setTimeout、setInterval、Ajax、DOM 事件
。
29.为什么微任务比宏任务执行更早?
微任务是 ES6 规定的,宏任务是 浏览器W3C 规定的
。
30.Promise 有哪三种状态?如何变化?
Promise三种状态:pending、fulfilled、rejected
;
两种变化:pending ---> fulfilled
或者 pendding ---> rejected
。
new Promise((r,e)=>setTimeout(()=>r()))
// Promise {<pending>} [[PromiseState]]: "fulfilled"
new Promise((r,e)=>setTimeout(()=>e()))
// Promise {<pending>} [[PromiseState]]: "rejected"
31.Promise的 then 和 catch 如何变化?
fulfilled 状态触发 then 回调,rejected 状态触发 catch 回调
。
resolve:
Promise.resolve(100).then(t=>t+10)
// Promise {<fulfilled>: 110}
Promise.resolve().then(()=>{throw new Error()})
// Promise {<rejected>: Error}
Promise.resolve().then(()=>{throw new Error('then error')}).catch((err=>console.error(err)))
// Promise {<fulfilled>: undefined}
reject:
Promise.reject('reject error')
// Promise {<rejected>: 'reject error'}
Promise.reject('reject error').catch(err=>console.error(err))
// Promise {<fulfilled>: undefined}
值得注意:then、catch正常执行完
返回fulfilled 状态
,有报错
返回rejected 状态
。
32.async/await 和 Promise 的关系是怎样的?
async/await 解决异步链式回调地域的语法糖
,用同步的写法实现异步,与 Promise 相辅相成,互补不冲突。
fulfilled 状态:
// 执行 async 函数返回 Promise 对象
async function fn1() {
// 相当于 return Promise.resolve(100)
return 100
}
fn1().then(t=>console.log(t)) // 100
// await = Promise的 then
// await 后面可追加 Promise 或者 async函数
async function fn1() {
const t = await Promise.resolve(100)
console.log(t) // 100
}
fn1()
rejected 状态:
// 执行 async 函数返回 Promise 对象
async function fn1() {
return Promise.reject('aync err')
}
fn1().catch(e=>console.error(e)) // aync err
// try...catch... = Promise的 catch
async function fn1() {
const t = await Promise.reject('aync err')
try {
console.log(t)
} catch(e) {
console.error(e) // aync err
}
}
fn1()
33.一道异步输出顺序的测试题?
async function async1() {
console.log('async1 start') // 2
await async2()
console.log('async1 end') // 6
}
async function async2() {
console.log('async2') // 3
}
console.log('script start') // 1
setTimeout(function(){
console.log('setTimeout') // 8
})
async1()
new Promise(function(reslove, reject){
console.log('promise1') // 4
reslove()
}).then(function(){
console.log('promise2') // 7
})
console.log('script end') // 5
输出顺序:
script start、async1 start、async2、promise1、 script end、async1 end、promise2、setTimeout
34.http 常见错误码有哪些?
1xx:服务器收到请求 2xx:请求成功,如:200成功 3xx:重定向,浏览器直接跳转: 如:301永久重定向(下次不会访问老地址)、 302临时重定向(下次还会访问老地址)、304资源未被修改 4xx:客户端错误,如:403权限、404资源找不到 5xx:服务端错误:如:500服务器错误、501、502、504网关超时
35.http 常见 header 有哪些?
request header:
accept、accept-encoding、accept-language、connection、cookie、host、user-agent
、
if-modified-since、if-none-match
response header:
access-control-allow-origin、access-control-allow-methods、access-control-allow-credentials
、
content-type、content-length、content-encoding、set-cookie
、
last-modified、etag、cache-control、expires
36.什么是 http 缓存?为什么需要缓存?哪些可以缓存?
什么是 http 缓存?将没必要重新请求的静态资源存在本地
。
为什么需要缓存?减少网络请求体积和数量,使页面加载更快
哪些可以缓存?静态资源js、css、img;html不行
37.描述下 http 的缓存机制?
http 缓存分为:强制缓存 cache-control、expires 和 协商缓存 etag、last-modified
。
描述强缓存:二次请求,不会向服务器发送请求,直接从本地缓存读取资源,返回 200 且 size 显示 from disk cache
。等缓存过期,才会再次向服务器发送请求。
实现强缓存:服务端在 response header 设置 cache-control 相应值来实现强缓存,expires 已过时
。
两个共存时:cache-control 的优先级高于 expires
。
描述协商缓存:二次请求,服务器去判断客户端资源,是否和服务器资源一样,一致则返回 304,否则返回 200 和最新的资源
实现协商缓存:服务端在 response header 设置 etag(资源唯一标识) 或 last-modified(资源最后修改时间) 来实现协商缓存。
两个共存时,etag 的优先级高于 last-modified
。
http 缓存总览图:
38.强制缓存 cache-control 常见有哪些值?分别代表什么意思?
cache-control值有:
s-maxage
:客户端缓存最大过期时间,包括代理服务器
max-age
:客户端缓存最大过期时间,不包括代理服务器
两个共存时:s-maxage 的优先级高于 max-age
。
no-cache
:不使用强缓存,可使用协商缓存
no-store
:不使用任何缓存
private
:资源仅能在客户端缓存
public
:资源在客户端和代理服务器都能缓存
假如:cache-control:max-age=3600,表示强制缓存3600秒,缓存1小时。在1小时内,直接从缓存中读取资源,只有1小时后,缓存过期了,才会再次请求服务器资源。
39.如何清除 http 缓存?
强制缓存策略在客户端,协商缓存策略在服务端。
正常操作:强制缓存没清除,协商缓存没清除
手动刷新:强制缓存清除,协商缓存没清除
强制刷新:强制缓存清除,协商缓存清除
40.http 和 https 区别?描述下 https 过程解析?
区别:是否加密
。
http 没有加密,https 有加密,包括非对称加密和对策加密。
描述下 https 过程解析:
四、一面(手写篇)
1.手写一个深拷贝?
typeof + instanceof + hasOwnProperty + 递归
function deepClone (obj = {}) {
if (typeof obj !== 'object' || obj == null) {
// 不是对象或数组,直接返回
return obj
}
// 初始化返回结果,判断是数组还是对象
let rst
if(obj instanceof Array) {
rst = []
} else {
rst = {}
}
// 迭代器
for (let key in obj) {
// 保证 key 不是原型的属性
if(obj.hasOwnProperty(key)) {
// 递归
rst[key] = deepClone(obj[key])
}
}
return rst
}
使用:
const obj1 = {
name: 'ww',
age: 30,
address: {
city: 'chengdu'
},
arr: ['a', 'b', 'c']
}
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
console.log(obj1.address.city) // chengdu
2.手写一个简易版 jQuery?
class + extends + prototype + querySelectorAll + addEventListener
class jQuery {
constructor(selector) {
const rst = document.querySelectorAll(selector)
const len = rst.length
for (let i = 0; i < len; i++) {
this[i] = rst[i]
}
this.length = len
this.selector = selector
}
get(index) {
return this[index]
}
each(fn) {
for(let i = 0 ; i < this.length; i++) {
const elem = this[i]
fn(elem)
}
}
on(type, fn) {
return this.each(elem => {
elem.addEventListener(type, fn, false)
})
}
// 扩展操作DOM API
}
// jQuery插件
jQuery.prototype.dialog = function(info) {
alert(info)
}
// 继承
class myJQuery extends jQuery {
constructor(selector) {
super(selector)
}
// 扩展自己的方法
addClass(className) {
}
}
使用:
<div class="box">
<p>one</p>
<p>two</p>
<p>three</p>
</div>
window.onload = function() {
const $p = new jQuery('p')
console.log($p.get(0))
$p.each(elem => console.log(elem.innerText))
$p.on('click', ()=> alert('click'))
$p.dialog('hello')
}
3.手写一个 bind 函数?
使用Function.prototype.apply
Function.prototype.myBind = function() {
// 参数拆解为数组
const args = [...arguments]
// 获取 this (数组第一项)
const t = args.shift()
// 获取 fn1.myBind()中 的 fn1
const self = this
// 返回一个函数
return function() {
return self.apply(t , args)
}
}
使用:
function fn1(a,b) {
console.log(this)
console.log(a, b)
return 'this is fn1'
}
const fn2 = fn1.myBind({x:200}, 10, 20)
fn2()
4.手写一个可缓存其他函数的高阶函数?
使用Function.prototype.apply
const memoize = fn => {
const cache = {}
return function (...args) {
const _args = JSON.stringify(args)
return cache[_args] || (cache[_args] = fn.apply(fn,args))
}
}
使用:
/** 求平方根 */
function sqrt(n) {
return Math.sqrt(n)
}
const cachedSqrt = memoize(sqrt)
cachedSqrt(4) // 2
cachedSqrt(4) // 第二次,不经过计算,直接输出结果2
5.手写一个柯里化函数?
使用递归
const currying = (fn, arr = []) => {
const len = fn.length
return function(...args) {
arr = [...arr, ...args]
if(arr.length < len) {
return currying(fn, arr)
} else {
return fn(...arr)
}
}
}
使用:
function sum(a,b,c,d,e,f){
return a+b+c+d+e+f
}
currying(sum)(1,2)(3,4)(5)(6) //21
6.手写一个简易版 Promise?
console.log('Promise A+')
/**
* MyPromise
*/
class MyPromise {
state = 'pending' // pending fulfilled rejected
value = undefined // fulfilled value
reason = undefined // rejected reason
resolveCallbacks = [] // fulfilled callback in pending status
rejectCallbacks = [] // rejected callback in pengding status
constructor(fn) {
const resolveHandler = (value) => {
if(this.state === 'pending') {
this.state = 'fulfilled'
this.value = value
this.resolveCallbacks.forEach(fn => fn(this.value))
}
}
const rejectHandler = (reason) => {
if(this.state === 'pending') {
this.state = 'rejected'
this.reason = reason
this.rejectCallbacks.forEach(fn => fn(this.reason))
}
}
try {
fn(resolveHandler, rejectHandler)
} catch (err) {
rejectHandler(err)
}
}
then(fn1, fn2) {
// fn1、fn2 in pending, need add in resolveCallbacks、rejectCallbacks
fn1 = typeof fn1 === 'function' ? fn1 : (v) => v
fn2 = typeof fn2 === 'function' ? fn2 : (e) => e
// return new promise
if(this.state === 'pending') {
const p = new MyPromise((resolve, reject)=>{
this.resolveCallbacks.push(()=>{
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch(err) {
reject(err)
}
})
this.rejectCallbacks.push(()=>{
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch(err) {
reject(err)
}
})
})
return p
}
if(this.state === 'fulfilled') {
const p = new MyPromise((resolve, reject)=>{
try {
const newValue = fn1(this.value)
resolve(newValue)
} catch(err) {
reject(err)
}
})
return p
}
if(this.state === 'rejected') {
const p = new MyPromise((resolve, reject)=>{
try {
const newReason = fn2(this.reason)
reject(newReason)
} catch(err) {
reject(err)
}
})
return p
}
}
catch(fn) {
// like then
return this.then(null, fn)
}
static resolve(value) {
return new MyPromise((resolve, reject)=>resolve(value))
}
static reject(err) {
return new MyPromise((resolve, reject)=>reject(err))
}
static all(list = []) {
const p = new MyPromise((resolve, reject)=>{
let count = 0
const rst = []
list.forEach(promise => {
promise.then(data => {
rst.push(data)
count++
if(count === list.length) {
resolve(rst)
}
}).catch(err => reject(err))
})
})
return p
}
static race(list = []) {
let resolved = false
const p = new MyPromise((resolve, reject)=>{
list.forEach(promise => {
promise.then(data => {
if(!resolved) {
resolve(data)
resolved = true
}
}).catch(err => reject(err))
})
})
return p
}
}
使用:
const p = new MyPromise((resolve, reject)=>{
// resolve(100)
// reject('错误')
setTimeout(()=>resolve(100),1000)
})
console.log(p)
const p1 = p.then(data=>data+1)
const p2 = p1.then(data=>data+2)
.then(data=>console.log(data))
.catch(err=>{console.error('err', err)})
MyPromise.resolve(200).then(data=>console.log(data))
MyPromise.reject('错误').catch(err=>console.error(err))
// 传入 prmoise 数组,pending 都变成 fulfilled 后,返回 新 promise,包含 所有的结果
MyPromise.all([p, p1, p2]).then(data=>console.log(data))
// 传入 promise 数组,pending 只要有一个 fulfilled 后,返回 新 promise
MyPromise.race([p, p1, p2]).then(data=>console.log(data))
7.手写一个事件代理,事件绑定函数?
addEventListener + matches + call
function bindEvent(element, type, selector, fn) {
// 三个参数的情况
if(fn == null) {
fn = selector
selector = null
}
element.addEventListener(type , event => {
const target = event.target
// 四个参数的情况
if(selector) {
// 代理绑定,当前触发的元素
if (target.matches(selector)) {
fn.call(target, event)
}
} else {
// 普通绑定,当前触发的元素
fn.call(target, event)
}
})
}
使用:
// 代理绑定
const ul = document.getElementById('ul')
bindEvent(div, 'click', 'li', ()=>alert('click'))
8.手写一个简易版 ajax?
XMLHttpRequest + Promise:
// ajax
const ajax = function(url, data) {
const p = new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest()
xhr.open('GET', url, true)
xhr.onreadystatechange = function(){
if(xhr.readyState === 4) {
if(xhr.status === 200) {
resolve(JSON.parse(xhr.responseText))
} else if(xhr.status === 404) {
reject(new Error('404 not found'))
} else if(xhr.status === 500) {
reject(new Error('500 server error'))
}
}
}
xhr.send(null)
})
return p
}
使用:
ajax('https://m.sredy.cn/api/sredy/getAlbumList', {pageNo:1, pageSize: 20})
.then(data=>console.log(data)).catch(err=>console.log(err))
9.手写一个防抖 debounce 函数?
防抖:用户输入结束或暂停时,才会触发 change 事件
const debounce = function(fn, delay = 500) {
let timer = null
return function () {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, [...arguments])
timer = null
}, delay)
}
}
使用:
const input = document.getElementById('input')
input.addEventListener('keyup', debounce(()=>console.log(input.value), 1000))
10.手写一个节流 throttle 函数?
节流:无论拖拽速度多快,都会每隔 100 ms触发一次
const throttle = function(fn, delay = 100) {
let timer = null
return function () {
if(timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, [...arguments])
timer = null
}, delay)
}
}
使用:
const div = document.getElementById('drag')
div.addEventListener('drag', throttle((e)=>console.log(e.offsetX , e.offsetY), 600))
11.手写一个获取最大值 max 函数?
function max(){
const nums = [...arguments]
let max = 0
nums.forEach(n => {
if(n > max) {
max = n
}
})
return max
}
使用:
max(1,2,3,100,200) // 200