HTML
如何理解HTML语义化?
- 已阅读
1、让人更容易读懂(增加代码可读性)
2、让搜索引擎更容易读懂
通过合理的语义化,可以使得网页结构更加清晰,有助于页面的可读性和可访问性的提高,同时也有利于搜索引擎的爬取和排名,使得网页可以更好地为用户所用。
HTML 语义化是一种编写 HTML 标记的方法,旨在使代码更具可读性和易于理解。它通过使用正确的 HTML 标签来标记内容的类型和结构,让页面具有良好的语义和结构,从而方便人类和机器都能快速理解网页内容。
以下是关于 HTML 语义化的关键点:
-
使用正确的 HTML 标签:
- 每个 HTML 标签都有特定的语义含义。例如,
<h1>标签表示最重要的标题,<p>标签表示段落,<ul>标签表示无序列表,等等。使用正确的标签可以将内容的类型和结构传达给搜索引擎和其他阅读器。
- 每个 HTML 标签都有特定的语义含义。例如,
-
代码结构清晰:
- 语义化使得页面即使在没有 CSS 样式的情况下,也能呈现出清晰的内容结构。
-
有利于 SEO (Search Engine Optimization):
-
爬虫依赖于标签来确定上下文和各个关键字的权重。因此,语义化有助于爬虫抓取更多的有效信息,与搜索引擎建立良好的沟通。
-
SEO 旨在增加网站的可见性,使其在搜索引擎中排名更高。通过优化网站内容、结构和其他因素,可以吸引更多的有针对性的访问者。
-
提升用户体验:
- 使用
title和alt属性可以解释名称或图片信息,而label标签的灵活运用也有助于用户体验。
- 使用
-
便于团队开发和维护:
- 语义化使得代码更具可读性,让其他开发人员更容易理解你的 HTML 结构,减少差异化。
-
方便其他设备解析:
- 屏幕阅读器、盲人阅读器、移动设备等可以以有意义的方式来渲染网页。
HTML5 还新增了许多语义化的标签,例如:
<header>:用于定义页面的头部区域,通常包括网站 logo、主导航、全站链接以及搜索框。<nav>:定义页面的导航链接部分区域。<main>:定义文档的主要内容,该内容在文档中应当是独一无二的。<article>:定义页面独立的内容,它可以有自己的<header>、<footer>、<section>等,专注于单个主题的博客文章、报纸文章或网页文章。<section>:表示文档中的一个区域(或节),比如内容中的一个专题组。<aside>:表示一个和其余页面内容几乎无关的部分,通常表现为侧边栏或嵌入内容。<footer>:定义最近一个章节内容或根节点元素的页脚,通常包含作者、版权数据或与文档相关的链接等信息。
meta标签有哪些,分别代表什么意思?
"meta"标签是HTML标签之一, 常用于对页面的元数据(metadata)进行描述和定义,包括网页的关键词、页描、作者、字符编码、视口大小、缩放比例等信息。以下是常见的"mate"标签及其含义:
<meta charset="UTF-8">:定义字符编码为UTF-8,确保页面能够正确地显示特殊字符;<meta name="viewport" content="width=device-width, initial-scale=1.0">:定义视口大小为设备宽度,缩放比例为1,确保在手机等移动设备上显示正常;<meta name="keywords" content="关键词1, 关键词2, ...">:定义网页的关键词,便于搜索引擎抓取和分析网页主题;<meta name="description" content="网页描述">:定义网页描述信息,便于搜索引擎显示搜索结果摘要描述;<meta name="author" content="作者">:定义网页作者信息;<meta HTTP-EQUIV="refresh" CONTENT="3;URL=http://www.example.com/">:定义网页跳转规则,如每隔3秒自动跳转到指定网址;<meta name="robots" content="index,follow">:定义搜索引擎对网页的抓取策略,"index"表示允许抓取该网页,"noindex"则表示不允许抓取;"follow"表示跟踪该网页中所有的链接,"nofollow"则表示不跟踪;<meta name="format-detection" content="telephone=no">:定义是否禁止自动检测页面中的电话号码,避免误触拨打电话。
默认情况下,哪些情况是块级元素、哪些是内联元素?
块级元素(独占一行):display:block/table;有
div,h1,h2,table,ul,ol,p等内联元素(不独占一行):display:inline/inline-block; 有
spanimginputbutton等
Canvas 与 SVG 区别
Canvas 和 SVG 都是用于在 web 页面上进行图形绘制的技术,它们各自具有不同的特点和适用场景。下面是它们的主要区别:
- 基于像素和基于矢量:Canvas 是一种基于像素绘图的技术,它创建的图形是由一个个像素点组成的。而 SVG 则是一种基于矢量图形的技术,它使用数学公式来描述图像,可以任意缩放且不失真。对于需要进行复杂矢量图形绘制的场景,SVG 更适用。而对于需要处理图像、实现像素级别的操作的场景,Canvas 更适用。
- 渲染方式:Canvas 是在 HTML5 中新增的一种元素,使用 JavaScript 脚本来进行绘制,绘制区域受到限制,一旦绘制完成就无法再进行修改。而 SVG 是一种矢量图形格式,可以在浏览器中直接嵌入和处理,与 HTML 实现无缝结合,通过修改 SVG 元素的 DOM 树结构,可以对图形进行修改和操作。
- 动画绘制:Canvas 通过 JavaScript 实现绘制,可以通过 setInterval、setTimeout 和 requestAnimationFrame 等函数实现动画效果。而 SVG 具有内置的动画效果,支持 SMIL(Synchronized Multimedia Integration Language)动画,也可以通过 JavaScript 实现动画效果。
- 兼容性:Canvas 是 HTML5 中新增的元素,对一些老版本的浏览器支持不够完善。而 SVG 可以在现代浏览器和 IE9+ 中正常显示。在处理大量数据时,Canvas 由于绘制速度快,但是浏览器的处理以及渲染运算量会增加,可能会有卡顿等性能问题;而 SVG 的各种鼠标和事件处理,会使得一些浏览器配合页面时产生一些性能和可用性方面的问题。
综上所述,Canvas 更适用于游戏、图像处理、大数据量的动态绘制等场景,SVG 更适用于数据可视化、图形编辑和预览等场景。在实际开发中需要根据具体业务场景和要解决的问题来选择合适的技术。
HTML5的新特性?
1、新增标签:语义化标签(header、footer等)
2、新增事件:拖放API(drag)
3、新增元素:video和audio元素,提供了播放视频和音频文件的标准方法
4、Canvas绘图;5、SVG绘图; 8、Web Worker;9、Web Storage
CSS
CSS盒模型
何时使用怪异盒子模型?
框架想要具备栅格系统,肯定要用border-sizing
margin纵向重叠问题
-
margin重叠是指在垂直方向上,相邻的两个元素的margin-top和margin-bottom会发生重叠的情况。
-
空白内容的 < p > < / p >也会重叠。
两个margin产生折叠的必备条件:
1、必须处于常规文档流(不能是浮动和定位)的块级盒子,并且处于同一个BFC当中。
2、没有线盒,没有空隙,没有padding和border将他们分割。
3、都处于垂直方向相邻的外边距。
margin(top,left,right,bottom)设置负值有何效果?
1、margin-left为负值时,自身元素会向左移动。
2、margin-top为负值时,自身元素会向上移动。
3、margin-right为负值时,自身元素不受影响,但是相邻元素会向左移动。
4、margin-bottom为负值时,自身元素不受影响,但是相邻元素会向上移动。
5、position: absolute时,margin-right和margin-bottom为负值时,自身元素会受影响。
BFC理解和应用
1、是什么?
先普及一下概念,FC就是Fomatting Context。它是页面中的一块渲染区域。而且有一套渲染规则,它决定了其子元素将怎样定位。以及和其它元素的关系和相互作用.
BFC和IFC都是常见的FC。 分别叫做Block Fomatting Context 和Inline Formatting Context。
答:Block format context——块级格式化上下文。 是一块独立渲染的区域,内部元素的渲染不会影响边界以外的元素。
2、形成BFC的条件?
float不是 none;
overflow不是 visible;
position是 fixed和absolute;
display是 flex,inline-block等
3、BFC常见应用= 清除浮动
一般来说分为以下4种情况:
1、相邻兄弟元素的margin-bottom和margin-top发生重叠,这时候我们可以设置其中一个元素为BFC即可解决。
2、没有内容的元素,自身的margin-top和margin-bottom发生重叠可以通过以下几种方法解决:
1、元素设置padding或border。 2、给元素设置一个高度。
3、父元素的margin-top和子元素的margin-top发生重叠,他们发生重叠是因为这两个元素是相邻的,所以可以通过以下几种方法来解决:
1、为父元素设置padding-top或border-top来分割他们。
2、设置父元素为BFC。
3、父元素和第一个子元素之间添加一个内联元素来进行分割。
4、高度为auto的父元素的margin-bottom和最后一个子元素的margin-bottom发生重叠,他们发生重叠一个原因是他们是相邻的,另一个原因是父元素的高度是不固定的,那么可以通过以下几种方法来解决:
1、为父元素设置padding-top或border-top来分割他们。
2、设置父元素为BFC。
3、父元素和第一个子元素之间添加一个内联元素来进行分割。
float布局问题(圣杯布局和双飞翼布局),以及claerfix
圣杯布局和双飞翼布局的目的以及如何实现?
目的:
三栏布局中间一栏最先加载和渲染(中间内容最重要)
两侧内容固定,中间内容随着宽度自适应
一般用于PC网页
如何实现?
写结构的时候要注意,父元素的的三栏务必先写中间盒子。因为中间盒子是要被优先渲染嘛~并且设置其自适应,也就是width:100% 。
圣杯布局:
- 利用浮动和负边距来实现。
- 父级元素设置左右的 padding,三列均设置向左浮动。
- 中间一列放在最前面,宽度设置为父级元素的宽度,因此后面两列都被挤到了下一行。
- 通过设置 margin 负值将其移动到上一行,再利用相对定位,定位到两边。
<!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>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="wrap">
<div id="header">header</div>
<div id="content">
<div id="middle">
middle
</div>
<div id="left">left</div>
<div id="right">right</div>
</div>
<div id="footer">footer</div>
</div>
</body>
</html>
*{
margin: 0;
padding: 0;
}
body{
min-width: 600px;
}
#header,#footer{
height: 50px;
width: 100%;
background: grey;
}
#middle,#left,#right{
float: left;
}
#content{
overflow: hidden; // 创建BFC
padding: 0 200px; // 重点,添加200px 左右padding
}
#left,#right{
width: 200px;
height: 200px;
background: pink;
}
#middle{
width: 100%;
height: 200px;
background: yellowgreen;
}
#left{
margin-left:-100% ; // 移动到上一行相同位置
position: relative;
left:-200px; /相对左移200px
}
#right{
margin-left: -200px; //往上行排
position: relative;
left:200px; //相对右移200px
}
原理:
当给margin添加%值时,是根据父元素的宽度来计算的,所以当设置margin-left:-100%后,left刚好被移动到上一行相同位置。而right不需要移动到上一行相同位置,只需要自身在上一行就行,所以只需要设置margin-left为自身的宽度负值后,自动会往上行排。
双飞翼布局:
- 双飞翼布局相对于圣杯布局来说,左右位置的保留是通过中间列的 margin 值来实现的。本质上来说,也是通过浮动和外边距负值来实现的。
- 通过设置 margin 负值将其移动到上一行,再利用相对定位,定位到两边。
<!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>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div class="wrap">
<div id="header">header</div>
<div id="content">
<div id="middle">
<div class="middle-inner">
middle
</div>
</div>
<div id="left">left</div>
<div id="right">right</div>
</div>
<div id="footer">footer</div>
</div>
</body>
</html>
*{
margin: 0;
padding: 0;
}
.wrap{
min-width: 600px;
}
#header,#footer{
height: 50px;
width: 100%;
background:grey;
}
#left,#right{
width: 200px;
height: 200px;
background: green;
}
#middle{
background: pink;
width: 100%;
float: left;
height:200px;
}
#content{
overflow: hidden;
}
#left{
float: left;
margin-left: -100%; // 移到上行相同位置
}
#right{
float: left;
margin-left: -200px; // 往上行排
}
.middle-inner{
margin: 0 200px; //重点,仅增加middle box的左右边距
}
手写claerfix(清除浮动)
.clearfix:after{
content: '';
display: table;
clear: both;
}
.clearfix{
*zoom:1;/*兼容IE低版本*/
}
flex布局
常用语法(容器属性):
flex-direction:项目排列方式
justify-content:项目横轴对齐方式
flex-wrap:是否换行
align-content:在项目为多行时需要加flex-wrap:wrap; ,项目纵轴如何对齐(不能控制单行的盒子内位置变换)
align-items(控制容器):项目纵轴如何对齐(能控制单行的盒子内位置变换)
align-self(控制子项):容器子项纵轴如何对齐
如何画三点色子?
<div class="third-face">
<div class="pip"></div>
<div class="pip"></div>
<div class="pip"></div>
</div>
/* 骰子的点的大小和阴影设置 */
.pip {
width: 24px;
height: 24px;
border-radius: 50%;
margin: 4px;
background-color: #333;
box-shadow: inset 0 3px #111, inset 0 -3px #555;
}
/* 骰子的面设置 */
[class$="face"] {
width: 104px;
height: 104px;
border-radius: 10px;
padding: 4px;
background-color: #e7e7e7;
/* 实现立体效果的重要代码-内阴影 */
box-shadow:
inset 0 5px #fff,
inset 0 -5px #bbb,
inset 5px 0 #d7d7d7,
inset -5px 0 #d7d7d7;
}
.third-face {
display: flex;
flex-direction: column;
justify-content: space-between;
/* align-items: flex-start; */
}
.pip:nth-child(2) {
align-self: center; /*第二项居中对齐*/
}
.pip:nth-child(3) {
align-self: flex-end; /*第三项尾对齐*/
}
第四面-4点
html结构
<div class="first-face">
<div class="col">
<div class="pip"></div>
<div class="pip"></div>
</div>
<div class="col">
<div class="pip"></div>
<div class="pip"></div>
</div>
</div>
css
.four-face {
display: flex;
justify-content: space-between;
}
.four-face .col {
display: flex;
flex-direction: column;
justify-content: space-between;
}
display的属性以及作用?
display: block, inline, inline-block
当谈到 CSS 布局时,block、inline 和 inline-block 是三个重要的概念。让我们详细了解它们之间的区别:
-
block:- 元素会独占一行,多个块级元素会另起一行。
- 可以设置
width、height、margin和padding属性。 - 默认情况下,块级元素的宽度是父元素的宽度。
- 常见的块级元素包括
<div>、<p>、<h1>到<h6>、<ul>、<ol>、<li>等。
-
inline:- 元素不会独占一行,设置
width和height属性无效。 - 可以设置水平方向的
margin和padding,但不能设置垂直方向的。 - 常见的内联元素包括
<a>、<span>、<strong>、<em>、<img>、<input>等。
- 元素不会独占一行,设置
-
inline-block:-
兼具两者的优点,可以挤在一起,也可以设置
width和height。 -
行内显示:
inline-block元素在同一行内显示,不会独占一整行,类似于inline元素。 -
尺度控制:与
block元素不同,inline-block可以设置宽度和高度,适用于需要同时控制布局和尺寸的情况。 -
适用于需要同时控制布局和尺寸的情况。
-
常见的内联块状元素包括
<img>和<input>。 -
应用场景:
- 按钮:如果你想创建一个既可以设置宽度又可以在同一行内显示的元素,可以使用
display: inline-block。这对于创建按钮、图标、导航链接等非块级元素非常有用。 - 图像和输入框:
img和input元素通常被视为inline-block,因为它们既可以在同一行内显示,又可以设置尺寸。
- 按钮:如果你想创建一个既可以设置宽度又可以在同一行内显示的元素,可以使用
为了更好地理解,我制作了一个简单的表格:
属性 特点 示例 block独占一行,可设置尺寸 <div>,<p>inline不独占一行,无尺寸 <span>,<a>inline-block同时具备两者特点 <img>,<input> -
display:none和visibility:hidden的区别?
- 首先是空间占据方面,设置了
display:none样式的元素,在文档流中是不占位置的,而且,浏览器也不会解析这个元素; - 而设置了
visibility:hidden样式的元素,在文档流中仍然占位,可以理解为,只是把透明度变成了0; - 一旦父节点元素应用了 display:none,父节点及其子孙节点元素全部不可见,而且无论其子孙元素如何不屈地挣扎都无济于事。
Grid & Flex
- Flex:
- Grid:
CSS3 和 HTML5 新特性
css3中新增了一些更加精确的选择器;
- 属性选择器:可以根据元素的属性值选择元素。比如:[attr]、[attr=value]、[attr~=value]、[attr|=value]、[attr^=value]、[attr$=value]、[attr*=value]。
- 伪类选择器:可以在特定状态下选取元素。比如:
:hover、:visited、:active、:focus、:first-child、:last-child、:nth-child(n)、:nth-of-type(n)、:not(selector)等。 - 伪元素选择器:可以在元素的特定部分选取元素。比如:
::before、::after、::first-letter、::first-line等。 - 多元素选择器:可以同时选择多个元素。比如:
p, h1, li。 - 相邻兄弟选择器:可以选择和某个元素相邻的下一个兄弟元素。比如:
h1 + p。 - 通用选择器:可以选择所有元素,比如:
*。
增加了一些新属性,如border-radius:圆角边框box-shadow:盒子阴影,background-size:背景图片大小;
弹性布局:CSS3提供了flexbox布局模型,使得实现响应式布局更加容易和方便。
还新增了一些动画,如transition:过渡,transform:转换(位移 旋转 缩放)还有animation:动画。
Demo:天猫案例——mouseover高亮
效果:
- 天猫的产品展示卡片
- 鼠标移入的时候,显示红色边框、图片逐渐变透明
<div class="main">
<ul>
<li>
<div class="img">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Good_Food_Display_-_NCI_Visuals_Online.jpg/1920px-Good_Food_Display_-_NCI_Visuals_Online.jpg" alt="">
</div>
<div class="goods_title">
展艺葡式蛋挞皮102个装 烘焙家用材料套餐带锡底半成品酥皮塔壳液
</div>
<div class="price">$499</div>
</li>
<li>
<div class="img">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/6/6d/Good_Food_Display_-_NCI_Visuals_Online.jpg/1920px-Good_Food_Display_-_NCI_Visuals_Online.jpg" alt="1">
</div>
<div class="goods_title">
砀山锦绣黄桃10斤桃子新鲜水果包邮应季黄金毛蜜桃当季整箱时令甜
</div>
<div class="price">$499</div>
</li>
</ul>
</div>
body {
background-color: #ddd;
}
.main {
margin: 100px auto;
width: 800px;
clear: both;
}
.main li {
background-color: #fff;
list-style: none;
width: 240px;
padding: 1px;
border: 1px solid rgba(255, 0, 0, 0);
cursor: pointer;
float: left;
margin: 3px;
}
.main li:hover {
border: 1px solid rgba(255, 0, 0, 1);
}
.main li:hover .img img {
opacity: 0.7;
}
.img img {
width: 240px;
transition: all 0.5s;
}
.goods_title {
margin: 10px;
color: #666;
height: 41px;
overflow: hidden;
}
.price {
margin: 10px;
color: red;
}
伪元素和伪类的区别和作用?
-
伪类 (Pseudo-classes) :
- 定义:伪类是一种选择器,用于
选择处于特定状态的元素,例如第一个元素、鼠标悬停时的元素等。 - 作用:伪类帮助我们在不添加额外类的情况下,根据元素的状态来
应用样式。这使得代码更灵活且易于维护。 - 示例:
:first-child选择器用于选中某个元素的第一个子元素,而不需要编辑HTML。其他示例包括:hover和:focus等用户行为伪类。
a:hover {color: #FF00FF} p:first-child {color: red} - 定义:伪类是一种选择器,用于
-
伪元素 (Pseudo-elements) :
- 定义:伪元素也是一种选择器,但它不是选择已有元素,而是创建一个虚拟的元素,但是这些元素实际上并不在文档中生成。它们只在外部显示可见,但不会在文档的源代码(DOM)中找到它们,因此,称为“伪”元素。
- 作用:伪元素用于在现有元素上
插入内容,例如选择某个元素的第一行或在元素前后插入内容。 - 示例:
::before和::after是常见的伪元素,用于在元素前后插入内容。例如,我们可以使用::before插入一个小箭头作为视觉提示。
p::before {content:"第一章:";} p::after {content:"Hot!";} p::first-line {background:red;} p::first-letter {font-size:30px;} -
组合使用:
- 如果你想让第一段的第一行加粗,可以将
:first-child和::first-line选择器组合起来。 - 通过
::before和::after伪元素与content属性的共同使用,我们可以在CSS中插入文本字符串或图标。
/* 选择第一个段落的第一行 */ p:first-child::first-line { color: red; font-weight: bold; } /* 在元素前插入文本字符串 */ p::before { content: "🌟 "; } /* 在元素后插入图标 */ p::after { content: url('path/to/icon.png'); } - 如果你想让第一段的第一行加粗,可以将
::before和:after有什么区别?
(1)冒号(:)用于CSS3伪类,双冒号(::)用于CSS3伪元素。 (2)::before就是以一个子元素的存在,定义在元素主体内容之前的一个伪元素。并不存在于dom之中,只存在在页面之中。
Less和sass是什么?我们为什么要使用他?
他们都是 CSS 预处理器,将 CSS 赋予了动态语言的特性,如变量,继承,运算, 函数,LESS 既可以在客户端上运行 (支持 IE 6+, Webkit, Firefox),也可以在服务端运行 (借助 Node.js)。
为什么要使用它们? 结构清晰,便于扩展。 可以方便地屏蔽浏览器私有语法差异。封装对浏览器语法差异的重复处理, 减少无意义的机械劳动。
可以轻松实现多重继承。 完全兼容 CSS 代码,可以方便地应用到老项目中。LESS 只是在 CSS 语法上做了扩展,所以老的 CSS 代码也可以与 LESS 代码一同编译。
px、em 和 rem
-
px(像素):
- 绝对单位,所谓像素就是呈现在我们显示器上的一个个小点,每个像素点都是大小等同的。
- 无法根据页面大小自动调整。
- 在IE浏览器中,无法调整使用px作为单位的字体大小。
- 国外的大部分网站使用em或rem作为字体单位,因为Firefox可以调整px和em、rem,但超过96%的中国网民使用IE浏览器(或内核)。
- 有些人会把px认为是相对长度,原因在于在移动端中存在设备像素比,px实际显示的大小是不确定的。这里之所以认为px为绝对单位,在于px的大小和元素的其他属性无关。
-
em:
- 相对单位,相对于当前对象内文本的字体尺寸。
- 如果当前行内文本的字体尺寸未被人为设置,那么相对于浏览器的默认字体尺寸(
1em = 16px)。 - em的值不是固定的,会继承父级元素的字体大小。
- 为了简化 font-size 的换算,我们需要在css中的 body 选择器中声明
font-size= 62.5%,这就使 em 值变为16px*62.5% = 10px。这样12px = 1.2em, 10px = 1em, 也就是说只需要将你的原来的px 数值除以10,然后换上 em作为单位就行了。
<div class="parent"> I'm parent div set to 20px <div class="child"> I'm the child div set to 2em, i.e 40px. </div> </div>.parent{ font-size: 20px; } .child{ font-size: 2em; /* 40px */ } -
rem (root em):
- 相对单位,相对于HTML根元素的字体尺寸。
- 使用rem为元素设定字体大小时,仍然是相对大小,但相对的只是HTML根元素。同理,如果想要简化
font-size的转化,我们可以在根元素html中加入font-size: 62.5%。
html {font-size: 62.5%; } /* 公式16px*62.5%=10px */
这样页面中1rem=10px、1.2rem=12px、1.4rem=14px、1.6rem=16px;使得视觉、使用、书写都得到了极大的帮助- rem可同时兼顾相对大小和绝对大小的优点,通过它可以调整所有字体大小而避免连锁反应。
- 和em不同的是rem总是相对于
根元素,而不像em一样使用级联的方式来计算尺寸 - 目前,除了IE8及更早版本外,所有浏览器均已支持rem。
对于只需要适配少部分手机设备,且分辨率对页面影响不大的,使用px即可。
对于需要适配各种移动设备,使用rem,例如只需要适配iPhone和iPad等分辨率差别比较挺大的设备。
vh、vw、vm
vh (view width) & vw (view width)
vw是根据窗口的宽度,分成100等份,100vw就表示满宽,50vw就表示一半宽。(vw 始终是针对窗口的宽),同理,
vh则为窗口的高度。
这里的窗口分成几种情况:
- 在桌面端,指的是浏览器的可视区域
- 移动端指的就是布局视口
像vw、vh,比较容易混淆的一个单位是%,不过百分比宽泛的讲是相对于父元素:
- 对于普通定位元素就是我们理解的父元素
- 对于position: absolute;的元素是相对于已定位的父元素
- 对于position: fixed;的元素是相对于 ViewPort(可视窗口)
vm
相对于视口的
宽度或高度中较小的那个。其中最小的那个被均分为100单位的vm。 举个例子:浏览器高度900px,宽度1200px,取最小的浏览器高度,1 vm = 900px/100 = 9 px。
定位
absolute和relative分别依据什么定位?
定位元素:absolute, relative ,fixed, body
relative依据自身位置定位,absolute依据最近一层(一层一层向上找父元素)的定位元素定位。
绝对定位和固定定位的元素会脱离文档流吗?
relative相对定位的元素不会脱离文档流;开启fixed绝对定位和absolute固定定位,会使元素脱离文档流
居中对齐有哪些实现方式?
水平居中:
inline元素:text-align: center
block元素:margin: auto
absolute元素: left: 50% + transform: translateX(-50%);
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<div class="centered-text">
<p>This is centered text.</p>
</div>
<div class="centered-block">
<p>This is a centered block element.</p>
</div>
<div class="centered-absolute">
<p>This is an absolutely positioned element.</p>
</div>
</body>
</html>
/* 水平居中的方法 */
.centered-text {
text-align: center; /* 对于inline元素 */
background-color: grey;
}
.centered-block {
margin: auto; /* 对于block元素 */
width: 50%; /* 设置宽度以使效果更明显 */
background-color: pink;
}
.centered-absolute {
position: absolute; /* 对于absolute定位的元素 */
left: 50%;
transform: translateX(-50%);/* 沿x轴左移自身width的一半 */
background-color: lightblue;
}
垂直居中:
inline元素:line-height的值等于 height的值
block元素:详见下一节 [margin: auto]
absolute元素: top:50% + margin-top负值(知道元素高度的情况下)
absolute元素: transform : translate(-50%,-50%)(不知道元素高度的情况下,对浏览器版本要求高)???
absolute元素: top,left,bottom,right都为0 + margin: auto(不知道元素高度的情况下,对浏览器版本无要求)???
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Vertical Centering Examples</title>
</head>
<body>
<div class="inline-container">
<span>Inline Element</span>
</div>
<div class="block-container">
<div>Block Element</div>
</div>
<div class="absolute-container">
<div class="absolute-content">Absolute Positioning</div>
</div>
</body>
</html>
/* 内联元素 */
.inline-container {
height: 100px;
border: 1px solid black;
line-height: 100px; /* 垂直居中 */
}
/* 块级元素 */
.block-container {
height: 100px;
border: 1px solid black;
display: flex;
align-items: center; /* 垂直居中 */
}
/* 绝对定位元素 */
.absolute-container {
position: relative;
height: 100px;
border: 1px solid black;
}
.absolute-content {
height: 100px;
position: absolute;
top: 50%;
margin-top: -10px;
}
margin: auto
使用 margin: auto 实现元素垂直水平居中
- 使用
margin: 0 auto去使块级元素水平居中这个使用场景大家应该不陌生。 - 但不知道有没有发现,按道理来说
margin: auto应该会使块级元素水平垂直居中,但是为啥使用之后只有水平方向起作用了,而垂直方向没有起作用呢? - 想要明白为啥,那就得先知道
auto是如何起作用的
auto 原理
-
auto:自动。作用为自动填充剩余空间。
-
前提:
-
块级元素的包含
宽度会自动扩充,填满父元素,而包含高度却不会。 -
对于一个块级元素,我们对其设置了宽高后,则:
它的包含宽度 = margin-left + border + padding-left + content-width + padding-right + border + margin-right
-
当我们给他的
margin设置为auto时,元素会自动平分剩下的距离,从而达到一个水平居中的效果 -
而包含高度不会自动扩充填满父元素,也就是说元素的包含高度是没有剩余空间的,设置了
auto也没得平分,所以直接对上下方向设置auto是不能实现垂直居中的
-
使用 auto 设置垂直水平居中
-
原理
- 通过定位扩充高度填满父元素,然后使用
margin: auto垂直水平方向上都自动平分剩下的距离达到垂直水平居中 - 对父元素设置
position: relative;,创建一个定位上下文,使得子元素可以相对于父元素进行绝对定位。 - 对子元素设置
position: absolute;,并对上下左右都设置为0,填充多余空间(这意味着子元素的四个边界都与父元素的四个边界对齐)。 - 这时候使用
margin: auto,就会去自动平分多余的空间,实现元素垂直水平居中了
- 通过定位扩充高度填满父元素,然后使用
-
代码
-
<div class="outsidebox"> <div class="insidebox"></div> </div> -
.outsidebox { position: relative; width: 400px; height: 400px; background: #e6f3ff; } .insidebox { position: absolute; /*脱离文档流BFC*/ margin: auto; width: 100px; height: 100px; top: 0; left: 0; right: 0; bottom: 0; background: skyblue; }
-
-
效果图
什么情况下 z-index 会失效?
- 问题标签元素的
z-index属性值低于父元素 --->提高父标签的z-index值 - 问题标签没有设置定位(
position)属性 --->浮动元素添加position属性(如relative,absolute等); - 问题标签含有浮动(float)属性。 --->除去浮动
- 父标签 position属性为relative --->父标签改为absolute
图文样式
line-height的继承问题
分为三种情况:
1.具体数值(子元素未设置具体行高数值,会自动继承父元素的行高)
2.按比例(子元素未设置行高,父元素行高为1.5或2)
计算:子元素行高会先继承父元素行高的1.5或2,然后和子元素字体大小(font-size)相乘。
3.百分比(子元素不会直接继承父元素行高,而是等父元素字体大小*行高百分比后在继承)
计算:子元素行高= 父元素字体大小(font-size)*行高百分比。
CSS样式定义的优先级顺序总结
!important>内联样式>ID选择器>类选择器>类型选择器>元素选择器>浏览器默认样式
CSS有哪些属性可以继承?
1、字体系列属性:font、font-family、font-weight、font-size、font-style;
2、文本系列属性: 2.1)内联元素:color、line-height、word-spacing、letter-spacing、 text-transform; 2.2)块级元素:text-indent、text-align;
3、元素可见性:visibility
4、表格布局属性:caption-side、border-collapse、border-spacing、empty-cells、 table-layout;
5、列表布局属性:list-style
6、光标属性:cursor
如何让超出宽度的文字显示为省略号?
text-overflow: ellipsis
如何解决IE6双倍margin的Bug?
使用display: inline
display:none和visibility:hidden的区别?
display:none和visibility:hidden都可以隐藏元素,但它们的行为略有不同。
display:none:将元素完全从页面中删除,元素既不显示也不占据页面空间。它会在DOM结构中删除该元素,因此不会保留元素的空间。
visibility:hidden:隐藏元素,但仍然占据在页面中的位置,元素不会被删除。隐藏后的元素仍在DOM结构中占据空间,因此它保留了隐藏元素的空间。
因此,如果您希望隐藏元素并释放该元素占据的空间,可以使用display:none。
而如果您只想隐藏元素但不改变页面布局,可以使用visibility:hidden。
响应式
什么是响应式?
当数据变换的时候,页面发生变化,视图发生变化。
如何实现响应式布局?
1、先使用媒体查询(media-query),根据不同的屏幕宽度设置根元素的font-size
2、后续页面开发中使用基于根元素的rem相对长度单位
怎么理解重构跟重绘?什么场景下会触发?
重构:当我们对 DOM 的修改引发了 DOM几何尺寸的变化(比如修改元素的宽、高或隐藏元素等)时,浏览器需要重新计算元素的几何属性,然后再将计算的结果绘制出来
触发时机:添加删除DOM节点;元素位置发生变化;元素尺寸发生变化,页面一开始渲染
重绘:当我们对 DOM的修改导致了样式的变化(color或background-color),却并未影响其几何属性时,浏览器不需重新计算元素的几何属性、直接为该元素绘制新的样式
触发时机:颜色修改,文本方向修改,阴影修改
如何减少重构:
- 对于那些复杂的动画,对其设置
position: fixed/absolute,尽可能地使元素脱离文档流,从而减少对其他元素的影响 - 如果想设定元素的样式,通过改变元素的
class类名 (尽可能在 DOM 树的最里层) - 避免设置多项内联样式
动画
transition:
/* 应用于1个属性 */
/* 属性值 | 持续时间 */
transition: margin-right 4s;
/* 属性值 | 持续时间 | 延迟 */
transition: margin-right 4s 1s;
/* 属性值 | 持续时间 | 持续时间 | 动画效果 */
transition: margin-right 4s ease-in-out;
/* 属性值 | 持续时间 | 动画效果 | 延迟 */
transition: margin-right 4s ease-in-out 1s;
/* 同时应用2个属性*/
transition: margin-right 4s, color 1s;
/* 应用于所有属性 | 持续时间 | 动画效果 */
transition: all 0.5s ease-out;
/* 全局变量 */
transition: inherit;
transition: initial;
transition: unset;
animation:
/* 持续时间 | 动画效果 | 延迟 | 重复次数 | 方向 | 过渡方式 | 是否暂停 | 动画名 */
animation: 3s ease-in 1s 2 reverse both paused xxx;
/* 持续时间 | 动画效果 | 延迟 | 动画名 */
animation: 3s linear 1s xxx;
/* 持续时间 | 动画名 */
animation: 3s xxx;
关于CSS3动画
transition 渐变动画(过渡)
transform转变动画(变形+移动)
animation(自定义动画)
ES6
ES6新特性
ES6(ECMAScript 2015)引入了许多新特性,这些特性包括语法改进、新的数据结构、内置方法、模块化、箭头函数、类和模块等。以下是 ES6 中一些重要的新特性:
-
let 和 const 关键字:用于声明变量,
let声明的变量具有块级作用域,const声明的变量为常量。 -
箭头函数:提供了更简洁的函数声明语法,同时具有词法作用域绑定
this的特性。 -
模板字符串:使用反引号 ` 和
${}来支持多行字符串和插值表达式。 -
默认参数:允许为函数参数设置默认值。
-
解构赋值:允许从数组或对象中提取值并赋值给变量。
-
对象扩展操作符:用于复制对象、合并对象或提取对象的部分属性。
-
Promise:用于异步编程的新的标准,可以更优雅地处理异步操作。
-
Class:引入了类和继承的语法糖,更加符合面向对象的编程思想。
-
模块化:引入了
import和export关键字,用于模块化 JavaScript 代码。 -
Map 和 Set 数据结构:提供了更灵活的数据结构,用于存储键值对和不重复的值。
-
Symbol 数据类型:引入了一种新的原始数据类型,用于创建独一无二的标识符。
-
生成器函数:使用
function*关键字定义的生成器函数,可以通过yield关键字暂停和恢复函数的执行。 -
数组新方法:引入了许多新的数组方法,如
Array.from()、Array.of()、find()、findIndex()等。 -
字符串新方法:引入了许多新的字符串方法,如
startsWith()、endsWith()、includes()、repeat()等。
这些新特性使得 JavaScript 编程更加现代化、高效和灵活,提高了开发效率和代码质量。
script标签中defer和async的区别?
defer 和 async属性都是去异步加载外部的JS脚本文件,它们都不会阻塞页面HTML的解析,其区别如下:
- 执行顺序: 多个带async属性的标签,不能保证加载的顺序;多个带defer属性的标签,按照加载顺序执行;
- 脚本是否并行执行:
async属性,表示后续文档的
加载和执行与js脚本的加载和执行是并行进行的,即异步执行;defer属性,加载后续文档的过程和js脚本的加载(此时
仅加载不执行)是并行进行的(异步),js脚本需要等到文档所有元素解析完成之后才执行,DOMContentLoaded事件触发执行之前。没有
defer或async,浏览器会立即加载并执行指定的脚本,之前加载到一半的HTML页面会停止下来,被阻塞加载。有
async,加载和渲染后续文档元素的过程将和script.js的加载与执行并行进行,将script变成异步,当scripet异步解析完成后,如果HTML页面还没有完成解析,又会继续阻塞页面的解析。有
defer,加载后续文档元素的过程将和script.js的加载并行进行,将script变成异步。但是script.js的执行要在所有元素解析完成之后,类似于将这个script放在了页面的底部。
总结:async和defer都是异步加载js。不同的是async是js只要一加载完毕就会马上执行 不管html有没有解析完毕。所以它有可能阻塞html解析。而defer要等到html解析完毕之后才执行。所以不会阻塞html解析。
let、const 和 var的区别?
let、const 和 var 是 JavaScript 中用于声明变量的关键字,它们之间有一些重要的区别:
-
var:var是 ES5 中引入的声明变量的关键字。- 它在全局作用域和函数作用域中都有效。
- 具有变量提升(hoisting)的特性,即变量可以在声明之前被使用,但其值为
undefined。 - 在浏览器中,全局作用域下的
var声明的变量会自动成为window对象的属性。所以允许重复声明同一个变量,不会抛出错误。 - 没有块级作用域,即在
if、for等块内声明的变量仍然可以在块外访问。
-
let:let是 ES6 中引入的声明变量的关键字。- 具有块级作用域,即在
{}内声明的变量只在该块内有效。 - 不具有变量提升,即在声明之前访问会抛出错误。
- 不允许重复声明同一个变量,在同一个作用域内重复声明会抛出错误。
-
const:const也是 ES6 中引入的声明变量的关键字,用于声明常量。- 具有块级作用域,同
let。 - 声明后不能再被赋值,但对于引用类型(如数组、对象),其内部的值是可变的。
- 同
let,不具有变量提升,并且不允许重复声明。
var存在变量提升;
let const不存在变量提升,只在自己的块级作用域中起作用,存在暂时性死区。
const声明之后要马上赋值,数值变量赋值后不可更改,引用数据类型赋值后可更改。
总的来说,推荐优先使用 const,除非变量的值需要被重新赋值,这时可以使用 let。避免使用 var,因为它具有一些不太友好的特性,比如变量提升和缺乏块级作用域。
箭头函数和普通函数的区别?
箭头函数(Arrow Functions)和普通函数(Regular Functions)之间有几个重要的区别:
-
语法:
- 箭头函数使用箭头 (
=>) 来定义函数,语法更加简洁。 - 普通函数使用
function关键字定义函数。
- 箭头函数使用箭头 (
-
this的绑定:- 箭头函数本身无this,其this指向父级作用域(即包含箭头函数的作用域)的this,并且call,bind,apply无法改变this的指向。
- 在普通函数中,
this的值在函数被调用时才确定,并且取决于函数调用方式(如作为对象方法调用、作为构造函数调用、使用call()或apply()等)。
-
arguments 对象:
- 箭头函数没有自己的
arguments对象,它会捕获外层函数的arguments对象。 - 普通函数有自己的
arguments对象,用于访问传入函数的参数。
- 箭头函数没有自己的
function regularFunction() {
console.log("Regular Function:");
console.log(arguments); // 输出传入函数的参数
}
regularFunction(1, 2, 3); // output: [1, 2, 3]
// 箭头函数
const arrowFunction = () => {
console.log("Arrow Function:");
console.log(arguments); // 输出外层函数的 arguments 对象
};
arrowFunction(4, 5, 6); // 调用箭头函数并传入参数
// 但在这里,由于箭头函数是在全局作用域中定义的,并不在任何函数内部,
// 因此它没有外层函数的 `arguments` 对象,
// 所以会抛出错误: "Uncaught ReferenceError: arguments is not defined"
-
构造函数:
- 箭头函数不能用作构造函数,不能使用
new关键字调用,因此没有prototype属性。 - 普通函数可以用作构造函数,通过
new关键字调用,可以创建实例对象,并且具有prototype属性。
- 箭头函数不能用作构造函数,不能使用
-
返回值:
- 箭头函数如果只有一条语句,且没有花括号
{},则该语句的结果会被自动作为返回值。 - 普通函数需要显式地使用
return语句来返回值。
- 箭头函数如果只有一条语句,且没有花括号
总的来说,箭头函数更适合于简单的函数表达式,而普通函数则更灵活,并且适用于更多不同的场景,比如作为构造函数、具有动态的 this 绑定等。
ES6中哪个方法可以实现数组去重?
const array = [1, 2, 2, 3, 3, 4, 5, 5];
const uniqueArray = [...new Set(array)];
console.log(uniqueArray); // 输出 [1, 2, 3, 4, 5]
在这个示例中,我们首先创建了一个包含重复值的数组 array。然后,我们通过创建一个 Set 实例 new Set(array),将数组传递给 Set 构造函数,这样就会自动去除重复的值。最后,我们通过展开运算符 ... 将 Set 转换回数组,得到了去重后的数组 uniqueArray。
展开运算符 ...
使用展开运算符 ... 可以将 Set 转换回数组。这是因为在 JavaScript 中,Set 数据结构具有一个迭代器(iterator),它可以通过 for...of 循环或展开运算符 ... 来迭代所有的值。
以下是将 Set 转换回数组的示例:
const set = new Set([1, 2, 3, 4, 5]);
const array = [...set];
console.log(array); // 输出 [1, 2, 3, 4, 5]
ES6中对象新增的方法有哪些?
1、Object.assign()方法用于对象的合并,将源对象的所有可枚举属性,复制到目标对象(target)。
const target = { a: 1, b: 2 };
const source = { b: 3, c: 4 };
const result = Object.assign(target, source);
console.log(result); // 输出 { a: 1, b: 3, c: 4 }
2、Object.is它用来比较两个值是否严格相等,与严格比较运算符(===)的行为基本一致。值和对象类型的值都可以,NAN这种特殊值也可以处理。
console.log(Object.is(5, 5)); // true
console.log(Object.is('hello', 'hello')); // true
console.log(Object.is(0, -0)); // false
console.log(Object.is(NaN, NaN)); // true
console.log(Object.is({}, {})); // false
3、Object.values方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键值。
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.values(obj)); // 输出 [1, 2, 3]
4、Object.keys方法返回一个数组,成员是参数对象自身的(不含继承的)所有可遍历(enumerable)属性的键名。
const obj = { a: 1, b: 2, c: 3 };
console.log(Object.keys(obj)); // 输出 ["a", "b", "c"]
class 和 function的区别
class 和 function 是 JavaScript 中用于创建对象的两种不同方式,它们之间有几个重要的区别:
-
语法:
class是 ES6 中引入的一种语法糖,用于声明类(class)。function是 JavaScript 中用于声明函数的关键字。
-
用途:
class用于创建类,类是一种模板,可以用来创建具有相同属性和方法的多个对象实例。function用于声明函数,函数可以被调用执行,可以接受参数并返回值。
-
构造函数:
- 在类中使用
constructor方法来创建构造函数,用于初始化新创建的对象实例的状态。 - 在函数中,可以通过函数体内的代码来实现构造函数的功能。
- 在类中使用
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
introduce() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person1 = new Person('Alice', 30);
person1.introduce(); // 输出 "Hello, my name is Alice and I am 30 years old."
function Person(name, age) {
this.name = name;
this.age = age;
}
Person.prototype.introduce = function() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
const person2 = new Person('Bob', 25);
person2.introduce(); // 输出 "Hello, my name is Bob and I am 25 years old."
通过函数体内的代码来初始化对象的状态。Person.prototype.introduce 方法被用于为 Person 对象添加了一个 introduce 方法,用于输出对象实例的介绍信息。创建 person2 对象时,构造函数 Person 被调用,从而初始化了 person2 的 name 和 age 属性。
- 继承:
- 类支持基于原型的继承,可以使用
extends关键字来实现类的继承。 - 函数也可以实现继承,但通常需要手动设置原型链。
- 类支持基于原型的继承,可以使用
class Animal {
constructor(name) {
this.name = name;
}
speak() { console.log(`${this.name} makes a noise`); }
}
class Dog extends Animal {
bark() { console.log(`${this.name} barks`); }
}
const dog = new Dog('Buddy');
dog.speak(); // 输出 "Buddy makes a noise"
dog.bark(); // 输出 "Buddy barks"
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
console.log(`${this.name} makes a noise`);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} barks`);
};
const dog = new Dog('Buddy');
dog.speak(); // 输出 "Buddy makes a noise"
dog.bark(); // 输出 "Buddy barks"
- 绑定上下文:
- 在类中定义的方法会自动绑定到类的实例上,即方法内部的
this指向调用方法的对象实例。 - 在普通函数中,
this的值取决于函数的调用方式,可能会指向全局对象或者调用者。
- 在类中定义的方法会自动绑定到类的实例上,即方法内部的
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person1 = new Person('Alice');
person1.sayHello(); // 输出 "Hello, my name is Alice"
function sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
const person1 = { name: 'Bob' };
const person2 = { name: 'Charlie' };
person1.sayHello = sayHello;
person2.sayHello = sayHello;
person1.sayHello(); // 输出 "Hello, my name is Bob"
person2.sayHello(); // 输出 "Hello, my name is Charlie"
总的来说,class 提供了更加清晰、语义化的语法来定义对象的模板,并且更易于实现继承和面向对象编程的概念,而 function 则是 JavaScript 中最基本的函数声明方式,可以用于各种目的,包括声明构造函数、普通函数、对象方法等。
相同点:
- 函数作为构造函数
function Person(name) {
this.name = name;
}
class Person {
constructor(name) {
this.name = name;
}
}
不同点:
- class构造函数必须使用new操作符
- class声明不可以提升
const person = new Person('Alice'); // 正常运行
function Person(name) {
this.name = name;
}
const person = new Person('Alice'); // 抛出错误:ReferenceError: Cannot access 'Person' before initialization
class Person {
constructor(name) {
this.name = name;
}
}
- class不可以用call、apply、bind改变this指向。
class Person {
constructor(name) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}`);
}
}
const person = new Person('Alice');
person.sayHello.call({ name: 'Bob' }); // 抛出错误
function greet(greeting) {
console.log(`${greeting}, my name is ${this.name}`);
}
const person = {
name: 'Alice'
};
greet.call(person, 'Hello'); // 使用 call 方法
greet.apply(person, ['Hi']); // 使用 apply 方法
const boundGreet = greet.bind(person); // 使用 bind 方法
boundGreet('Hola');
在这个示例中,我们定义了一个 greet 函数,它接受一个参数 greeting,用于打印问候语和名字。然后,我们创建了一个 person 对象,其中包含一个 name 属性。
接下来,我们使用 call 方法将 greet 函数绑定到 person 对象,并且传递了 'Hello' 作为参数,这样 greet 函数内部的 this 就指向了 person 对象。同样,我们也可以使用 apply 方法来实现相同的效果。
最后,我们使用 bind 方法创建了一个新函数 boundGreet,该函数会在调用时将 this 绑定到 person 对象,然后我们调用了 boundGreet 函数,并传递了 'Hola' 作为参数,输出了相应的问候语和名字。
Symbol 含义及使用方法
在 JavaScript 中,Symbol 是一种原始数据类型,用于创建独一无二的标识符。Symbol 实例是唯一且不可变的。它的主要作用是创建对象属性的唯一键,防止属性名冲突。
含义:
- 独一无二的标识符:每个
Symbol实例都是唯一的,即使创建时传入相同的参数,也不会相等。 - 不可改变性:
Symbol实例是不可改变的,一旦创建就无法修改其值。
使用方法:
- 创建 Symbol:
const symbol1 = Symbol();
const symbol2 = Symbol('description'); // 可选的描述字符串
- 作为对象属性的键:
const mySymbol = Symbol();
const obj = {
[mySymbol]: 'value'
};
console.log(obj[mySymbol]); // 输出 "value"
- 使用全局 Symbol 注册表:
const mySymbol = Symbol.for('mySymbol'); // 全局 Symbol 注册表
const anotherSymbol = Symbol.for('mySymbol');
console.log(mySymbol === anotherSymbol); // 输出 true
- 获取 Symbol 的描述:
const symbol = Symbol('description');
console.log(symbol.description); // 输出 "description"
- 遍历对象的 Symbol 属性:
const symbol1 = Symbol('symbol1');
const symbol2 = Symbol('symbol2');
const obj = {
[symbol1]: 'value1',
[symbol2]: 'value2',
regularProperty: 'regularValue'
};
console.log(Object.getOwnPropertySymbols(obj)); // 输出 [Symbol(symbol1), Symbol(symbol2)]
Symbol 的使用可以在很多场景中发挥作用,比如用作对象属性的键,防止属性名冲突,或者用作常量来表示一些特殊的标识符。
// 用作对象属性的键,防止属性名冲突
const mySymbol = Symbol();
const obj = {
[mySymbol]: 'value'
};
console.log(obj[mySymbol]); // 输出 "value"
// 用作常量来表示一些特殊的标识符
const RED = Symbol();
const BLUE = Symbol();
function setColor(color) {
switch (color) {
case RED:
console.log('Set color to red');
break;
case BLUE:
console.log('Set color to blue');
break;
default:
console.log('Unknown color');
}
}
setColor(RED); // 输出 "Set color to red"
setColor(BLUE); // 输出 "Set color to blue"
如何理解和使用Promise.all和Promise.race
Pomise.all的使用
Promise.all 接收一个可迭代对象(通常是一个数组),并返回一个新的 Promise 对象。该 Promise 对象在所有输入的 Promise 对象都成功时才会成功,如果其中一个 Promise 对象失败,则整个 Promise.all 返回的 Promise 对象会立即失败,且返回第一个失败的 Promise 对象的结果。
const promise1 = Promise.resolve(1);
const promise2 = new Promise((resolve, reject) => setTimeout(reject, 100, 'Error'));
const promise3 = Promise.resolve(3);
Promise.all([promise1, promise2, promise3])
.then(values => {
console.log(values); // 不会被执行
})
.catch(error => {
console.error(error); // 输出 "Error"
});
Promise.race的使用
顾名思义,Promse.race就是赛跑的意思,意思就是说,Promise.race([p1, p2, p3])里面哪个结果获得的快,就返回那个结果,不管结果本身是成功状态还是失败状态。
const promise1 = new Promise(resolve => setTimeout(resolve, 1000, 'Promise 1 resolved'));
const promise2 = new Promise(resolve => setTimeout(resolve, 500, 'Promise 2 resolved'));
Promise.race([promise1, promise2])
.then(value => {
console.log(value); // 输出 "Promise 2 resolved"
});
异步
同步和异步的区别是什么?
同步和异步是指程序执行过程中任务的执行顺序和执行方式的不同。
同步(Synchronous)
在同步执行中,任务是按顺序执行的,一个任务执行完毕后才会执行下一个任务。在一个任务执行的过程中,程序会阻塞,直到任务执行完毕才会继续执行下一个任务。
console.log('Task 1');
console.log('Task 2');
console.log('Task 3');
在上面的示例中,Task 1、Task 2 和 Task 3 是按照顺序同步执行的,一个任务执行完毕后才会执行下一个任务。
异步(Asynchronous)
在异步执行中,任务不是按照顺序执行的,而是通过回调函数、事件监听等方式来处理。一个任务开始执行后,程序不会阻塞等待其完成,而是继续执行下一个任务,当任务完成后会通过回调函数等方式通知程序执行结果。
console.log('Task 1');
setTimeout(() => {
console.log('Task 2 (after 1 second)');
}, 1000);
console.log('Task 3');
在上面的示例中,Task 1 和 Task 3 是同步执行的,而 Task 2 是异步执行的。Task 2 使用 setTimeout 函数设置了一个定时器,它会在 1 秒后执行回调函数,因此 Task 2 的执行会在 Task 1 和 Task 3 之后。
进程和线程的区别,进程和线程的通信方式,进程挂了会不会影响其它进程,线程挂了进程会不会挂?
进程(Process)
- 定义:进程是程序的一次执行过程,是cpu分配资源的最小单位;(是能拥有资源和独立运行的最小单位),每个进程都有独立的内存空间。
- 特点:进程之间相互独立,拥有独立的内存空间和资源,进程间通信需要通过特定的机制。
- 通信方式:进程间通信(Inter-Process Communication,IPC)的方式包括管道、消息队列、信号量、共享内存、套接字等。
- 进程挂了影响:一个进程挂了一般不会影响其他进程,因为每个进程都有独立的内存空间。但是,进程挂了可能会导致系统资源占用不当,或者占用的资源不能正常释放,从而影响其它进程的运行。
线程(Thread)
- 定义:线程是操作系统进行运算调度的最小单位,是进程中的实际运作单位。
- 特点:线程是进程的一部分,共享同一进程的内存空间和资源,但拥有独立的执行流。
- 通信方式:线程之间通信相对简单,可以直接共享内存等资源,也可以使用锁、信号量等同步机制。
- 线程挂了影响:如果一个线程挂了,可能会导致整个进程挂掉,因为线程共享了进程的内存空间和资源。
在浏览器中,每个标签页通常都是一个独立的进程,这样可以隔离不同页面的运行环境,提高安全性和稳定性。当我们在浏览器中新开一个标签页时,就会创建一个新的进程来处理该标签页的内容。而在这个进程中,又可以有多个线程来并行处理不同的任务。
举个例子,当我们在浏览器中新开一个标签页并访问一个网站时,这个进程中可能会有以下几个线程同时工作:
渲染线程:负责将 HTML、CSS 和 JavaScript 转换为可视化页面,处理页面的渲染和绘制。HTTP 请求线程:负责发送网络请求和接收响应,处理与服务器的通信。JavaScript 引擎线程:负责解析 JavaScript 代码并执行,处理页面的交互和动态效果。
需要知道的是,线程之间是可以一起工作的,这些线程在进程中互相协作,完成各自的任务。但是,渲染线程和 JavaScript 引擎线程是互斥的,即同一时间只能有一个线程在执行。这是因为渲染线程需要访问 DOM 树和样式表来进行页面渲染,而 JavaScript 引擎线程可能会修改 DOM 树或样式表,所以需要互斥执行,以免出现冲突。
做个简单的比喻:进程=火车,线程=车厢
- 线程在进程下行进(单纯的车厢无法运行)
- 一个进程可以包含多个线程(一辆火车可以有多个车厢)
- 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
- 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
- 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
- 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
- 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进到不同的轨道上)
- 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
- 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”
*补充
多机(multiple machines) :
- 多机环境指的是多台计算机联网工作,可以通过网络进行通信。
- 适合进程:在多机环境中,通常会将不同的任务分配到不同的计算机上进行处理,每个计算机可以作为一个独立的进程运行。这样可以充分利用多台计算机的计算资源,提高整体的处理能力。
多核(multiple cores) :
- 多核环境指的是一台计算机拥有多个处理器核心,可以同时执行多个线程。
- 适合进程:在多核环境中,不同的进程可以在不同的核心上并行执行,各自独立地进行计算,避免了单核处理器的性能瓶颈。
- 适合线程:多核环境中,每个核心可以同时执行多个线程,因此可以通过多线程并发地利用每个核心的计算能力,提高整体的处理速度。
进程间的通信方式:
进程间通信是指在操作系统中不同进程之间传递数据或进行交互的过程。进程间通信方式有多种,常见的包括无名管道、命名管道、信号量、共享内存和消息队列。
-
无名管道(pipe):
- 无名管道是一种半双工的通信方式,只能在有亲缘关系的进程之间使用。
- 无名管道通过
pipe()系统调用创建,它有一个读端和一个写端,进程可以向管道的写端写入数据,同时从管道的读端读取数据。 - 无名管道通常用于父子进程之间或者同一进程内的不同线程之间的通信。
-
命名管道(FIFO):
- 命名管道是一种特殊类型的文件,可以在不相关的进程之间进行通信。
- 命名管道通过
mkfifo()系统调用创建,它与普通文件类似,但有一个特殊的文件类型。 - 命名管道的写入端和读取端可以在不同的进程之间,通过文件系统进行通信。
-
信号量(Semaphore):
- 信号量是一种用于多进程或多线程之间同步和互斥的机制。
- 信号量可以用来控制对共享资源的访问,保证多个进程或线程之间的顺序执行。
- 信号量通常用于进程间的同步和互斥,如生产者-消费者模型。
-
共享内存(Shared Memory):
- 共享内存是一种通过操作系统将内存区域映射到多个进程的机制,使得多个进程可以直接读写共享的内存区域。
- 共享内存适用于需要高效地进行大量数据交换的场景,如数据库管理系统、图形处理等。
-
消息队列(Message Queue):
- 消息队列是一种在进程之间传递消息的通信方式,可以通过操作系统提供的消息队列服务实现。
- 消息队列通常包括发送消息和接收消息两个步骤,发送方将消息放入队列,接收方从队列中取出消息进行处理。
- 消息队列适用于需要异步、可靠、顺序传递消息的场景,如进程间的事件通知、任务调度等。
这些进程间通信方式各有特点,选择合适的通信方式取决于具体的应用场景和需求。
线程之间的通信方式:
1. 锁机制(Locks)
JavaScript 中没有内置的锁机制,但可以使用互斥量来模拟。下面是一个简单的示例:
const lock = new Mutex();
function printNumbers() {
lock.lock();
for (let i = 0; i < 5; i++) {
console.log(i);
}
lock.unlock();
}
const thread1 = new Thread(printNumbers);
const thread2 = new Thread(printNumbers);
thread1.start();
thread2.start();
在这个示例中,我们假设有 Mutex 类来模拟互斥量的功能,printNumbers 函数在执行期间会首先获取互斥量的锁,然后输出数字,并在完成后释放锁。这样可以确保在任何时候只有一个线程可以输出数字,避免了输出混乱的情况。
2. 信号量机制(Semaphores)
JavaScript 中没有内置的信号量机制,但可以使用 Promise 来实现类似的功能。下面是一个简单的示例:
const semaphore = new Semaphore(2);
function printNumbers() {
semaphore.acquire();
for (let i = 0; i < 5; i++) {
console.log(i);
}
semaphore.release();
}
const thread1 = new Thread(printNumbers);
const thread2 = new Thread(printNumbers);
thread1.start();
thread2.start();
在这个示例中,我们假设有 Semaphore 类来模拟信号量的功能,其中 acquire 和 release 方法用于获取和释放信号量。两个线程在执行期间会尝试获取信号量,如果信号量计数未达到上限,则可以继续执行,否则会被阻塞。
3. 信号机制(Signals)
JavaScript 中没有内置的信号机制,但可以使用事件来模拟。下面是一个简单的示例:
process.on('SIGINT', () => {
console.log('Received SIGINT signal');
});
console.log('Waiting for signal...');
在这个示例中,我们注册了对 SIGINT 信号的监听,当程序收到 Ctrl+C 时,会触发 SIGINT 事件,执行对应的处理函数打印接收到的信号。
JS是单线程的,如何开启多进程?
Web Worker
首先JS是单线程语言,所以无法开启多线程,可以通过webWorker实现多进程。
Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行。在主线程运行的同时,Worker 线程在后台运行,两者互不干扰。等到 Worker 线程完成计算任务,再把结果返回给主线程。
使用 Web Worker 在浏览器中实现多线程并行处理,虽然它不是真正的多进程,但可以模拟多线程的效果,从而提高 Web 应用的性能和响应速度。
Web Worker 是在浏览器中运行的脚本,可以在后台执行任务而不阻塞主线程。主线程和 Web Worker 之间可以通过消息进行通信,这样就可以实现并行处理和任务分配。
以下是一个简单的使用 Web Worker 的示例:
// 主线程代码
const worker = new Worker('worker_script.js');
// 监听来自 Worker 的消息
worker.onmessage = function(event) {
console.log('Message from worker:', event.data);
};
// 向 Worker 发送消息
worker.postMessage('Hello from main thread!');
// worker_script.js
// Worker 线程代码
// 监听来自主线程的消息
self.onmessage = function(event) {
console.log('Message from main thread:', event.data);
// 向主线程发送消息
self.postMessage('Hello from worker!');
};
在这个示例中,主线程通过 new Worker() 创建了一个新的 Web Worker,并向其发送了一条消息。Worker 线程接收到消息后,在控制台打印消息,并向主线程发送了一条回复消息。主线程监听来自 Worker 的消息,一旦收到消息就在控制台打印出来。
这样主线程和 Worker 线程就可以通过消息通信来进行工作分配和结果传递,从而实现了类似多线程的并行处理效果。需要注意的是,Web Worker 不能访问 DOM,因此它主要用于执行一些计算密集型的任务,而不是操作 DOM 或执行与界面相关的操作。
创建多个子进程
JavaScript 是一种单线程语言,但是可以通过使用多进程来实现并行处理。在 Node.js 中,可以使用 child_process 模块来创建和管理子进程,从而实现多进程的并行执行。
以下是使用 child_process 模块创建多个子进程的简单示例:
const { fork } = require('child_process');
// 创建子进程
const child1 = fork('child_script.js');
const child2 = fork('child_script.js');
// 监听子进程的消息
child1.on('message', (message) => {
console.log('Message from child 1:', message);
});
child2.on('message', (message) => {
console.log('Message from child 2:', message);
});
// 向子进程发送消息
child1.send({ hello: 'child1' });
child2.send({ hello: 'child2' });
在这个示例中,我们使用 fork 方法创建了两个子进程,并分别执行了名为 child_script.js 的脚本。父进程可以通过监听子进程的 message 事件来接收子进程发送的消息,同时通过 send 方法向子进程发送消息。
在 child_script.js 中,可以通过 process.on('message', ...) 来监听父进程发送的消息,并通过 process.send(...) 来向父进程发送消息。这样就可以实现多进程之间的通信和并行处理。
总的来说,虽然 JavaScript 是单线程的,但是通过创建多个子进程并进行通信,可以实现多进程的并行执行,从而提高程序的性能和效率。
前端使用异步的场景有哪些?
1、网络请求 2、定时任务
请描述 event loop(事件循环/事件轮询)的机制,可画图
event loop是异步回调的实现原理。
首先计算机执行我们的代码是一行一行从上到下的执行,每执行一行话,就将其放置到call stack调用,执行完就进行销毁。
然后如果代码中遇到异步操作时,会将其进行“记录”(Promise这些放入微任务队列中,setTimeout,ajax这些放入宏任务队列);对于宏任务队列,等待时机(定时到了,网络请求完成)则加入到callback Queue。
当call stack中没有代码时,会先执行微任务队列,然后尝试渲染DOM,
最后会开启event loop事件循环机制,不断的查找callback Queue,如果callback Queue中有,则移动到Call Stack执行。
然后继续进行循环查询(像永动机一样)。
宏任务有哪些?微任务有哪些?和DOM渲染的关系
JavaScript 运行时的事件循环机制中,任务分为宏任务(macro task)和微任务(micro task)。
常见的宏任务有:
- setTimeout 和 setInterval 的回调函数
- DOM 事件
- XMLHttpRequest 中的readystatechange事件
- requestAnimationFrame 中的回调函数
- I/O 操作和网络请求的回调函数
- Node.js 中的文件读写操作的回调函数
- Node.js 中的进程事件
常见的微任务有:
- Promise 的回调函数(then、catch、finally)
- MutationObserver 监听函数
- process.nextTick 回调函数
- Object.observe 的回调函数
Promise
Promise 是 JavaScript 中处理异步操作的一种方式,它表示一个异步操作的最终完成(或失败)及其结果的值。Promise 对象有三种状态:pending(进行中)、fulfilled(已成功)和rejected(已失败)。
创建 Promise 对象
const myPromise = new Promise((resolve, reject) => {
// 异步操作
setTimeout(() => {
const randomNumber = Math.random();
if (randomNumber > 0.5) {
resolve(randomNumber); // 成功时调用 resolve
} else {
reject(new Error('Number is too small')); // 失败时调用 reject
}
}, 1000);
});
使用 Promise
myPromise.then((result) => {
console.log('Promise fulfilled with result:', result);
}).catch((error) => {
console.error('Promise rejected with error:', error);
});
在这个示例中,我们创建了一个 Promise 对象 myPromise,它模拟了一个异步操作,延迟一秒钟后根据随机数的大小来决定成功或失败。然后我们使用 then 方法来处理 Promise 成功时的结果,使用 catch 方法来处理 Promise 失败时的结果。
Promise Chaining(Promise 链式调用)
const myPromise = new Promise((resolve, reject) => {
resolve(1);
});
myPromise.then((result) => {
console.log(result); // 输出 1
return result + 1;
}).then((result) => {
console.log(result); // 输出 2
return result + 1;
}).then((result) => {
console.log(result); // 输出 3
}).catch((error) => {
console.error('Promise rejected with error:', error);
});
在 Promise 链式调用中,每个 then 方法都返回一个新的 Promise 对象,可以继续进行链式调用。如果在链中的任何一个 then 方法中抛出异常或者返回一个 rejected 的 Promise,后续的 then 方法会被跳过,直到遇到一个 catch 方法为止。
promise实现异步的原理?
我们知道Promise一共由三种状态: Penning、Fulfilled、Rejected, 而状态是不可逆的,Promise正是通过修改状态的方式,在合适的时机触发相应状态的回调来达到处理异步的目的。通过then的链式调用也避免了回调地域的问题。
回调地狱
Promise 回调地狱是指在使用 Promise 进行异步编程时,多个嵌套的回调函数造成代码可读性降低、维护困难的情况。这种情况经常出现在多个异步操作依赖于上一个异步操作的结果,需要进行连续调用的场景中。
以下是一个典型的 Promise 回调地狱示例:
asyncFunc1()
.then((result1) => {
return asyncFunc2(result1);
})
.then((result2) => {
return asyncFunc3(result2);
})
.then((result3) => {
return asyncFunc4(result3);
})
.then((result4) => {
console.log('Final result:', result4);
})
.catch((error) => {
console.error('An error occurred:', error);
});
在这个示例中,asyncFunc1()、asyncFunc2()、asyncFunc3() 和 asyncFunc4() 都是异步函数,它们返回的是 Promise 对象。这些函数按顺序执行,每个函数的结果会作为下一个函数的参数传递。当需要进行多个依赖关系的异步操作时,就会出现多个 .then() 方法嵌套的情况,导致代码缩进增加、结构复杂,降低了代码的可读性和可维护性。
为了解决 Promise 回调地狱问题,可以采用以下方法:
- 使用 async/await:async/await 是 ES2017 引入的新特性,可以更清晰地编写异步代码,避免了回调地狱的情况。
async function asyncTask() {
try {
const result1 = await asyncFunc1();
const result2 = await asyncFunc2(result1);
const result3 = await asyncFunc3(result2);
const result4 = await asyncFunc4(result3);
console.log('Final result:', result4);
} catch (error) {
console.error('An error occurred:', error);
}
}
asyncTask();
- 使用 Promise 的链式调用:结合使用箭头函数和链式调用可以简化代码结构,使其更易读。
asyncFunc1()
.then(result1 => asyncFunc2(result1))
.then(result2 => asyncFunc3(result2))
.then(result3 => asyncFunc4(result3))
.then(result4 => console.log('Final result:', result4))
.catch(error => console.error('An error occurred:', error));
通过这些方法,可以有效地避免 Promise 回调地狱问题,使异步代码更易读、更易维护。
Promise的三种状态,如何变化?
Promise 的三种状态分别是:pending(待定)、fulfilled(已完成)、rejected(已拒绝)。
一个 Promise 对象可以处于以下三种状态之一:
-
pending(待定):初始状态,既不是成功(fulfilled)也不是失败(rejected)状态。这意味着,Promise 对象刚被创建后的初始状态。
-
fulfilled(已完成):表示操作成功完成。此时 Promise 对象的操作已经完成,且成功地返回了一个值。一旦 Promise 进入这个状态,它就会停止执行其他状态的回调函数。
-
rejected(已拒绝):表示操作失败。此时 Promise 对象的操作失败了,可能由于某些原因无法完成。一旦 Promise 进入这个状态,它就会停止执行其他状态的回调函数。
Promise 对象的状态是可以变化的,但是一旦状态发生变化,就会凝固,无法再次改变。状态的变化通常是由 Promise 的执行器函数决定的,它根据执行结果决定是调用 resolve 还是 reject 来改变 Promise 的状态。例如:
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
// 模拟异步操作成功
resolve('成功的结果');
// 模拟异步操作失败
// reject(new Error('操作失败'));
}, 1000);
});
promise.then(
(result) => {
console.log('成功:', result);
},
(error) => {
console.error('失败:', error);
}
);
在这个例子中,Promise 对象在一秒后会根据执行结果调用 resolve 或 reject 方法来改变状态。如果操作成功,Promise 进入 fulfilled 状态,then 方法的第一个回调函数会被调用;如果操作失败,Promise 进入 rejected 状态,then 方法的第二个回调函数会被调用。
Promise 和 async/await 的异常处理?
promise.catch(e=>{ // TODO sth with e })
async function test(){
try{ await promise; }
catch(e){ // TODO sth with e } }
JavaScript脚本延迟加载的方式有哪些?
defer 属性,async 属性,使用 setTimeout 延迟方法,让 JS 最后加载
延迟加载 JavaScript 脚本的目的是为了优化网页性能,提高页面加载速度。延迟加载可以延迟某些 JavaScript 文件的下载和执行,直到页面其他内容加载完成后再进行加载,从而减少页面的加载时间。
以下是几种常见的 JavaScript 脚本延迟加载的方式:
-
async 和 defer 属性:
- 使用
<script>标签的 async 和 defer 属性可以实现脚本的异步加载,不会阻塞页面的渲染和其他资源的加载。 <script src="script.js" async></script>:异步加载脚本,下载完成后立即执行,不会按顺序执行,适用于不依赖其他脚本的情况。<script src="script.js" defer></script>:延迟加载脚本,等待页面其他内容加载完成后再执行,按顺序执行,适用于需要按顺序执行的情况。
- 使用
-
动态创建 script 标签:
- 可以使用 JavaScript 动态创建
<script>标签,并将其添加到页面中,从而实现脚本的延迟加载。 -
var script = document.createElement('script'); script.src = 'script.js'; document.body.appendChild(script);
- 可以使用 JavaScript 动态创建
-
延迟执行函数:
- 将 JavaScript 脚本封装到一个函数中,并使用
setTimeout或requestAnimationFrame等方法延迟执行,从而实现脚本的延迟加载。 -
setTimeout(function() { // 脚本内容 }, 2000); // 2秒延迟执行
- 将 JavaScript 脚本封装到一个函数中,并使用
-
懒加载(Lazy Loading):
- 在需要时再加载 JavaScript 脚本,可以通过监听页面滚动、鼠标事件等触发条件来实现懒加载,提高页面加载速度。
-
window.addEventListener('scroll', function() { if (window.scrollY > 1000) { var script = document.createElement('script'); script.src = 'script.js'; document.body.appendChild(script); } });
这些方式可以根据具体需求和页面特点选择合适的延迟加载策略,从而优化页面性能,提升用户体验。
JS-Web-API
== 和 ===
在 JavaScript 中,== 和 === 都是用于比较两个值是否相等的运算符,但它们的行为有所不同。
==(宽松相等)
== 是宽松相等运算符,也叫“非严格相等”。它在比较两个值时,会先尝试将它们转换为相同的类型,然后再进行比较。因此,它可能会在比较之前进行隐式类型转换。
示例:
console.log(5 == '5'); // true
console.log(0 == false); // true
console.log(null == undefined); // true
在这些示例中,
==会进行类型转换:
5 == '5':字符串'5'被转换为数字5,所以比较结果为true。0 == false:布尔值false被转换为数字0,所以比较结果为true。null == undefined:它们被认为是相等的,因为在宽松相等比较中null和undefined被特殊处理。
===(严格相等)
=== 是严格相等运算符。在进行比较时,它不会进行类型转换,只有在两个值类型相同且值相等时,才会返回 true。
示例:
console.log(5 === '5'); // false
console.log(0 === false); // false
console.log(null === undefined); // false
在这些示例中,
===不会进行类型转换:
5 === '5':因为一个是数字,另一个是字符串,类型不同,所以结果是false。0 === false:因为一个是数字,另一个是布尔值,类型不同,所以结果是false。null === undefined:因为它们的类型不同,所以结果是false。总结
==会在比较前进行类型转换,如果两个值在转换后相等,则返回true。===不会进行类型转换,只有在类型和值都相等的情况下才会返回true。一般来说,推荐使用
===,因为它更为严格,能够避免由于隐式类型转换而引发的潜在错误。
DOM
DOM属于哪种数据结构
树
DOM操作的常用API
创建:createElement
插入:appendChild
删除:removeChild
获取子节点:childNodes
获取子节点:parentNodes
attribute和property(都是属性)的区别
两者都有可能引起DOM重新渲染
property : 修改对象属性,不会体现到html结构中
attribute : 修改html属性,会改变html结构
如何识别浏览器的类型
navigator.userAgent
什么是事件冒泡?什么是事件代理?
事件代理和事件冒泡是JavaScript事件处理中的两个重要概念。理解它们对于优化事件处理、提高性能和简化代码编写非常有帮助。
1. 事件冒泡(Event Bubbling)
事件冒泡指的是,当一个事件发生在元素上时,这个事件会从最深层的元素开始,逐级向上传播到其父元素,直到
document对象。换句话说,事件会从触发它的元素,向上冒泡到祖先元素。
事件冒泡的示例:
<div id="parent">
<button id="child">Click me!</button>
</div>
document.getElementById('parent').addEventListener('click', function() {
console.log('Parent element clicked');
});
document.getElementById('child').addEventListener('click', function(event) {
console.log('Child element clicked');
// event.stopPropagation(); // 如果不想冒泡,可以取消事件的冒泡行为
});
如果用户点击了child按钮,控制台将显示:
Child element clicked
Parent element clicked
这是因为事件首先在child元素上触发,然后冒泡到parent元素。
2. 事件代理(Event Delegation)
事件代理是一种利用事件冒泡机制的技术。事件代理允许我们将一个事件处理器绑定到一个父元素,而不是为每个子元素分别绑定处理器。当事件触发时,它会冒泡到父元素并在该元素上被处理。这对于动态创建的元素特别有用,因为我们不需要在每次创建新元素时都为其附加事件处理器。
事件代理的示例:
<ul id="list">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
document.getElementById('list').addEventListener('click', function(event) {
if (event.target.tagName === 'LI') {
console.log('List item clicked:', event.target.textContent);
}
});
在这个例子中,我们只为ul元素绑定了一个点击事件处理器,而不是为每个li元素绑定。通过检查event.target,我们可以确定是哪一个li被点击了。
好处:
- 减少内存占用:我们只需要为父元素添加一个事件处理器,而不是为每个子元素都添加。
- 处理动态内容:即使在
ul中动态添加新的li元素,也不需要重新绑定事件处理器,父元素的事件处理器仍然可以处理新的元素。
3. 事件冒泡的停止
如果不希望事件冒泡到父元素,可以使用
event.stopPropagation()方法来阻止事件冒泡。例如:
document.getElementById('child').addEventListener('click', function(event) {
console.log('Child element clicked');
event.stopPropagation(); // 阻止事件冒泡
});
使用stopPropagation()后,点击child按钮时,事件将不会冒泡到parent元素。
总结
- 事件冒泡:事件从最深层的元素开始,逐级向上传播到其父元素。
- 事件代理:利用事件冒泡机制,将事件处理器绑定到父元素,从而可以处理所有子元素的事件,特别是动态添加的元素。
理解事件代理和事件冒泡有助于更有效地处理复杂的事件流,优化性能,并使代码更易于维护。
ajax
手写一个简易的ajax
- 创建 xhr 对象
- 准备发送请求
- 监听事件,处理响应
- 发送请求
以下是一个简易的 AJAX 实现示例,使用原生 JavaScript:
get:
function ajax(url){
return new Promise((resolve,reject)=>{
let xhr = new XMLHttpRequest();
xhr.open("get",url,true)
xhr.onreadstatechange = function(){
if (xhr.readyState === 4){ // 请求已完成
if(xhr.status === 200){ // 请求已成功
resolve()
}else{
reject()
}
}
}
xhr.send(null)
})
}
get & post:
function ajax(options) {
return new Promise(function(resolve, reject) {
var xhr = new XMLHttpRequest();
xhr.open(options.method || 'GET', options.url);
xhr.onreadystatechange = function() {
if (xhr.readyState !== XMLHttpRequest.DONE) return;
if (xhr.status >= 200 && xhr.status < 300) {
resolve(xhr.responseText);
} else {
reject(new Error('Request failed with status ' + xhr.status));
}
};
xhr.send(options.body);
});
}
// 示例用法
ajax({
url: 'https://api.example.com/data',
method: 'GET'
}).then(function(response) {
console.log('Response:', response);
}).catch(function(error) {
console.error('Error:', error);
});
在这个示例中,ajax 函数接受一个参数 options,该参数包含了 AJAX 请求的配置信息,包括请求的 URL、请求方法、请求体等。函数返回一个 Promise 对象,可以通过 then 方法处理请求成功的响应,通过 catch 方法处理请求失败的情况。
在 ajax 函数内部,首先创建了一个 XMLHttpRequest 对象,然后使用 open 方法设置请求的方法和 URL。在 onreadystatechange 事件处理函数中,监听了 XMLHttpRequest 对象的状态变化,当状态为 DONE 时表示请求已完成,根据响应的状态码来判断请求的成功或失败,并分别调用 resolve 或 reject 方法。
这个简易的 AJAX 实现只支持基本的 GET 和 POST 请求,并没有考虑跨域请求、请求超时、请求头设置等复杂情况。在实际应用中,可以根据需求扩展功能,使其更加完善。
Ajax 、Fetch、 Axios的区别?
三者都是网络请求,但是维度不同。
- Ajax:是一种技术统称
- Fetch:是一个具体的浏览器原生API,用于网络请求;它和xmlhttpRequest是一个级别的,但是其语法更加简洁,更加易用,并且支持Promise
- Axios:是一个第三方库。内部可以通过XMLHttpRequest和Fetch来实现
跨域常用的实现方法?
跨域是指浏览器出于安全考虑,限制了页面中的 JavaScript 代码对不同域下资源的访问。常见的跨域实现方法包括以下几种:
-
JSONP (JSON with Padding): JSONP 是一种利用
<script>标签跨域加载数据的技术。它利用了浏览器对<script>标签的加载不受同源策略的限制的特点,通过在请求 URL 中指定一个回调函数的名称,服务端在返回数据时将数据作为参数传递给回调函数,从而实现跨域加载数据。 -
CORS (Cross-Origin Resource Sharing): CORS 是一种官方的跨域解决方案,通过在服务端设置相应的响应头来控制跨域访问。当浏览器发起跨域请求时,如果服务器返回的响应包含了 CORS 相关的头部信息,浏览器就会根据这些信息来决定是否允许跨域请求。
-
代理(Proxy): 代理(Proxy)是一种通过在同一个域下部署一个代理服务器来转发跨域请求的方法,从而解决跨域访问的问题。代理服务器充当了客户端和目标服务器之间的中间人,接收客户端的请求并转发给目标服务器,然后将目标服务器的响应返回给客户端,这样就避免了浏览器的同源策略限制。
代理的工作原理如下:
客户端向代理服务器发送跨域请求。- 代理服务器接收到请求后,将请求转发给
目标服务器。 - 目标服务器处理请求并返回响应给
代理服务器。 - 代理服务器收到目标服务器的响应后,再将响应返回给
客户端。
通过部署代理服务器,客户端与目标服务器之间实现了间接通信,从而实现了跨域访问。由于代理服务器和目标服务器在同一个域下,不存在跨域问题。
代理的实现方式可以是使用反向代理服务器(如 Nginx、Apache)、中间层代理服务器(如 Node.js 服务器)等。以下是一个简单的
Node.js实现示例:const http = require('http'); const request = require('request'); const proxy = http.createServer((clientReq, clientRes) => { const options = { method: clientReq.method, url: clientReq.url, headers: clientReq.headers, }; request(options, (err, res, body) => { if (err) { console.error('Error:', err); clientRes.writeHead(500); clientRes.end('Internal Server Error'); } else { clientRes.writeHead(res.statusCode, res.headers); clientRes.end(body); } }); }); const PORT = 3000; proxy.listen(PORT, () => { console.log(`Proxy server is running on port ${PORT}`); });在这个示例中,我们使用 Node.js 创建了一个简单的代理服务器。客户端发送的请求会被代理服务器接收,并通过
request模块转发给目标服务器。目标服务器的响应再由代理服务器返回给客户端。通过这种方式,我们可以实现跨域访问。 -
WebSocket: WebSocket 是一种全双工的通信协议,它不受同源策略的限制,可以通过 WebSocket 建立跨域的持久连接,实现跨域通信。
-
跨域资源共享(Cross-Domain Resource Sharing): 类似 CORS,也是通过在服务端设置响应头来实现跨域资源的共享,但是它不限于 XMLHttpRequest 或 Fetch 请求,可以支持更多类型的请求,例如 Font、XHR2、Canvas 等。
-
PostMessage: PostMessage 是一种 HTML5 提供的 API,用于在不同窗口或不同域之间进行跨窗口通信。通过调用
window.postMessage()方法发送消息,并在对方窗口监听message事件来接收消息,从而实现跨域通信。
以上是一些常见的跨域实现方法,根据具体的需求和场景选择合适的方法来解决跨域问题。
Vite 和 Webpack
Vite 和 Webpack 都是现代前端开发中常用的打包工具,它们都能够将多个模块打包成一个或多个 bundle 文件,但在一些方面有一些明显的区别:
-
打包原理:
- Vite:Vite 使用 ES Module 作为开发时的模块解析方式,并且利用浏览器原生的 ESM 特性,在开发环境下不需要提前将代码打包成静态资源文件,而是直接以 ES Module 的方式在浏览器中运行,利用浏览器的原生模块加载器加载模块。
- Webpack:Webpack 使用 CommonJS 或 AMD 等方式解析模块,并且将所有模块打包成一个或多个 bundle 文件,在生产环境下需要提前将代码打包成静态资源文件。
-
开发体验:
- Vite:Vite 提供了快速的
开发重载和热模块替换(HMR)功能,支持快速的冷启动和快速的模块热更新,开发者在修改代码后能够实时在浏览器中看到更新效果。 - Webpack:Webpack 在开发时通常需要等待一段时间来进行打包,然后才能在浏览器中看到更新效果,开发体验相对于 Vite 有一定的延迟。
- Vite:Vite 提供了快速的
-
生态和插件:
- Webpack:Webpack 拥有庞大的生态系统和丰富的插件库,能够满足各种复杂的项目需求,并且支持更多的扩展功能。
- Vite:Vite 的生态系统相对较新,但也在不断壮大,而且由于其简洁的设计和基于浏览器原生特性的开发模式,使得一些原本需要插件来实现的功能在 Vite 中可能更加简单。
-
构建速度:
- Vite:由于 Vite 是基于浏览器原生 ESM 特性运行的,并且在开发环境下不需要将代码打包成静态资源文件,因此 Vite 在启动和开发时的构建速度非常快。
- Webpack:Webpack 在启动和开发时通常需要较长的构建时间,尤其是在大型项目中,由于需要将所有模块打包成一个或多个 bundle 文件,因此构建速度可能会较慢。
总的来说,Vite 更适合于轻量级、快速的开发体验和小型项目,而 Webpack 更适合于复杂的项目和大型应用,能够提供更丰富的功能和更强大的扩展性。选择哪个打包工具取决于项目需求和个人偏好。
性能优化
前端常见性能优化方案
①加载优化:
- 减少资源体积:压缩代码。
- 减少访问次数∶合并代码、SSR服务器端渲染、緩存。
- 使用更快的网络:CDN。
②渲染优化:
- 对 DOM 查询进行缓存。
- 节流 throttle 防抖 debounce。
- 懒加载
- 频繁 DOM 操作,合并到一起插入 DOM 结构。
- CSS 放在 head,JS 放在 body 最下面。
- 尽早开始执行JS,用 DOMContentloaded 触发。
防抖和节流是什么?手写一个
-
防抖(Debouncing) :
- 防抖也是一种性能优化方法,用于减少因频繁触发事件而导致的函数多次执行。
- 当事件触发时,函数不会立即执行,而是等待一段时间后再执行。如果在等待期间再次触发事件,会重新计时。
- 适用于需要避免频繁调用函数的场景,例如输入框内容校验、窗口大小变化等。
- 示例:在输入框输入内容时,只在用户停止输入一段时间后执行校验函数。
使用场景:监听一个输入框,文字改变后触发change事件
存在问题:直接监听keyup,会频繁的触发change事件
防抖:用户输入结束或暂停时,才触发change事件
- 防抖函数的定义
function debounce(fn, delay = 500) {
let timer = null; // 定义一个变量来存储定时器的ID
return function() {
if (timer) {
clearTimeout(timer); // 如果定时器已经存在,则清除它
}
// 设置一个新的定时器,在指定的延迟时间后执行传入的函数
timer = setTimeout(() => {
fn.apply(this, arguments); // 执行传入的函数,并将当前的上下文和参数传递过去
timer = null; // 清空定时器ID,准备下一次的防抖操作
}, delay);
};
}
代码详解:
-
debounce(fn, delay):- 这是一个闭包函数,它接受两个参数:
fn:需要防抖处理的函数。delay:防抖的延迟时间(默认是 500 毫秒)。
- 函数内部维护一个变量
timer,用于存储setTimeout的返回值,即定时器的 ID。
- 这是一个闭包函数,它接受两个参数:
-
return function():- 防抖函数的核心是返回一个新的函数,新的函数会在每次触发时被调用,而这个函数会根据逻辑决定是否执行原函数
fn。 - 如果
timer已经存在(即上一次调用还未执行),就清除当前的定时器,避免执行原函数fn。 - 然后重新设置一个新的定时器,延迟
delay毫秒后执行fn。
- 防抖函数的核心是返回一个新的函数,新的函数会在每次触发时被调用,而这个函数会根据逻辑决定是否执行原函数
-
fn.apply(this, arguments):- 这段代码确保了
fn函数在执行时,能够正确地使用当前上下文(this)和传入的参数。 apply方法将this上下文和传入的参数arguments传递给fn,保持其功能的一致性。
- 这段代码确保了
2. 防抖函数的使用
const input1 = document.getElementById('input1'); // 获取页面上的输入框元素
input1.addEventListener("keyup", debounce(function(e) {
console.log(e.target); // 打印出触发事件的目标元素,即输入框
console.log(this.value); // 打印出输入框中的当前值
}, 600)); // 绑定事件监听器,并使用防抖函数进行处理,延迟600毫秒执行
代码详解:
-
获取输入框元素:
document.getElementById('input1')获取页面中id为input1的输入框元素。
-
事件监听器:
input1.addEventListener("keyup", debounce(...))为输入框绑定keyup事件的监听器。keyup事件在用户释放键盘按键时触发。- 使用
debounce包装后的函数,只有在用户停止输入 600 毫秒后,传入的回调函数才会执行。
-
回调函数:
- 在这个回调函数中:
console.log(e.target)打印触发事件的目标元素(即input1输入框)。console.log(this.value)打印输入框的当前值。
- 在这个回调函数中:
-
节流(Throttling) :
- 节流是一种性能优化方法,用于限制函数在一定时间间隔内的调用次数。
- 当事件触发时,函数会按照设定的时间间隔执行一次,而不管事件触发的频率有多高。
- 适用于需要控制函数调用频率的场景,例如滚动事件、鼠标移动事件等。
- 示例:在滚动页面时,只在每隔一段时间内执行一次滚动处理函数。
使用场景:拖拽某个元素,要随时知道该元素被拖拽的位置
存在问题:直接监听使用drag拖拽,会频繁的触发change事件
节流:无论拖拽速度多快,都每100ms触发一次
这段代码实现了一个 节流函数(throttle),用于控制一个函数在指定的时间间隔内只能执行一次。这种模式在需要频繁触发事件时非常有用,比如 scroll、resize 或 drag 事件。以下是对代码的详细解释:
代码解析
- 节流函数的定义
function throttle(fn, delay = 100) {
let timer = null; // 定义一个变量用于存储定时器的 ID
return function() {
if (timer) return; // 如果定时器存在,则函数直接返回,不执行后续操作
timer = setTimeout(() => {
fn.apply(this, arguments); // 执行传入的函数,并将当前上下文和参数传递过去
timer = null; // 执行完毕后清空定时器 ID,允许下次执行
}, delay);
};
}
代码详解:
-
throttle(fn, delay):- 这是一个闭包函数,接受两个参数:
fn:需要节流处理的函数。delay:节流的时间间隔,默认值为 100 毫秒。
- 内部维护一个
timer变量,用于存储定时器的 ID。
- 这是一个闭包函数,接受两个参数:
-
return function():- 节流函数的核心是返回一个新的函数,该函数每次被调用时,首先检查
timer是否存在。 - 如果
timer存在,说明上一次的延迟任务还未完成,直接返回,阻止fn执行。 - 如果
timer不存在,则设置一个新的定时器,延迟delay毫秒后执行fn。 - 在定时器回调函数中,使用
fn.apply(this, arguments)来保持fn的上下文和传入的参数一致。 - 执行完成后,将
timer设置为null,以便下次触发函数时能够重新设置定时器。
- 节流函数的核心是返回一个新的函数,该函数每次被调用时,首先检查
2. 节流函数的使用
const div1 = document.getElementById('div1'); // 获取页面上的 div 元素
div1.addEventListener("drag", throttle(function(e) {
console.log(e.offsetX, e.offsetY); // 打印出鼠标相对于元素的偏移量
}, 600)); // 绑定事件监听器,并使用节流函数处理,时间间隔为 600 毫秒
代码详解:
-
获取 div 元素:
document.getElementById('div1')获取页面中id为div1的元素。
-
事件监听器:
div1.addEventListener("drag", throttle(...))为 div 绑定drag事件的监听器。drag事件会在元素被拖动时频繁触发。
-
节流函数:
- 使用
throttle包装后的函数,确保在 600 毫秒的时间间隔内,drag事件的处理函数fn只会被执行一次,避免因频繁触发而造成的性能问题。
- 使用
节流 vs. 防抖
-
节流(Throttle):
- 节流函数确保在一定时间间隔内,目标函数最多只执行一次。适用于频繁触发的事件,如页面滚动、窗口调整大小等。
- 举例:当用户持续拖动一个元素时,使用节流函数可以限制拖动事件的处理函数每隔一定时间才执行一次。
-
防抖(Debounce):
- 防抖函数在事件结束一段时间后才执行目标函数,如果在这段时间内事件再次触发,则重新计时。适用于需要等用户停止操作后才执行的情况,如表单验证、搜索输入等。
- 举例:在用户输入时,使用防抖函数可以避免频繁触发搜索请求,只有用户停止输入后才发起搜索。
总结:
- 两者都是优化高频率执行JS代码的一种手段。
- 节流是
限制函数调用频率,防抖是延迟函数执行。 - 节流适用于高频率事件,防抖适用于频繁触发事件。