专栏视角
通过浏览器搭建前端知识体系:
- 1.1 js的异步编程模型
页面都是执行在主线程上,而一些耗时的任务,如:下载网络文件任务、获取摄像头等设备信息任务,会交给主线程之外的进程或线程去执行。
页面主线程发起了一个任务,交给了另一个进程去处理,主线程继续处理消息队列中的任务;等待进程将任务处理结果交给渲染线程的消息队列中排队,由循环事件系统取出消息队列中的任务进行处理,并执行相关的回调操作。
web页面的单线程架构决定了异步回调,而异步回调影响了我们的编码方式(多次异步回调导致代码逻辑不连贯、 不线性、不符合人的直觉)。
参考:
什么是回调函数满足下述3 个条件的就是回调函数:
作为实参传入函数1,自己定义的2,由别人或系统调用3的函数
回调函数有同步的:
- Promise的excutor函数(在使用 new Promise() 创建一个新对象的时候,传递给Promise构造函数的那个函数参数); ```javascript //代码演示 let arr = [1,2,3]; arr.forEach(function (item) {//遍历回调,该同步回调函数不会放入列队,一上来就要执行完 console.log(item); }) console.log(‘forEach()之后’);
也有异步的:
- 定时器回调
- ajax回调
- Promise的成功或失败的回调
```javascript
//代码演示
setTimeout(function () {//该异步回调函数,会放在队列中
console.log('setTimeout()正在执行');
},0);
console.log('setTimeout()之后');
//结果:先输出“setTimeout()之后”,最后输出“setTimeout()正在执行”
-
- 1.2 封装异步代码,使处理逻辑变得线性
由于我们重点关注的是输入内容(请求信息)和输出内容(回复信息),至于中间的异步请求过程,我们不想在代码里面体现太多,因为这会干扰核心的代码逻辑。整体思路如下图所示:
1.3 新的问题:回调地狱
XFetch(makeRequest('https://time.geekbang.org/?category'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org/column'),
function resolve(response) {
console.log(response)
XFetch(makeRequest('https://time.geekbang.org')
function resolve(response) {
console.log(response)
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
}, function reject(e) {
console.log(e)
})
存在2 个问题:
第一是嵌套调用,下面的任务依赖上个任务的请求结果,并在上个任务的回调函数内部执行新的业务逻辑,这样当嵌套层次多了之后,代码的可读性就变得非常差了。
- 第二是任务的不确定性,执行每个任务都有两种可能的结果(成功或者失败),所以体现在代码中就需要对每个任务的执行结果做两次判断,这种对每个任务都要进行一次额外的错误处理的方式,明显增加了代码的混乱程度。
解决思路:
- 第一是消灭嵌套调用;
- 第二是合并多个任务的错误处理。
2.Promise 解决了什么问题?
Promise 解决的是异步编码风格的(也就是上面提到的2个)问题,而不是一些其他的问题。
Promise是怎么消灭嵌套调用和合并多个任务的错误处理的。
解决嵌套回调:
- promise实现了延迟绑定回调函数onResolve/onReject;
- 将回调函数 onResolve 的返回值穿透到最外层。
解决合并多任务的错误处理:
- Promise 对象的错误具有“冒泡”性质,会一直向后传递,直到被 onReject 函数处理或 catch 语句捕获为止。
const PENDING = "pending";
const RESOLVED = "resolved";
const REJECTED = "rejected";
function MyPromise(fn) {
// 保存初始化状态
var self = this;
// 初始化状态
this.state = PENDING;
// 用于保存 resolve 或者 rejected 传入的值
this.value = null;
// 用于保存 resolve 的回调函数
this.resolvedCallbacks = [];
// 用于保存 reject 的回调函数
this.rejectedCallbacks = [];
// 状态转变为 resolved 方法
function resolve(value) {
// 判断传入元素是否为 Promise 值,如果是,则状态改变必须等待前一个状态改变后再进行改变
if (value instanceof MyPromise) {
return value.then(resolve, reject);
}
// 保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态为 pending 时才能转变,
if (self.state === PENDING) {
// 修改状态
self.state = RESOLVED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.resolvedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 状态转变为 rejected 方法
function reject(value) {
// 保证代码的执行顺序为本轮事件循环的末尾
setTimeout(() => {
// 只有状态为 pending 时才能转变
if (self.state === PENDING) {
// 修改状态
self.state = REJECTED;
// 设置传入的值
self.value = value;
// 执行回调函数
self.rejectedCallbacks.forEach(callback => {
callback(value);
});
}
}, 0);
}
// 将两个方法传入函数执行
try {
fn(resolve, reject);
} catch (e) {
// 遇到错误时,捕获错误,执行 reject 函数
reject(e);
}
}
MyPromise.prototype.then = function(onResolved, onRejected) {
// 首先判断两个参数是否为函数类型,因为这两个参数是可选参数
onResolved =
typeof onResolved === "function"
? onResolved
: function(value) {
return value;
};
onRejected =
typeof onRejected === "function"
? onRejected
: function(error) {
throw error;
};
// 如果是等待状态,则将函数加入对应列表中
if (this.state === PENDING) {
this.resolvedCallbacks.push(onResolved);
this.rejectedCallbacks.push(onRejected);
}
// 如果状态已经凝固,则直接执行对应状态的函数
if (this.state === RESOLVED) {
onResolved(this.value);
}
if (this.state === REJECTED) {
onRejected(this.value);
}
};
then(onFulfilled, onReject){
// 保存前一个promise的this
const self = this;
return new MyPromise((resolve, reject) => {
// 封装前一个promise成功时执行的函数
let fulfilled = () => {
try{
const result = onFulfilled(self.value); // 承前
return result instanceof MyPromise? result.then(resolve, reject) :
resolve(result); //启后
}catch(err){
reject(err)
}
}
// 封装前一个promise失败时执行的函数
let rejected = () => {
try{
const result = onReject(self.reason);
return result instanceof MyPromise? result.then(resolve, reject) :
reject(result);
}catch(err){
reject(err)
}
}
switch(self.status){
case PENDING:
self.onFulfilledCallbacks.push(fulfilled);
self.onRejectedCallbacks.push(rejected);
break;
case FULFILLED:
fulfilled();
break;
case REJECT:
rejected();
break;
}
})
}
3.Promise 与微任务
使用微任务,可以达到延时(异步)调用onResolve的目的,也能提升代码的执行效率(实时性)。
面试视角
一、浏览器安全
🚩1. 什么是 XSS 攻击?
(1)概念
(2)攻击类型
🚩2. 如何防御 XSS 攻击?
🚩3. 什么是 CSRF 攻击?
(1)概念
(2)攻击类型
🚩4. 如何防御 CSRF 攻击?
5. 什么是中间人攻击?如何防范中间人攻击?
6. 有哪些可能引起前端安全的问题?
7. 网络劫持有哪几种,如何防范?
二、进程与线程
🚡1. 进程与线程的概念
🚩2. 进程和线程的区别
3. 浏览器渲染进程的线程有哪些
🚩4. 进程之前的通信方式—-了解即可
5. 僵尸进程和孤儿进程是什么?—-了解即可(或者不需要看)
🚩6. 死锁产生的原因? 如果解决死锁的问题?—-了解即可(或者不需要看)
🚡7. 如何实现浏览器内多个标签页之间的通信?
8. 对Service Worker的理解
三、浏览器缓存
🚩1. 对浏览器的缓存机制的理解
🚡2. 浏览器资源缓存的位置有哪些?
🚩3. 协商缓存和强缓存的区别
(1)强缓存
(2)协商缓存
4. 为什么需要浏览器缓存?
5. 点击刷新按钮或者按 F5、按 Ctrl+F5 (强制刷新)、地址栏回车有什么区别?
四、浏览器组成
对浏览器的理解
🚡2. 对浏览器内核的理解
🚡3. 常见的浏览器内核比较
🚩4. 常见浏览器所用内核—-了解即可
🚡5. 浏览器的主要组成部分
五、浏览器渲染原理
🚩1. 浏览器的渲染过程
🚩2. 浏览器渲染优化
🚡3. 渲染过程中遇到 JS 文件如何处理?什么是文档的预解析?
- CSS 如何阻塞文档解析?
-
六、浏览器本地存储
🚩1. 浏览器本地存储方式及使用场景
(1)Cookie
(2)LocalStorage
(3)SessionStorage
🚡2. Cookie有哪些字段,作用分别是什么
🚩3. Cookie、LocalStorage、SessionStorage区别
🚩4. 前端储存的⽅式有哪些? -
七、浏览器同源策略
🚩1. 什么是同源策略
🚩2. 如何解决跨越问题
(1)CORS
减少OPTIONS请求次数:
CORS中Cookie相关问题:
(2)JSONP
(3)postMessage 跨域
(4)nginx代理跨域
(5)nodejs 中间件代理跨域
(6)document.domain + iframe跨域
(7)location.hash + iframe跨域
(8)window.name + iframe跨域
(9)WebSocket协议跨域 正向代理和反向代理的区别—-了解即可
-
八、浏览器事件机制
🚩1. 事件是什么?事件模型? 如何阻止事件冒泡
🚩3. 对事件委托的理解
(1)事件委托的概念
(2)事件委托的特点
(3)局限性
🚩4. 事件委托的使用场景- 同步和异步的区别
🚩6. 对事件循环的理解
🚩7. 宏任务和微任务分别有哪些 - 什么是执行栈
🚩9. Node 中的 Event Loop 和浏览器中的有什么区别?process.nextTick 执行顺序? - 事件触发的过程是怎样的
九、浏览器垃圾回收机制—-了解即可
🚩1. V8的垃圾回收机制是怎样的
🚡2. 哪些操作会造成内存泄漏?