Web前端面试题目及答案汇总
html
1. h5的改进
- 新元素:画布canva、音频audio、视频video
- 新属性:拖放draggable(图片标签中使用)、可编辑contenteditable
- 本地存储
- Web应用
2. 行级元和块级元素和空元素
行内元素:a、b、span、img、input、strong、select、label、em、button、textarea(可以共存一行)
块级元素:div、ul、li、dl、dt、dd、p、h1-h6、blockquote(元素自行换行)
空元素:即系没有内容的HTML元素,例如:br、meta、hr、link、input、img
3. 语义化的html
根据内容的结构(内容语义化),选择合适的标签(代码语义化)
为了在没有CSS的情况下,页面也能呈现出很好地内容结构、代码结构:为了代码更好看的懂;
- 尽可能少的使用无语义的标签div和span;
- 在语义不明显时,既可以使用div或者p时,尽量用p, 因为p在默认情况下有上下间距, 对兼容特殊终端有利;
- 不要使用纯样式标签,如:b、font、u等,改用css设置。
- 需要强调的文本,可以包含在strong或者em标签中(浏览器预设样式,能用CSS指定就不用他们),strong默认样式是加粗(不要用b),em是斜体(不用i) ;
- 使用表格时,标题要用caption,表头用thead,主体部分用tbody包围,尾部用tfoot包围。表头和一般单元格要区分开,表头用th,单元格用td;
- 表单域要用fieldset标签包起来,并用legend标签说明表单的用途;
- 每个input标签对应的说明文本都需要使用label标签,并且通过为input设置id属性,在lable标签中设置for=someld来让说明文本和相对应的input关联起来。
4. cookies,sessionStorage 和 localStorage 的区别
-
cookie:
- cookie是网站为了标示用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。
- cookie数据始终在同源的http请求中携带(即使不需要),记会在浏览器和服务器间来回传递。
-
sessionStorage和localStorage不会自动把数据发给服务器,仅在本地保存。
-
存储大小:
- cookie数据大小不能超过4k。
- sessionStorage和localStorage 虽然也有存储大小的限制,但比cookie大得多,可以达到5M或更大。
-
有期时间:
- localStorage 存储持久数据,浏览器关闭后数据不丢失除非主动删除数据;
- sessionStorage 数据在当前浏览器窗口关闭后自动删除。
- cookie 设置的cookie过期时间之前一直有效,即使窗口或浏览器关闭
-
作用域不同:
- sessionStorage不在不同的浏览器窗口中共享,即使是同一个页面;
- localStorage 在所有同源窗口中都是共享的;cookie也是在所有同源窗口中都是共享的。
5. src和href的区别
src:指向外部资源的位置, 用于替换当前元素, 比如js脚本, 图片等元素
href:指向网络资源所在的位置, 用于在当前文档和引用资源间确定联系, 加载css
css
6. CSS3新特性
- 新增各种CSS选择器 (: not(.input): 所有class不是“input”的节点)
- 圆角border-radiuis
- 多列布局:multi-column layout
- 阴影和反射: multi-column layout
- 文字特效:text-shadow
- 线性渐变: gradient
- 旋转:transform
- 缩放,定位,倾斜,动画,多背景:transform: \scale(0.85,0.90) \ translate(0px, -30px) \ skew(-9deg, 0deg) \ Animation
补充. rem
使用原理:
- 1rem = 1html字号大小
- 根据@media不同屏幕宽度设置不同html的ont-size 可实现适配
- 使用flexible.js来解决各个屏幕适配的@media
7. css居中的方式
实现效果:
1) 只适用: 宽高已定
- 设置position: absolute(父元素记得设置: relative), 然后top和left设置50%, 50%, 再设置margin-left=宽/2, margin-top:宽/2
.div1{
width:500px;
height:500px;
border:1px solid black;
position: relative; /*很重要,不能忘*/
}
.div2{
background: yellow;
width:300px;
height:200px;
margin-left:-150px;
margin-top:-100px;
top:50%;
left:50%;
position: absolute;
}
- 子div的上下左右都设置成0,然后margin设置auto
.div1{
width:500px;
height:500px;
border:1px solid black;
position: relative; /*很重要,不能忘*/
}
.div2{
background: yellow;
width:300px;
height:200px;
margin:auto;
bottom: 0;
top:0;
left:0;
right:0;
position: absolute;
}
2) 适用: 不论是否固定宽高都可用. 问题在于兼容性. ie9及以下不支持
- 设置父级flex属性: display:flex; justify-content:center; align-items: center;
.div1{
width:500px;
height:500px;
border:1px solid black;
display: flex;
justify-content: center; /*使垂直居中*/
align-items:center; /*使水平居中*/
}
.div2{
background: yellow;
/*width:300px;
height:200px;*/
}
3) 适用: 指定宽高百分比
- 保证left和right的百分数一样就可以实现水平居中,保证top和bottom的百分数一样就可以实现垂直居中。但是这种方法不能由内部元素自动调节div的宽高,而是通过父元素大小控制的
.div1{
width:500px;
height:500px;
border:1px solid black;
position: relative;
}
.div2{
background: yellow;
position: absolute;
left: 30%;
right: 30%;
top:40%;
bottom: 40%;
}
8. display:none 和 visibility: hidden的区别
- display:none 隐藏对应的元素,在文档布局中不再给它分配空间,它各边的元素会合拢,就当他从来不存在。
- visibility:hidden 隐藏对应的元素,但是在文档布局中仍保留原来的空间。
9. 清除浮动
- clear:left 清除左浮动
- clear:right 清除右浮动
- clear:both 清除所有浮动
10. 盒子模型
文档中的每个元素被描绘为矩形盒子,渲染引擎的目的就是判定大小,属性——比如它的颜色、背景、边框方面——及这些盒子的位置。
在CSS中,这些矩形盒子用标准盒模型来描述。这个模型描述了一个元素所占用的空间。每一个盒子有四条边界:外边距边界margin edge,边框边界border edge,内边距边界padding edge和内容边界content edge。
内容区域是真正包含元素内容的区域,位于内容边界的内部,它的大小为内容宽度或content-box宽及内容高度或content-box高。如果box-sizing为默认值,width、min-width、max-width、height、min-height和max-height控制内容大小。
内边距区域padding area用内容可能的边框之间的空白区域扩展内容区域。通常有背景——颜色或图片(不透明图片盖住背景颜色)。
边框区域扩展了内边距区域。它位于边框边界内部,大小为border-box宽和border-box高。
外边距区域margin area用空白区域扩展边框区域,以分开相邻的元素。它的大小为margin-box的高宽。
在外边距合并的情况下,由于盒之间共享外边距,外边距不容易弄清楚。
对于非替换的行内元素来说,尽管内容周围存在内边距与边框,但其占用空间(行高)由line-height属性决定。
11. css动画和js动画
CSS3的动画
-
优点:
- 1.在性能上会稍微好一些,浏览器会对CSS3的动画做一些优化(比如专门新建一个图层用来跑动画)
- 2.代码相对简单
-
缺点:
- 1.在动画控制上不够灵活
- 2.兼容性不好
- 3.部分动画功能无法实现(如滚动动画,视差滚动等)
JavaScript的动画
-
优点:
- 1.控制能力很强,可以单帧的控制、变换
- 2.兼容性好,写得好完全可以兼容IE6,且功能强大。
-
缺点:
- 计算没有css快,另外经常需要依赖其他的库。
结论
- 所以,不复杂的动画完全可以用css实现,复杂一些的,或者需要交互的时候,用js会靠谱一些
js
12. 数据类型
字符串(String)、数字(Number)、布尔(Boolean)、对空(Null)、未定义(Undefined)、Symbol(ES6 引入的一种新的原始数据类型,表示独一无二的值)。
13. 操作dom(操作html)
-
获取dom for in var div1 = document.getElementById("box1"); //方式一:通过id获取单个标签
var arr1 = document.getElementsByTagName("div1"); //方式二:通过 标签名 获得 标签数组,所以有s var arr2 = document.getElementsByClassName("hehe"); //方式三:通过 类名 获得 标签数组,所以有s -
创建dom 新的标签(元素节点) = document.createElement("标签名");
-
插入dom 方式1:
父节点.appendChild(新的子节点);解释:父节点的最后插入一个新的子节点。
方式2:
父节点.insertBefore(新的子节点,作为参考的子节点); -
删除dom 父节点.removeChild(子节点);
-
复制dom
要复制的节点.cloneNode(); //括号里不带参数和带参数false,效果是一样的。 要复制的节点.cloneNode(true);括号里带不带参数,效果是不同的。解释如下:
- 不带参数/带参数false:只复制节点本身,不复制子节点。
- 带参数true:既复制节点本身,也复制其所有的子节点。
14. 操作字符串
-
length 属性返回字符串的长度:
-
indexOf() 方法返回字符串中指定文本首次出现的索引(位置)
-
lastIndexOf() 方法返回指定文本在字符串中最后一次出现的索引
如果未找到文本, indexOf() 和 lastIndexOf() 均返回 -1。
两种方法都接受作为检索起始位置的第二个参数。
-
search() 方法搜索特定值的字符串,并返回匹配的位置 这两种方法是不相等的。区别在于:
search() 方法无法设置第二个开始位置参数。ndexOf() 方法无法设置更强大的搜索值(正则表达式)。
-
slice() 提取字符串
-
substr() 类似于 slice() 不同之处在于第二个参数规定被提取部分的长度。
-
replace() 替换字符串
-
toUpperCase() 把字符串转换大写
-
toLowerCase() 把字符串转换小写
-
trim() 删除字符串两端的空白符
-
split() 分割字符串 将字符串转换为数组
15. 操作json对象
-
遍历对象 for in
-
删除对象属性 delete arr.key
-
新增对象属性 arr.key = ''
-
json对象转json字符串 JSON.stringify(arr)
-
json字符串转json对象 JSON.parse(arr)
16. 操作json数组
-
删除数组元素 arr.remove(name)、arr.splice(index,1)
-
新增对象属性 arr.key = ''
-
替换数组元素 arr.splice(index,n,data1,data2,......)
index:需要替换的元素的起始位置;
n:需要替换的元素的个数,实质是删除;
data1,data2:需要插入元素,用逗号隔开;
-
push 在数组尾部添加,返回添加后的数组元素的个数
-
unshift 在数组头部添加,返回数组添加后元素的个数
-
shift 删除数组的第一个元素,返回被删除的元素
-
pop 删除数组的最后一个元素,返回被删除的元素
-
splice 删除、插入、替换 执行的是操作,改变原数组的值,不返回任何值
-
slice(start,end) 截取数组的一部分,返回截取的数组,start为起始位置,end为结束为止,不包括结束的位置元素
-
concat a.concat(b) 将两个数组拼接在一起,返回一个拼接后的新数组,且a内部的元素在新数组的前部
-
join(''& '') 将数组以&符号间隔转换为字符串 返回一个字符串
-
split(‘’& ‘’) 将字符串以&符号位标记转换成数组 返回一个新数组
-
数组排序 arr.sort() 按照字符编码 返回 [a,b,c]
-
数组倒序 arr.reverse() 返回 [c,a,b]
-
打乱数组排序
function shuffle(arr) {
let i = arr.length
while (i) {
let j = Math.floor(Math.random() * i--)
[arr[j], arr[i]] = [arr[i], arr[j]]
}
}
shuffle(arr)
补充. http状态码
-
1** 信息,服务器收到请求,需要请求者继续执行操作
-
2** 成功,操作被成功接收并处理
-
3** 重定向,需要进一步的操作以完成请求
-
301 永久移动。请求的资源已被永久的移动到新URI,返回信息会包括新的URI,浏览器会自动定向到新URI。今后任何新的请求都应使用新的URI代替
-
302 临时移动。与301类似。但资源只是临时被移动。客户端应继续使用原有URI
-
307 临时重定向。与302类似。使用GET请求重定向
-
-
4** 客户端错误,请求包含语法错误或无法完成请求
-
401 请求要求用户的身份认证
-
403 服务器理解请求客户端的请求,但是拒绝执行此请求
-
404 服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
-
-
5** 服务器错误,服务器在处理请求的过程中发生了错误
补充. 跨域(CORS)
-
指的是浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对javascript施加的安全限制。
-
同源策略:是指协议,域名,端口都要相同,其中有一个不同都会产生跨域
-
解决方法:Jsonp,Nginx代理(反向代理),后端开放请求头
17. 闭包
-
闭包是指有权访问另一个函数作用域中的变量的函数. 创建闭包常见方式,就是在一个函数内部创建另一个函数.
-
作用:
- 匿名自执行函数 (function (){ ... })(); 创建了一个匿名的函数,并立即执行它,由于外部无法引用它内部的变量,因此在执行完后很快就会被释放,关键是这种机制不会污染全局对象。
- 缓存, 可保留函数内部的值
- 实现封装
- 实现模板
18. undefined和null的区别
-
null表示没有对象, 即此处不该有此值. 典型用法:
- (1) 作为函数的参数,表示该函数的参数不是对象。
- (2) 作为对象原型链的终点。
- ( 3 ) null可以作为空指针. 只要意在保存对象的值还没有真正保存对象,就应该明确地让该对象保存null值.
-
undefined表示缺少值, 即此处应该有值, 但还未定义.
-
(1)变量被声明了,但没有赋值时,就等于undefined。
-
(2) 调用函数时,应该提供的参数没有提供,该参数等于undefined。
-
(3)对象没有赋值的属性,该属性的值为undefined。
-
(4)函数没有返回值时,默认返回undefined。
-
19. ES6新特性
1) 箭头操作符 inputs=>outputs: 操作符左边是输入的参数,而右边则是进行的操作以及返回的值
2) 支持类, 引入了class关键字. ES6提供的类实际上就是JS原型模式的包装
3) 增强的对象字面量.
1. 可以在对象字面量中定义原型 proto : xxx //设置其原型为xxx,相当于继承xxx
2. 定义方法可以不用function关键字
3. 直接调用父类方法
4) 字符串模板: ES6中允许使用反引号 ` 来创建字符串,此种方法创建的字符串里面可以包含由美元符号加花括号包裹的变量${vraible}。
5) 自动解析数组或对象中的值。比如若一个函数要返回多个值,常规的做法是返回一个对象,将每个值做为这个对象的属性返回。但在ES6中,利用解构这一特性,可以直接返回一个数组,然后数组中的值会自动被解析到对应接收该值的变量中。
6) 默认参数值: 现在可以在定义函数的时候指定参数的默认值了,而不用像以前那样通过逻辑或操作符来达到目的了。
7) 不定参数是在函数中使用命名参数同时接收不定数量的未命名参数。在以前的JavaScript代码中我们可以通过arguments变量来达到这一目的。不定参数的格式是三个句点后跟代表所有不定参数的变量名。比如下面这个例子中,…x代表了所有传入add函数的参数。
8) 拓展参数则是另一种形式的语法糖,它允许传递数组或者类数组直接做为函数的参数而不用通过apply。
9) let和const关键字: 可以把let看成var,只是它定义的变量被限定在了特定范围内才能使用,而离开这个范围则无效。const则很直观,用来定义常量,即无法被更改值的变量。
- for of值遍历 每次循环它提供的不是序号而是值。
17) Promises是处理异步操作的一种模式
微信小程序
20. 参数传值方法
-
给HTML元素添加data-* 属性来传递我们需要的值,然后通过e.currentTarget.dataset或onload的param参数获取。但data-名称不能有大写字母和不可以存放对象
-
设置id 的方法标识来传值通过e.currentTarget.id获取设置的id的值,然后通过设置全局对象的方式来传递数值
-
在navigator中添加参数传值(?传的值的名称=所传的值在onLoad(option)用option来接收并获取)
补充 小程序设置安全区域
-
padding-bottom: constant(safe-area-inset-bottom);
-
padding-bottom: env(safe-area-inset-bottom);
-
constant()方法是小程序自带方法 专门用来设置地步安全区域
-
env()是css通用方法
-
都是用于设置底部安全距离的CSS属性值,但它们的使用方式和适用环境略有不同。具体使用哪个取决于您所使用的环境或框架的要求。
21. 提高微信小程序的应用速度
-
提高页面加载速度
-
用户行为预测
-
减少默认data的大小
-
组件化方案
22. 小程序授权登录流程
授权,微信登录获取code,微信登录,获取 iv , encryptedData 传到服务器后台,如果没有注册,需要注册。
23. 生命周期函数
-
onLoad——页面加载,调一次
-
onShow——页面显示,每次打开页面都调用
-
onReady——初次渲染完成,调一次
-
onHide——页面隐藏,当navigateTo或底部tab切换时调用
-
onUnload——页面卸载,当redirectTo或navigateBack时调用
补充 小程序组件生命周期
-
created——组件实例刚刚被创建时执行
-
attached——在组件实例进入页面节点树时执行
-
ready——在组件在视图布局完成后执行
-
moved——在组件实例被移动到节点树另一个位置时执行
-
detached——在组件实例被从页面节点树移除时执行
-
error——当组件方法抛出错误时执行
24. 小程序的双向绑定和vue哪里不一样
- 小程序直接this.data的属性是不可以同步到视图的,必须调用this.setData({})
25. 小程序内的页面跳转
-
wx.navigateTo——保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面(参数必须为字符串)
-
wx.redirectTo——关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
-
wx.switchTab——跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面,路径后不能带参数
-
wx.navigateBack——关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
-
wx.reLaunch——关闭所有页面,打开到应用内的某个页面
26. 小程序 wx:if 和 hidden 的区别
-
wx:if : 有更高的切换消耗。
-
hidden : 有更高的初始渲染消耗。
使用
-
频繁切换使用 hidden, 运行时条件变化使用 wx: if
vue
27. vue的两个核心点
-
数据驱动、组件系统
-
数据驱动:ViewModel,保证数据和视图的一致性。
-
组件系统:应用类UI可以看作全部是由组件树构成的。
-
28. v-show和v-if指令的共同点和不同点以及优先级
-
共同点:都能控制元素的显示和隐藏;
-
不同点:实现本质方法不同,v-show本质就是通过控制css中的display设置为none,控制隐藏,只会编译一次;v-if是动态的向DOM树内添加或者删除DOM元素,若初始值为false,就不会编译了。而且v-if不停的销毁和创建比较消耗性能。
-
总结:如果要频繁切换某节点,使用v-show(切换开销比较小,初始开销较大)。如果不需要频繁切换某节点使用v-if(初始渲染开销较小,切换开销比较大)。
-
当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中。所以,不推荐v-if和v-for同时使用。
-
如果v-if和v-for一起用的话,vue中的的会自动提示v-if应该放到外层去。
29. 引进组件的步骤
-
在template中引入组件;
-
在script的第一行用import引入路径;
-
用component中写上组件名称。
30. 父子组件间传值和传递事件
-
vue父组件向子组件通过 props 传递数据
-
vue子组件向父组件用 $emit 方法传递事件与参数
-
父组件可以使用res来调用子组件的方法或读取子组件数值
-
$parent 获取父组件的实例对象
-
$children 获取子组件的实例对象
-
bus 通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件,巧妙而轻量地实现了任何组件间的通信,包括父子、兄弟、跨级
-
provide/inject 父组件使用provide发布变量 子组件使用inject来接收
31. 封装 vue 组件的过程
-
建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。
-
准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
-
准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
-
封装完毕了,直接调用即
32. Vue中双向数据绑定是如何实现
-
vue 双向数据绑定是通过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
-
核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法。
33. v-modal的使用
-
v-model用于表单数据的双向绑定,其实它就是一个语法糖,这个背后就做了两个操作:
v-bind绑定一个value属性
v-on指令给当前元素绑定input事件。
34. vue 生命周期(钩子)
总共分为8个阶段创建前/后,载入前/后,更新前/后,销毁前/后。
创建前/后: 在beforeCreated阶段,vue实例的挂载元素$el和 数据对象 data都为undefined,还未初始化。在created阶段,vue实例的数据对象data有了,$el还没有。
载入前/后:在beforeMount阶段,vue实例的$el和data都初始化了,但还是挂载之前为虚拟的dom节点,data.message还未替换。在mounted阶段,vue实例挂载完成,data.message成功渲染。
更新前/后:当data变化时,会触发beforeUpdate和updated方法。
销毁前/后:在执行destroy方法后,对data的改变不会再触发周期函数,说明此时vue实例已经解除了事件监听以及和dom的绑定,但是dom结构依然存在。
-
每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听、编译模板、将实例挂载到 DOM 并在数据变化时更新 DOM 等。同时在这个过程中也会运行一些叫做 生命周期钩子 的函数,这给了用户在不同阶段添加自己的代码的机会。(ps:生命周期钩子就是生命周期函数)例如,如果要通过某些插件操作DOM节点,如想在页面渲染完后弹出广告窗, 那我们最早可在mounted 中进行。
-
第一次页面加载会触发8个钩子
- beforeCreate *
- 在new一个vue实例后,只有一些默认的生命周期钩子和默认事件,其他的东西都还没创建。在beforeCreate生命周期执行的时候,data和methods中的数据都还没有初始化。不能在这个阶段使用data中的数据和methods中的方法
- created *
- data 和 methods都已经被初始化好了,如果要调用 methods 中的方法,或者操作 data 中的数据,最早可以在这个阶段中操作
- beforeMount *
- 执行到这个钩子的时候,在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
- mounted *
- 执行到这个钩子的时候,就表示Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
- beforeUpdate
- 当执行这个钩子时,页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
- updated
- 页面显示的数据和data中的数据已经保持同步了,都是最新的
- beforeDestory
- Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。还没有真正被销毁
- destroyed
- 这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。 (不用记完主要是带 * 号的四个)
- beforeCreate *
-
created和mounted的区别
-
created:在模板渲染成html前调用,即通常初始化某些属性值,然后再渲染成视图。
-
mounted:在模板渲染成html后调用,通常是初始化页面完成后,再对html的dom节点进行一些需要的操作。
-
-
vue获取数据在哪个周期函数
- 一般 created/beforeMount/mounted 皆可。比如如果你要操作 DOM , 那肯定 mounted 时候才能操作.
35. vuex
-
vue框架中状态管理。在main.js引入store,注入。
-
场景有:单页应用中,组件之间的状态。音乐播放、登录状态、加入购物车
-
有5种属性 分别是 State、 Getter、Mutation 、Action、 Module
-
tate => 基本数据(数据源存放地)
-
getters => 从基本数据派生出来的数据
-
mutations => 提交更改数据的方法,同步!
-
actions => 像一个装饰器,包裹mutations,使之可以异步。
-
modules => 模块化Vuex
-
36. vue常用的UI组件库
- Mint UI,element,VUX
37. vue修改打包后静态资源路径的修改
- cli2版本:将 config/index.js 里的 assetsPublicPath 的值改为 './'
build: {
...
assetsPublicPath: './',
...
}
- cli3版本:在根目录下新建vue.config.js 文件,然后加上以下内容:(如果已经有此文件就直接修改)
module.exports = {
publicPath: '', // 相对于 HTML 页面(目录相同)
}