一,在网页上调试代码
代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script type="text/javascript">
function test(){
console.log(1)
test1()
console.log(3)
}
function test1(){
console.log(2)
}
console.log(0)
test()
console.log(4)
</script>
</body>
</html>
在浏览器打开之后,打开调试面板,在source面板,找到对应的代码,打断点,刷新页面即可开始调试:
调试时主要的几个按钮及其作用:
1,恢复代码执行
当我们打了个断点之后,【恢复代码执行】就会跳过这个断点,继续执行后面的代码,直到遇到下一个断点。
2,跳过函数,单步执行
可以看到,连续点击时,两次它都不会进入这个test1函数。也就是说,**这个按钮是单步执行,只会从函数调用中出来,而不会进入函数调用之中。**并且,这个单步执行是指:每一步代码点击这按钮后都会断住,一步一行。
3,进入函数调用
和2中相反,2中不会进入函数调用,而这个按钮是遇到函数调用就会进入,并且也是单步执行。
function test(){
console.log(1)
console.log(2)
}
function test1(){
console.log(3)
setTimeout(()=>{
console.log(7)
},1000)
console.log(4)
console.log(5)
}
console.log(0)//断点位置打这里
test()
test1()
console.log(6)
比较特殊的是,它的名字是【单步进入函数调用】,遇到test1函数中的异步,如setTimeout的时候,它的下一个断点是console.log(7),也就是遇到异步后,它会把同步代码执行完毕,也就是会将主线程中的代码全部执行完毕,然后断点断在下一次的Event Loop事件循环【也就是异步执行完毕后排在事件队列中】。这里来看就是下一个断点在异步后的第一行。
4,跳出函数调用
这个比较简单,就是将当前断点所在的函数体的剩余代码执行完毕,然后跳出这个函数体。下一个断点就在刚跳出这个函数体的地方。
5,强制单步执行
这个和3中的进入函数调用很像,但是不同的地方在于【进入函数调用】在遇到异步代码的时候,会将主线程中的代码全部执行完毕,然后断点断在下一次的Event Loop事件循环【也就是异步执行完毕后排在事件队列中】。
而强制单步执行,就是按照代码执行德顺序,一步步打断点。
6,让断点失效
顾名思义,这个德作用就是让我们打的断点失效掉,比如说我们打了好几个断点。然后将这个按钮点亮,再刷新,会发现所有德断点不再起作用。
7,在报错处断住
就是开启这个按钮后,代码如果有错误,则在运行过程中,会在报错那一行断住。
function test(){
console.log(1)
console.log(2)
}
function test1(){
console.log(a)
setTimeout(()=>{
console.log(7)
},1000)
console.log(4)
console.log(5)
}
console.log(0)
test()
test1()
console.log(6)
当我没有定义a时,运行代码,就会在这里断住:
二,在vscode中调试代码
1,创建调试配置
如上图所示,便可进入代码调试。
2,vscode的调试面板
3,调试的工具面板
这个内容和浏览器上的调试工具差不多,如下图所示:
三,sourceMap的原理
实际开发过程中,JavaScript脚本正变得越来越复杂。大部分源码(尤其是各种函数库和框架)都要经过转换,才能投入生产环境。
常见的源码转换,主要是以下三种情况:
(1)压缩,减小体积。比如jQuery 1.9的源码,压缩前是252KB,压缩后是32KB。
(2)多个文件合并,减少HTTP请求数。
(3)其他语言编译成JavaScript。最常见的例子就是CoffeeScript。
这三种情况,都使得实际运行的代码不同于开发代码,除错(debug)变得困难重重。于是需要引入sourceMap。
sourceMap通常的原理是这样的:
1,在生成线上文件的同时,生成一个映射规则;
2,根据映射规则,将线上文件映射成源文件。
1,生成.map 文件
第一步一般由开发者通过自己的某些工具(如 gulp/webpack)等生成一个 .map 文件:
{
"version":3,//sourcemap 的版本,一般为 3
"file":"bundle.js",//编译后的文件名
"mappings":";;AAAA;AACA;AACA;AACA,c",//位置信息
"sources":["webpack://tes-test/./index.js"],//源码文件名,可能有多个源文件
"sourcesContent":["console.log(\"222\")\r\nvar a=2\r\nlet b=4\r\nconsole.log(b)"],//每个 sources 对应的源码的内容,每个文件就是数组的一项
"names":[],//转换前的变量名
"sourceRoot":""//源码根目录
}
然后在线上文件的结尾加上这一行:
//@ sourceMappingURL=/path/to/file.js.map
指明线上代码使用的map文件,这样一来,线上文件就可以通过 /path/to/file.js.map 进行 source map 处理。
2,解析映射规则,并将线上文件通过它映射成源文件
这个操作一般由支持 source map 的浏览器来完成。
当第一步指映射文件后,浏览器运行了线上文件,如果浏览器支持了source map,则会对map文件进行解析,主要就是mapping数组的解析,它主要有三层:
"mappings": "ABCDA,ZSDFS,SDDAF;EEZAV"//位置信息
第一层是行对应,以分号(;)表示,每个分号对应转换后源码的一行。所以,第一个分号前的内容,就对应源码的第一行,以此类推。
第二层是位置对应,以逗号(,)表示,每个逗号对应转换后源码的一个位置。所以,第一个逗号前的内容,就对应该行源码的第一个位置,以此类推。
第三层是位置转换,以VLQ编码表示,代表该位置对应的转换前的源码位置。
主要是第三层的位置转化,使用的是VLQ编码:值得注意的是,第五位名称索引可省略。源文件索引, 源文件行号, 源文件列号也可同时省略。
这表示映射点的数组长度可能是 1、4 或 5。
源映射所有行列号都是从 0 开始计数的。
[生成文件的列]
[生成文件的列,源文件索引,源文件行号,源文件列号]
[生成文件的列,源文件索引,源文件行号,源文件列号,名称索引]
3,mapping的编码规则
示例:
console.log("222");
let b=4;
console.log(b);
第一步:先写处出基本的mapping数组
这三行代码,其中的mapping需要经过如下计算,对应这个[生成文件的列,源文件索引,源文件行号,源文件列号,名称索引]来说的话,这三行分别是:
[0000],[0010],[0020]
第二步:计算相对值
将映射点中每个数字替换成当前映射点和上一个映射点相应位置的差,就是后一个数组的对应值减去上一个的对应值(少于5位的补0即可),于是mapping数组变成:
[0000],[0010],[0010]
第三步:合并数字
将 mappings 中出现的所有数字写成一行,不同映射点使用,(逗号)隔开,不同的行使用;(分号)隔开。于是变成:
0000;0010;0010
第四步:使用VLQ编码将数字编码成字母
1. 如果数字是负数,则取其相反数。
2. 将数字转为等效的二进制。并在末尾补符号位,如果数字是负数则补 1 否则补 0。
3. 从右往左分割二进制,一次取 5 位,不足的补0。如果最高位所在的段不足5位,则前面补0,低位段的后面补0。
4. 将分好的二进制进行倒序。(有的超过5位,如)
5. 每段二进制前面补 1,最后一段二进制补 0。这样每段二进制就是 6 位,其值范围是 0 到 64(含0,不含64)。
6. 根据 Base64 编码表将每段二进制转为字母:
示例:
以 170 为例,
1)转为二进制即:10101010
2)170 是正数,右边补 0:101010100
3)从右往左分割二进制:10100, 1010。
4)不足5位的补 0:01010, 10100
5)倒序:10100, 01010
6)除最后一个前面补 0,其它每段前面补 1:110100, 001010
7)转为十进制:52, 10。
8)查表得到:0K
于是mapping变成:
AAAA;AACA;AACA
浏览器运行了线上文件,如果浏览器支持了source map,发现链接的bundle.js代码尾部有对应的map文件地址,则会解析这个map文件,找到每行代码对应的源代码。这就实现了源代码和编译后代码的映射。
4,使用webpack生成map文件
新建一个项目
npm init -y
npm install webpack webpack-cli -D
根目录配置webpack的webpack.config.js,主要是source-map:
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
mode: "none",
devtool:"source-map"
}
新建index.js文件:
console.log("222");
let b=4;
console.log(b);
然后命令行运行npx webpack,就能生成打包文件dist,注意到这个文件夹有两个文件,一个编译后的代码bundle.js:
/******/ (() => { // webpackBootstrap
var __webpack_exports__ = {};
console.log("222");
let b=4;
console.log(b);
/******/ })()
;
//# sourceMappingURL=bundle.js.map
另一个则是map文件,可以看到其中的mapping就是上文计算出来的:
{
"version":3,
"file":"bundle.js",
"mappings":";;AAAA;AACA;AACA,e",
"sources":["webpack://tes-test/./index.js"],
"sourcesContent":["console.log(\"222\");\r\nlet b=4;\r\nconsole.log(b);"],
"names":[],
"sourceRoot":""
}
然后再在index.html中引入这个bundle.js:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<script src="./dist/bundle.js"></script>
</body>
</html>
打开浏览器,便可以看到映射的源代码:
四,webpack的source map配置
const path = require("path");
module.exports = {
entry: "./index.js",
output: {
path: path.resolve(__dirname, "dist"),
filename: "bundle.js"
},
mode: "none",
devtool:"source-map"
}
在webpack配置不生成map文件,devtool有以下几种情况:
false:不使用 source-map
eval:mode是 develpment的情况下默认值,不会生成 source-map文件,它会在 eval执行的代码中添加 //# sourceURL=注释,同时调试也能定位到代码位置:
webpack有个sourceMap的配置正则表达式:
"^(inline-|hidden-|eval-)?(nosources-)?(cheap-(module-)?)?source-map$"
当我们根据自己的需要,就可以配置出不同的source-map,根据这个正则表达式,可以分成以下几层:
第一层
没有值:没有值的时候,就是剩下source-map:会生成map文件,并且编译后的文件会指向这个map文件。
inline:不会生成map文件,而是将映射文件以dataUrl 的方式内联在打包后的代码最后。
hidden:生成map文件,但是编译后的代码最后不会指向这个map文件,即不做关联
eval:不会生成map文件,但是每个模块,会用一个eval包裹在编译后的代码中
第二层
nosources:map文件中有sourceContent对应的源码内容,其实根据mapping已经能映射源码了,这里的源码内容就可以不要掉,可以缩减map文件的大小。当配置这个的时候,map文件就没有sourcesContent内容。
第三层
cheap:map文件中,通常会有行和列的信息,而实际上,我们只需要映射到行信息即可,列信息可以省略,所以这里可以加上这个cheap参数,把列信息忽略。
第四层
module:当a模块使用loaderA转译一次,再使用B模块转译一次的时候,如果我们想编译后的代码可以直接映射源码,而不是只是映射到最后一次B转移的代码。那么就可以加上这个参数,不管中间转译了多少次,都直接映射源码。
第五层
source-map:只要不是配置的false或者单独一个eval,它是必须要配置的,就是生成map文件,当然,它受前几个配置的影响。
也就是说配置可以用下图总结:
按照上文的说法,在实际开发过程中,我们常常使用:cheap-module-source-map来配置。
五,调试vue项目代码
我的项目是直接使用webpack搭建的,没有使用vue-cli,当webpack的devtool配置cheap-module-source-map的时候。