TCP协议
- TCP 和 UDP 的区别?
- TCP 三次握手的过程?
- 为什么是三次而不是两次、四次?
- 三次握手过程中可以携带数据么?
- 说说 TCP 四次挥手的过程
- 为什么是四次挥手而不是三次?
- 半连接队列和 SYN Flood 攻击的关系
- 如何应对 SYN Flood 攻击?
- 介绍一下 TCP 报文头部的字段
- TCP 快速打开的原理(TFO)
- 说说TCP报文中时间戳的作用?
- TCP 的超时重传时间是如何计算的?
- TCP 的流量控制
- TCP 的拥塞控制
- 说说 Nagle 算法和延迟确认?
- 如何理解 TCP 的 keep-alive?
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
浏览器篇
- 浏览器缓存?
- 说一说浏览器的本地存储?各自优劣如何?
- 说一说从输入URL到页面呈现发生了什么?
- 谈谈你对重绘和回流的理解
- XSS攻击
- CSRF攻击
- HTTPS为什么让数据传输更安全?
- 实现事件的防抖和节流?
- 实现图片懒加载?
好了,现在我们来执行一下 bundle.js,看看AST是否成功生成。
成功。又是不出所料的成功。
不过,我们需要知道的是,当前我们解析出来的不单单是index.js文件里的内容,它也包括了文件的其他信息。
而它的内容其实是它的属性program里的body里。如图所示
我们可以改成打印ast.program.body看看
// 获取主入口文件
const fs = require(‘fs’)
const parser = require(‘@babel/parser’)
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,‘utf-8’)
const ast = parser.parse(body,{
sourceType:‘module’ //表示我们要解析的是ES模块
});
console.log(ast.program.body);
}
getModuleInfo(“./src/index.js”
执行
看,现在打印出来的就是 index.js文件里的内容(也就是我们再index.js里写的代码啦).
现在我们需要 遍历AST,将用到的依赖收集起来。什么意思呢?其实就是将用import语句引入的文件路径收集起来。我们将收集起来的路径放到deps里。
前面我们提到过,遍历AST要用到@babel/traverse依赖包
npm install @babel/traverse
现在,我们引入。
const fs = require(‘fs’)
const path = require(‘path’)
const parser = require(‘@babel/parser’)
const traverse = require(‘@babel/traverse’).default
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,‘utf-8’)
const ast = parser.parse(body,{
sourceType:‘module’ //表示我们要解析的是ES模块
});
// 新增代码
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file)
const abspath = ‘./’ + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
console.log(deps);
}
getModuleInfo(“./src/index.js”)
我们来看下官方文档对@babel/traverse的描述
好吧,如此简略
不过我们不难看出,第一个参数就是AST。第二个参数就是配置对象
我们看看我们写的代码
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file)
const abspath = ‘./’ + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
配置对象里,我们配置了ImportDeclaration方法,这是什么意思呢?
我们看看之前打印出来的AST。
ImportDeclaration方法代表的是对type类型为ImportDeclaration的节点的处理。
这里我们获得了该节点中source的value,也就是node.source.value,
这里的value指的是什么意思呢?其实就是import的值,可以看我们的index.js的代码。
import add from “./add.js”
import {minus} from “./minus.js”;
const sum = add(1,2);
const division = minus(2,1);
console.log(sum);
console.log(division);
可见,value指的就是import后面的 ‘./add.js’ 和 ‘./minus.js’
然后我们将file目录路径跟获得的value值拼接起来保存到deps里,美其名曰:收集依赖。
ok,这个操作就结束了,执行看看收集成功了没?
oh my god。又成功了。
现在我们需要把获得的ES6的AST转化成ES5,前面讲到过,执行这一步需要两个依赖包
npm install @babel/core @babel/preset-env
我们现在将依赖引入并使用
const fs = require(‘fs’)
const path = require(‘path’)
const parser = require(‘@babel/parser’)
const traverse = require(‘@babel/traverse’).default
const babel = require(‘@babel/core’)
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,‘utf-8’)
const ast = parser.parse(body,{
sourceType:‘module’ //表示我们要解析的是ES模块
});
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file)
const abspath = “./” + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
新增代码
const {code} = babel.transformFromAst(ast,null,{
presets:[“@babel/preset-env”]
})
console.log(code);
}
getModuleInfo(“./src/index.js”)
我们看看官网文档对@babel/core 的transformFromAst的介绍
害,又是一如既往的简略。。。
简单说一下,其实就是将我们传入的AST转化成我们在第三个参数里配置的模块类型。
好了,现在我们来执行一下,看看结果
我的天,一如既往的成功。可见 它将我们写const 转化成var了。
好了,这一步到此结束,咦,你可能会有疑问,上一步的收集依赖在这里怎么没啥关系啊,确实如此。收集依赖是为了下面进行的递归操作。
经过上面的过程,现在我们知道getModuleInfo是用来获取一个模块的内容,不过我们还没把获取的内容return出来,因此,更改下getModuleInfo方法
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,‘utf-8’)
const ast = parser.parse(body,{
sourceType:‘module’ //表示我们要解析的是ES模块
});
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file)
const abspath = “./” + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
const {code} = babel.transformFromAst(ast,null,{
presets:[“@babel/preset-env”]
})
// 新增代码
const moduleInfo = {file,deps,code}
return moduleInfo
}
我们返回了一个对象 ,这个对象包括该模块的路径(file),该模块的依赖(deps),该模块转化成es5的代码
该方法只能获取一个模块的的信息,但是我们要怎么获取一个模块里面的依赖模块的信息呢?
没错,看标题,,你应该想到了就算递归。
现在我们来写一个递归方法,递归获取依赖
const parseModules = (file) =>{
const entry = getModuleInfo(file)
const temp = [entry]
for (let i = 0;i<temp.length;i++){
const deps = temp[i].deps
if (deps){
for (const key in deps){
if (deps.hasOwnProperty(key)){
temp.push(getModuleInfo(deps[key]))
}
}
}
}
console.log(temp)
}
讲解下parseModules方法:
-
我们首先传入主模块路径
-
将获得的模块信息放到temp数组里。
-
外面的循坏遍历temp数组,此时的temp数组只有主模块
-
循环里面再获得主模块的依赖deps
-
遍历deps,通过调用getModuleInfo将获得的依赖模块信息push到temp数组里。
目前bundle.js文件:
const fs = require(‘fs’)
const path = require(‘path’)
const parser = require(‘@babel/parser’)
const traverse = require(‘@babel/traverse’).default
const babel = require(‘@babel/core’)
const getModuleInfo = (file)=>{
const body = fs.readFileSync(file,‘utf-8’)
const ast = parser.parse(body,{
sourceType:‘module’ //表示我们要解析的是ES模块
});
const deps = {}
traverse(ast,{
ImportDeclaration({node}){
const dirname = path.dirname(file)
const abspath = “./” + path.join(dirname,node.source.value)
deps[node.source.value] = abspath
}
})
const {code} = babel.transformFromAst(ast,null,{
presets:[“@babel/preset-env”]
})
const moduleInfo = {file,deps,code}
return moduleInfo
}
// 新增代码
const parseModules = (file) =>{
const entry = getModuleInfo(file)
const temp = [entry]
for (let i = 0;i<temp.length;i++){
const deps = temp[i].deps
if (deps){
for (const key in deps){
if (deps.hasOwnProperty(key)){
temp.push(getModuleInfo(deps[key]))
}
}
}
}
console.log(temp)
}
parseModules(“./src/index.js”)
按照目前我们的项目来说执行完,应当是temp 应当是存放了index.js,add.js,minus.js三个模块。
,执行看看。
牛逼!!!确实如此。
不过现在的temp数组里的对象格式不利于后面的操作,我们希望是以文件的路径为key,{code,deps}为值的形式存储。因此,我们创建一个新的对象depsGraph。
const parseModules = (file) =>{
const entry = getModuleInfo(file)
const temp = [entry]
const depsGraph = {} //新增代码
for (let i = 0;i<temp.length;i++){
const deps = temp[i].deps
if (deps){
for (const key in deps){
if (deps.hasOwnProperty(key)){
temp.push(getModuleInfo(deps[key]))
}
}
}
}
// 新增代码
temp.forEach(moduleInfo=>{
depsGraph[moduleInfo.file] = {
deps:moduleInfo.deps,
code:moduleInfo.code
}
})
console.log(depsGraph)
return depsGraph
}
ok,现在存储的就是这种格式啦
我们现在的目的就是要生成一个bundle.js文件,也就是打包后的一个文件。其实思路很简单,就是把index.js的内容和它的依赖模块整合起来。然后把代码写到一个新建的js文件。
我们把这段代码格式化一下
// index.js
“use strict”
var _add = _interopRequireDefault(require(“./add.js”));
var _minus = require(“./minus.js”);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { “default”: obj }; }
var sum = (0, _add[“default”])(1, 2);
var division = (0, _minus.minus)(2, 1);
console.log(sum); console.log(division);
// add.js
“use strict”;
Object.defineProperty(exports, “__esModule”, { value: true});
exports[“default”] = void 0;
var _default = function _default(a, b) { return a + b;};
exports[“default”] = _default;
但是我们现在是不能执行index.js这段代码的,因为浏览器不会识别执行require和exports。
不能识别是为什么?不就是因为没有定义这require函数,和exports对象。那我们可以自己定义。
我们创建一个函数
const bundle = (file) =>{
const depsGraph = JSON.stringify(parseModules(file))
}
我们将上一步获得的depsGraph保存起来。
现在返回一个整合完整的字符串代码。
怎么返回呢?更改下bundle函数
const bundle = (file) =>{
const depsGraph = JSON.stringify(parseModules(file))
return `(function (graph) {
function require(file) {
(function (code) {
eval(code)
})(graph[file].code)
}
require(file)
})(depsGraph)`
}
我们看下返回的这段代码
(function (graph) {
function require(file) {
(function (code) {
eval(code)
})(graph[file].code)
}
require(file)
})(depsGraph)
其实就是
-
把保存下来的depsGraph,传入一个立即执行函数。
-
将主模块路径传入require函数执行
-
执行reuire函数的时候,又立即执行一个立即执行函数,这里是把code的值传进去了
-
执行eval(code)。也就是执行主模块的code这段代码
我们再来看下code的值
// index.js
“use strict”
var _add = _interopRequireDefault(require(“./add.js”));
var _minus = require(“./minus.js”);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { “default”: obj }; }
var sum = (0, _add[“default”])(1, 2);
var division = (0, _minus.minus)(2, 1);
console.log(sum); console.log(division);
没错执行这段代码的时候,又会用到require函数。此时require的参数为add.js的路径,哎,不是绝对路径,需要转化成绝对路径。因此写一个函数absRequire来转化。怎么实现呢?我们来看下代码
(function (graph) {
function require(file) {
function absRequire(relPath) {
return require(graph[file].deps[relPath])
}
(function (require,code) {
eval(code)
})(absRequire,graph[file].code)
}
require(file)
})(depsGraph)
实际上是实现了一层拦截。
-
执行require(’./src/index.js’)函数
-
执行了
(function (require,code) {
eval(code)
})(absRequire,graph[file].code)
-
执行eval,也就是执行了index.js的代码。
-
执行过程会执行到require函数。
-
这时会调用这个require,也就是我们传入的absRequire
- 而执行absRequire就执行了
return require(graph[file].deps[relPath])
这段代码,也就是执行了外面这个require
在这里return require(graph[file].deps[relPath])
,我们已经对路径转化成绝对路径了。因此执行外面的require的时候就是传入绝对路径。
- 而执行require(“./src/add.js”)之后,又会执行eval,也就是执行add.js文件的代码。
是不是有点绕?其实是个递归。
这样就将代码整合起来了,但是有个问题,就是在执行add.js的code时候,会遇到exports这个还没定义的问题。如下所示
// add.js
“use strict”;
Object.defineProperty(exports, “__esModule”, { value: true});
exports[“default”] = void 0;
var _default = function _default(a, b) { return a + b;};
exports[“default”] = _default;
最后
小编的一位同事在校期间连续三年参加ACM-ICPC竞赛。从参赛开始,原计划每天刷一道算法题,实际上每天有时候不止一题,一年最终完成了 600+:
凭借三年刷题经验,他在校招中很快拿到了各大公司的offer。
入职前,他把他的刷题经验总结成1121页PDF书籍,作为礼物赠送给他的学弟学妹,希望同学们都能在最短时间内掌握校招常见的算法及解题思路。
整本书,我仔细看了一遍,作者非常细心地将常见核心算法题和汇总题拆分为4个章节。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
而对于有时间的同学,作者还给出了他结合众多数据结构算法书籍,挑选出的一千多道题的解题思路和方法,以供有需要的同学慢慢研究。
d.js")之后,又会执行eval,也就是执行add.js文件的代码。
是不是有点绕?其实是个递归。
这样就将代码整合起来了,但是有个问题,就是在执行add.js的code时候,会遇到exports这个还没定义的问题。如下所示
// add.js
“use strict”;
Object.defineProperty(exports, “__esModule”, { value: true});
exports[“default”] = void 0;
var _default = function _default(a, b) { return a + b;};
exports[“default”] = _default;
最后
小编的一位同事在校期间连续三年参加ACM-ICPC竞赛。从参赛开始,原计划每天刷一道算法题,实际上每天有时候不止一题,一年最终完成了 600+:
凭借三年刷题经验,他在校招中很快拿到了各大公司的offer。
入职前,他把他的刷题经验总结成1121页PDF书籍,作为礼物赠送给他的学弟学妹,希望同学们都能在最短时间内掌握校招常见的算法及解题思路。
整本书,我仔细看了一遍,作者非常细心地将常见核心算法题和汇总题拆分为4个章节。
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
而对于有时间的同学,作者还给出了他结合众多数据结构算法书籍,挑选出的一千多道题的解题思路和方法,以供有需要的同学慢慢研究。