STAY HUNGRY , STAY FOOLISH.

求知若饥,虚心若愚。

       浏览:

来自工作近10年前端开发的吐槽

本篇内容如下:

  • 一、吐槽前端开发
    • 前端是一个持续学习才能胜任的工作
    • 前端卷的不行,面试造火箭,入职拧螺丝
  • 二、吐槽成都前端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 过程解析?
  • 四、一面(手写篇)
    • 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 的前端开发

boss1


boss2


boss3

总结了下一般 HR 都会问的问题。
您好,请问您这边学历是全日制的还是非全日制?
您是否有学位证呢?总部会卡双证这块。
你这边英文怎么样呢?英语口语怎么样?
要是你英文好点也可以,但是你工资太高了,我们这边给不到的。英文也是个问题啊,不好意思。。。
你的工资要求超出我们的范围的。
你的工资是有些高,至少我们这个职位给不了。


成都 22k 对我来说是个什么水平呢? 7年前我在上海的水平

xinshui


成都朋友一直跟我说,不要拿上海的薪资去和成都对比,这不公平。尽管他说的对,但是反问一下自己,假如自己拿着7年前上海的工资在成都工作,平时加班到无所谓,如果公司是外包、外派性质,周末并非双休,是大小休,甚至单休,性价比真的高吗?

假设一:
成都22k = 7年前上海工资 + 周末双休 + 全额社保公积金,
勉强能接受,尽管不是很情愿。


假设二:
成都22k = 7年前上海工资 + 周末单休 + 外包/外派 + 最低基数社保公积金,
完全接受不了


假设三:
成都26k = 7年前上海工资 + 周末双休 + 全额社保公积金,
完美,这是我在成都最最理想的公司。


假设四:
成都26k = 7年前上海工资 + 周末单休 + 外包/外派 + 最低基数社保公积金,,
当时能接受,但长期看,我应该也不会接受
理由:尽管自己快30岁,但到35岁找工作不好找,还是希望自己尽量找一个业务稳定的公司,长久干下去。


分析一波,那么问题就变得特别简单,假如自己在成都能够找到22k+、周末双休、全额社保公积金、业务稳定的公司,是可以回来的。
但目前成都的行情,能找到这样的公司,还是需要花费不少功夫的。
既然改变不了环境就改变自己,那么后面开始系统回顾下前端基础知识、算法、数据结构、项目经验来应对后面残酷的面试筛选

在写技术前,讲个题外话,
我在是否继续留在上海,或是重回故乡成都这一事情上,自己其实纠结了很久。
既然成都环境不容乐观,为什么还要从上海回到成都?
理由1:自己在上海的目的基本都已实现,房车有了,学历提升了,能力认知有了,成都的房子装修好了
理由2:钱挣再多也买不了亲情,是时候回来多陪陪自己和老婆的父母
理由3:上海没房,始终成本太高,出于成本考虑
理由4:小孩读书问题,孩子教育挺重要的,家在成都,教育资源相比没落户上海的资源好
点到为止,对目前的自己来说,这个事情不单单是一个人的事情,而是一个家庭的事情,自己的决定会影响整个家庭的幸福


三、一面(基础知识篇)

1.如何理解语义化?

1
2
3
4
5
6
7
8
<div>标题</div>
<div>
<div>一段文字</div>
<div>
<div>列表一</div>
<div>列表二</div>
</div>
</div>
1
2
3
4
5
6
7
8
<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.盒模型宽度如何计算?

1
2
3
4
5
6
7
8
9
<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

1
box-sizing: border-box

4.margin 纵向重叠问题?

相邻的margin-top、marin-bottom会发生重叠

1
2
3
4
5
6
7
8
9
10
<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: bothoverflow: hiddenclearfix

1
2
3
4
5
.clearfix:after {
content: ''; /* 生成内容为空 */
display: table; /* 转为块级元素 使用display: block; 也可以 */
clear: both; /* 清除浮动 */
}

8.实现一个圣杯布局?双飞翼布局?

圣杯布局和双飞翼布局解决的问题:两边顶宽,中间自适应的三栏布局

圣杯布局:float + padding + margin-left + right + margin-right

shengbei

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<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

shuangfeiyi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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 布局画骰子?

shaizi

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<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 的继承问题?

1
2
3
4
5
6
7
8
9
10
11
12
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
<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 是什么?

1
2
3
4
5
6
7
8
window.screen.width // 屏幕宽度
window.screen.height // 屏幕高度

window.innerWidth // 网页视口宽度 100vw
window.innerHeight // 网页视口高度 100vh

document.body.clientWidth // body 宽度
document.body.clientHeight // body 高度
1
2
3
4
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

1
2
3
4
5
6
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 能识别所有值类型识别函数判断是否是引用类型、但不可再细分

1
2
3
4
5
6
7
8
9
10
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()

1
2
3
4
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 之外,其他都一律用 ==
由于相等(==)和不相等(!==)操作符存在类型转换问题,因此推荐使用全等(===)和不全等(!==)操作符。这样有助于在代码中保持数据类型的完整性。

1
2
3
4
const obj = { x: 100 }
if (obj.a == null) { }
// 相当于
if (obj.a === null || obj.a === undefined) { }

18.如何判断一个变量是不是数组?

共3种方式

1
2
3
Array.isArray([]) // true
Object.prototype.toString.call([]) // [object Array]
[] instanceof Array // true

