http+作用域和闭包+异步和单线程+异步--进阶

129 阅读7分钟

一、http面试

1、状态码

1.1 状态码分类

  • 1xx 服务器收到请求
  • 2xx 请求成功-如200
  • 3xx 重定向-如302
  • 4xx 客户端错误-如404
  • 5xx 服务端错误-如500

1.2 常见状态码

  • 200 成功
  • 301 永久重定向(配合location,浏览器自动处理)
  • 302 临时重定向(配合location,浏览器自动处理)
  • 304 资源未被修改--缓存中很重要
  • 404 资源未找到
  • 403 没有权限-如未登录或权限等
  • 500 服务器错误
  • 504 网关超时

2、http methods

2.1 传统methods

  • get 获取服务器数据
  • post 服务器提交数据

2.2 现在methods

  • get 获取数据
  • post 新建数据
  • patch、put 更新数据
  • delete 删除数据

2.3 Restful API

  • 是一种新的API设计方法
  • 传统API设计是把每一个url当作一个功能(function)
  • Restful API是把每一个url当作一个唯一的资源的标识

2.4 如何设计成一个资源

  • 尽量不用url参数,如(/api/list?pageIndex=2)
  • 用method表示操作类型,如(/api/list/2)

3、http headers

3.1 常见的Request Headers

  • Accept 浏览器可接收的数据格式
  • Accept-Encoding 浏览器可接收的压缩算法,如gzip
  • Accept-Languange 浏览器可接收的语言,如zh-CN
  • Connection:keep-alive 一次TCP链接重复使用
  • cookie
  • Host
  • User-Agent(简称UA)浏览器信息,如什么浏览器
  • Content-type 发送数据的格式,如application/json

3.2 常见的Response Headers

  • Content-type 返回数据的格式,如application/json
  • Content-length 返回数据的大小,多少字节
  • Content-Encoding 返回的压缩算法,如gzip
  • Set-Cookie

3.3 缓存相关的headers

  • Cache-Control Expries
  • Last-Modified If-Modified-Since
  • Etag If-None-Match

4、http缓存

4.1 关于缓存介绍

  • 何为缓存?
  • 为什么需要缓存?
  • 哪些资源可以被缓存?----静态资源(js、css、img)

4.2 http缓存策略(强制缓存+协商缓存)

4.21 强制缓存

  • 在Response Headers中
  • 控制强制缓存的逻辑
  • 例如Cache-Control:max-age=31536000(单位是秒)
  • Cache-Control的值
  • max-age 缓存过期事件
  • no-cache 不用强制缓存,正常向服务器请求
  • no-store 不用强制缓存,也不用服务端做缓存,直接让服务端重新返回一份资源
  • private 只能用户做缓存
  • public 中间代理也可做缓存
  • 关于Expires
  • 同在Response Headers中
  • 同为控制缓存过期
  • 已被Cache-Control代替

4.22 协商缓存

  • 服务器端缓存策略----服务端来判断一个是否可以被缓存
  • 服务器判断客户端资源,是否和服务端资源一样
  • 一致则返回304,否则返回200和最新的资源

4.23 资源标识

  • 在Response Headers中,有两种
  • Last-Modified If-Modified-Since 资源的最后修改时间
  • Etag If-None-Match 资源的唯一标识(一个字符串,类似人类的指纹)

4.24 Last-Modified和Etag

  • 会优先使用Etag
  • Last-Modified只能精确到秒级
  • 如果资源被重复生成,而内容不变,则Etag更精准

二、作用域和闭包

1、作用域和自由变量

  • 某个变量合法的使用范围
  • 全局作用域、函数作用域、块级作用域(ES6新增)
  • 自由变量
  • 一个变量在当前作用域没有定义,但被使用了
  • 向上级作用域。一层一层依次寻找,直至找到为止
  • 如果到全局作用域都还没找到,则报错xx is not defined
  • 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方查找

2、闭包

  • 实际上是作用域应用的特殊情况,有两种表现
  • 1、函数作为参数被传递
  • 2、函数作为返回值被返回
  • 所有的自由变量的查找,是在函数定义的地方,向上级作用域查找,不是在执行的地方查找

3、this几种赋值情况

  • 作为普通函数去调用
  • 使用call apply bind
  • 作为对象方法去调用
  • 在class方法中去调用
  • 箭头函数
  • 重点:this取何值,是在函数执行的时候确定的,不是在函数定义的时候定义的,以上五种场景都适用

4、this在不同应用场景下,应该如何取值?重要之中的重要

5、手写bind函数

6、闭包在实际开发中的使用场景,举例说明

  • 隐藏数据
  • 如做一个简单的cache工具

三、异步和单线程

1、两者区别

为什么会有异步
1、js是单线程语言,只能同时做一件事
2、浏览器和nodejs已经支持JS启动进程,如Web Worker
3JSDOM渲染共用同一个进程,因为JS可以修改DOM结构
4、如果遇到等待(如网络请求,定时任务)时,不能卡住,所以需要异步
5、异步是基于callback回调函数形式来调用的
6、基于JS是单线程语言
7、异步不会阻塞代码执行,同步会阻塞代码执行

