仓库源码 gitee.com/jelly-mulbe…
一、介绍
什么是 Markdown ?
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档。
三个库分别是什么?
EJS : 一款强大的 JavaScript模板 引擎,它可以帮助我们在HTML中嵌入动态内容。使用EJS,您可以轻松地将Markdown转换为美观的HTML页面。
Marked: 一个流行的Markdown解析器和编译器,它可以将Markdown语法转换为 HTML 标记。
BrowserSync: 一个强大的开发工具,它可以帮助您实时预览和同步您的网页更改。当您对Markdown文件进行编辑并将其转换为HTML时,BrowserSync可以自动刷新您的浏览器,使您能够即时查看转换后的结果 。
二、 实操
- 安装三个库
npm i ejs marked browser-sync
2. 新建
JavaScrip 模版引擎
template.js
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><%= title %></title>
<link rel="stylesheet" href="./index.css">
</head>
<body>
<%- content %>
</body>
</html>
新建 CSS 样式
细心的小伙伴可以看到上面模版 ejs 中有样式
index.css
/* Markdown通用样式 */
/* 设置全局字体样式 */
body {
font-family: Arial, sans-serif;
font-size: 16px;
line-height: 1.6;
color: #333;
}
/* 设置标题样式 */
h1,
h2,
h3,
h4,
h5,
h6 {
margin-top: 1.3em;
margin-bottom: 0.6em;
font-weight: bold;
}
h1 {
font-size: 2.2em;
}
h2 {
font-size: 1.8em;
}
h3 {
font-size: 1.6em;
}
h4 {
font-size: 1.4em;
}
h5 {
font-size: 1.2em;
}
h6 {
font-size: 1em;
}
/* 设置段落样式 */
p {
margin-bottom: 1.3em;
}
/* 设置链接样式 */
a {
color: #337ab7;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
/* 设置列表样式 */
ul,
ol {
margin-top: 0;
margin-bottom: 1.3em;
padding-left: 2em;
}
/* 设置代码块样式 */
pre {
background-color: #f7f7f7;
padding: 1em;
border-radius: 4px;
overflow: auto;
}
code {
font-family: Consolas, Monaco, Courier, monospace;
font-size: 0.9em;
background-color: #f7f7f7;
padding: 0.2em 0.4em;
border-radius: 4px;
}
/* 设置引用样式 */
blockquote {
margin: 0;
padding-left: 1em;
border-left: 4px solid #ddd;
color: #777;
}
/* 设置表格样式 */
table {
border-collapse: collapse;
width: 100%;
margin-bottom: 1.3em;
}
table th,
table td {
padding: 0.5em;
border: 1px solid #ccc;
}
/* 添加一些额外的样式,如图片居中显示 */
img {
display: block;
margin: 0 auto;
max-width: 100%;
height: auto;
}
/* 设置代码行号样式 */
pre code .line-numbers {
display: inline-block;
width: 2em;
padding-right: 1em;
color: #999;
text-align: right;
user-select: none;
pointer-events: none;
border-right: 1px solid #ddd;
margin-right: 0.5em;
}
/* 设置代码行样式 */
pre code .line {
display: block;
padding-left: 1.5em;
}
/* 设置代码高亮样式 */
pre code .line.highlighted {
background-color: #f7f7f7;
}
/* 添加一些响应式样式,适应移动设备 */
@media only screen and (max-width: 768px) {
body {
font-size: 14px;
line-height: 1.5;
}
h1 {
font-size: 1.8em;
}
h2 {
font-size: 1.5em;
}
h3 {
font-size: 1.3em;
}
h4 {
font-size: 1.1em;
}
h5 {
font-size: 1em;
}
h6 {
font-size: 0.9em;
}
table {
font-size: 14px;
}
}
新建一个md文件
README.md
# 欢迎使用 Marked
## 我是果冻桑
```JavaScrip
const fs = require('fs');
const marked = require('marked');
// 读取 Markdown 文件
fs.readFile('readme.md', 'utf8', (err, data) => {
你好我是果冻桑, 今天我们来讲一下小米 MIX3 的故事
});
```
app.js 启动
创建browser 并且开启一个服务 设置根目录和 index.html 文件
const browserSync = require('browser-sync')
const openBrowser = () => {
const browser = browserSync.create()
browser.init({
server: {
baseDir: './',
index: 'index.html',
}
})
return browser
}
完整代码 ( 历史或者后续版本可以通过 gitte 进行查看 )
目录:
// 下面是对 app2.js 进行进一步的优化 封装
const { error, time } = require('console')
const ejs = require('ejs') // 引入 ejs 模板引擎
const fs = require('fs') // 文件读写
const marked = require('marked') // Markdown 转 HTML
const readme = fs.readFileSync('README.md') // 读取 README.md 文件
const readmePath = 'README.md'
// 具体步骤:
// - 读取文件 readme.md
// - 把 markdown 转换为 HTML
// - 把上面的 html 嵌入到 使用 template.ejs 模版 => 形成完整的html
// ( 具体可以对比下面的 content 以及 data 的打印出来的)
// 1. 优化一: 去掉回调函数
const utils = require('util')
const ejsrenderFilePromise = utils.promisify(ejs.renderFile)
const content = marked.parse(readme.toString());
// console.log(content)
const parameters = {
content: content,
title: "markdown to html",
}
// 2. 启动浏览器 函数
let browser
const browserSync = require("browser-sync"); // 导入browser-sync库,用于实时预览和同步浏览器
const { resolve } = require('path')
const { rejects } = require('assert')
const openBrowser = async () => {
const browser = browserSync.create();
browser.init({
server: {
baseDir: "./",
index: "index.html",
},
});
return browser;
};
// 写入 index.html 函数
let path = "index.html" // 路径
const writeFile = async (html)=>{
return new Promise((resolve,reject) =>{
let writeStream = fs.createWriteStream(path)
writeStream.write(html)
writeStream.close()
writeStream.on('finish', resolve)
writeStream.on('error', reject)
})
}
// 3. 新增热更新功能 函数
const generateHtmlAndReloadBrowser = async () => {
const readmeContent = fs.readFileSync(readmePath);
const content = marked.parse(readmeContent.toString());
parameters.content = content; // 更新参数
const Allhtml = await ejsrenderFilePromise('template.ejs', parameters);
await writeFile(Allhtml);
if (browser) {
browser.reload(); // 重新加载浏览器
}
};
// 监听文件变化
const watchReadmeFile = () => {
// fs.watchFile(readmePath, (curr, prev) => {
// if( curr.mtime !== prev.mtime) {
// generateHtmlAndReloadBrowser(); // 文件变化时重新生成 HTML 并重新加载浏览器
// }
// });
const debouncedGenerateHtmlAndReloadBrowser = debounce(generateHtmlAndReloadBrowser, 1000);
fs.watch(readmePath, (eventType, filename) => {
// console.log(`File ${filename} has been ${eventType}, regenerating HTML...`);
// generateHtmlAndReloadBrowser(); // 文件变化时重新生成 HTML 并重新加载浏览器
// 防抖
debouncedGenerateHtmlAndReloadBrowser()
});
};
// 防抖函数
function debounce(func ,wait) {
let timeout
return function executeFunction(...args) {
const later = ()=>{
clearTimeout(timeout);
func(...args)
}
clearTimeout(timeout);
timeout = setTimeout(later, wait);
}
}
// 启动
const generateHtmlAndOpenBrowser = async () => {
// 返回是完整 html
const Allhtml = await ejsrenderFilePromise('template.ejs', parameters)
await writeFile(Allhtml)
browser = await openBrowser() // 启动浏览器
}
generateHtmlAndOpenBrowser()
.then(watchReadmeFile)
.catch((err) => {
console.error(err); // 使用 console.error 打印错误信息
});
代码分析:
总体思路:
- 通过 fs.readFileSync 方式读取文件,获取 readme.md 的文件
- 通过 marked.parse(readme.toString())markdown 转为 html
- 通过工具类中 promisify 的方式,ejs.renderFile 实现返回 Promise
- const Allhtml = await ejsrenderFilePromise("template.ejs", parameters);把转化后的html 以参数的形式嵌入到 JavaScript模板引擎,其中 parameters 就是上面第二步的获取的html
- 通过函数 writeFile 函数写入 ,如 index.html
- 启动浏览器
- 监听浏览器,如果 readme.md 文件有变化,就会进行重新写入以及刷新浏览器的操作
- 简单优化一下,加入防抖的函数,避免多次更改导致多次写入
-
读取
const readme = fs.readFileSync('README.md')
这行代码使用 fs 模块的 readFileSync 方法同步读取 README.md 文件的内容,
以 Buffer 对象的形式存储在 readme 变量中。如果需要字符串形式,可以指定编码,例如 fs.readFileSync('README.md', 'utf8')。当然可以使用 toStirng 方法
- toString
A. 基本数据类型转换
对于原始值(如数字、字符串、布尔值等),toString() 方法可以将它们转换为字符串。
const num = 10;
const strNum = num.toString(); // "10"
const bool = true;
const strBool = bool.toString(); // "true"
const undef = undefined;
const strUndef = undef.toString(); // "undefined"
const nullValue = null;
const strNull = nullValue.toString(); // "null"
B. 对象转换
对于对象,toString() 方法返回一个字符串,该字符串表示对象的类型和在内存中的地址。通常这不是一个有用的字符串,因为它不提供对象的内容信息。
const obj = { name: "大钊" };
const strObj = obj.toString(); // "[object Object]"
C. 数组 字符串 的 toString
const arr = [1, 'hello', true];
const strArr = arr.toString(); // "1,hello,true"
const strObj = new String("Hello");
const strStrObj = strObj.toString(); // "Hello"
D. 自定义 toString 方法
你可以在自定义对象中重写 toString() 方法,以提供更有用的字符串表示。
const obj = {
name: "hello",
toString: function() {
return `Name: ${this.name}`;
}
};
const strObj = obj.toString(); // "Name: hello"
3. 监听文件 fs.watch
fs.watch(filename, [options], [listener])
-
filename:要监听的文件或目录的名称。 -
options:(可选)一个字符串或对象,控制监听的行为。-
如果是字符串,它指定被监听的事件类型,可以是
'rename'或'change'。 -
如果是对象,它包含以下属性:
persistent:(默认为true)如果设置为false,则在发生事件后停止监听。recursive:(默认为false)如果设置为true,则监听一个目录及其所有子目录。encoding:(默认为'buffer')文件名的编码。
-
-
listener:(可选)一个回调函数,当文件发生变化时被调用。
const fs = require('fs');
// 监听文件的变化
fs.watch('example.txt', (eventType, filename) => {
if (eventType === 'rename') {
console.log('文件被重命名或删除:', filename);
} else if (eventType === 'change') {
console.log('文件内容发生变化:', filename);
}
});
- 如果你需要更可靠的文件监听功能,可以考虑使用第三方库,如
chokidar。
- 防抖
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
监听文件变化
// 监听文件变化
const watchReadmeFile = () => {
const debouncedGenerateHtmlAndReloadBrowser = debounce(
generateHtmlAndReloadBrowser,
1000
);
fs.watch(readmePath, (eventType, filename) => {
debouncedGenerateHtmlAndReloadBrowser();
});
};
理解:
A. 最简单结构最不好的防抖:
let t = null // 全局变量
onInput.oniput = function() {
if(t!= null) clearTimeout(t)
t = setTimeout(()=>{
console.log(111)
},1000)
}
B. 改善代码结构
// 防抖函数
// 返回值是一个函数
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const later = () => {
func(...args); // 这里 ...args就是返回函数后,调用时传递的所有参数
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
结合监听事件addEventListener, 可以写一个箭头函数,传入参数
简单案例:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>防抖的简单理解</title>
</head>
<body>
<button class="btn">点击</button>
</body>
<script>
let btn = document.querySelector(".btn");
function test(...args) {
console.log("防抖");
console.log(...args)
}
btn.addEventListener("click", ()=>debounce(test, 1000)(1,2,3,4))
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const later = () => {
console.log(...args)
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
</script>
</html>
关于 this 指向问题分析
先看一个案例
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>防抖的简单理解</title>
</head>
<body>
<button class="btn">点击</button>
</body>
<script>
let btn = document.querySelector(".btn");
btn.addEventListener("click", ()=>{
console.log("箭头函数",this) // Windows
})
btn.addEventListener("click",function(e) {
console.log("普通函数",this) // 指向 btn
})
</script>
</html>
分析:
普通函数中的 this: 普通函数的 this 指向调用它的上下文。在这个例子中,普通函数是作为 addEventListener 的回调函数被调用的,而 addEventListener 是 btn 元素的方法。因此,当点击事件发生时,这个普通函数的 this 指向调用它的对象,即 btn 元素。
箭头函数中的this
箭头函数不创建自己的 this 上下文,因此它没有自己的 this 值。箭头函数内部的 this 值由外围最近一层非箭头函数的执行上下文决定
所以当大家说“箭头函数没有 this”时,他们的意思是箭头函数不创建自己的 this 上下文,this 是继承自外围作用域
而说箭头函数有this的情况,则是说继承自外围作用域的this。这个this不是箭头函数自己的,而是继承来的
下面我们分析一下防抖这段代码中this指向问题
btn.addEventListener("click", debounce(test, 1000))
// 3. 点击后,调用函数 debounce(test, 1000) 也就是 返回的 executeFunction 函数
// 因为 executeFunction 是一个普通函数, 所以此时 this 指向应该是 btn 元素
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const later = () => {
console.log(this)
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
//1. 由于 later 是箭头函数 ,
//所以 this 指向外围作用区域 this 也就是 executeFunction 的this
//2. 由于 executeFunction 的this 和执行有关, 所以看到上面第一行
};
}
如果有参数:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>防抖的简单理解</title>
</head>
<body>
<button class="btn">点击</button>
<button id="debounceButton">点击我</button>
</body>
<script>
let btn = document.querySelector(".btn");
function test(...args) {
console.log(...args);
console.log("test", this);
}
// 创建一个防抖版本的test函数
const debounceTest = debounce(function () {
test.call(this, "test");
}, 1000);
btn.addEventListener("click", debounceTest);
function handleClick(message) {
console.log(message);
console.log("handleClick", this); // 这里的 this 指向按钮
}
const debounceClick = debounce(function () {
// console.log("debounceClick",this) // 从下面的 func.apply(context,args)
// 可以知道 这里的 this 在点击后, 绑定的 button 元素
// 如果下面 只是调用
handleClick(this, "按钮被点击了(没有call,隐式调用 this 绑定在 window 中)")
handleClick.call(this, "按钮被点击了!(有call, this 绑定在 btn 元素中 )"); // 使用 call 将 this 绑定到按钮
}, 1000)
document.getElementById("debounceButton").addEventListener("click", debounceClick);
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const context = this
const later = () => {
func.apply(context, args);
console.log("executeFuction",...args)
// func.apply(this, args) // 这也是一样的,因为箭头函数没有this
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// btn.addEventListener("click", ()=>{
// console.log("箭头函数",this) // Windows
// })
// btn.addEventListener("click",function(e) {
// console.log("普通函数",this) // 指向 btn
// })
</script>
</html>
示例一: ( 有参数 )
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>防抖按钮示例</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f4f4f4;
}
button {
padding: 10px 20px;
font-size: 16px;
cursor: pointer;
}
</style>
</head>
<body>
<button id="debounceButton">点击我</button>
<script>
function debounce(fn, delay) {
let timeout;
return function(...args) {
const context = this; // 保存当前上下文
clearTimeout(timeout);
timeout = setTimeout(() => fn.apply(context, args), delay);
};
}
function handleClick(message) {
console.log(message);
console.log("handleClick", this); // 这里的 this 指向按钮
}
const debounceClick = debounce(function() {
handleClick.call(this, '按钮被点击了!'); // 使用 call 将 this 绑定到按钮
}, 1000);
document.getElementById('debounceButton').addEventListener('click', debounceClick);
</script>
</body>
</html>
示例二: ( 在 绑定事件传递参数 )
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Debounce with ...args Example</title>
</head>
<body>
<button id="myButton">Click Me!</button>
</body>
<script>
// 防抖函数
function debounce(func, wait) {
let timeout;
return function executeFunction(...args) {
const context = this;
console.log("executeFunction", this)
const later = () => {
func.apply(context, args);
console.log("executeFunction", ...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// 按钮点击事件处理函数,接受任意数量的参数
function handleClick(...args) {
console.log(this)
console.log("Button clicked with args:", ...args);
}
// 获取按钮元素
const button = document.getElementById("myButton");
// 创建防抖包装后的事件处理函数
const debouncedHandleClick = debounce(handleClick, 2000);
// 给按钮添加点击事件监听器
button.addEventListener("click", function (event) {
console.log("addEventListener", this);
return debouncedHandleClick(event, "arg1", "arg2", "arg3");
// return debouncedHandleClick.apply(this,event, "arg1", "arg2", "arg3");
});
</script>
</html>