css
H5 C3 的新特性有哪些
h5的新特性有:
- 1、语义化标签;
- 2、表单增强;
- 3、视频和音频支持;
- 4、canvas绘图;
- 5、本地存储;
- 6、拖拽释放api;
- 7、地理api。
css3的新特性有:
- 1、rgba和hsla颜色模式;
- 2、文本阴影;
- 3、边框圆角
- 4、盒模型;
- 5、多列布局;
- 6、弹性盒子布局;
- 7、网格布局;
- 8、渐变和阴影;
- 9、过渡和动画
css的盒子模型
css的盒子模型有哪些:标准盒子模型,IE盒子模型。 盒子设置为IE盒模型,不论内边距距,边框如何改变盒子的真实宽高都不会发生改变。
标准盒子模式:margin boreder padding content
IE盒子模型:margin content(boreder+padding+content)
通过css如何转换盒子模型
box-sizing:content-box 标准盒子模型
box-sizing:boreder-box IE盒子模型
BFC
-
什么是 BFC:
- BFC 是一个独立的布局环境,在该环境中的元素布局不会影响到外面的元素。
- BFC 内的元素垂直方向的边距会发生重叠 相邻开启了BFC的元素不会重叠。
- BFC 在页面中拥有自己的渲染规则,它可以包含浮动元素,并阻止父元素因子元素浮动引起的高度塌陷。
-
如何创建 BFC:
- 根元素()或包含根元素的盒子。
- 浮动(float 不为 none)的元素。
- 绝对定位元素(position 为 absolute 或 fixed)。
- 行内块(inline-block)元素。
- 表格单元格(table-cell)元素。
- overflow 值不为 visible 的块级盒子。
解决了什么问题
- 解决margin塌陷的问题
- 避免外边距margin重叠(margin合并)
- 清除浮动
- 阻止元素被浮动元素覆盖
【CSS】什么是BFC?BFC有什么作用?_css bfc-CSDN博客
高度塌陷
父元素的高度无法自动适应子元素的高度,导致父元素高度塌陷,常见于使用浮动或绝对定位的子元素
- 给父元素写固定高度
- 给外部的父盒子也添加浮动,让其也脱离标准文档流
- 父元素添加声明overflow:hidden;(触发一个BFC) 或者float
- 在元素中内容的最后添加一个伪元素
- 额外添加一个兄弟元素 clear: both; 清除浮动
box:after{
content:"";
clear: both;
display: block;
height: 0;
overflow: hidden;
visibility: hidden;
}
JS 动画与 CSS 动画区别是什么
CSS 动画
- 性能优化: CSS 动画通常由浏览器的 GPU 加速来执行,因此在性能上通常比 JS 动画更高效。
- 简单动画: 适用于简单的动画效果,如过渡、渐变、旋转等。
- 关键帧动画: 支持使用
@keyframes
规则定义复杂的动画序列。 - 响应式设计: 可以很好地与响应式设计结合,根据媒体查询或其他 CSS 条件自动适应不同的屏幕尺寸和设备。
JS 动画
- 灵活性: 通过 JavaScript 编程能力,可以实现更复杂、更具交互性的动画效果。
- 动画控制: 可以更精确地控制动画的开始、暂停、取消和结束,以及在动画执行过程中的状态监控。
- 动态性: 可以根据用户交互、数据变化或其他动态条件来触发动画,实现更加动态和个性化的效果。
- 跨浏览器兼容性: 在一些旧版浏览器中,可能对 CSS 动画支持不完善,而通过 JavaScript 编写的动画可以更好地实现跨浏览器兼容性。
综合考虑
在实际开发中,通常会根据具体需求来选择使用 CSS 动画还是 JS 动画。简单的动画效果可以通过 CSS 轻松实现,而复杂的、交互性强的动画则可能需要借助 JavaScript 来实现。同时,对于性能要求较高的动画,尤其是需要在移动设备上流畅运行的动画,通常会倾向于使用 CSS 动画以获得更好的性能表现。
js
this
this
的指向取决于函数被调用的方式
- 默认绑定
- 隐式绑定
- new绑定
- 显示绑定
箭头函数没有自己的this 外层作用域this绑定谁箭头函数的this就绑定谁
JavaScript中的模块化编程
JavaScript
模块是指将代码封装在单独的文件中,并按需导出和导入其中的功能。它们可以包含变量、函数、类和其他任何 JavaScript
实体。模块有自己的作用域,而不是和脚本一样在全局作用域内执行代码。一组相互导入、导出的模块组成的图形通常被称为模块树。
实现模块化编程的主要方式有以下几种:
-
CommonJS 模块: Node.js 中使用的模块化系统,使用
require()
导入块,module.exports
导出模块。 -
ES6 模块: 从 ECMAScript 2015 (ES6) 开始引入的原生模块系统,使用
import
和export
关键字。 -
AMD (Asynchronous Module Definition): 一种异步加载模块的规范,使用
define()
和require()
函数。 -
UMD (Universal Module Definition): 一种兼容多种模块系统的模块定义方式。
-
自定义模块模式: 使用立即执行函数表达式 (IIFE) 或对象字面量来实现模块化。
事件循环 微任务和宏任务
在JavaScript的执行中,代码分为同步和异步两种。首先,引擎会优先执行所有的同步代码。随后,才会转向异步代码的执行。异步代码进一步细分为微任务和宏任务。在异步执行过程中,优先处理所有的微任务,随后再按顺序执行宏任务。每当一个宏任务执行完毕后,引擎都会检查微任务队列是否为空,如果不为空,则继续执行微任务。这样的循环过程会持续进行,直至所有的微任务和宏任务都被处理完毕。
-
首先执行所有的同步代码。
-
执行完所有同步代码后,会检查微任务队列,如果有任务就全部执行完。
-
执行完微任务队列后,会从宏任务队列中取出第一个宏任务执行。
-
执行完这个宏任务后,再次检查微任务队列,如果有微任务就全部执行完,然后会接着检查宏任务队列,如果有会接着执行宏任务,执行完当前宏任务,会查看微任务队列是否有微任务,有即清空一直循环直到宏任务队列清空
浅拷贝深拷贝
深拷贝和浅拷贝都是对于复杂数据类型进行复制的操作,区别在于复制的方式不同。
浅拷贝是指创建一个新对象,这个新对象有着原始对象属性值的一份精确拷贝,如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址,所以如果其中一个对象改变了这个地址,就会影响到另一个对象。
深拷贝是指创建一个新对象,这个新对象的值和原始对象的值完全没有关联,即便原始对象中有引用类型的属性,新对象也会开辟新的内存地址,完全拷贝一份新的对象,修改一个对象不会影响到另一个对象。
闭包
闭包是定义在一个函数内部的函数,内层函数可以访问外层函数的局部变量,这些变量被内层函数引用不会被回收,好处是使局部变量拥有更长的生命周期可以用来封装一段逻辑,坏处是闭包常驻内存造成内存泄露。
- 一个普通的函数function,如果它可以访问外层作用域的自由变量,那么这个函数和周围环境就是一个闭包;
- 从广义的角度来说:JavaScript中的函数都是闭包;
- 从狭义的角度来说:JavaScript中一个函数,如果访问了外层作用域的自由变量,那么它是一个闭包
当闭包内部的函数持续引用外部函数的变量时,这些变量无法被垃圾回收机制回收,导致内存泄漏
for of和for in的区别
for...of
和 for...in
是两种不同的循环方式,用于遍历数据结构,它们的主要区别在于适用的数据结构和遍历方式不同,for...in 循环出的是 key,for...of 循环出的是 value
for…of
for...of
,如数组、Map、Set、字符串等,不会遍历对象(可枚举的)和原型链上的属性或方法。 ES6 中引入的遍历方法,适用于遍历可迭代对象(Iterables)的值
for...of 不能循环普通的对象,构造函数创造的对象,需要通过和 Object.keys()转换为一个可迭代对象搭配使用
for…in
for...in
主要用于遍历对象的属性,包括原型链上可枚举的属性。在遍历数组时,for...in
也会遍历数组的索引(属性名),不仅仅是数组元素本身。因此,通常不推荐在遍历数组时使用 for...in
。
当一个对象实现了Iterable Protocol(可迭代协议)它就是一个可迭代对象,这个对象要求包含一个键为 Symbol.iterator 的属性,该属性的值是一个函数iterator
方法,通过for of遍历其实就是通过Symbol.iterator 属性返回的方法去拿到需要遍历的值允许通过 for…of 循环、扩展运算符(…)、Array.from() 等方式进行迭代
可枚举对象是指对象的属性可以通过对象遍历机制(如 for…in 循环)访问到的属性除非该属性名是一个Symbol,可枚举属性是对象的一种属性特性,用来控制属性是否会被遍历到通过 Object.keys()、Object.values()、Object.entries() 等方法可以提取对象的可枚举属性,
Set与Map的区别
Set 和 Map 是 ES6 中新增的两种数据结构,它们都用于存储数据集合
-
Set:
- Set 对象允许你存储任何类型的唯一值,无论是原始值还是引用值。它不允许重复值存在。
- Set 中的值是唯一的,可以用于去除数组中的重复元素。
- Set 内部的元素只能通过值来操作,不能直接访问到特定位置的元素。
- Set 通常用于存储一组不重复的值,并且不需要与特定值相关联的情况。
- Set 提供了迭代器(Iterator)接口,可以使用 for...of 循环或 forEach 方法对 Set 进行迭代
-
Map:
- Map 是一组键值对的集合,其中键是唯一的,值可以重复。
- Map 中的键可以是任意数据类型,包括原始值、对象引用等。
- Map 中的元素可以通过键来访问和操作,可以根据键对值进行增删改查。
- Map 提供了遍历方法和属性,可以方便地操作键值对集合。
- Map 的大小是根据其键值对的数量来计算的。对于需要频繁增删键值对的操作,Map 通常具有更好的性能。
如果只需要存储唯一值并且不关心顺序,使用 Set;如果需要将值与键关联,并且需要根据键进行查找和操作,使用 Map。
new
new 关键字在 JavaScript 中用于调用构造函数,通过 new 关键字调用构造函数可以创建一个新的对象。构造函数可以看作是用来初始化对象的特殊函数,它会为新对象设置属性和方法。当使用 new 关键字和构造函数一起调用时,会创建一个空对象,并将这个空对象绑定到构造函数中的 this 关键字。然后构造函数中的代码会初始化这个对象的属性和方法,最终返回这个新的对象。通过这种方式我们可以轻松地创建多个拥有相似属性和方法的对象,实现了代码的重用和结构的清晰化
- 创建一个新的对象obj
- 将对象与构建函数通过原型链连接起来
- 将构造函数中的this绑定到新建的对象obj上
- 根据构造函数返回类型作判断,如果是原始值则被忽略,如果是返回对象,需要正常处理
function myNew(fn,...args){
//1.创建一个空对象
let obj={}
//2.将新创建对象的原型指向构造函数的原型对象上
obj.__ptoto__=fn.prototype
//3.将构造函数的this指向新创建的对象上
let result = fn.apply(obj,args)
//判断返回值是否是对象是的话直接返回不是不处理
return result instanceof Object ? result : obj
}
function Person(name, age) {
this.name = name
this.age = age
}
let Person1 = myNew(Person,'yuyss', 17)
Promise
一种用于处理异步操作的对象,它提供了一种更优雅和可控的方式来处理异步代码,解决回调地狱问题:使用回调函数处理异步操作时,容易出现"回调地狱"的问题,通过链式调用解决问题.then(data).then(data)
- 处理异步操作的结果:Promise 可以将异步操作的结果封装在一个对象中,方便进行后续的处理。
- 链式调用:Promise 可以通过链式调用的方式,依次处理多个异步操作,提高代码的可读性和可维护性。
- 错误处理:Promise 提供了一种统一的错误处理方式,方便捕获和处理异步操作中的错误。
三种状态
-
Pending(待定):这是 Promise 的初始状态。在异步操作开始时,Promise 处于此状态。
-
Fulfilled(已成功):当异步操作成功完成时,Promise 转换为此状态。在此状态下,Promise 会返回一个值,称为 fulfillment value。
-
Rejected(已拒绝):当异步操作失败时,Promise 转换为此状态。在此状态下,Promise 会返回一个原因,称为 rejection reason,表示为什么异步操作会失败。
Promise的实现
讲了一下Promise实现的几个关键技术点:
- 重点是需要实现
Promise.then
方法- 维护一个
fullfilled
的事件队列和一个rejected
事件队列- 在
Promise.then
方法里需要判断一下当前Promise的状态以及参数类型- 最后需要实现两个事件队列的自执行,用来处理链式调用的情况
- 在执行方法时使用
setTimeout
模拟异步任务
Promise的九大方法
Promise.resolve
将传递给它的参数填充到 (fulfilled),Promise 对象会立即进入确定(fulfilled)状态
Promise.reject
Promise.reject
方法的作用是创建一个被拒绝的 Promise
对象。它接受一个参数,通常是一个错误对象或描述错误的消息,用于表示拒绝的原因。
当使用 Promise.reject
创建的 Promise
对象被调用时,它会立即进入拒绝状态,并触发相应的错误处理逻辑。你可以通过 then
方法的第二个回调函数或 catch
方法来处理拒绝的情况。
const rejectedPromise = Promise.reject(new Error('出现了错误'));
//1.catch(()=>{})
rejectedPromise.then(res=>{
})
.catch(err => {
console.error(error);
}
))
2.then(res=>{},err=>{})
rejectedPromise.then(
(value) => {
// 不会执行
},
(error) => {
console.error(error);
}
);
Promise.then
用于注册当 Promise 解决(fulfilled)或拒绝(rejected)时应当调用的回调函数
- 如果Promise对象状态变为fulfilled,则会执行then()方法传入的第一个回调函数(onFulfilled)。
- 如果Promise对象状态变为rejected,则会执行then()方法传入的第二个回调函数(onRejected)。
- 如果then()方法中返回一个新的Promise对象,那么下一个then()方法会等待这个新的Promise完成后再执行。
- 如果then()方法中返回一个普通值,那么会将这个值传递给下一个then()方法的回调函数。
- 链式调用:
.then
方法返回一个新的 Promise,允许链式调用,即可以连续调用多个.then
方法。 - 非阻塞:
.then
方法不会阻塞代码的执行,它会按照 JavaScript 的事件循环机制在适当的时候执行回调函数。 - 错误处理:如果在
.then
的回调函数中抛出错误,返回的新的 Promise 将被拒绝,并将错误作为拒绝理由。 - 穿透值:如果
.then
方法中的回调函数没有显式返回任何值(或者返回undefined
),那么链中的下一个.then
将接收到上一个 Promise 的值。
Promise.catch
用于处理 Promise 在执行过程中可能出现的错误或拒绝情况
- 为Promise对象添加一个拒绝(rejected)的回调函数。
- 当Promise对象状态变为rejected时,catch()方法中的回调函数就会被执行。
- catch()方法也可以链式调用,返回一个新的Promise对象。
- 如果Promise对象状态变为rejected,则会执行catch()方法传入的回调函数(onRejected)。
- 在then()方法中如果发生了异常,也会被catch()方法捕获到。
- catch()方法可以用来处理Promise链中任何一个环节出现的错误,起到错误处理的作用。
- 如果catch()方法中返回一个新的Promise对象,那么下一个then()或catch()方法会等待这个新的Promise完成后再执行。
- 如果catch()方法中返回一个普通值,那么会将这个值传递给下一个then()方法的回调函数。
Promise.finally
promise.finally
方法的回调函数不接受任何参数,这意味着finally
没有办法 知道,前面的Promise
状态到底是fulfilled
还是rejected
。这表明,finally
方法里面的操作,应该是与Promise
状态无关的,不依赖于 Promise
的执行结果,无论失败成功都会执行
Promise.all
用于将多个 Promise 对象组合成一个新的 Promise 对象。它接收一个可迭代(数组)作为参数,返回一个新的 Promise 对象这个新的 Promise,等所有Promise 对象都成功,.then返回结果,如果任何一个输入的 Promise 被拒绝(rejected),那么整个 Promise.all()
就会立即被拒绝,.catch返回被拒绝的 Promise 的原因。返回的结果顺序不会改变,按照传入数据的顺序返回,即使更快返回也会按照传入的数据顺序返回
做一个操作可能得同时需要不同的接口返回的数据,这时我们就可以使用Promise.all
问题:使用Promise.all里面有一个报错了如果继续执行下去
//核心就是在调用.all的时候,map全部函数,就算失败了也给一个返回值,确保函数全部执行
Promise.all([fn,fn1...].map(p=>{
//.then返回需要的东西 .catch返回一些错误信息
return p.then(e=> {
return p
}).catch(err=> return '错误了')
})).then( res => {
//拿到需要的数据
}).catch(reason => {
console.log(reason)
})
和try catch一样,你把错误截获了,也就是你把throw new Error()替换成了你catch里的内容
Promise.allSettled
有多个不依赖于彼此成功完成的异步任务时,或者你总是想知道每个 promise 的结果时,使用 Promise.allSettled()
无论成功失败一起返回then数据不会走进catch
{ status: 'fulfilled', value:value }
:resolve{ status: 'rejected', reason: reason }
:reject
Promise.race
当你想要第一个异步任务完成时,但不关心它的最终状态(即它既可以成功也可以失败)时,它就非常有用。
好几个服务器的好几个接口都提供同样的服务,不知道哪个快,就可以使用Promise.race
Promise.any
传入数组只要其中有一个Promise
成功执行,就会返回已经成功执行的Promise
的结果和all不同我们只会得到一个兑现值,如果都失败了会走catch返回一个AggregateError
错误
AggregateError
对象代表了包装了多个错误对象的单个错误对象。当一个操作需要报告多个错误时,例如 Promise.any(),当传递给它的所有承诺都被拒绝时,就会抛出该错误。
Promise的九大方法(resolve、reject、then、catch、finally、all、allSettled、race、any)你都用过那些?_promise方法有哪些-CSDN博客
asyan await
async await 原理(generator+自动执行器)
在底层,async
函数通过将 async
关键字添加到函数声明来自动将函数转换为一个 Generator
函数。await
关键字则被转换成 yield
表达式。此外,内置的自动执行器会处理 Promise
的链式调用,从而无需手动执行 next
方法。
- 基本概念: async/await 是 JavaScript 中用于处理异步操作的语法糖,用同步的思维去解决异步的代码,使得代码更加清晰易读。
- async 函数: 是用来声明一个异步函数的关键字,异步函数的内部代码执行过程和普通的函数是一致的,默认情况下也是会被同步执行,async函数它会返回一个 Promise 对象。在 async 函数内部,可以使用 await 关键字来等待一个 Promise 对象的解析。
//异步函数也可以有返回值,但是异步函数的返回值会被包裹到Promise.resolve中: async function foo() { return "abc" } //foo() //Promise {<fulfilled>: 'abc'} foo().then(res => { console.log("res:", res)//abc }) //如果我们的异步函数的返回值是Promise,Promise.resolve的状态会由Promise决定: async function foo() { return new Promise((resolve, reject) => { // resolve("aaa") reject("bbb") }) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) }) //如果我们的异步函数的返回值是一个对象并且实现了thenable,那么会由对象的then方法来决定: async function foo() { return { then: function(resolve, reject) { // resolve(111) reject(222) } } } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- await 关键字: await 关键字用于等待一个 Promise 对象的解析结果还可以接 async 函数。当遇到 await 关键字时,函数会暂停执行,直到 Promise 对象状态变为 resolved,然后将 resolved 的结果返回。
//如果await后面是一个普通的值,那么会直接返回这个值: async function foo() { console.log("foo函数开始~") const result = await 123 console.log("代码继续执行:", result) } //如果await后面是一个theable的对象,那么会根据对象的then方法调用来决定后续的值: async function foo() { console.log("foo函数开始~") const result = await { then: function(resolve, reject) { resolve("aaa") } } console.log("代码继续执行:", result) } //如果await后面的表达式,返回的Promise是reject的状态,那么会将这个reject结果直接作为foo的Promise的reject值: function requestData(url) { console.log("调用了requestData请求") return new Promise((resolve, reject) => { setTimeout(() => { if (url === "coderwhy") { // 发送成功了 resolve("一组成功数据") } else { // 发送失败了 reject("请求url错误") } }, 1000); }) } async function foo() { console.log("foo函数开始~") const result = await requestData("kobe") console.log("代码继续执行:", result) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- 错误处理:如果我们在async中抛出了异常,那么程序它并不会像普通函数一样报错,而是会作为Promise的reject来传递,await报错也一样,使用函数.catch()接收,也可以使用 try/catch 自己来捕获异步操作中的错误,await reject后的异步函数里面的代码不会继续执行
function requestData(url) { console.log("调用了requestData请求") return new Promise((resolve, reject) => { setTimeout(() => { if (url === "coderwhy") { // 发送成功了 resolve("一组成功数据") } else { // 发送失败了 reject("请求url错误") } }, 1000); }) } async function foo() { console.log("foo函数开始~") const result = await requestData("coderwhy") return result console.log("代码继续执行:", result) } foo().then(res => { console.log("res:", res) }).catch(err => { console.log("err:", err) })
- 与 Promise 的关系: async/await 是建立在 Promise 基础之上的一种语法糖,它可以更加方便地处理异步操作,减少了回调函数的嵌套。
Generator 生成器函数
Generator 函数是 ES6 引入的一种特殊的函数,它允许你在函数执行过程中暂停执行,并在稍后的某个时刻从停止的地方恢复执行。这种能力使得 Generator 函数非常适合处理异步操作,因为它可以在等待异步操作完成时暂停执行,而不需要复杂的回调嵌套。
基本语法
Generator 函数通过在函数声明之前添加一个星号 *
来定义。在函数体内,你可以使用 yield
表达式来指定暂停和恢复执行的位置。
function* generatorFunction() {
yield 'Hello';
yield 'World';
return 'End';
}
使用方法
当你调用一个 Generator 函数时,它不会立即执行,而是返回一个迭代器(Iterator)对象。要执行函数中的代码,你需要调用迭代器的 next()
方法。
const iterator = generatorFunction();
console.log(iterator.next()); // { value: 'Hello', done: false }
console.log(iterator.next()); // { value: 'World', done: false }
console.log(iterator.next()); // { value: 'End', done: true }
每次调用 next()
方法时,函数会执行到下一个 yield
表达式,并返回一个包含 value
和 done
属性的对象。value
是 yield
表达式的结果,而 done
是一个布尔值,表示函数是否已经执行完毕。
特点
- 暂停和恢复执行:通过
yield
表达式,Generator 函数可以在任何地方暂停执行,并在需要时恢复。 - 惰性求值:Generator 函数只有在请求下一个值时才会执行,这种惰性求值的特点使得它非常适合于生成序列和异步操作。
- 状态保持:当 Generator 函数暂停时,它的状态会被保留,下次恢复执行时可以从该状态继续执行。
应用场景
Generator 函数通常用于以下场景:
- 异步流程控制:处理基于回调的异步代码时,使用 Generator 函数可以使代码看起来像同步代码一样。
- 数据生成:可以用来生成序列数据,例如斐波那契数列或其他复杂的数据结构。
- 协程:在多线程编程中,Generator 函数可以用来实现协作式多任务处理。
面向对象
继承
对象的 [[prototype]] __proto__
(隐式原型)
- 每个对象在创建时都有一个内部的
[[prototype]]
属性,它指向该对象的原型对象。这个原型对象可以是另一个对象,也可以是null
。 - 这个原型对象被用于实现对象之间的继承关系,当我们访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到为止。
函数的 prototype
(显示原型)
- 每个函数对象都有一个名为
prototype
的属性,它指向一个对象。这个对象通常被用作构造函数创建的实例对象的原型对象。 - 通过操作函数的
prototype
属性,我们可以为该函数创建的实例对象添加共享的属性和方法,实现基于原型的继承机制。
当你使用 new
关键字调用一个函数时,
- JavaScript 会创建一个新的对象,
- 并将该对象的
__proto__
属性指向构造函数的prototype
对象。 - 这样,新创建的对象就可以访问和继承构造函数的原型对象上的属性和方法。
总结:__proto__
是对象特有的属性,用于指向对象的原型,而 prototype
是函数特有的属性,用于指向构造函数的原型对象。通过 __proto__
属性,对象可以访问和继承原型对象的属性和方法,而通过 prototype
属性,函数可以定义和共享供实例对象继承的属性和方法。
原型链
在 JavaScript 中,每个对象都有一个指向原型对象的引用,这个引用通常被称为 __proto__
。当访问一个对象的属性或方法时,如果对象本身没有这个属性或方法,JavaScript 引擎会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端(即 null
)。
这条从对象到原型对象再到原型对象的原型对象,直到 null
的链条被称为原型链。通过原型链,对象可以继承其原型对象的属性和方法,实现了在 JavaScript 中的简单继承机制。
当查找一个属性或方法时,对象会顺着 __proto__
不断向上查找,直到找到属性或方法或者到达原型链的末端(null
)。这种从对象到原型对象再到原型对象的原型对象的链条就是原型链。
constructor
-
对象的
constructor
属性:可以帮助我们了解对象的构造来源,创建新的对象实例,判断对象的类型,以及在继承中配置constructor
保持正确的构造函数引用。-
通过constructor
属性指向创建当前对象的构造函数。obj.constructor
可以获取创建该对象的构造函数。 -
通过
new obj.constructor()
可以创建一个新的对象实例,该对象与原对象具有相同的构造函数。 -
可以使用
obj.constructor === SomeConstructor
来判断对象是否由某个特定的构造函数创建。 -
在实现继承时,可以通过
SubClass.prototype.constructor = SubClass
来设置子类的constructor
属性,以保证对象实例的constructor
属性指向正确的构造函数。
-
-
函数的
constructor
属性:主要用于标识函数的类型,创建新的函数实例,判断函数的类型,以及在继承中配置constructor
保持正确的构造函数引用- 标识函数的类型,函数本身也是对象,因此函数也可以有
constructor
属性。对于函数对象来说,constructor
属性指向Function
构造函数,箭头函数,constructor
属性不存在,因为箭头函数没有自己的constructor,
new Function()
语法来创建新的函数实例,这个新函数的constructor
属性会指向Function
构造函数-
可以使用
func.constructor === Function
来判断一个函数是否是普通函数。
可以使用func.constructor.name
来获取函数的名称。 -
在实现函数继承时,可以通过
SubFunc.prototype.constructor = SubFunc
来设置子函数的constructor
属性,以保证对象实例的constructor
属性指向正确的构造函数。
- 标识函数的类型,函数本身也是对象,因此函数也可以有
对象的 constructor
属性指向创建该对象的构造函数,而函数的 constructor
属性指向 Function
构造函数。这些 constructor
属性在一些情况下可以用来确定对象的类型或者函数的构造函数。
继承的方法
//原型链继承 实现了基本继承但有重复的属性代码
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.running = function () {
console.log('me is running');
}
function Student(name, age, height) {
this.name = name
this.age = age
this.height = height
}
let p = new Person('oyss',18)
Student.prototype = p
Student.prototype.studying=function(){
console.log('me is studying');
}
let s1 = new Student('yuyss',22,180)
console.log(s1.name,s1.age,s1.height);
s1.running()
s1.studying()
//构造函数继承(借助 call) 只继承了属性没有方法
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.running=function (){
console.log('me is running call')
}
function Student(name,age,height){
Person.call(this,name,age)
this.height=height
}
Student.prototype.studying=function(){
console.log('me is studying call');
}
let stu=new Student('yuyss',22,180)
console.log(stu);
// 组合原型借用继承 结合继承了属性和方法
function Person(name,age){
this.name=name
this.age=age
}
Person.prototype.running=function (){
console.log('me is running call')
}
function Student(name,age,height){
Person.call(this,name,age)
this.height=height
}
Student.prototype=new Person('oyss',18)
//let p = new Person('oyss',18)
//Student.prototype=p
Student.prototype.studying=function(){
console.log('me is studying call');
}
let stu=new Student('yuyss',22,180)
console.log(stu);
stu.running()
stu.studying()
// 原型式方法继承 借助Object.create方法
function Person(name, age) {
this.name = name
this.age = age
}
Person.prototype.running = function () {
console.log('me is running create')
}
function Student(name, age, height) {
Person.call(this, name, age)
this.height = height
}
// 1.使用Object.create创建一个新对象传入的参数就是这个新对象的显示原型
// Student.prototype=Object.create(Person.prototype)
// 2.创建一个函数这个函数创建一个新对象,父类的显示原型赋值给新对象的隐式原型,最好将这
个对象赋值到子类的显示原型上
// function object(obj) {
// let newObj = {}
// newObj.__proto__ = Person.prototype
// return obj.prototype = newObj
// }
// object(Student)
//function object(obj) {
// var newObj = {}
// Object.setPrototypeOf(newObj, obj)
// console.log(newObj);
// return newObj
//}
Student.prototype=object(Person.prototype)
Student.prototype.studying = function () {
console.log('me is studying create');
}
let stu = new Student('yuyss', 22, 180)
console.log(stu);
stu.running()
stu.studying()
//寄生式继承函数 寄生:寄生到传入的对象身上,缺点:如果函数中有自定义方法每次执行都会创建重复方法
function object(obj) {
function Func() { }
Func.prototype = obj
return new Func()
}
function createStudent(person) {
var newObj = object(person)
console.log(newObj);
newObj.studying = function () {
console.log(this.name + ' me is studying 寄生式继承函数');
}
return newObj
}
var person = {
name: 'yuyss',
age: 18
}
var stu = createStudent(person)
stu.studying()
// 寄生组合式继承
function object(obj) {
function Func() { }
Func.prototype = obj
return new Func()
}
function inheritPrototype(subType, superType) {
subType.prototype = object(superType.prototype)
Object.defineProperty(subType.prototype, "constructor", {
enumerable: false,
configurable: true,
writable: true,
value: subType
})
}
function Person(name, age, friends) {
this.name = name
this.age = age
this.friends = friends
}
Person.prototype.running = function () {
console.log("running~")
}
Person.prototype.eating = function () {
console.log("eating~")
}
function Student(name, age, friends, sno, score) {
Person.call(this, name, age, friends)
this.sno = sno
this.score = score
}
inheritPrototype(Student, Person)
Student.prototype.studying = function () {
console.log("studying~")
}
let stu = new Student('yuyss', 18, 180, 20, 100)
console.log(stu);
stu.running()
stu.studying()
多态
维基百科对多态的定义:多态(英语:polymorphism)指为不同数据类型的实体提供统一的接口,或使用一个单一的符号来表示多个不同的类型。
个人的总结:不同的数据类型进行同一个操作,表现出不同的行为,就是多态的体现。
function sum(a,b){//上面定义来看一定存在多态
return a+b
}
sum(1,2)
sum(abc,cba)
继承方法放原型上属性在函数里
在使用构造函数创建对象时,将属性放在函数内部而将方法放在原型上是一种常见的最佳实践
-
内存优化:
- 如果将方法也放在构造函数内部, 那么每次创建新对象时, 都会重新创建一份方法的副本。这样会占用更多的内存空间。
- 而将方法放在原型上, 所有通过该构造函数创建的对象都可以共享同一个方法, 不需要为每个对象重新创建。这样可以大大节省内存。
-
封装性:
- 将属性放在构造函数内部可以更好地实现数据封装。构造函数内部的属性是私有的, 外部无法直接访问和修改。
- 而将方法放在原型上, 可以被所有实例对象访问和使用, 方便实现共享功能。
-
继承:
- 将方法放在原型上, 可以更方便地实现基于原型的继承。子类可以直接继承父类原型上的方法, 而不需要重复定义。
-
性能:
- 相比于每次创建对象时都要初始化属性, 从原型上查找方法的性能开销要小得多。
共享new对象创建的原型方法
function Person(name, age, height, address) {
this.name = name
this.age = age
this.height = height
this.address = address
}
Person.prototype.eating = function() {
console.log(this.name + "在吃东西~")
}
Person.prototype.running = function() {
console.log(this.name + "在跑步~")
}
var p1 = new Person("why", 18, 1.88, "广州市")
var p2 = new Person("kobe", 30, 1.98, "北京市")
p1.eating()
p2.running()
vue2
data 为什么必须是函数
每个组件都是 Vue 的实例,组件共享 data 属性,当 data 的值是同一个引用类型的值时,改变其中一个会影响其他
组件中的 data 写成一个函数,数据以函数返回值形式定义,这样每复用一次组件,就会返回一份新的 data,类似于给每个组件实例创建一个私有的数据空间,让各个组件实例维护各自的数据。而单纯的写成对象形式,就使得所有组件实例共用了一份 data,就会造成一个变了全都会变的结果。
v-show和v-if
控制手段:
v-show隐藏则是为该元素添加css--display:none,dom元素依旧还在。
v-if显示隐藏是将dom元素整个添加或删除
编译过程:
v-if切换有一个局部编译/卸载的过程切换过程中合适地销毁和重建内部的事件监听和子组件;
v-show只是简单的基于css切换
编译条件:
v-if是真正的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。
性能消耗:v-if有更高的切换消耗;v-show有更高的初始渲染消耗;
数组响应式更新方法
- push() 尾部添加
- pop() 尾部删除
- shift() 头部删除
- unshift() 头部添加
- splice() 数组的添加 删除 替换
- sort() 数组的排序
- reverse()数组的翻转
利用索引直接设置一个数组项 X 使用this.$set解决
直接修改数组的长度 X 使用splice解决
filter()、concat()、 slice()
-
纯函数操作:
filter()
、concat()
、slice()
这些数组方法都是纯函数,它们不会改变原始数组,而是返回一个新的数组。Vue 的响应式系统是基于侦测数据对象的属性的变化来进行视图更新的,而不是检测整个数组对象的变化。因此,只有在直接修改原始数组的情况下,Vue 才能够检测到数组的变化并触发响应式更新。 -
引用发生变化:使用这些数组方法会返回一个新数组,而不是直接修改原始数组,导致数据的引用发生变化。Vue 在检测数据变化时是基于引用的变化来进行的,如果引用发生了改变,Vue 会认为数据已经发生变化,并进行视图更新。
可以通过将新的数组赋值给 Vue 实例中的数据属性来触发更新
v-for key的作用
key是给每一个vnode的唯一id,也是diff的一种优化策略,可以根据key,更准确, 更快的找到对应的vnode节点,有了唯一的 key
标识,Vue 可以更准确地判断哪些节点需要被移动、删除或创建,从而提高了 DOM 操作的准确性
index作key有什么问题
当以数组为下标的index作为key值时,其中一个元素(例如增删改查)发生了变化就有可能导致所有的元素的key值发生改变。
diff算法时比较同级之间的不同,以key来进行关联,当对数组进行下标的变换时,比如删除第一条数据,那么以后所有的index都会发生改变,那么key自然也跟着全部发生改变,所以index作为key值是不稳定的,而这种不稳定性有可能导致性能的浪费,导致diff无法关联起上一次一样的数据。因此,都不使用index作为key就不使用index。
用 index 拼接一个随机字符串做 key 可以解决v-for问题吗
使用 index
拼接一个随机字符串作为 key
可以在一定程度上解决在 v-for
循环中遇到的问题,但并不是一种推荐的做法。这种方式可能会导致以下问题:
-
性能问题: 使用随机字符串作为
key
可能会增加页面重新渲染的开销,因为每次更新时都会生成新的key
,导致不必要的重新渲染。 -
内存问题: 大量的随机字符串作为
key
也可能导致内存占用过高,特别是在大数据量的列表中。 -
可维护性问题: 使用随机字符串作为
key
可能会使代码难以维护和理解,降低代码可读性。
更推荐的做法是尽可能使用稳定性且唯一的 id
或数据中的某个唯一标识作为 key
。如果您在使用 v-for
循环时遇到具体问题,可以考虑调整数据结构或者其他方式来解决,或者给数据中的每个元素添加一个稳定的唯一标识。
vue自定义指令
全局自定义指令 局部自定义指令 /dəˈrektɪv/ 自动聚焦输入框
自定义指令中的钩子函数参数
-
el:绑定元素的真实Dom
-
binding:是个对象,包含了指令的信息,如指令的值、修饰符、传递的参数等。
-
vnode: Vue 实例中的虚拟 DOM 节点
自定义指令中的钩子函数
- bind:只调用一次,在指令绑定到元素上时触发。
- inserted:当被绑定的元素插入到DOM中触发
- update:当被绑定的元素所在的模板更新时调用,而不论绑定值是否变化
- componentUpdated:当被绑定的元素所在的模板完成一次更新周期时调用
- unbind:只调用一次,在指令与元素解绑时触发
this.$nextTick
等待下次 DOM 更新后执行指定的回调
主要用于处理数据动态变化后,DOM还未及时更新的问题,使用nextTick回调中获取更新后的 DOM
keep-alive
keep-alive
是 Vue 内置的一个组件,可以使被包含的组件保留状态,或避免重新渲染。比如列表页,去了详情页 回来,还是在原来的页面,保留其状态或避免重复渲染。
-
缓存组件状态:
keep-alive
可以缓存动态组件的实例,包括组件的状态、数据和DOM结构。当组件被切换出去再切换回来时,能够保持之前的状态,避免重新渲染和重新加载数据。 -
节省性能消耗:
通过缓存组件的实例,keep-alive
能够减少组件的销毁与重建,从而节省了性能消耗。特别是对于一些开销较大的组件,如列表页或复杂表单,使用keep-alive
可以有效减少页面切换时的渲染时间和数据请求时间。 -
保持页面状态:
在一些需要保持页面状态的场景下,比如在表单填写过程中切换路由需要保留填写内容,可以使用keep-alive
来保存组件状态,确保输入内容不丢失。 -
动态组件缓存:
keep-alive
适用于动态组件,即需要根据某个条件动态加载的组件通过include和exclude属性可以指定哪些组件需要被缓存或排除缓存。
在keep-alive中,有两个生命周期钩子函数,分别是activated()和deactivated()。我们可以使用activated(),在组件激活的时候会被调用,每次进入页面的时候,都会执行,在这个里面进行数据的请求,用来替代mounted。所以,只要将mounted替换为activated就可以解决问题了也可以对数据进行重新渲染。
include(包含)和 exclude(除了)的属性值是组件的名称,也就是组件的name属性值,值为字符串或正则表达式或数组,max(最多缓存数量 控制一下被缓存的组件的个数,后缓存的就会把先缓存的给挤掉线了)
插槽区别 作用域插槽特点
父组件向子组件传递内容的方式。通过插槽,决定父组件的内容
放在子组件
位置,实现动态传递内容的效果
- 默认插槽:又名匿名插槽,当slot没有指定name属性值的时候一个默认显示插槽,一个组件内只有有一个匿名插槽。内容在那里展示子组件决定
- 具名插槽:带有具体名字的插槽,也就是带有name属性的slot,一个组件可以出现多个具名插槽。
- 作用域插槽:默认插槽、具名插槽的一个变体,可以是匿名插槽,也可以是具名插槽,该插槽的不同点是在子组件渲染作用域插槽时,可以将子组件内部的数据传递给父组件。
<slot :games="games"></slot>
内容分发 父组件的数据和子组件的模板配合起来使用就是内容分发
$set Vue.set()
Vue.set()是将set函数绑定在Vue构造函数上,this.$set()是将set函数绑定在Vue原型上
在 Vue.js 中,$set
是一个用于在响应式数据对象中添加新属性的方法。通常直接给响应式对象赋值新属性时,新属性可能不会触发视图更新,这时就可以使用 $set
来手动触发响应式更新
- 如果属性已经存在,则会更新该属性的值,并触发响应式更新。
- 如果属性不存在,则会添加新的属性,并确保该属性具有响应式特性。
1. 动态添加数组元素:当使用Vue的响应式数组时,直接使用索引添加新元素时,
Vue无法检测到新元素的添加。这时可以使用`$set`来动态添加数组元素,并触发响应式更新。
Vue.$set(array, index, value);
2. 动态添加对象属性:当使用Vue的响应式对象时,如果需要在运行时添加新属性,
可以使用`$set`来添加新属性,并触发响应式更新。
Vue.$set(object, propertyName, value);
生命周期
- beforeCreate 执行时组件实例还未创建,通常用于插件开发中执行一些初始化任务
- created 组件初始化完毕,各种数据可以使用,常用于异步数据获取 发送网络请求
- beforeMount 未执行渲染、更新,dom未创建
- mounted 初始化结束,dom已创建,可用于获取访问数据和dom元素
- beforeUpdate 更新前,可用于获取更新前各种状态
- updated 更新后,所有状态已是最新
- beforeDestroy 销毁前,可用于一些定时器或订阅的取消
- destroyed 组件已销毁,作用同上
数据请求在created和mouted的区别
created是在组件实例一旦创建完成的时候立刻调用,这时候页面dom节点并未生成;mounted是在页面dom节点渲染完毕之后就立刻执行的。触发时机上created是比mounted要更早的,两者的相同点:都能拿到实例对象的属性和方法。 讨论这个问题本质就是触发的时机,放在mounted中的请求有可能导致页面闪动(因为此时页面dom结构已经生成),但如果在页面加载前完成请求,则不会出现此情况。建议对页面内容的改动放在created生命周期当中
Vuex
由哪几部分组成
- State:定义了应用状态的数据结构,可以在这里设置默认的初始状态。
- Getter:允许组件从
Store
中获取数据,mapGetters
辅助函数仅仅是将store
中的getter
映射到局部计算属性。 - Mutation:是唯一更改
store
中状态的方法,且必须是同步函数。 - Action:用于提交
mutation
,而不是直接变更状态,可以包含任意异步操作。 - Module:允许将单一的
Store
拆分为多个store
且同时保存在单一的状态树中
Vuex中mutation为什么是同步的action为什么是异步
Vuex 的核心理念是单向数据流,通过 mutation 来修改状态,确保所有状态的变更都可以被追踪。由于 mutation 是同步的,可以更精准地追踪状态的变化。Action 是异步因为在实际开发中,很多情况下需要进行异步操作,比如发起网络请求、定时器等。将异步操作放到 action 中,可以更好地管理异步任务的流程和状态
如果我们在mutation中写了异步,commit在触发mutation事件时,异步的回调函数不知道是什么时候执行的,所以在devtools中难以追踪变化,actions 可以做异步操作,但是并不是直接修改数据,而是通过提交mutations 里面的方法
vuex存储和本地存储区别
Vuex 是 Vue.js 应用的状态管理模式,而本地存储(Local Storage)是浏览器提供的一种机制,用于在用户的浏览器中长期保存数据,即使页面刷新或浏览器关闭后数据也不会丢失。
- 存储位置不同:Vuex 存储在 Vue 应用的内存中,而本地存储存储在用户浏览器本地中。
- 数据持久性:本地存储会将数据永久保存在浏览器中,除非主动删除,否则数据不会消失;Vuex 的状态只在当前页面会话中保存,页面刷新或关闭后状态会丢失。
- 访问方式不同:本地存储需要使用浏览器提供的 API(如 localStorage 或 sessionStorage),Vuex 状态通过组件的计算属性或方法访问。
- 应用范围:Vuex 用于 Vue 应用内组件之间的状态管理,本地存储可以跨多个网页或多个 Vue 应用共享数据
VueX持久化
localStorage
vuex-persistedstate 坡c丝听
注意Vuex本身做不到,需要通过vuex-persistedstate插件去做持久化存储
vue-router
路由守卫
路由钩子函数有三种:
全局钩子: beforeEach(全局前置守卫)、 afterEach(全局后置钩子)
路由独享的守卫(单个路由里面的钩子): beforeEnter
组件路由:beforeRouteEnter、 beforeRouteUpdate、 beforeRouteLeave
1、to:即将要进入的目标路由对象;
2、from:当前导航即将要离开的路由对象;
3、next :调用该方法后,才能进入下一个钩子函数(afterEach)。
vue3
生命周期
Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名:
beforeDestroy改名为 beforeUnmount
destroyed改名为 unmounted
Vue3.0也提供了 Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:
beforeCreate===>setup()
created=======>setup()
beforeMount ===>onBeforeMount
mounted=======>onMounted
beforeUpdate===>onBeforeUpdate
updated =======>onUpdated
beforeUnmount ==>onBeforeUnmount
unmounted =====>onUnmounted
Object.defineProperty 和 Proxy 的区别
- Proxy 可以直接监听对象而非属性;
- Proxy 可以直接监听数组的变化;
- Proxy 有多达 13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等
- 等是 Object.defineProperty 不具备的;
- Proxy 返 回 的 是 一 个 新 对 象 , 我 们 可 以 只 操 作 新 的 对 象 达 到 目 的 , 而Object.defineProperty 只能遍历对象属性直接修改;
Object.defineProperty 兼容性好,支持 IE9,而 Proxy 的存在浏览器兼容性问题
- object.defineproperty 无法监控到数组下标的变化,导致通过数组下标添加元素,无法实时响应
- object.defineProperty 只能劫持对象的属性,从而需要对每个对象,每个属性进行遍历,如果,属性值是对象,还需要深度遍历。
- 无法检测到对象属性的新增或删除
ref 与 reactive 的区别
script setup
Vue3的语法糖,简化了组合式API的写法,并且运行性能更高
- 属性和方法无需返回,直接使用;
- 引入组件的时候,会自动注册;
- 使用
defineProps
接收父组件传递的值; - 使用
useAttrs
获取属性,useSlots
获取插槽,defineEmits
获取自定义事件; - 默认不会对外暴露任何属性,如果有需要使用
defineExpose
;
如何看待Composition API 和 Options API
- OptionsAPI:
- 选项式API,通过定义
data、computed、watch、method
等属性与方法,共同处理页面逻辑; - 缺点:
- 当组件变得复杂的时候,导致对应属性的列表也会增长,可能会导致组件难以阅读和后期维护成本变高;
- 选项式API,通过定义
- CompositionAPI:
- 组合式API,组件根据逻辑功能来组织,一个功能所定义的所有API会放在一起(高内聚,低耦合);
- 优点:
- 内部的功能容易碎片化,像某一个功能相关的数据放在一块,容易阅读和维护(不用翻来翻去找);
- 将某个逻辑关注点相关的代码全都放在一个函数里,这样,当需要修改一个功能时,就不再需要在文件中跳来跳去;
- 逻辑复用:
- 在Vue2中,当混入多个
mixin
会存在两个非常明显的问题:命名冲突,数据来源不清晰; - 而组合式API可以通过编写多个函数就很好的解决了;
- 在Vue2中,当混入多个
- 总结:
- 在逻辑组织和逻辑复用这方面,组合式API是优于选项式API的;
- 组合式API中见不到this的使用,减少了this指向不明的情况;
- 组合式API几乎都是函数,会有更好的类型推断;
Webpack
__dirname:代表当前模块所在的目录的绝对路径
loader
Loader本质就是一个函数,在该函数中对接收到的内容进行转换,返回转换后的结果。因为 Webpack 只认识 JavaScript,所以 Loader 就成了翻译官,对其他类型的资源进行转译的预处理工作,配置里的module.rules数组配置了一组规则,告诉 Webpack 在遇到哪些文件时使用哪些 Loader 去加载和转换打包成js。Loader 的执行顺序是由后到前的
- file-loader 把⽂件输出到⼀个⽂件夹中,在代码中通过相对 URL 去引⽤输出的⽂件。
- url-loader 和 file-loader 类似,但是能在⽂件很⼩的情况下以 base64 的⽅式把⽂件内容注⼊到代码中去。
- image-loader 加载并且压缩图⽚⽂件。
- babel-loader 将ES6转化为ES5。
- css-loader 加载 CSS,⽀持模块化、压缩、⽂件导⼊等特性。
- style-loader 把 CSS 代码注⼊到 JavaScript 中,通过 DOM 操作去加载 CSS。
- less-loader 可以打包处理.less相关的文件。
- sass-loader 可以打包处理.scss相关的文件。
- postcss-loader 自动添加浏览器兼容前缀(postcss.config配置)
- vue-loader 处理vue文件。
Plugin
Plugin 是用来扩展 Webpack 功能的,通过在构建流程里注入钩子实现,它给 Webpack 带来了很大的灵活性,它们会运行在 Webpack 的不同阶段(钩子 / 生命周期),贯穿了Webpack整个编译周期。plugins属性传入new实例对象
Webpack 是通过plugins属性来配置需要使用的插件列表的。plugins属性是一个数组,里面的每一项都是插件的一个实例,在实例化一个组件时可以通过构造函数传入这个组件支持的配置属性。
- cleanWebparkPlugin:用于在每次构建前清理构建目录
- HtmlWebparkPlugin:用于生成 HTML 文件,并将打包后的脚本注入其中
- DefinePlugin:用于定义全局常量
- HotModuleReplacementPlugin:不刷新整个页面的情况下,更新修改的模块
loader和Plugin 区别
loader:用于特定的模块类型进行转换
Plugin:可以用于执行更广泛的任务,比如打包优化,资源管理,环境变量注入
HMR热模块更新
借助webpack.HotModuleReplacementPlugin(),devServer开启hot
模块热更新是webpack的一个功能,它可以使得代码修改之后,不用刷新浏览器就可以更新。在应用过程中替换添加删出模块,无需重新加载整个页面,是高级版的自动刷新浏览器。优点:只更新变更内容,以节省宝贵的开发时间。调整样式更加快速,几乎相当于在浏览器中更改样式
借助Webpack来优化性能
- JS代码压缩
- CSS代码压缩
- Html文件代码压缩
- 文件大小压缩
- 图片压缩
- Tree Shaking
- 代码分离
- 内联 chunk
网络
http状态码
1xx 信息性状态码:
100 Continue:服务器已经收到了请求的首部,并且客户端应该继续发送请求的主体部分。
101 Switching Protocols:服务器已经理解了客户端的请求,并将通过协商的方式更改协议。
2xx 成功状态码:
200 OK:请求已成功。
201 Created:请求已经被实现,而且有一个新的资源已经依据请求的需要而建立。
3xx 重定向状态码:
300 Multiple Choices:被请求的资源有一系列可供选择的回馈信息,用户或浏览器能够自行选择一个首选的。
301 Moved Permanently:请求的资源已被分配了新的 URI。
4xx 客户端错误状态码:
400 Bad Request:服务器未能理解请求。
401 Unauthorized:请求要求身份验证。
404 Not Found:服务器找不到请求的资源。
5xx 服务器错误状态码:
500 Internal Server Error:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。
503 Service Unavailable:服务器当前无法处理请求。
http版本
http1.0
服务器和浏览器进行短暂连接,每一次请求都需要进行TCP连接
http1.1
引入了持久连接,同一个TCP连接可以进行复用,采用的是有序阻塞,上一个请求没有完成会影响下一个,新增了一些请求头、请求方法、响应头
http2.0
采用二进制格式来传输数据,而非http1.x的文本格式,二进制协议解析起来更高效
- 将请求和响应数据分割为更小的帧,并且它们采用二进制编码,http2中同域名下所有通信都在单个连接上完成该连接可以承载任意数量的双向数据流
- 每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,根据帧组装,这也是多路复用同时发送数据的实现条件
采用了报头压缩降低冗余
在客户端和服务器端使用 首部表 来跟踪和存储之前发送的键值对,对于相同的数据,不再通过每次请求和响应发送,每个新的首部键值对,要么被追加到当前表的末尾,要么替换表中已存在的键值对
采用多路复用
同域名下的所有通信,都在单个连接上完成,单个连接可以承载任意数量的双向数据流
服务器推送
服务器会顺便把一些客户端需要的资源一起推送到客户端,如在响应一个页面请求中,就可以随同页面的其他资源
http3.0
基于QUIC协议,使用UDP传输,可以更快地建立连接和进行数据传输,从而降低了延迟。因为UDP本身没有连接的概念,连接建立时只需要一次交互,半个握手的时间而且QUIC的加密协议采用了TLS协议的最新版本TLS1.3相对之前的TLS1.1-1.2,TLS1.3允许客户端无需等待TLS握手完成就开始发送应用程序数据的操作从而达到快速连接的效果
http和https的区别
http是明文数据传输协议 https是ssl加密数据传输
http和https默认的端口不一样 http是80 https是443
https因为设计加密多次握手,性能不如http
为什么 https比http安全
- 加密传输:https 使用 SSL/TLS 协议对数据进行加密传输,防止数据在传输过程中被窃取或篡改。
- 身份验证:https 可以对服务器进行身份验证,确保用户连接到的是真实的服务器,而不是恶意的伪造服务器。
- 完整性保护:加密可以确保数据的完整性,即数据在传输过程中没有被篡改。
ssl协议加密实现的技术
- 对称加密 采用协商的密钥对数据加密
- 非对称加密 实现身份认证和密钥协商
- 摘要算法 对身份进行认证
- 数字签名 身份验证
说说地址栏输入 URL 敲下回车后发生了什么? | 前端面试题整理
地址栏输入 URL 敲下回车后发生了什么
- 对www.baidu.com这个网址进行DNS域名解析,得到对应的IP地址
- 根据这个 IP,找到对应的服务器,发起 TCP 的三次握手
- 建立 TCP 连接后, 发起 HTTP 请求
- 服务器响应 HTTP 请求,浏览器得到 html 代码
- 浏览器解析 html 代码,并请求 html 代码中的资源(如 js、css、图片等)(先得到 html 代码,才能去找这些资源)
- 服务器响应对应的资源
- 响应数据完毕, 四次挥手,关闭 TCP 连接
- 浏览器对页面进行渲染呈现给用户
页面渲染
当浏览器接收到服务器响应的资源后,首先会对资源进行解析:
查看响应头的信息,根据不同的指示做对应处理,比如重定向,存储 cookie,解压 gzip,缓存资源等等
查看响应头的 Content-Type 的值,根据不同的资源类型采用不同的解析方式
- 解析 HTML,构建 DOM 树
- 解析 CSS ,生成 CSS 规则树
- 合并 DOM 树和 CSS 规则,生成 render 树
- 布局 render 树( Layout / reflow ),负责各元素尺寸、位置的计算
- 绘制 render 树( paint ),绘制页面像素信息
- 浏览器会将各层的信息发送给 GPU,GPU 会将各层合成( composite ),显示在屏幕上
Token
Token是一种用于身份验证和授权的令牌。在用户登录成功后,服务器会生成一个Token,并将其颁发给客户端。客户端在后续的请求中使用Token作为凭证,向服务器证明其身份和权限。Token通常包含一些加密的信息,如用户ID、角色、访问权限等。
同源策略
http:// www. aaa.com:8080/index/vue.js
协议 子域名 主域名 端口号 资源
主要指的就是协议+域名+端口号三者一致,若其中一个不一样则不是同源,会产生跨域
三个允许跨域加载资源的标签:img link script
跨域是可以发送请求,后端也会正常返回结果,只不过这个结果被浏览器拦截了
这些可以解决跨域问题 JSONP CORS websocket 反向代理
浏览器的缓存策略
强缓存(本地缓存) 协商协议(弱缓存)
强缓:不发起请求,直接使用缓存里的内容,浏览器把js,css,image等存到内存中,下次用户访问直接从内存中取,提高性能。
协缓:需要像后台发送请求,通过判断来决定是否使用协商缓存,如果请求内容没有变化,则返回304,浏览器就用缓存里的内容
get和post区别
- get一般是获取数据,post一般是提交数据
- get参数会放在url上,所以安全性比较差,post是放在body中
- get请求时会被缓存,post请求不会被缓存
- get请求刷新服务器或退回是没有影响的,post请求退回时会重新提交数据
- get请求会被保存在浏览器历史记录中,post不会
- get请求只能进行url编码,post可以表单 在提交表单数据时,数据可以使用多种编码方式,包括 URL 编码、Multipart 编码等
post请求发送2次
- 当你的
POST
请求包含一些特殊的头部信息(如Content-Type: application/json
)时,浏览器会先发送一个OPTIONS
请求作为 Preflight 请求,用于检查服务器是否支持该请求。 - 只有在 Preflight 请求得到肯定响应后,浏览器才会发送实际的
POST
请求。这是为了确保跨域请求的安全性。
CDN的好处
- 隐藏服务器源ip。
- 连接所响应速度最快的节点提高访问速度。
- CDN节点缓存减少网站服务器访问压力
前端如何实现即时通讯
- 短轮询。即客户端每隔一段时间就向服务器发送消息,询问有没有新的数据
- 长轮询,发起一次请求询问服务器,服务器可以将该请求挂起,等到有新消息时再进行响应。响应后,客户端立即又发起一次请求,重复整个流程。
- websocket,握手完毕后会建立持久性的连接通道,随后服务器可以在任何时候推送新消息给客户端
WebSocket
websocket 协议 HTML5 带来的新协议,即时通信 不刷新获取数据,WebSocket是一种在单个 TCP 连接上进行全双工通信的协议,它允许在客户端和服务器之间进行实时、双向的数据传输。在前端开发中,WebSocket可以用于实现实时通信,例如聊天应用、在线游戏等,在WebSocket连接时使用wss协议(WebSocket over TLS)来加密数据传输
const socket = new WebSocket('ws://example.com/socketServer');//创建的办法
WebSocket实例提供了一些事件,可以通过事件监听来处理连接状态、接收消息等操作。常见的事件包括:
open
:连接建立时触发。message
:接收到服务器发送的消息时触发。close
:连接关闭时触发。error
:连接出现错误时触发。
socket.onopen = function(event) {
console.log('WebSocket连接已建立');
};
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
socket.onclose = function(event) {
console.log('WebSocket连接已关闭');
};
socket.onerror = function(event) {
console.error('WebSocket连接出错');
};
使用WebSocket实例的send()
方法向服务器发送消息,服务器接收到消息后可以进行相应的处理。服务器同样可以通过发送消息到客户端来实现实时通信。
// 发送消息
socket.send('Hello, WebSocket!');
// 接收消息会触发onmessage事件
socket.onmessage = function(event) {
console.log('接收到消息:', event.data);
};
在不需要使用WebSocket时,可以使用方法close()来显式关闭连接。
socket.close()
css加载
css不会阻塞dom树的解析 css会阻塞dom树的渲染 css加载会阻塞后面js的执行
- CSS不会阻塞DOM树的解析。在浏览器解析HTML文档时,如果遇到外部CSS文件,浏览器会并行下载CSS资源,同时继续解析HTML文档中的DOM结构。这意味着即使CSS文件还未完全加载和解析完成,DOM树仍然会继续解析构建,而不会被CSS加载所阻塞。
-
当浏览器解析HTML文档时,会逐行加载解析,而当遇到外部CSS文件时,浏览器会停止解析HTML文档,去下载并解析CSS文件,然后再继续解析HTML文档。这个过程会阻塞页面的渲染,可能会导致页面的延迟加载。
-
JavaScript执行阻塞:在某些情况下,CSS加载也可能会阻塞JavaScript的执行,尤其是在加载过程中可能会抢占网络资源,导致后续JavaScript脚本的加载和执行受到影响。