19.什么是原型?什么是原型链?

原型:原型分为显式原型和隐式原型
1.每个都有显式原型 prototype
2.每个实例都有隐式原型 __proto__
3.实例的__proto__指向类的prototype(类与实例的关系)

1
2
3
Object.prototype // 显式原型
({}).__proto__ // 隐式原型
({}).__proto__ === Object.prototype // true

举例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//父类
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}`)
}
}

prototype

1
2
3
4
5
6
7
8
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(子类与父类的关系)

prototype_link

1
2
3
4
5
6
7
8
9
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

1
2
3
4
5
xialuo instanceof Student // true
xialuo instanceof People // true
xialuo instanceof Object // true

xialuo instanceof Array // false

class 用法:class T {}
class 原理:本质是函数function,ES6 语法糖

1
2
class T {}
typeof T // function

21.如何理解作用域、自由变量?

作用域:约束变量的区域,分为:全局作用域、函数作用域、块级作用域

1.全局作用域:变量可全局使用

1
2
3
4
5
6
7
8
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.函数作用域:只能在定义的函数内使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
if(true) {
let x = 100
}
console.log(x) //Uncaught ReferenceError: x is not defined

自由变量:一个变量在当前作用域没定义但被使用

zuoyongyu

fn3() 内虽然没定义a、a1、a2,但它们被使用,可以把它们理解成自由变量

自由变量会向上级作用域,一层一层寻找,找到则取值,如果到全局作用域还是找不到,则报x is not defined


22.如何理解闭包?其常见的表现形式有哪些?

闭包:函数的定义和执行不在同一个作用域
常见表现形式:函数作为返回值函数作为参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 函数作为返回值
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// 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类中。

1
2
3
4
5
6
7
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使用方式一样,一个等待执行,一个立即执行

1
2
3
4
5
6
7
8
9
10
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 的过程?

event_loop

Call Stack:调用栈
Web APIs:调用setTimeout、setInterval
Callback Queue:回调队列
Event Loop:事件循环

1
2
3
4
5
6
7
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

1
2
3
4
5
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 回调

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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 状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 执行 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 状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 执行 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.一道异步输出顺序的测试题?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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

cache

描述强缓存:二次请求,不会向服务器发送请求,直接从本地缓存读取资源,返回 200 且 size 显示 from disk cache。等缓存过期,才会再次向服务器发送请求。

实现强缓存:服务端在 response header 设置 cache-control 相应值来实现强缓存,expires 已过时

两个共存时:cache-control 的优先级高于 expires


etag

描述协商缓存:二次请求,服务器去判断客户端资源,是否和服务器资源一样,一致则返回 304,否则返回 200 和最新的资源

实现协商缓存:服务端在 response header 设置 etag(资源唯一标识) 或 last-modified(资源最后修改时间) 来实现协商缓存。

两个共存时,etag 的优先级高于 last-modified


http 缓存总览图:

http-cache


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 过程解析:

https


四、一面(手写篇)

1.手写一个深拷贝?

typeof + instanceof + hasOwnProperty + 递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
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) {

}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
<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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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)
}
}

使用:

1
2
3
4
5
6
7
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

1
2
3
4
5
6
7
const memoize = fn => {
const cache = {}
return function (...args) {
const _args = JSON.stringify(args)
return cache[_args] || (cache[_args] = fn.apply(fn,args))
}
}

使用:

1
2
3
4
5
6
7
/** 求平方根 */
function sqrt(n) {
return Math.sqrt(n)
}
const cachedSqrt = memoize(sqrt)
cachedSqrt(4) // 2
cachedSqrt(4) // 第二次,不经过计算,直接输出结果2

5.手写一个柯里化函数?

使用递归

1
2
3
4
5
6
7
8
9
10
11
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)
}
}
}

使用:

1
2
3
4
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?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
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
}
}

使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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)
}
})
}

使用:

1
2
3
// 代理绑定
const ul = document.getElementById('ul')
bindEvent(div, 'click', 'li', ()=>alert('click'))

8.手写一个简易版 ajax?

XMLHttpRequest + Promise:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 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
}

使用:

1
2
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 事件

1
2
3
4
5
6
7
8
9
10
11
12
const debounce = function(fn, delay = 500) {
let timer = null
return function () {
if(timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, [...arguments])
timer = null
}, delay)
}
}

使用:

1
2
3
const input = document.getElementById('input')

input.addEventListener('keyup', debounce(()=>console.log(input.value), 1000))

10.手写一个节流 throttle 函数?

节流:无论拖拽速度多快,都会每隔 100 ms触发一次

1
2
3
4
5
6
7
8
9
10
11
12
const throttle = function(fn, delay = 100) {
let timer = null
return function () {
if(timer) {
return
}
timer = setTimeout(() => {
fn.apply(this, [...arguments])
timer = null
}, delay)
}
}

使用:

1
2
const div = document.getElementById('drag')
div.addEventListener('drag', throttle((e)=>console.log(e.offsetX , e.offsetY), 600))

11.手写一个获取最大值 max 函数?

1
2
3
4
5
6
7
8
9
10
function max(){
const nums = [...arguments]
let max = 0
nums.forEach(n => {
if(n > max) {
max = n
}
})
return max
}

使用:

1
max(1,2,3,100,200) // 200