Blog Logo

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

写于2023-03-10 16:31 阅读耗时41分钟 阅读量


本篇内容如下:

  • 一、吐槽前端开发
    • 前端是一个持续学习才能胜任的工作
    • 前端卷的不行,面试造火箭,入职拧螺丝
  • 二、吐槽成都前端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.如何理解语义化?

<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: leftposition: absoulte/fixedoverflow: hiddendisplay: flex/inline-block


7.清除浮动有几种方法?手写 clearfix?

3种,clear: bothoverflow: hiddenclearfix

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

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

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

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

shengbei

<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

<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

<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}`)
  }
}

prototype

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

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

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

zuoyongyu

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 的过程?

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-agentif-modified-since、if-none-match

response header: access-control-allow-origin、access-control-allow-methods、access-control-allow-credentialscontent-type、content-length、content-encoding、set-cookielast-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 + 递归

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
Headshot of Maxi Ferreira

怀着敬畏之心,做好每一件事。