常见面试题汇总
②有利于SEO: 爬虫依赖标签来确定关键字的权重,因此可以和搜索引擎建立良好的沟通,帮助爬虫抓取更多的有效信息
③提升用户体验: 例如title、alt可以用于解释名称或者解释图片信息,以及label标签的灵活运用。
④便于团队开发和维护: 语义化使得代码更具有可读性,让其他开发人员更加理解你的html结构,减少差异化。
⑤方便其他设备解析: 如屏幕阅读器、盲人阅读器、移动设备等,以有意义的方式来渲染网页。
-
块级元素(Block-level Elements):
- 块级元素以块的形式显示,占据一整行或多整行的空间。
- 块级元素可以设置宽度、高度、内外边距等样式属性。
- 常见的块级元素有
<div>
、<p>
、<h1>
-<h6>
、<ul>
、<ol>
、<li>
等。
-
行级元素(Inline Elements):
- 行级元素以行内的形式显示,只占据自身内容所需的空间,不会独占一行。
- 行级元素的宽度和高度通常由其内容决定,不能设置宽度、高度和上下内外边距。
- 常见的行级元素有
<span>
、<a>
、<strong>
、<em>
、<img>
、<input>
等。
<button>
、<select>
和<textarea>
。这些元素在显示上类似于行级元素,但是可以设置宽度、高度和上下内外边距。部分借鉴源:https://blog.csdn.net/LQlove1/article/details/129283317
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
3. 表格布局属性:caption-side、border-collapse、border-spacing、empty-cells、
table-layout; 5. 列表布局属性:list-style
不能继承的属性
4. display:规定元素应该生成的框的类型;
5. 文本属性:vertical-align、text-decoration; 3. 盒子模型的属性:width、height、margin 、border、padding; 4. 背景属性:background、background-color、background-image; 5. 定位属性:float、clear、position、top、right、bottom、left、min-width、
min-height、max-width、max-height、overflow、clip;
第一种:是 W3C 标准的盒子模型(标准盒模型)
第二种:IE 标准的盒子模型(怪异盒模型)
标准盒模型与怪异盒模型的表现效果的区别之处:
1、标准盒模型中 width 指的是内容区域 content 的宽度
height 指的是内容区域 content 的高度
标准盒模型下盒子的大小 = content + border + padding + margin
2、怪异盒模型中的 width 指的是内容、边框、内边距总的宽度(content + border +
padding);height 指的是内容、边框、内边距总的高度
怪异盒模型下盒子的大小=width(content + border + padding) + margin
2.浮动的特点:脱离文档流,容易造成盒子塌陷,影响其他元素的排列
3.解决塌陷问题:①父元素中添加overflow:hidden;
②给父元素添加高度。
③建立空白div,添加clear
④在父级添加伪元素::after{ content : ' ', clear : both , display : table}
-
!important:具有
!important
声明的样式具有最高优先级,会覆盖其他所有样式规则。但是滥用!important
会导致样式管理困难,请谨慎使用。 -
内联样式:直接在HTML元素的
style
属性中定义的样式具有较高的优先级。例如,<div style="color: red;">Hello</div>
。 -
ID选择器:通过元素的ID选择器指定的样式具有较高的优先级。例如,
#myDiv { color: blue; }
。 -
类选择器、伪类选择器和属性选择器:通过类选择器(如
.myClass
)、伪类选择器(如:hover
)或属性选择器(如[type="text"]
)指定的样式优先级较低于ID选择器。如果存在多个具有相同优先级的样式规则,则后面定义的规则会覆盖先前定义的规则。 -
元素选择器和伪元素选择器:通过元素选择器(如
div
)或伪元素选择器(如::before
)指定的样式优先级最低。如果存在多个具有相同优先级的样式规则,则后面定义的规则会覆盖先前定义的规则。
-
像素(px):最常用的单位之一,表示固定的像素值。例如:
width: 200px;
-
百分比(%):相对于父元素的尺寸进行计算。例如:
width: 50%;
表示宽度为父元素宽度的50%。 -
视窗单位(vw、vh、vmin、vmax):相对于视口(浏览器窗口)的尺寸进行计算。
- vw(viewport width):视口宽度的百分比。例如:
width: 50vw;
表示宽度为视口宽度的50%。 - vh(viewport height):视口高度的百分比。例如:
height: 100vh;
表示高度等于视口高度。 - vmin(viewport minimum):视口宽度和高度中较小的那个。例如:
padding: 5vmin;
表示内边距为视口宽度和高度中较小值的5%。 - vmax(viewport maximum):视口宽度和高度中较大的那个。例如:
font-size: 4vmax;
表示字体大小为视口宽度和高度中较大值的4%。
- vw(viewport width):视口宽度的百分比。例如:
-
相对单位:
- em:相对于当前元素的字体大小。例如:
padding: 1em;
表示内边距为当前元素字体大小的1倍。 - rem:相对于根元素(通常是
<html>
)的字体大小。例如:font-size: 1rem;
表示字体大小为根元素字体大小的1倍。
- em:相对于当前元素的字体大小。例如:
-
无单位或自动(auto):默认情况下,某些属性(如
margin
、padding
、width
和height
)可以设置为无单位或自动,表示由浏览器自动计算尺寸。
BFC的介绍
BFC是一种规则,就像孙悟空三打白骨精里面孙悟空划了一个圈让师父和师弟在圈里,这样子白骨精就不能伤害师父和师弟。
BFC的作用就是一种隔离和保护。
这样子的广告一般都是用BFC创建的,因为用户点击关闭以后不能影响整个页面的布局,
position:absolute或position:fixed
float:left或float:right,只要不是float:none就可以
overflow:auto或overflow:hidden或overflow:scroll,只要不是visible均可
display:inline-block或display:flex
1. 设置元素相对父级定位`position:absolute;left:50%;top:50%`,
让自身平移自身高度50%`transform: translate(-50%,-50%);`,这种方式兼容性好,被广泛使用的一种方式
2. 设置元素的父级为弹性盒子`display:flex`,
设置父级和盒子内部子元素水平垂直都居中`justify-content:center; align-items:center`
width: 200px; height: 200px; border: 1px solid; display: flex; align-items: center; // 纵轴对齐方式,默认是纵轴 子元素垂直居中 justify-content: center; //纵轴对齐方式,默认是纵轴
.box { width: 200px; height: 200px; border: 1px solid; display: flex; } .child { background: red; margin: auto; // 水平垂直居中 }
position: absolute; left: 50%; top: 50%; transform: translate(-50%, -50%);
.box { width: 200px; height: 200px; border: 1px solid; display: table-cell; vertical-align: middle; // 设置元素在垂直方向上的对齐方式 text-align: center; } .child { background: red; display: inline-block; }
width: 200px; height: 200px; border: 1px solid; display: grid; align-items: center; justify-content: center;
.box {
width: 200px;
height: 200px;
border: 1px solid;
display: grid;
}
.child {
margin: auto;
}
.box { width: 200px; height: 200px; border: 1px solid; text-align: center; } .box::after { content: ""; line-height: 200px; } .child { display: inline-block; background: red; }
.box { width: 200px; height: 200px; border: 1px solid; position: relative; } .child { background: red; width: 100px; height: 40px; position: absolute; left: 0; top: 0; right: 0; bottom: 0; margin: auto; }
2.父元素设置flex,左右宽度固定,中间flex:1(剩余部分空间)
示例:
<html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>三栏布局</title> <style media="screen"> html *{ padding: 0; margin: 0; } .layout { margin-bottom: 20px; } .layout article div{ min-height: 100px; } </style> </head> <body> <!-- 需求: 实现一个三栏布局,左边200px,右边300px,中间自适应--> <!-- 方式一:浮动布局 --> <section class="layout float"> <style media="screen"> .layout.float .left{ float:left; width:200px; background: red; } .layout.float .center{ background: yellow; } .layout.float .right{ float:right; width:300px; background: blue; } </style> <h1>三栏布局-浮动解决方案</h1> <article class="left-right-center"> <div class="left"></div> <div class="right"></div> <div class="center"></div> </article> </section> <!-- 方式二:绝对定位 --> <section class="layout absolute"> <style> .layout.absolute .left-center-right>div{ position: absolute; } .layout.absolute .left{ left:0; width: 300px; background: red; } .layout.absolute .center{ left: 300px; right: 300px; background: yellow; } .layout.absolute .right{ right:0; width: 300px; background: blue; } </style> <h1>三栏布局-绝对定位解决方案</h1> <article class="left-center-right"> <div class="left"></div> <div class="center"></div> <div class="right"></div> </article> </section> <!-- 方式三: flex布局 --> <section class="layout flexbox"> <style> .flexbox { margin-top: 110px; } .flexbox .left-center-right{ display: flex; } .flexbox .left-center-right .left{ width: 200px; background: red; } .flexbox .left-center-right .center { flex: 1; background: yellow; } .flexbox .left-center-right .right { width: 300px; background: blue; } </style> <h1>三栏布局-弹性布局解决方案</h1> <article class="left-center-right"> <div class="left"></div> <div class="center"></div> <div class="right"></div> </article> </section> <!-- 方式四: table布局 --> <section class="layout table"> <style> .table .left-center-right{ width: 100%; height: 100px; display: table; } .table .left-center-right div{ display: table-cell; } .table .left { width: 200px; background: red; } .table .center { background: yellow; } .table .right { width: 300px; background: blue; } </style> <h1>三栏布局-表格布局解决方案</h1> <article class="left-center-right"> <div class="left"></div> <div class="center"></div> <div class="right"></div> </article> </section> <!-- 方式五:网格布局 --> <section class="layout grid"> <style> .grid .left-center-right{ width: 100%; display: grid; grid-template-rows: 100px; grid-template-columns: 200px auto 300px; } .grid .left { background:red; } .grid .center { background: yellow; } .grid .right { background: blue; } </style> <h1>三栏布局-网格布局解决方案</h1> <article class="left-center-right"> <div class="left"></div> <div class="center"></div> <div class="right"></div> </article> </section> </body> </html>
dispaly:none 设置该属性后,该元素下的元素都会隐藏,占据的空间消失
visibility:hidden 设置该元素后,元素虽然不可见了,但是依然占据空间的位置
区别
1.visibility 具有继承性,其子元素也会继承此属性,若设置 visibility:visible,则子元 素会显示
2.visibility 不会影响计数器的计算,虽然隐藏掉了,但是计数器依然继续运行着。
3.在 CSS3 的 transition 中支持 visibility 属性,但是不支持 display,因为 transition 可以延迟执行,因此配合 visibility 使用纯 CSS 实现 hover 延时显示效果可以提高用户体验
4.display:none 会引起回流(重排)和重绘 visibility:hidden 会引起重绘
-
原始类型(基本类型):
- 数值(Number):用于表示数字,包括整数和浮点数。
- 字符串(String):用于表示文本,包含在单引号、双引号或反引号中。
- 布尔值(Boolean):表示真或假的值,只有两个可能的值:true和false。
- 空值(null):表示一个空对象指针。
- 未定义(undefined):表示变量声明但未初始化的值。
原始类型的特点是它们是不可变的,即不能直接修改原始类型的值,只能创建新的值并赋给变量。同时,原始类型的值在比较时比较的是值本身,而非引用地址。
-
引用类型(对象类型):
- 对象(Object):表示一组键值对的无序集合。
- 数组(Array):表示有序的数据集合,可以存储多个值。
- 函数(Function):用于定义可执行的代码块。
- 日期(Date):表示日期和时间的对象。
- 正则表达式(RegExp):表示一种模式匹配的对象。
引用类型的特点是它们是可变的,即可以修改引用类型的属性和方法。同时,引用类型的值在比较时比较的是引用地址,即两个变量要指向同一个对象才会被认为相等。
- undefined:表示变量已声明但未初始化或者不存在的属性。当我们声明一个变量但未给它赋值时,默认的初始值就是undefined。例如:
let x; console.log(x); // 输出 undefined
const obj = {}; console.log(obj.property); // 输出 undefined
- null:表示明确地指示变量为空,相当于对变量进行了赋值为null操作。我们可以将变量的值设置为null来表示该变量不引用任何对象。例如:
let x = null; console.log(x); // 输出 null
const obj = { property: 'value' }; obj.property = null; console.log(obj.property); // 输出 null
class MyClass { constructor() { this.property = 'value'; } } const instance = new MyClass(); instance.property = null; console.log(instance.property); // 输出 null
。。说一说JavaScript有几种方法判断变量的类型?
- typeof操作符:typeof是一元操作符,用于获取变量的类型字符串。它返回一个字符串,表示变量的类型。例如:
let x = 10; console.log(typeof x); // 输出 "number" let y = "Hello"; console.log(typeof y); // 输出 "string" let z = true; console.log(typeof z); // 输出 "boolean"
- instanceof操作符:instanceof用于检查对象是否属于某个特定类的实例。它需要一个对象和一个构造函数作为参数,返回一个布尔值。例如:
class MyClass {} let obj = new MyClass(); console.log(obj instanceof MyClass); // 输出 true let arr = []; console.log(arr instanceof Array); // 输出 true
- Object.prototype.toString方法:这是Object原型上的toString方法,通过调用这个方法并传入要判断类型的变量作为this,可以返回一个表示变量类型的字符串。例如:
let x = 10; console.log(Object.prototype.toString.call(x)); // 输出 "[object Number]" let y = "Hello"; console.log(Object.prototype.toString.call(y)); // 输出 "[object String]" let z = true; console.log(Object.prototype.toString.call(z)); // 输出 "[object Boolean]"
- Array.isArray方法:Array.isArray是一个静态方法,用于确定给定值是否为数组。它接受一个参数并返回一个布尔值。例如:
let arr1 = [1, 2, 3]; console.log(Array.isArray(arr1)); // 输出 true let arr2 = "Hello"; console.log(Array.isArray(arr2)); // 输出 false
- 使用Set:ES6中引入了Set数据结构,它是一种类似于数组的集合,可以用于存储各种类型的唯一值,包括原始值和引用值。使用Set可以很方便地去重一个数组,具体代码如下:
let arr = [1, 2, 3, 2, 1]; let uniqueArr = [...new Set(arr)]; console.log(uniqueArr); // 输出 [1, 2, 3]
- 使用indexOf:利用indexOf方法可以判断某个元素是否在数组中存在,如果不存在则返回-1。我们可以遍历数组,通过比较当前元素在数组中的位置来去重。具体代码如下:
let arr = [1, 2, 3, 2, 1]; let uniqueArr = []; for (let i = 0; i < arr.length; i++) { if (uniqueArr.indexOf(arr[i]) === -1) { uniqueArr.push(arr[i]); } } console.log(uniqueArr); // 输出 [1, 2, 3]
- 使用includes:includes方法也可以用来判断某个元素是否存在于数组中。利用这个方法,我们可以遍历数组,判断当前元素是否已经存在于去重后的数组中,如果不存在则添加到数组中。具体代码如下:
let arr = [1, 2, 3, 2, 1]; let uniqueArr = []; for (let i = 0; i < arr.length; i++) { if (!uniqueArr.includes(arr[i])) { uniqueArr.push(arr[i]); } } console.log(uniqueArr); // 输出 [1, 2, 3]
- 使用reduce:利用reduce方法可以将数组元素逐个迭代,通过返回值逐步累积得到一个最终的结果。我们可以在reduce中判断当前元素是否已经存在于累积的数组中,如果不存在则将其添加到数组中。具体代码如下:
let arr = [1, 2, 3, 2, 1]; let uniqueArr = arr.reduce((acc, cur) => { if (!acc.includes(cur)) { acc.push(cur); } return acc; }, []); console.log(uniqueArr); // 输出 [1, 2, 3]
-
类型:数组是JavaScript中的一种数据类型,它是由一组按顺序排列的值组成的对象。而伪数组并不是一种数据类型,它通常是一个普通的对象或类数组对象。
-
长度:数组具有length属性,可以准确表示数组中的元素数量。而伪数组可能没有length属性,或者有length属性,但其值不能正确反映伪数组中的元素数量。
-
方法:数组对象具有一系列原生方法和属性,例如push、pop、slice等。这些方法使得处理数组更加简单和灵活。而伪数组通常没有这些方法,因此无法像数组一样直接使用它们。
-
迭代:数组可以使用for循环、forEach、map等迭代方法来遍历元素。而伪数组也可以通过类似的方式进行迭代,但需要手动编写代码来实现。
-
可变性:数组是可变的,可以通过添加、修改、删除元素来改变数组的内容。而伪数组的元素通常是只读的,不能直接修改其中的元素。
-
原型链:数组是Object的实例,继承了Array原型对象上的方法和属性。而伪数组通常没有继承自Array原型对象的方法和属性。
-
返回值:map()方法会返回一个新的数组,这个数组包含的是对原数组每个元素进行处理后的结果,而forEach()方法没有返回值,它只是对原数组进行遍历操作。
-
对原数组的影响:map()方法不会改变原数组,它创建了一个新的数组来存储处理后的结果,而forEach()方法只是对原数组进行遍历操作,并不修改原数组的内容。
-
能否中途跳出循环:map()方法无法中途跳出循环,它会一直遍历完整个数组并返回新的数组。而forEach()方法也无法中途跳出循环,但是可以使用return语句停止继续执行当前循环中的代码,从而达到类似于中途跳出循环的目的。
-
参数的不同:map()方法可以接收两个参数,第一个参数是callback函数,第二个参数是可选的this值。而forEach()方法只接受一个参数,就是callback函数。
-
是否能修改元素的值:map()方法可以修改原数组中的元素的值,因为它遍历数组时返回的是处理后的新值。而forEach()方法不能修改原数组中的元素的值,因为它只是对原数组进行遍历操作。
ES6中引入了箭头函数(arrow functions),它是一种更为简洁的函数定义语法。下面是关于ES6箭头函数的几个特点:
-
简洁的语法:箭头函数使用箭头(=>)来定义函数,可以省略function关键字和大括号。例如:
// 普通函数 function add(a, b) { return a + b; } // 箭头函数 const add = (a, b) => a + b;
-
适用于匿名函数:由于其简洁的语法,箭头函数非常适合定义匿名函数,尤其是在回调函数的情况下。
-
自动绑定this:箭头函数会捕获函数定义时的上下文作为自己的执行上下文,这意味着它们不会创建自己的this值,而是继承外层作用域的this值。这解决了传统函数中this指向的问题。
-
没有arguments对象:箭头函数没有自己的arguments对象,因此无法通过arguments获取传入的参数。可以使用剩余参数(rest parameters)或通过解构赋值来获取参数。
-
不能作为构造函数:箭头函数没有自己的原型,因此不能使用new操作符来实例化对象。
-
简化的返回语法:如果箭头函数只有一行代码,可以省略大括号,并且该行代码会被隐式返回。如果需要多行代码或显式返回值,仍然需要使用大括号和return语句。
需要注意的是,箭头函数并不适用于所有情况。当需要使用动态的this、arguments对象或作为构造函数时,应该使用传统的函数定义方式。
总结来说,ES6中的箭头函数是一种更为简洁的函数定义语法,它具有自动绑定this、适用于匿名函数等特点,但也存在一些限制,例如没有arguments对象和不能作为构造函数。在代码编写时需要根据具体情况选择合适的函数定义方式。
-
函数参数中的剩余参数:使用扩展符可以将不定数量的参数捕获到一个数组中。这在需要处理可变数量参数的函数中非常有用。例如:
function sum(...nums) { return nums.reduce((acc, num) => acc + num, 0); } console.log(sum(1, 2, 3)); // 输出:6 console.log(sum(4, 5)); // 输出:9
-
数组字面量中的展开元素:扩展符可以将数组中的元素展开为单独的值,方便地创建新的数组。例如:
const arr1 = [1, 2, 3]; const arr2 = [...arr1, 4, 5]; console.log(arr2); // 输出:[1, 2, 3, 4, 5]
-
对象字面量中的展开属性:扩展符可以将对象中的属性展开到新的对象中,创建一个浅拷贝。例如:
const obj1 = { a: 1, b: 2 }; const obj2 = { ...obj1, c: 3 }; console.log(obj2); // 输出:{ a: 1, b: 2, c: 3 }
-
函数调用时的参数传递:扩展符可以将数组展开为独立的参数,方便将数组中的元素作为参数传递给函数。例如:
function greet(name, age) { console.log(`Hello ${name}, you are ${age} years old.`); } const userData = ['Alice', 25]; greet(...userData); // 输出:Hello Alice, you are 25 years old.
闭包是一种特殊的函数,它可以访问其词法环境中定义的变量,即使在该环境已经执行完毕后仍然可以访问。简单来说,闭包是函数+其词法环境的组合。
要理解闭包,需要了解以下几个关键点:
-
函数内部的词法作用域:JavaScript中的每个函数都有自己的词法作用域。这意味着函数内部可以访问外部函数或全局作用域中的变量。
-
词法环境:当函数被调用时,会创建一个词法环境,用于存储函数的变量和作用域链。词法环境包含函数内部的变量以及对外部作用域的引用。
-
函数作为值返回:闭包的关键特点是函数作为值返回给其他函数或储存在其他数据结构中。这样,闭包函数就可以在其创建的词法环境之外被调用和访问。
通过使用闭包,我们可以实现以下功能:
-
封装私有变量:闭包可以创建私有变量,只能通过闭包函数内部的函数来访问和修改,从而实现信息隐藏和封装。
-
保持变量状态:闭包可以使函数在多次调用之间保持状态。由于闭包内的词法环境在函数调用后不会被销毁,函数可以记住之前的环境和变量值。
-
创建函数工厂:闭包可以用于创建函数工厂,即返回不同配置或参数的函数。这样可以提高代码的可重用性和灵活性。
需要注意以下几点:
-
内存管理:由于闭包会保持对外部环境的引用,如果没有手动释放闭包函数,会导致内存泄漏的问题。
-
性能影响:闭包的使用可能会增加内存消耗和函数调用的开销,因此需要谨慎使用。
总而言之,闭包是一种强大的特性,通过它我们可以创建私有变量、保持变量状态和创建函数工厂等。了解和正确使用闭包可以帮助我们编写更灵活、模块化的JavaScript代码。
JavaScript 中的变量提升是指在代码执行之前,JavaScript 引擎会将变量和函数声明提升到其作用域的顶部。
具体来说,变量提升包括以下几个方面:
- 变量声明的提升:使用 var 关键字声明的变量会被提升到其作用域的顶部。这意味着在变量声明之前就可以引用该变量,但它的值会是 undefined。
例如: console.log(x); // 输出 undefined var x = 10;
- 函数声明的提升:使用 function 关键字声明的函数会完整地被提升到其作用域的顶部,而不仅仅是函数名。
例如: foo(); // 可以在函数定义之前调用函数 function foo() { console.log('Hello'); }
- 函数表达式的提升:使用函数表达式(将函数赋值给变量)声明的函数不会被提升,只有变量声明会被提升。
例如: bar(); // TypeError: bar is not a function var bar = function() { console.log('World'); };
需要注意的是,虽然变量和函数的声明会被提升,但它们的赋值操作不会被提升。因此,在调用变量或函数之前进行赋值操作是不可取的。
同时,ES6 引入了 let 和 const 关键字来声明块级作用域变量,它们不会像 var 一样被提升。在使用 let 或 const 声明的变量前进行引用会导致 ReferenceError 错误。
总结起来,JavaScript 中的变量提升是在代码执行之前将变量和函数声明提升到其作用域的顶部,使得我们可以在声明之前引用它们。但需要注意的是,只有声明会被提升,赋值操作不会被提升,并且使用 let 和 const 声明的块级作用域变量不会被提升。因此,在编写代码时要注意变量的声明和使用顺序,以避免意外的问题。
变量提升的原则如下:
变量声明提升:变量的声明会被提升到作用域的顶部,但是赋值操作并不会提升。因此,如果在变量声明之前访问变量,其值会是 undefined。
函数声明提升:函数声明会被完全提升,包括函数体。因此,在函数声明之前就可以调用这个函数。
函数表达式不会提升:使用函数表达式(如匿名函数赋值给变量)定义的函数不会被提升,只有函数声明才会被提升。
。。说一说this指向(普通函数、箭头函数)
this 指向当前执行上下文中的对象。根据不同的函数类型和执行情况,this 的指向可能会发生变化。
- 普通函数中的 this:普通函数中的 this 默认指向全局对象(浏览器中是 window 对象,在 Node.js 中是 global 对象),除非使用了 call()、apply() 或 bind() 方法来显式地绑定 this 到其他对象。
例如: function foo() { console.log(this); // 输出 window 对象 } foo();
- 对象方法中的 this:当函数作为对象的方法调用时,this 指向该对象。
例如: javascriptCopy Code var obj = { name: 'Alice', sayName: function() { console.log(this.name); } }; obj.sayName(); // 输出 Alice
- 构造函数中的 this:当使用 new 关键字创建实例时,构造函数内部的 this 指向新创建的实例对象。
例如: javascriptCopy Code function Person(name) { this.name = name; } var person = new Person('Bob'); console.log(person.name); // 输出 Bob
- 箭头函数中的 this:箭头函数中的 this 指向外层函数的 this,或者在没有外层函数的情况下指向全局对象。箭头函数不能使用 call()、apply() 或 bind() 方法来改变 this 的指向。
例如: javascriptCopy Code var name = 'Tom'; var obj = { name: 'Alice', sayName: () => { console.log(this.name); } }; obj.sayName(); // 输出 Tom
需要注意的是,this 的指向并不是固定不变的,它依赖于函数的执行情况和所处的上下文环境。
总结:
对于普通函数:
当函数作为方法调用时,this 指向调用该方法的对象。
当函数以函数形式调用时,严格模式下,this 为 undefined;非严格模式下,this 指向全局对象(浏览器环境下指向 window 对象,Node.js 环境下指向 global 对象)。
当函数通过 call()、apply() 或 bind() 进行显式绑定时,this 指向显式绑定的对象。
当函数作为构造函数使用时(通过 new 关键字),this 指向新创建的对象。
当函数作为事件处理函数时,this 指向触发事件的元素。
当函数作为普通函数嵌套在其他函数内部时,在严格模式下,this 为 undefined;非严格模式下,this 指向全局对象。
而对于箭头函数:
箭头函数没有自己的 this 绑定机制,它会捕获所在上下文的 this 值,并且无法通过 call()、apply() 或 bind() 来改变 this 的指向。
箭头函数的 this 取决于其外部作用域的 this 值。
。。 说一说call apply bind的作用和区别?
call、apply 和 bind 是 JavaScript 中函数对象的方法,用于改变函数的执行上下文和绑定函数中的 this 值。
- call 方法:call() 方法可以在指定的作用域中调用函数,并将一个指定的对象设置为函数执行时的 this 值。接受多个参数,第一个参数是要绑定的 this 值,之后的参数是函数调用时所需的参数列表。
例如: function greeting() { console.log('Hello, ' + this.name); } var person = { name: 'Alice' }; greeting.call(person); // 输出 "Hello, Alice"
- apply 方法:apply() 方法与 call() 方法类似,区别在于传递参数的方式。它也可以在指定的作用域中调用函数,并将一个指定的对象设置为函数执行时的 this 值。接受两个参数,第一个参数是要绑定的 this 值,第二个参数是一个数组或类数组对象,其中包含了函数调用时所需的参数列表。
例如: function sum(a, b) { console.log(a + b); } sum.apply(null, [1, 2]); // 输出 3
- bind 方法:bind() 方法会创建一个新函数,称为绑定函数。绑定函数会将指定的对象作为 this 值,并预先设置部分或全部参数。返回的绑定函数可以在稍后被调用。
例如: var person = { name: 'Alice', sayHello: function() { console.log('Hello, ' + this.name); } }; var greet = person.sayHello.bind(person); greet(); // 输出 "Hello, Alice"
区别:
- call 和 apply 方法可以立即调用函数,并指定函数执行时的 this 值和参数列表,而 bind 方法返回一个新的绑定函数。
- call 方法的参数是按逐个传递的方式,而 apply 方法的参数是以数组或类数组对象的形式传递。
- bind 方法允许预先设置部分或全部参数,并在调用时补充剩余的参数。call 和 apply 则需要在调用时一次性传入所有参数。
总结: call、apply 和 bind 都是用来改变函数的执行上下文并绑定 this 值的方法,它们的区别在于参数的传递方式和是否立即执行函数。根据具体的需求,选择合适的方法来实现对 this 值的绑定
。。说一说js继承的方法和优缺点?
JavaScript 提供了几种实现继承的方法,包括原型链继承、构造函数继承、组合继承、原型式继承、寄生式继承和 ES6 的类继承。
常见的继承方法:
-
原型链继承:通过将一个对象的原型设置为另一个对象的实例来实现继承。子对象可以访问父对象的属性和方法。但是,如果修改了父对象的属性或方法,会影响所有子对象,并且无法传递参数给父对象的构造函数。
-
构造函数继承:通过在子对象的构造函数中调用父对象的构造函数来实现继承。这样可以解决原型链继承中父对象属性共享的问题,但是无法继承父对象的原型上的方法。
-
组合继承:结合了原型链继承和构造函数继承的优点。使用原型链继承父对象的原型上的方法,通过构造函数继承父对象的属性。这样可以实现共享方法和独立属性,但是会调用两次父对象的构造函数。
-
原型式继承:通过创建一个临时构造函数并将父对象作为该构造函数的原型来实现继承。这样可以使用父对象的属性和方法,但是无法解决原型链继承中属性共享的问题。
-
寄生式继承:在原型式继承的基础上,通过在返回的对象上添加额外的属性和方法来增强对象。这样可以实现类似于构造函数继承的效果,但是会增加对象的复杂性和内存消耗。
-
ES6 类继承:使用
class
和extends
关键字来定义类和继承关系。类继承提供了更简洁、易读的语法,并且支持super
关键字调用父类的构造函数和方法。
优点:
- 实现代码重用,避免重复编写相同的属性和方法。
- 实现多态性,子类可以重写父类的方法。
- 可以实现对象之间的关系,并按照需求进行组织和扩展。
缺点:
- 不同的继承方法有各自的限制和问题,如原型链继承中属性共享和无法传递参数,构造函数继承无法继承原型上的方法,组合继承调用两次父类构造函数等。
- 过多的继承层级可能导致原型链过长,影响性能和代码可读性。
- 继承关系过于复杂可能导致代码难以维护和理解。
- 某些继承方法可能带来不必要的内存消耗或性能损失。
当使用 new
操作符创建一个对象时,会发生以下几个过程:
-
创建一个空对象:
new
操作符会创建一个空对象,这个对象将成为新创建的实例。 -
将构造函数的作用域赋给新对象:
new
操作符会将新对象的原型指向构造函数的prototype
属性,这样新对象就可以访问构造函数原型上的属性和方法。 -
执行构造函数:使用
new
操作符调用构造函数,将新对象作为构造函数的上下文(this
),并执行构造函数中的代码。在执行过程中,可以通过this
来设置新对象的属性和方法。 -
返回新对象:如果构造函数没有显式返回一个对象,则
new
操作符会默认返回新创建的对象,否则返回构造函数中显式返回的对象。
示例: function Person(name) { this.name = name; } // 使用 new 操作符创建一个 Person 实例 var person1 = new Person('Alice'); console.log(person1.name); // 输出 "Alice"
在上述示例中,使用 new
操作符创建了一个 Person
对象的实例 person1
。Person
构造函数被调用,并将其上下文设置为新创建的对象 person1
。构造函数内部的 this.name
表达式将 name
属性设置为 "Alice"。最后,new
操作符返回新创建的 person1
对象。
需要注意的是,new
操作符并不是必须的,JavaScript 也允许直接调用构造函数创建对象。然而,使用 new
操作符能够明确地标识出正在创建一个对象的实例,并且保证正确设置原型链和上下文。
defer和async都可以实现脚本的异步加载,提高页面的加载性能。如果脚本需要等待DOM解析完成后再执行,可以使用defer;如果脚本独立,并且不依赖于其他脚本或DOM,可以使用async。
。。说一说promise是什么与使用方法
Promise 是 JavaScript 中用于处理异步操作的对象。它表示一个尚未完成但最终会完成的操作,并可以通过链式的方式处理异步操作的结果。
Promise 有三个状态:
- Pending(进行中):初始状态,表示操作正在进行中。
- Fulfilled(已完成):操作成功完成。
- Rejected(已拒绝):操作失败。
使用 Promise 的一般步骤如下:
-
创建一个 Promise 对象:
const promise = new Promise((resolve, reject) => { // 异步操作的代码逻辑 });
-
在 Promise 构造函数的参数中编写异步操作的代码逻辑。通常,这是一个包含异步操作的函数。
- 如果操作成功完成,调用
resolve(value)
将 Promise 状态设置为 Fulfilled,并将操作的结果传递给value
。 - 如果操作失败,调用
reject(reason)
将 Promise 状态设置为 Rejected,并将操作失败的原因传递给reason
。
- 如果操作成功完成,调用
-
处理 Promise 结果:
-
使用
.then()
方法处理操作成功完成的结果:promise.then((result) => { // 处理操作成功完成的结果 });
-
使用
.catch()
方法处理操作失败的结果:promise.catch((error) => { // 处理操作失败的结果 });
-
使用
.finally()
方法在 Promise 完成后执行一些清理操作:promise.finally(() => { // 执行清理操作 });
-
使用
async/await
语法可以更方便地处理 Promise:async function myFunction() { try { const result = await promise; // 等待 Promise 完成 // 处理操作成功完成的结果 } catch (error) { // 处理操作失败的结果 } finally { // 执行清理操作 } }
-
Promise 的优点是它可以避免回调地狱(callback hell)的问题,通过链式调用 .then()
方法可以对多个异步操作进行串联。同时,Promise 还提供了其他一些方法,如 Promise.all()
、Promise.race()
等,用于处理多个 Promise 对象。
示例:
const fetchData = new Promise((resolve, reject) => { setTimeout(() => { const data = 'Some data from an asynchronous operation.'; if (data) { resolve(data); // 操作成功完成 } else { reject('An error occurred.'); // 操作失败 } }, 2000); }); fetchData .then((result) => { console.log('Result:', result); }) .catch((error) => { console.error('Error:', error); }) .finally(() => { console.log('Cleanup'); });
在上述示例中,创建了一个模拟异步操作的 Promise 对象 fetchData
。使用 .then()
方法处理成功完成的结果,.catch()
方法处理操作失败的结果,并使用 .finally()
方法执行清理操作。在异步操作完成后,根据操作结果将触发相应的处理函数。
。。说一说JS实现异步的方法?
-
回调函数(Callbacks):将一个函数作为参数传递给异步操作,在操作完成后调用该函数来处理结果。
function fetchData(callback) { setTimeout(() => { const data = 'Some data from an asynchronous operation.'; callback(data); }, 2000); } fetchData((result) => { console.log('Result:', result); });
-
Promise:使用 Promise 对象可以更清晰地处理异步操作,通过
.then()
和.catch()
方法链式处理操作结果。function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = 'Some data from an asynchronous operation.'; if (data) { resolve(data); // 操作成功完成 } else { reject('An error occurred.'); // 操作失败 } }, 2000); }); } fetchData() .then((result) => { console.log('Result:', result); }) .catch((error) => { console.error('Error:', error); });
-
async/await:使用
async
函数和await
关键字可以更简洁地处理异步操作,使其看起来像同步代码一样。async function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { const data = 'Some data from an asynchronous operation.'; if (data) { resolve(data); // 操作成功完成 } else { reject('An error occurred.'); // 操作失败 } }, 2000); }); } async function myFunction() { try { const result = await fetchData(); console.log('Result:', result); } catch (error) { console.error('Error:', error); } } myFunction();
-
订阅/发布模式(Pub/Sub):使用事件订阅和发布的方式,通过发布者和订阅者之间的消息传递来处理异步操作。
// 发布者 const publisher = { subscribers: [], publish(data) { this.subscribers.forEach(subscriber => subscriber(data)); }, subscribe(callback) { this.subscribers.push(callback); } }; // 订阅者 function fetchData(callback) { setTimeout(() => { const data = 'Some data from an asynchronous operation.'; callback(data); }, 2000); } fetchData((result) => { publisher.publish(result); }); publisher.subscribe((data) => { console.log('Result:', data); });
。。如何让函数按顺序执行
-
回调函数(Callbacks):将要按顺序执行的函数作为回调传递给前一个函数,在前一个函数完成后调用回调函数执行下一个函数。
function firstFunc(callback) { // 第一个函数的操作逻辑 console.log('First function'); callback(); } function secondFunc() { // 第二个函数的操作逻辑 console.log('Second function'); } firstFunc(() => { secondFunc(); });
-
Promise:使用 Promise 对象的
.then()
方法可以链式调用多个函数,并确保它们按顺序执行。asyncOperation() .then(result1 => { console.log(result1); // 处理第一个异步操作的结果 return anotherAsyncOperation(); // 返回另一个异步操作的 Promise 对象 }) .then(result2 => { console.log(result2); // 处理第二个异步操作的结果 return thirdAsyncOperation(); // 返回第三个异步操作的 Promise 对象 }) .then(result3 => { console.log(result3); // 处理第三个异步操作的结果 }) .catch(err => { console.error(err); // 捕获所有错误 });
-
async/await:使用
async
函数和await
关键字可以让函数按照顺序执行,看起来更像同步代码。async function firstFunc() { // 第一个函数的操作逻辑 console.log('First function'); } async function secondFunc() { // 第二个函数的操作逻辑 console.log('Second function'); } async function executeFunctions() { await firstFunc(); await secondFunc(); } executeFunctions();
无论是回调函数、Promise 还是 async/await,都可以确保函数按照顺序执行。具体选择哪种方法取决于项目的需求和个人偏好。回调函数适用于简单的场景,而 Promise 和 async/await 更适用于复杂的异步操作和错误处理。
。。微任务、宏任务?
微任务(microtask)和宏任务(macrotask)是 JavaScript 中用于处理异步操作的两个概念。
-
宏任务(macrotask):代表较大的任务单元,通常包括以下几种:
- setTimeout 和 setInterval 回调函数
- DOM 事件处理程序
- XMLHttpRequest 和 fetch 的回调函数
- Node.js 的 I/O 操作
宏任务被添加到任务队列中,并在主线程执行时按顺序执行。当主线程空闲时,会选择从宏任务队列中获取任务进行执行。
-
微任务(microtask):代表较小的任务单元,通常包括以下几种:
- Promise 的
.then()
和.catch()
方法 - Object.observe 和 MutationObserver 的回调函数
微任务会在当前任务执行完毕后立即执行,而不需要等待其他任务。也就是说,微任务会在下一个事件循环之前执行。
- Promise 的
事件循环机制: 当 JavaScript 运行时,会通过事件循环机制来处理任务队列中的任务。事件循环包括以下几个步骤:
- 执行当前宏任务中的同步代码,直到遇到异步代码。
- 将遇到的微任务添加到微任务队列中。
- 如果有宏任务,则将其添加到宏任务队列中。
- 检查是否存在浏览器 UI 渲染更新,如果有则更新。
- 执行微任务队列中的所有微任务。
- 如果浏览器支持 requestAnimationFrame,则执行回调函数。
- 执行下一个宏任务(从宏任务队列中获取)。
- 重复上述步骤。
总结来说,宏任务代表较大的任务单元,会在主线程空闲时按顺序执行。而微任务代表较小的任务单元,在当前任务执行完毕后立即执行,不需要等待其他任务。事件循环机制确保异步代码按照特定顺序执行,避免阻塞主线程。
。。说一说cookie sessionStorage localStorage 区别?
-
存储容量:
- cookie 的存储容量最大为 4KB,并且会随着每个 HTTP 请求发送到服务器端。
- sessionStorage 的存储容量也为 5MB,数据仅在当前会话期间有效。
- localStorage 的存储容量也为 5MB,数据永久有效,除非被清除或手动删除。
-
数据生命周期:
- cookie 可以设置一个过期时间(expires),在过期时间之前会一直存在,即使浏览器关闭。
- sessionStorage 数据仅在当前会话期间有效。当用户关闭浏览器窗口后,数据将被清除。
- localStorage 数据长期有效,除非被清除或手动删除。
-
数据与服务器的通信:
- cookie 在每次 HTTP 请求中都会被发送到服务器端,用于实现用户状态的跟踪和身份验证。
- sessionStorage 和 localStorage 的数据不会自动发送给服务器,仅保存在浏览器中。
-
访问权限:
- cookie 的访问权限受到同源策略的限制,只能在设置该 cookie 的域名及其子域名下访问。
- sessionStorage 和 localStorage 也受到同源策略的限制,但是不同页面或标签页之间可以共享相同的 sessionStorage 或 localStorage。
-
使用方式:
- cookie 可以通过 JavaScript 的
document.cookie
进行读取和设置。 - sessionStorage 和 localStorage 可以通过 JavaScript 的
sessionStorage
和localStorage
对象进行读取和设置。
- cookie 可以通过 JavaScript 的
综上所述,cookie 适合用于在客户端和服务器之间传递少量的数据,并具有过期时间。sessionStorage 适用于在当前会话期间存储临时数据,而 localStorage 适用于长期存储数据,可以在浏览器关闭后仍然保留。
。。说一说如何实现可过期的localstorage数据?
localStorage 是一种持久化保存在浏览器中的数据存储方式,它默认是永久有效的,不具备自动过期功能。但是我们可以通过一些手段来实现可过期的 localStorage 数据。
一种常见的实现方式是在 localStorage 中存储数据时,同时保存一个过期时间戳,然后在读取数据时检查当前时间是否超过了过期时间。如果超过了过期时间,则将数据视为过期并进行清除。
以下是一个示例代码:
// 存储数据到 localStorage,并设置过期时间(单位为毫秒) function setLocalStorageWithExpiration(key, value, expiration) { const item = { value: value, expiration: new Date().getTime() + expiration }; localStorage.setItem(key, JSON.stringify(item)); } // 从 localStorage 中读取数据,并检查是否过期 function getLocalStorageWithExpiration(key) { const itemString = localStorage.getItem(key); if (!itemString) { return null; } const item = JSON.parse(itemString); const currentTime = new Date().getTime(); // 检查过期时间 if (currentTime > item.expiration) { localStorage.removeItem(key); // 过期则清除数据 return null; } return item.value; }
使用示例:
// 设置一个过期时间为 1 小时的 localStorage 数据 setLocalStorageWithExpiration('myData', 'Hello!', 60 * 60 * 1000); // 读取 localStorage 数据 const data = getLocalStorageWithExpiration('myData'); console.log(data); // 输出: "Hello!"(如果在过期时间内)
这样,在读取 localStorage 数据时,会先检查过期时间是否已超过,如果超过了过期时间,则会清除数据,并返回 null。这样就实现了可过期的 localStorage 数据。
。。说一下token 能放在cookie中吗?
是的,可以将 token 存储在 cookie 中。Token 是一种用于身份验证和授权的令牌,通常用于跟踪用户状态和验证用户的身份。
将 token 存储在 cookie 中的好处是,可以自动随每个 HTTP 请求发送到服务器端,实现无缝的身份验证。当服务器收到请求时,可以从 cookie 中提取 token,并进行验证。
要将 token 存储在 cookie 中,可以使用 JavaScript 中的 document.cookie
属性来设置和获取 cookie。例如,可以将 token 设置到一个名为 "token" 的 cookie 中:
document.cookie = "token=your_token_value; expires=expiration_time; path=/";
上述代码会在浏览器中创建一个名为 "token" 的 cookie,其值为 "your_token_value"。可以通过指定过期时间、路径等来配置 cookie 的行为。
需要注意的是,在将 token 存储在 cookie 中时,需要采取一些安全措施以防止跨站点脚本攻击(XSS)和跨站请求伪造(CSRF)。可以通过设置 httponly
属性来禁止 JavaScript 访问 cookie,从而增加安全性:
document.cookie = "token=your_token_value; expires=expiration_time; path=/; httponly";
这样,即使在页面上有恶意脚本,也无法通过 JavaScript 访问到该 cookie 的值,从而减少了风险。
总结来说,将 token 存储在 cookie 中可以实现方便的身份验证和授权,但需要注意安全性问题,并采取适当的安全措施来保护用户的身份和数据。
。。说一说axios的拦截器原理及应用?
Axios 基于 Promise 的 HTTP 客户端库。它提供了拦截器机制,允许我们在发送请求和响应之前对它们进行拦截、转换和处理。
拦截器的原理是通过在 Axios 实例中注册拦截器函数来实现的。Axios 实例中有两种类型的拦截器:请求拦截器和响应拦截器。
- 请求拦截器:在发送请求之前被调用,可以对请求进行修改或添加自定义配置。请求拦截器可以用来添加认证信息、设置请求头、转换请求数据格式等。示例代码如下:
// 添加请求拦截器 axios.interceptors.request.use( function(config) { // 在发送请求之前做些什么 config.headers.Authorization = 'Bearer ' + getToken(); // 添加认证信息 return config; }, function(error) { // 对请求错误做些什么 return Promise.reject(error); } );
- 响应拦截器:在接收到响应之后被调用,可以对响应进行处理、转换或捕获错误。响应拦截器可以用来统一处理错误、解析响应数据等。示例代码如下:
// 添加响应拦截器 axios.interceptors.response.use( function(response) { // 对响应数据做点什么 const data = response.data; if (data.code !== 200) { // 处理错误逻辑 return Promise.reject(data.message); } return data; }, function(error) { // 对响应错误做点什么 return Promise.reject(error); } );
通过使用拦截器,可以实现以下应用场景:
- 全局处理请求和响应的错误,例如统一处理请求超时、网络错误等。
- 统一添加认证信息到每个请求中,避免在每次请求中手动添加。
- 在请求发送前,对请求数据进行预处理,例如序列化数据格式。
- 在接收到响应后,对响应数据进行统一处理,例如解析数据格式、处理错误状态码。
。。说一说创建ajax过程?
Ajax(Asynchronous JavaScript and XML)是一种基于 JavaScript 的异步数据传输技术,可以在不刷新页面的情况下向服务器发送请求和接收响应数据。创建 Ajax 请求的过程通常包括以下几个步骤:
- 创建 XMLHttpRequest 对象:
const xhr = new XMLHttpRequest();
- 设置请求参数:
xhr.open(method, url, async);
其中,method
表示 HTTP 请求的方法,如 GET、POST 等;url
表示请求的地址;async
表示是否启用异步,默认为 true。
- 发送请求:
xhr.send(data);
其中,data
表示请求发送的数据,通常用于 POST 请求。
- 监听响应事件:
xhr.onreadystatechange = function() { if (xhr.readyState === 4 && xhr.status === 200) { // 处理响应数据 const response = xhr.responseText; } }
其中,readyState
表示请求的状态,有五个值:0(未初始化)、1(已打开)、2(已发送)、3(接收中)、4(已完成)。status
表示响应的状态码,如 200 表示成功。
- 取消请求:
xhr.abort();
通过以上步骤,我们可以创建一个简单的 Ajax 请求。需要注意的是,在处理响应时,需要根据返回的数据类型进行解析,可以使用 XMLHttpRequest 的 responseText
和 responseXML
属性来获取响应数据。此外,为了防止跨域问题,通常需要在服务器端进行 CORS(跨域资源共享)配置或使用 JSONP 等技术。
除了原生的 XMLHttpRequest,现代浏览器还提供了一些新的 API 来简化 Ajax 请求,如 Fetch API、Axios 等,它们提供了更方便的 API 和拦截器等扩展功能,可以更轻松地完成复杂的 Ajax 交互。
请求状态码 readyState:
从 0 到 4 发生变化
0: 请求未初始化(还没有调用到open方法)
1: 服务器连接已建立(已调用send方法,正在发生请求)
2: 请求已接收(send方法完成,已接收到全部请求内容)
3: 请求处理中(解析响应内容)
4: 请求已完成,且响应已就绪
响应状态码 status:
200:"OK"
404:未找到页面
500: 服务器内部错误
。。说一下fetch 请求方式?
fetch()
是一个基于 Promise 的 Web API,用于发送 HTTP 请求。与传统的 XMLHttpRequest(XHR)相比,fetch()
具有更加简洁、灵活和可读性强的 API 接口,并且支持流式响应、缓存控制和请求取消等功能。
使用 fetch()
发送请求的基本语法如下:
fetch(url, options) .then(response => { // 处理响应数据 }) .catch(error => { // 处理错误 });
其中,url
表示请求的URL地址,可以是字符串或者 Request 对象;options
是一个配置对象,用于设置请求的参数,如方法类型、请求头、请求体等。
fetch()
函数返回一个 Promise 对象,可以通过 then()
方法监听成功响应的处理函数,或者通过 catch()
方法监听请求失败的处理函数。
fetch()
提供了许多选项,其中一些最常用的包括:
mode
:请求模式,包括 'cors'、'no-cors'、'same-origin' 和 'navigate' 等。cache
:缓存模式,包括 'default'、'no-store'、'reload'、'no-cache' 和 'force-cache'。credentials
:证书模式,包括 'omit'、'same-origin' 和 'include'。redirect
:重定向模式,包括 'follow'、'error' 和 'manual'。headers
:请求头,可以是一个 Headers 对象或者一个普通的对象。
下面是一个使用 fetch()
发送 GET 请求的示例代码:
fetch('https://api.example.com/data') .then(response => { if (response.ok) { return response.json(); } else { throw new Error('请求失败!'); } }) .then(data => { console.log(data); }) .catch(error => { console.error(error); });
需要注意的是,fetch()
仅在接收到网络错误时才会拒绝 Promise,如果请求响应状态码为 404 或 500 等,在 then()
中仍然会被视为成功响应。因此,在处理响应数据时,需要检查响应的状态码并抛出错误或者返回正确的数据。
此外,fetch()
还提供了其他方法,如 fetch.abort()
可以取消请求、fetch.blob()
可以返回一个 Blob 对象等。
。。说一下有什么方法可以保持前后端实时通信?
1.轮询、长轮询、 iframe流、WebSocket、SSE
2.轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。
3.长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,如果没有新消息,就一直等待。有新消息,才会返回给客户端。
4.iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
5.WebSocket是类似Socket的TCP长连接的通讯模式,一旦WebSocket连接建立后,后续数据都以帧序列的形式传输。
6.SSE(Server-Sent Event)是建立在浏览器与服务器之间的通信渠道,然后服务器向浏览器推送信息。
轮询适用于:小型应用,实时性不高
长轮询适用于:一些早期的对及时性有一些要求的应用:web IM 聊天
iframe适用于:客服通信等
WebSocket适用于:微信、网络互动游戏等
SSE适用于:金融股票数据、看板等
。。说一下浏览器输入URL发生了什么?
1、DNS域名解析
2、使用IP协议、ARP协议、OSPF等协议将消息上传到服务器上
3、客户端和服务端建立连接
4、客户端发送HTTP请求
5、服务端响应HTPP请求
6、浏览器解析html代码,并请求HTML代码中的资源
7、断开TCP连接
8、浏览器对页面进行渲染呈现给用户
。。说一下浏览器如何渲染页面的?
1.解析HTML,生成DOM树
2.解析CSS,生成CSSOM树
3.两棵树结合,生成Render树
4.计算布局,绘制页面的所有节点
5.绘制布局
。。说一下重绘、重排区别如何避免?
重绘(Repaint)和重排(Reflow)是浏览器渲染页面时的两个关键过程。
重绘是指当元素样式发生改变但不影响其布局时,浏览器将根据新的样式重新绘制元素。这通常涉及到更新元素的颜色、背景、边框等可视化效果。
而重排是指当页面的布局或几何属性发生改变时,浏览器需要重新计算元素的几何属性,重新确定元素在页面中的位置和大小。
重绘和重排都会带来一定的性能开销,因此在编写前端代码时,我们应该尽量避免不必要的重绘和重排操作,以提高页面的渲染性能。
如何避免重绘和重排:
-
批量更新样式:当需要对多个元素进行样式修改时,可以先将这些样式修改放入一个临时的 CSS 类中,然后将该类添加到元素上,以减少重绘和重排的次数。
-
使用 CSS3 动画和过渡:CSS3 动画和过渡通过使用 GPU 加速来实现平滑动画效果,从而减少了重绘和重排的次数。
-
避免频繁访问布局属性:频繁使用引起重排的属性,如 offsetTop、offsetLeft、offsetWidth、offsetHeight、scrollTop、scrollLeft 等,应尽量减少对它们的多次访问。
-
使用文档片段(Document Fragment):当需要在 DOM 中插入多个元素时,可以先创建一个文档片段,在片段中完成所有插入操作,然后一次性将片段插入到文档中。这样可以避免每次插入都引起的重排。
-
使用 CSS3 transform 属性进行动画:CSS3 的 transform 属性不会触发重排,因此适用于实现动画效果。而且合理使用 transform 还能利用 GPU 加速。
-
分离读写操作:将读取和写入 DOM 的操作尽量分离开来。首先进行读取操作,将结果存储在变量中,然后再进行多次写入操作。这样可以最小化重排的次数。
-
使用虚拟 DOM:一些现代前端框架如 React 和 Vue.js 使用虚拟 DOM 技术,通过比较虚拟 DOM 的差异,只更新真正需要改变的部分,减少了不必要的重绘和重排。
总结:避免不必要的样式修改、减少访问布局属性、合理使用 CSS3 动画和过渡、优化 DOM 操作的顺序、使用文档片段和虚拟 DOM 等方法,可以有效减少重绘和重排,提升页面性能。
区别:重绘不一定会发生重排,重排必然导致重绘
。。 说一下浏览器垃圾回收机制?
浏览器垃圾回收机制是指浏览器自动管理和释放不再使用的内存资源的一种机制。由于 JavaScript 是一种高级语言,其内存管理是由垃圾回收器来负责的,开发人员无需手动进行内存分配和释放操作。
浏览器垃圾回收机制的主要原理是通过标记和清除两个阶段来完成。
-
标记阶段:垃圾回收器首先会遍历所有的对象,标记出所有活动对象(即仍然被引用的对象),并在对象中添加标记。而未被标记的对象就被判定为垃圾对象。
-
清除阶段:在标记阶段完成后,垃圾回收器将清除所有未被标记的垃圾对象,释放它们占用的内存空间。这个过程是自动进行的,开发人员无需干预。
浏览器垃圾回收机制的具体实现可能因浏览器厂商和版本而有所差异,但通常采用的算法有以下几种:
-
引用计数算法(Reference Counting):该算法会为每个对象维护一个引用计数器,当对象被引用时计数器加一,当引用失效时计数器减一。当计数器为零时,即表示对象不再被引用,可以被回收。但这种算法无法解决循环引用的问题。
-
标记清除算法(Mark and Sweep):该算法通过标记和清除两个阶段来回收垃圾对象。首先进行标记阶段,标记出所有活动对象,然后进行清除阶段,将未被标记的对象回收。这种算法可以处理循环引用的情况。
-
增量标记算法(Incremental Marking):该算法将标记阶段分为多个小步骤,与应用程序交替执行,避免长时间的阻塞。通过增量标记,减少垃圾回收的耗时,提高用户体验。
总体而言,浏览器的垃圾回收机制会在合适的时机自动触发,并根据不同算法来识别和回收不再使用的内存资源
总结
全局上下文内的变量,因为页面不关闭,全局上下文的内容就不会释放。所以:
1、尽量少用全局变量。
2、如果用了,用来及时释放,变量=null;
非全局上下文内的内容,依靠自动垃圾回收机制。
1、对于值类型来说,优化的方式,尽量在不用的时候不使用特殊方式保存上下文。
2、对于引用类型来说,优化的方式,不再使用一个引用类型时,及时释放(将所有的引用都去掉)变量=null;
。。说一说跨域是什么?如何解决跨域问题?
跨域(Cross-Origin)是指在浏览器中,当一个网页的资源(如脚本、样式表、图片等)试图访问另一个域名下的资源时,会出现跨域问题。浏览器出于安全考虑,限制了跨域资源的访问,防止恶意的网站获取用户的敏感信息或进行攻击。
浏览器中的同源策略(Same-Origin Policy)规定了默认的跨域行为,即只允许在同一域名下的网页间进行跨资源的访问。同源策略要求两个网页具有相同的协议、主机和端口,才能进行跨域交互。
解决跨域问题的常用方法包括:
-
JSONP(JSON with Padding):利用
<script>
标签没有跨域限制的特性,可以通过动态创建<script>
标签来加载包含回调函数的 JSON 数据。服务器返回的数据必须包装在回调函数中,然后在客户端定义该回调函数来处理返回的数据。 -
CORS(Cross-Origin Resource Sharing):在服务器端设置合适的响应头,允许跨域请求。通过在服务器端设置
Access-Control-Allow-Origin
响应头,指定允许访问的域名列表,以实现跨域资源共享。 -
代理服务器:在服务器端设置一个代理服务器,让代理服务器去请求跨域的资源,并将结果返回给客户端。客户端只与同域名的代理服务器进行交互,从而避免了跨域问题。
-
WebSocket:WebSocket 是一种全双工通信协议,可以在浏览器和服务器之间建立持久连接。由于 WebSocket 采用不同的协议,因此不受同源策略的限制,可以实现跨域通信。
-
CORS-Anywhere:CORS-Anywhere 是一个反向代理服务器,它会添加必要的 CORS 头信息到请求中,从而绕过浏览器的跨域限制。通过将请求发送到 CORS-Anywhere 服务器,再由服务器转发到目标网址,可以实现跨域请求。