callback hell 和 Promise
1、callback回调地狱
2Promise正是解决回调地狱的方法

2、手写Promise加载一张图片

function loadImage(src) {
    return new Promise((resolve,reject)=>{
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
}

const url = 'https://i0.hdslb.com/bfs/banner/01155bdb8cb180cdae85209e1251261727eae95b.jpg@976w_550h_1c.webp'

loadImage(url).then(img => {
    console.log(img.width)
    return img
}).then(img => {
    console.log(img.height)
}).catch(ex => console.error(ex))

3、前端使用异步的场景有哪些

  • 网络请求,如Ajax图片加载
  • 定时任务,如setTimeout
  • 这两个时候,cpu是空闲的,不能浪费资源

四、异步--进阶

1、event loop

1.1 请描述event loop(事件循环/事件轮询)的机制,可画图

  • js是单线程运行的
  • 异步要基于回调来实现
  • event loop就是异步回调的实现原理
  • js是从前到后,一行一行执行,如果某一行执行报错,则停止下面的代码的执行
  • 先把同步代码执行完,再执行异步

1.11 总结event loop过程1

  • 同步代码,一行一行放在Call Stack执行
  • 遇到异步,会先“记录”下,等待时机(定时、网络请求等)
  • 时机到了,就移动到Callback Queue

1.12 总结event loop过程2

  • 如Call Stack为空(即同步代码执行完)Event Loop开始工作
  • 轮询查找Callback Queue,如有则移动到Call Stack执行
  • 然后继续轮询查找(永动机一样)

2、promise进阶

2.1 Promise有哪三种状态?

pending resolved rejected
pending->resolved或pending->rejected
变化不可逆

2.2 如何表现和变化?

pending状态,不会触发then和catch
resolved状态,会触发后续的then回调函数
rejected状态,会触发后续的catch回调函数
const p1 = Promise.resolve(100);
console.log('p1',p1)
p1.then(data=>{
    console.log('data',data); //100
}).catch(err=>{
    console.log('err', err);
})

const p2 = Promise.reject('error');
console.log('p2',p2)
p1.then(data=>{
    console.log('data',data); //100
}).catch(err=>{
    console.log('err2', err);
})

2.3 then和catch对状态的影响?

then正常返回resolved,里面有报错则返回rejected
const p1 = Promise.resolve().then(() => {
    return 100
})
console.log('p1', p1); //resolved状态,会触发后续的then回调函数
p1.then(()=>{
    console.log('123');
})

const p2 = Promise.resolve().then(() => {
    throw new Error('then error')
})
console.log('p2', p2); //rejected状态,会触发后续的catch回调函数
p2.then(()=>{
    console.log('456');
}).catch(err=>{
    console.log('err100', err);
})
catch正常返回resolved,里面有报错则返回rejected
const p3 = Promise.reject('my err').catch(err=>{
    console.error('err', err);
})
console.log('p3', p3);
p3.then(()=>{
    console.log(10000);
})

const p4 = Promise.reject('my err').catch(err=>{
    throw new Error('catch error');
})
console.log('p4', p4);
p4.then(()=>{
    console.log(20000);
}).catch(()=>{
    console.log('some error');
});

2.4 场景题-promise then和catch的连接(返回什么状态,触发后续什么回调函数)

// 第一题
Promise.resolve().then(()=>{ //Promise.resolve()返回resolved状态,会触发后续的then回调函数
    console.log(1); //1
}).catch(()=>{
    console.log(2); // 上一步触发then,所以这里不打印
}).then(()=>{
    console.log(3); // 3
})

// 第二题
Promise.resolve().then(()=>{ //Promise.resolve()返回resolved状态,会触发后续的then回调函数
    console.log(1); //1
    throw new Error('error'); // then正常返回resolved,里面有报错则返回rejected,会触发后续的catch回调函数
}).catch(()=>{
    console.log(2); // 上一步触发catch,所以这里打印2,catch正常返回resolved状态,触发then
}).then(()=>{
    console.log(3); // 3
})

// 第三题
Promise.resolve().then(()=>{ //Promise.resolve()返回resolved状态,会触发后续的then回调函数
    console.log(1); //1
    throw new Error('error'); // then正常返回resolved,里面有报错则返回rejected,会触发后续的catch回调函数
}).catch(()=>{
    console.log(2); // 上一步触发catch,所以这里打印2,catch正常返回resolved状态,触发then
}).catch(()=>{
    console.log(3); // 这里不被执行
})

3、async、await

3.1 背景

异步回调callback hell
开始是用Promise then catch链式调用,但是也是基于回调函数
但是有了async/await是同步语法,彻底消灭回调函数

3.2 场景题-async/await语法

function loadImage(src) {
    return new Promise((resolve, reject) => {
        const img = document.createElement('img')
        img.onload = () => {
            resolve(img)
        }
        img.onerror = () => {
            reject(new Error(`图片加载失败 ${src}`))
        }
        img.src = src
    })
}

const src1 = 'https://img.bosszhipin.com/beijin/upload/image/20191225/3f7fda0998317f22ec614bfc392848b9.jpg?x-oss-process=image/format,jpg'
const src2 = 'https://img.bosszhipin.com/beijin/upload/image/20191225/719b5568228bda8229408e1401457f13.jpg?x-oss-process=image/format,jpg';

(async function(){
    //同步的写法
    
    //img1
    const img1 = await loadImage(src1)
    console.log(img1.width,img1.height)

    //img2
    const img2 = await loadImage(src2)
    console.log(img2.width,img2.height)
})()

3.3 async/await和Promise的关系

async/await是消灭异步的终极武器
但是和Promise并不互斥不冲突
反而两者相辅相成
1、执行async函数,返回的是Promise对象
async function fn1() {
    // return 100 // 相当于是 return Promise.resolve(100)
    return Promise.resolve(200);
}

const res1 = fn1() // 执行async函数,返回的是一个Promise对象
console.log('res1', res1); // Promise对象
res1.then(data => {
    // console.log('data', data); // data 100
    console.log('data', data); // data 200
});
2await相当于Promise的then,(注)
!(async function(){
    const p1 = Promise.resolve(300)
    const data = await p1 // await后跟着一个Promise 那么 await 相当于 Promise then
    console.log("data", data); // 300
})()

!(async function () {
    const data1 = await 400 // await后跟着一个值 可以理解为 await Promise.resolve(400)
    console.log("data1", data1); // 300
})()

!(async function () {
    const data2 = await fn1() // fn1返回的是一个Promise对象,await相当于then
    console.log("data2", data2); // 200
})()

// 注
!(async function () {
    const p5 = Promise.reject('err222'); // rejected状态
    const res = await p5 // await相当于then,但是现在是rejected状态,(返回什么状态,触发后续什么回调函数),rejected状态,会触发后续的catch回调函数
    console.log(res);
})()
3try...catch可捕获异常,代替了Promisecatch
!(async function () {
    const p4 = Promise.reject('err111'); // rejected状态
    try {
        const res = await p4
        console.log(res);
    } catch (ex) {
        console.error(ex) // try...catch相当于promise catch
    }
})()

3.4 异步的本质

async/await只是一个语法糖
// 例1
async function async1() {
    console.log("async1 start"); // 2
    await async2()
    // await 的后面,都可以看作是callback里的内容,即异步,包括下一步的log
    // 类似,event loop , setTimeout(cb1)
    // setTimeout(function(){console.log("async1 end")})或
    // Promise.resolve().then(()=>{console.log("async1 end"))
    console.log("async1 end"); // 5
}

async function async2() {
    console.log("async2"); // 3
}

console.log("script start"); // 1
async1()
console.log("script end"); // 4
// 4时,这时同步代码已经执行完毕


// 例2
async function async1() {
    console.log("async1 start"); // 2
    await async2()

    // 下面三行都是异步回调callback的内容
    console.log("async1 end"); // 5
    await async3()

        // 下面一行是异步回调callback的内容
        console.log("async1 end 2"); // 7
}

async function async2() {
    console.log("async2"); // 3
}

async function async3() {
    console.log("async3"); // 6
}

console.log("script start"); // 1
async1()
console.log("script end"); // 4
// 4时,这时同步代码已经执行完毕

3.5 for...of

for...in(以及forEach for)都是常规的同步遍历
for...of常用于异步的遍历
function muti(num) {
    return new Promise(resolve => {
        setTimeout(() => {
            resolve(num * num)
        }, 1000)
    })
}

const nums = [1, 2, 3];
// 同步循环
nums.forEach(async (i) => {
    const res = await muti(i)
    console.log(res);
})

// 异步循环
!(async function () {
    for (let i of nums) {
        const res = await muti(i);
        console.log(res);
    }
})()

4、微任务(macroTask)和宏任务(microTask)

4.1 什么是宏任务和微任务?

宏任务:setTimeoutsetIntervalAjaxDOM事件(不是异步,但是也是依赖event loop来执行的,所以也把它放在这了)
微任务:Promise async/await
微任务执行时机比宏任务要早(先记住)
代码演示
console.log(100); // 1
// 宏任务
setTimeout(() => {
    console.log(200); // 4
})
// 宏任务
setTimeout(() => {
    console.log(201); // 5
})
// 微任务
Promise.resolve().then(() => {
    console.log(300); // 3
})
console.log(400); // 2

4.2 event loop 和 DOM渲染

再次回顾一遍event loop的过程
JS是单线程的,而且和DOM渲染共用一个线程
JS执行的时候,得留一些时机供DOM渲染
每次Call Stack 清空(即每次轮询结束),即同步任务执行完毕,都是DOM重新渲染的机会,DOM结构如有改变则重新渲染
然后再去触发下一次event loop

4.3 两者有什么区别?

宏任务:DOM渲染后触发,如setTimeout
微任务:DOM渲染前触发,如Promise

4.4 从event loop 解释,为什么微任务执行更早

微任务、宏任务、DOM渲染的关系
1、Call Stack清空-->2、执行当前的微任务-->3、尝试DOM渲染-->4、触发event 
微任务是ES6语法规定的
宏任务是由浏览器规定的