LOADING

加载过慢请开启缓存 浏览器默认开启

耀耀切克闹

试了下ai面试,于是我就用魔法打败魔法

面试 2024/6/17

前言

试了下ai面试,感觉有点蠢,好多话语没识别出来。有些说了几分钟,然后没识别,让重新说说,确实整的有点难受。不过根据ai出的题,倒也获益匪浅。那我就用ai打败ai,用魔法打败魔法,看看ai怎么回答ai的问题的。

题目1:什么是事件冒泡和事件捕获?如何利用这些机制进行事件处理?

事件冒泡和事件捕获是事件处理机制中的两个概念。

事件冒泡是指事件从触发元素开始,逐层向上级元素传播,直到到达根元素或被阻止。

事件捕获是指事件从根元素开始,逐层向触发元素传播,直到到达触发元素或被阻止。

在事件处理中,可以使用事件冒泡和事件捕获来控制事件的行为。例如,可以使用事件冒泡来在触发元素的上层元素上触发事件,或者使用事件捕获来在触发元素的下层元素上触发事件。

在Vue中,可以使用v-on指令来绑定事件处理函数,并使用.stop修饰符来阻止事件冒泡

题目2:如何处理异步操作中的错误,并比较回调、Promise和async/await的错误处理策略。

处理异步操作中的错误非常重要,可以保证程序的稳定性和可靠性。回调、Promise 和 async/await 这三种方式在错误处理上的策略:

  1. 回调函数:
    在使用回调函数处理异步操作时,通常会将错误作为回调函数的第一个参数传递。我们可以在回调函数中检查这个错误参数,并进行适当的处理。例如:
function asyncOperation(callback) {
  // 异步操作
  if (error) {
    callback(error);
  } else {
    callback(null, result);
  }
}

asyncOperation(function(error, result) {
  if (error) {
    // 错误处理
  } else {
    // 处理正确结果
  }
});
  1. Promise:
    在 Promise 中,可以使用 .then().catch() 或 .then().then().catch() 这种链式操作来处理错误。在 .catch() 中捕获错误并进行处理,确保任何操作链上的错误都能被捕获到。例如:
function asyncOperation() {
  return new Promise((resolve, reject) => {
    // 异步操作
    if (error) {
      reject(error);
    } else {
      resolve(result);
    }
  });
}

asyncOperation()
  .then(result => {
    // 处理正确结果
  })
  .catch(error => {
    // 错误处理
  });
  1. async/await:
    使用 async/await 可以让异步操作看起来像同步操作一样,同时配合 try-catch 来处理错误。通过 try-catch 块捕获错误,并在 catch 中处理异常情况。例如:
async function asyncOperation() {
  try {
    // 异步操作
    let result = await someAsyncFunction();
    // 处理正确结果
  } catch (error) {
    // 错误处理
  }
}

asyncOperation();

总的来说,async/await 提供了一种更为直观、简洁的方式来处理异步操作中的错误,因为可以结合 try-catch 来捕获异常。而 Promise 的 .then().catch() 风格对于链式操作非常方便且易于理解。回调函数则是传统方式,相对来说在处理多个异步操作时会显得比较复杂。根据实际需求和个人喜好,可以选择适合自己项目的错误处理策略。

题目3:请描述您在项目进度紧张,任务困难度较大的情况下,如何按期保质完成开发任务的。

  1. 制定详细的开发计划:在接到任务后,首先制定详细的开发计划,将任务分解成多个小任务,明确每个任务的完成时间,并将任务分配给相应的开发人员。
  2. 优化开发流程:在开发过程中,不断优化开发流程,提高开发效率。例如,可以使用一些自动化工具来简化重复的代码编写和测试工作,或者采用一些高效的开发方法,如敏捷开发、持续集成等。
  3. 加强沟通和协作:在开发过程中,加强沟通和协作,确保开发人员之间的信息对称,避免重复劳动。同时,及时与客户或产品经理沟通,了解需求的变化,并根据需求调整开发计划。
  4. 监控项目进度:在开发过程中,实时监控项目进度,确保每个任务都能按期完成。如果某个任务进度落后,可以适当调整其他任务的进度,以保证整体进度的完成。
  5. 加强自测和优化:在开发过程中,不断进行测试和优化,确保代码的质量和稳定性。

题目4:请描述一个你在项目遇到的实际问题,你是如何分析这个问题并找到解决方案的。请具体说明你所采取的步骤,包括但不限于如何定义问题、收集信息、识别问题上下文、检验关键假设等。

这个肯定没有固定答案,根据自己项目实际中遇到的问题说说,应该就是说说项目中遇到的难点,以及自己是怎么解决的

题目5:请描述您如何结合自己的经验制定有效的学习计划,并说明在该过程中如何进行时间管理以优化您的学习效率和成果。

制定有效的学习计划:

  1. 分析自身情况: 首先,我会分析自己的知识结构和薄弱环节,找出需要重点学习的知识点。同时,我会评估自己的学习进度和目标,明确学习方向。
  2. 制定学习目标: 根据分析结果,我会制定具体的学习目标,包括短期目标和长期目标。短期目标可以是每天或每周需要完成的任务,长期目标可以是某个时间节点需要达到的成就。
  3. 选择学习资源: 根据学习需求,我会选择合适的教材、课程、视频等学习资源。同时,我会参考一些学习建议和经验,选择最适合自己的学习方式。
  4. 制定学习计划: 我会将学习目标分解成具体的任务,并列出任务清单。然后,我会根据任务的重要性和紧急程度进行排序,制定详细的学习计划。

时间管理:

  1. 制定时间计划: 我会根据学习任务和时间期限,制定详细的时间计划。我会将时间分成小块,为每个任务分配相应的时间段,确保充分利用时间。
  2. 设置deadline: 我会为每个学习任务设置一个deadline,这样可以增加紧迫感,提高学习效率。同时,我会将deadline告诉身边的人,让他们监督我的学习进度。
  3. 避免时间浪费: 我会避免在无意义的事情上浪费时间,例如刷社交媒体、看无关的视频等。同时,我会合理规划时间,将学习任务分散到一天中的不同时间段,避免长时间专注导致疲劳。
  4. 记录时间开销: 我会记录每个任务的时间开销,分析时间使用情况,找出时间浪费的原因,并相应调整学习计划。

优化学习效率和成果:

  1. 采用高效学习方法: 我会尝试采用一些高效学习方法,例如费曼学习法、SQ3R阅读法等,提高学习效果。同时,我会不断调整学习方法,找到最适合自己的学习方式。
  2. 进行学习总结: 在学习过程中,我会定期进行总结,回顾学习成果和不足。同时,我会根据总结结果调整学习计划,优化学习效率。
  3. 保持学习动力: 为了保持学习动力,我会设定奖励机制,完成任务后给予相应的奖励。同时,我会寻找学习伙伴,互相监督、互相学习,提高学习效率。
  4. 进行学习评估: 在学习结束后,我会进行学习评估,评估自己的学习成果和效率。同时,我会总结学习过程中的经验和教训,为将来的学习提供参考。

题目5:描述一种情境,你如何运用有效的沟通技巧来解决团队内部的冲突,并提出你采取的具体措施。

情境:成员A和成员B在某个功能的设计上产生了激烈的争论。成员A主张采用方案A,而成员B则坚持方案B。双方都无法说服对方,导致项目进度停滞。

具体措施:

  1. 召开会议: 一般立即召开一个团队会议,让成员A和成员B当面表达自己的意见。这样可以避免信息传递过程中的误解和误传。
  2. 引导讨论: 在会议中,让成员A和成员B就方案进行讨论,让他们充分表达自己的意见。我会确保双方都能被公平倾听。
  3. 分析利弊: 我会分析方案A和方案B的优缺点,以及它们对项目整体目标的影响。这样可以辅助团队成员更好地理解冲突的实质。
  4. 提出折中方案: 我会提出一个折中方案,这个方案应该能够满足双方的基本需求。我会详细解释这个方案的优点,以及如何满足双方的目标。
  5. 制定实施计划: 一旦成员A和成员B接受解决方案,我会制定一个详细的实施计划,确保双方都能遵守承诺,执行解决方案。
  6. 监控执行: 在解决方案实施过程中,我会监控成员A和成员B的执行情况,确保双方都能按照计划执行。如果有任何一方未能按计划执行,我会采取相应的措施,确保解决方案的顺利实施。

题目7:在您过去的项目中,如何进行需求分析以确保开发出来的软件满足用户的核心需求?请结合实际案例简要描述您识别和处理显性与隐性需求的经验。

我的第一反应是,这不是产品需要考虑的事吗?(手动狗头)

个人观点啊:

在实际案例中,我们在开发一个xxx的项目中遇到了显性和隐性需求的情况。首先,我们与用户进行了深入的访谈和调研,收集了他们的显性需求,比如希望平台具有什么什么等基本功能。这些显性需求是用户明确提出的,是他们认为必须要在软件中实现的功能。

然而,在进一步的需求分析过程中,我们也发现了一些用户的隐性需求。通过与用户交流和观察,我们发现用户希望啥啥啥。这些隐性需求并没有明确被用户提及,但却对他们的满意度有着重要影响。

处理这些显性和隐性需求,我们采取了以下措施:

  1. 对用户的显性需求进行优先级排序,确保首先满足用户必须要求的功能。
  2. 通过原型设计和用户测试来验证用户的需求,及时调整和改进软件功能。
  3. 与用户保持持续沟通,收集用户的反馈和建议,以不断优化软件的功能和用户体验。
  4. 在软件上线后,持续监测用户的行为和数据,分析用户的实际需求并做出相应调整

题目8:请解释CSS预处理器(如Sass, Less)的优点和缺点

CSS预处理器,如Sass和Less,是用来扩展CSS的功能,提供变量、嵌套、混入和其他高级功能的编程语言。下面是Sass和Less的一些优点和缺点:

优点:

  1. 代码复用:预处理器允许你使用变量和混合(mixin)来复用代码,这样可以避免重复和维护性问题。
  2. 结构化:通过嵌套规则,你可以更直观地组织样式表,使得代码更易于阅读和理解。
  3. 可扩展性:随着项目规模的扩大,预处理器的模块化特性使得样式表更容易扩展。
  4. 简化了CSS:预处理器提供了函数和运算功能,这使得编写CSS更加简单,而且可以避免硬编码值。
  5. 动态行为:预处理器提供了条件语句(如Sass的@if, @for, @while等),这允许你编写更灵活的样式表。
  6. 导入:你可以使用@import来组织样式表,使其更易于管理和维护。

缺点:

  1. 兼容性:预处理器的代码需要编译为标准的CSS,这样可能影响网站的加载速度,并且在某些情况下(如字体图标、动画等),可能需要额外的工具来处理。
  2. 学习曲线:预处理器的学习曲线可能比纯CSS要陡,需要学习新的语法和概念。
  3. 性能问题:编译过程可能会增加项目的构建时间,对于大型项目来说,这可能会成为一个性能瓶颈。
  4. 浏览器支持:虽然编译后的CSS应该能够在所有现代浏览器上运行,但是预处理器的某些特性(如变量和函数)可能不会立即得到支持。
  5. 依赖性:使用预处理器意味着你的项目将依赖于额外的构建工具和编译过程,这可能需要额外的配置和维护。

总的来说,CSS预处理器提供了强大的功能来帮助开发者编写更可维护、更可扩展的样式表,但同时也带来了一些额外的复杂性和构建过程中的开销。

题目9:前端在将后端返回的数据渲染在页面上时,如何防范 xss 攻击

在前端防范 XSS 攻击主要有以下几种策略:

  • 转义 HTML: 在将后端数据插入到 HTML 页面之前,对数据中的 HTML 特殊字符进行转义。常见的 HTML 特殊字符包括 &、<、>、” 和 ‘。
  • 使用框架的内置防护: 许多现代前端框架(如 React, Angular, Vue.js)默认会对插入到 DOM 中的数据进行转义。确保使用框架提供的方法来绑定和渲染数据。
  • 内容安全策略 (CSP): 通过设置 HTTP 头部的 Content-Security-Policy 来限制资源的加载和执行。例如,可以限制只允许加载同源的脚本。
  • 验证和清理数据: 在将数据显示到前端之前,对数据进行验证和清理,移除可能的恶意脚本。可以使用库如 DOMPurify 来清理 HTML 内容。

题目10:如何在ECharts中实现图表的响应式布局,使其适应不同大小的屏幕? 如果让你开发一个可视化大屏的页面 要求在不同设备下 一屏展示。对于布局你有哪些考量,并如何解决

ECharts 的 responsive 选项:在 ECharts 的配置项中使用 responsive 选项,根据不同的屏幕尺寸调整图表的配置。

option = {
    responsive: true,
    ...
};

开发可视化大屏页面的布局考量:

  1. 布局的流动性和弹性:使用百分比、视窗单位(vw/vh)等相对单位而非固定像素,确保元素大小和间距能够根据屏幕大小自动调整。

  2. 视觉层次和清晰度:在不同设备上保持足够的字体大小和适当的元素间距,确保信息的可读性和易用性

  3. 断点的设置:通过媒体查询在关键断点处调整布局和样式,以适应不同的屏幕尺寸和方向。

另外可能需要考虑性能相关的问题:考虑到大屏幕可能需要加载和渲染大量数据,优化数据加载和图表渲染的性能是关键。

题目11:解释HTML5中, localStorage、sessionStorage、cookie的区别及用途

之前有总结过,上链接吧:

cookie、localStorage、sessionStorage | 耀耀切克闹 (yaoyaoqiekenao.com)

题目12:你和另一个同事都在feature分支上开发 现在你在本地进行了一些工作,你需要将这些更改合并到主分支master 你会如何操作 ,并解释每个步骤的目的?

这个回答时,我记得当时是识别出来的最不对的来着,难道是我英文发音不准?

  1. 确保本地 feature 分支是最新的
git checkout feature
git pull origin feature

2.切换到主分支 master

git checkout master

3.确保主分支 master 也是最新的

git pull origin master

4从 feature 分支合并到 master

git merge feature
  1. 解决可能的合并冲突
# 查看冲突文件
git status

# 解决冲突后
git add <解决冲突的文件>

6.完成合并&&并且推送更改到远程 master 分支

git commit
git push origin master
阅读全文

WEB前端基础知识梳理(二)-笔记

前端随笔 2024/4/15

JS

  • 理解深拷贝和浅拷贝?

    • 浅拷贝:是指在拷贝对象时,如果是基本数据类型,就是拷贝的这个基本数据类型的值,但如果拷贝的是引用类型数据拷贝的是这个引用数据类型的内存地址,当其中一个对象发生改变时,会导致另一个对象受到影响

    • 浅拷贝的实现方式: object.assgin()、对象扩展符

    • 深拷贝:是指从内存中完整地拷贝一个对象出来,并在堆内存中为其分配一个新的内存区域来存放,并且修改该对象的属性不会影响到原来的对象。

    • 深拷贝的实现方式:1.通过JSON.parse()和JSON.stringify()序列化实现 2.手动递归实现

    JavaScript 继承的几种实现方式?

    (1)第一种是以原型链的方式来实现继承,但是这种实现方式存在的缺点是,在包含有引用类型的数据时,会被所有的实例对象所共享,容易造成修改的混乱。还有就是在创建子类型的时候不能向超类型传递参数。

    (2)第二种方式是使用借用构造函数的方式,这种方式是通过在子类型的函数中调用超类型的构造函数来实现的,这一种方法解决了不能向超类型传递参数的缺点,但是它存在的一个问题就是无法实现函数方法的复用,并且超类型原型定义的方法子类型也没有办法访问到。

    (3)第三种方式是组合继承,组合继承是将原型链和借用构造函数组合起来使用的一种方式。通过借用构造函数的方式来实现类型的属性的继承,通过将子类型的原型设置为超类型的实例来实现方法的继承。这种方式解决了上面的两种模式单独使用时的问题,但是由于我们是以超类型的实例来作为子类型的原型,所以调用了两次超类的构造函数,造成了子类型的原型中多了很多不必要的属性。

    (4)第四种方式是原型式继承,原型式继承的主要思路就是基于已有的对象来创建新的对象,实现的原理是,向函数中传入一个对象,然后返回一个以这个对象为原型的对象。这种继承的思路主要不是为了实现创造一种新的类型,只是对某个对象实现一种简单继承,ES5 中定义的 Object.create() 方法就是原型式继承的实现。缺点与原型链方式相同。

    (5)第五种方式是寄生式继承,寄生式继承的思路是创建一个用于封装继承过程的函数,通过传入一个对象,然后复制一个对象的副本,然后对象进行扩展,最后返回这个对象。这个扩展的过程就可以理解是一种继承。这种继承的优点就是对一个简单对象实现继承,如果这个对象不是我们的自定义类型时。缺点是没有办法实现函数的复用

    Javascript中如何实现函数缓存?函数缓存有哪些应用场景?

    • 函数缓存:函数缓存指的是将函数的返回值缓存下来,当相同的参数再次调用该函数时,直接返回缓存的结果,而不是再次执行函数。这可以提高程序的执行效率。

    • 如何实现:在JavaScript中,可以通过创建一个闭包存储结果来实现函数缓存,也可以使用ES6中的Map对象来存储函数的结果。

    • 应用场景:

    1. 计算量大的函数,比如阶乘、斐波那契数列等,由于计算结果具有重复性,因此可以采用函数缓存来节省计算时间。
    2. 数据请求函数,当相同的请求参数被多次调用时,可以使用函数缓存来避免重复请求及提高响应速度。

    Javascript 数字精度丢失的问题,如何解决?

    前提须知: JavaScript 使用 IEEE 754 标准的双精度浮点数,即 64 位二进制表示,其中 1 位表示符号位,11 位表示指数位,52 位表示小数点后的数字。因此,JavaScript 中的数字精度限制在 16 位小数。

    • 原因:在计算机角度,计算机算的是二进制,而不是十进制。有些数二进制后变成了无限不循环的数,而计算机可支持浮点数的小数部分可支持到52位,所有两者相加,在转换成十进制,得到的数就不准确了。
    • 解决方案:

    1.三方库Math.js、BigDecimal.js
    2.将浮点数转为整数运算,再对结果做除法

VUE

vue2和vue3核心diff算法的区别

Vue 2 使用的是基于递归的双指针的 diff 算法,而 Vue 3 使用的是基于数组的动态规划的 diff 算法。 Vue 3 的算法效率更高,因为它使用了一些优化技巧,例如按需更新、静态标记等。

vuex和pinia

1.vuex有5个属性:

  • state: 存放数据源的地方 this.$store.state
  • getter:计算数据
  • mutation:同步,唯一能修改state数据源的地方 this.$store.commit
  • actions:异步 store.dispatch
  • modules:模块化

2.pinia有三个属性:

  • state:存放数据源的地方
  • actions:同步和异步
  • getters:计算数据
    pinia体积更小(性能更好),pinia可以直接修改state里的数据,而vuex只能通过mutation中去修改。

Vue template 到 render 的过程(vue的编译过程)

  1. 解析模板为AST:利用parse方法将template转换为AST树,这种树形式的JavaScript对象描述了整个模板的结构。在解析过程中,会使用正则表达式顺序解析模板,构造AST树,其中包含普通元素、表达式和纯文本等节点类型。
  2. 静态节点优化:对AST进行深度遍历,标记静态节点,这些节点的DOM永远不会改变,因此在后续更新渲染时可以直接跳过静态节点,实现优化
  3. 生成代码:通过generate方法将AST编译成render函数的字符串,并将静态部分放到staticRenderFns中。最后,通过new Function(render)生成render函数。

如果从0到1自己架构一个vue项目,你会怎么做?

  1. 从0创建一个项目我大致会做以下事情:项目构建、引入必要插件、代码规范、提交规范、常用库和组件
  2. 项目构建我会用vite或者create-vue创建项目
  3. 接下来引必要的插件:路由插件vue-router、状态管理vuex/pinia、ui库则是后台管理的一般是elemnet-plus如果是h5项目则是vant,http工具我一般选择axios
  4. 还会引入一些常用库如:vueuseprettie,nprogress,图标会使用vite-svg-loader
  5. 代码规范:prettier,eslint
  6. 提交规范:githooks+husky+lint-staged+commitlint
  7. 最后就是目录结构,如api:用来放http的一些接口配置,utils:用来放项目中的工具方法类,views:用来放项目的页面文件等等

nextTick的使用和原理

Vue有个异步更新策略,意思是如果数据变化,Vue不会立刻更新DOM,而是开启一个队列,把组件更新函数保存在队列中,在同一事件循环中发生的所有数据变更会异步的批量更新。这一策略导致我们对数据的修改不会立刻体现在DOM上,此时如果想要获取更新后的DOM状态,就需要使用nextTick。

  1. 开发时,有两个场景我们会用到nextTick:
  • created中想要获取DOM时;
  • 响应式数据变化后获取DOM更新后的状态,比如希望获取列表更新后的高度。

vue响应式

所谓数据响应式就是能够使数据变化可以被检测并对这种变化做出响应的机制
采用了”发布-订阅”的设计模式,通过Object.defineProperty()劫持各个属性的getter、setter,在数据变动时调用Dep.notify发布消息给订阅者Watcher,使之更新相应的视图,如果是数组的话,对数组原型链上的方法进行一些修改才能实现监听

vue的生命周期以及其各阶段做的事

Vue 生命周期指的是 Vue 实例在创建、更新、销毁等过程中经历的各个阶段。Vue 生命周期可以分为三个阶段:创建阶段、挂载阶段和销毁阶段。

beforeCreate:通常用于插件开发中执行一些初始化任务

created:组件初始化完毕,可以访问各种数据,获取接口数据等

mounted:dom已创建,可用于获取访问数据和dom元素;访问子组件等。

beforeUpdate:此时view层还未更新,可用于获取更新前各种状态

updated:完成view层的更新,更新后,所有状态已是最新

beforeunmount:实例被销毁前调用,可用于一些定时器或订阅的取消

unmounted:销毁一个实例。可清理它与其它实例的连接,解绑它的全部指令及事件监听器

Vue要做权限管理该怎么做?控制到按钮级别的权限怎么做?

1.权限管理一般分为页面权限和按钮权限

2.具体实现的时候分后端和前端两种方案:

  • 前端方案会把所有路由信息在前端配置,通过路由守卫要求用户登录,用户登录后根据角色过滤出路由表。比如我会配置一个asyncRoutes数组,需要认证的页面在其路由的meta中添加一个roles字段,等获取用户角色之后取两者的交集,若结果不为空则说明可以访问。此过滤过程结束,剩下的路由就是该用户能访问的页面,最后通过router.addRoutes(accessRoutes)方式动态添加路由即可。

  • 后端方案会把所有页面路由信息存在数据库中,用户登录的时候根据其角色查询得到其能访问的所有页面路由信息返回给前端,前端再通过addRoutes动态添加路由信息

  • 按钮权限的控制通常会实现一个指令,例如v-permission,将按钮要求角色通过值传给v-permission指令,在指令的moutned钩子中可以判断当前用户角色和按钮是否存在交集,有则保留按钮,无则移除按钮。

如何实现图片懒加载?

1.使用img标签的 loading 属性

loading 属性指定浏览器是应立即加载图像还是延迟加载图像。
设置 loading=”lazy” 只有鼠标滚动到该图片所在位置才会显示

2.自定义指令 vue实现懒加载指令v-lazy

主要是通过IntersectionObserver实现的,IntersectionObserver 是一个在浏览器中提供的用于异步观察目标元素与其祖先元素或视口交叉情况的API。它可以有效地用于实现懒加载、无限滚动等场景。

const lazyLoad = {
  // mounted 在绑定元素的父组件 
  // 及他自己的所有子节点都挂载完成后调用
  mounted(el, binding) {
    // 如果有需要可以先设置src为 loading 图 
    // el.setAttribute('src', 'loading 图的路径');
    const options = {
      rootMargin: '0px',
      threshold: 0.1,
    };
    const observer = new IntersectionObserver((entries) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          // binding 是一个对象,binding.value 是传递给指令的值
          el.setAttribute('src', binding.value);
          observer.unobserve(el);
        }
      });
    }, options);
    observer.observe(el);
  },
};

export default lazyLoad;

vue3.0哪些优化

1.源码优化

引入了ts

2.性能优化

体积优化

引入tree-shaking,可以将无用模块“剪辑”,仅打包需要的,使打包的整体体积变小了

编译优化

  • Patch Flag:在Vue 3.0中,编译的生成vnode会根据节点patch的标记,只对需要重新渲染的数据进行响应式更新,不需要更新的数据不会重新渲染,从而大大提高了渲染性能。
  • 静态属性提升:做了静态提升后,未参与更新的元素,被放置在render 函数外,每次渲染的时候只要取出即可。同时该元素会被打上静态标记值为-1,特殊标志是负整数表示永远不会用于 Diff。
  • 事件监听缓存:默认情况下绑定事件行为会被视为动态绑定(没开启事件监听器缓存),所以每次都会去追踪它的变化。开启事件侦听器缓存后,没有了静态标记。也就是说下次diff算法的时候直接使用。

数据劫持优化

从object.defineProperty改成了proxy,proxy可以监听整个对象。

3.语法优化

可以使用composition API,优化逻辑组织、优化逻辑复用

Proxy 与 Object.defineProperty 对比

  • Proxy 是一个对象的代理,Object.defineProperty只能代理某个属性

  • Proxy可以在读取时递归代理,Object.defineProperty只能在创建时递归所有

  • 对象上新增属性,Proxy可以监听到,Object.defineProperty不能

  • 数组修改,Proxy可以监听到, object.defineProperty不能

  • Proxy兼容性差

构建工具

vite的热更新原理

  • 1.webSocket,同时监听本地的文件变化。
  • 2.当用户修改了本地的文件时,webSocket的服务端会拿到变化的文件的ID或者其他标识,并推送给客户端
  • 3.客户端获取到变化的文件信息后,便去请求最新的文件并刷新页面

webpack的构建流程

在了解 Webpack 原理前,需要掌握以下几个核心概念,以方便后面的理解:

  • entry:入口,Webpack 执行构建的第一步将从 entry 开始,可抽象成输入
  • module:模块,在 Webpack 里一切皆模块,一个模块对应着一个文件。Webpack 会从配置的 entry 开始递归找出所有依赖的模块
  • chunk:代码块,一个 chunk 由多个模块组合而成,用于 代码合并与分割
  • loader:模块转换器,用于把模块原内容按照需求转换成新内容
  • plugin:扩展插件,在 Webpack 构建流程中的特定时机会广播出对应的事件,插件可以监听这些事件的发生,在特定时机做对应的事情

webpack的构建流程:

  1. 初始化参数:从配置文件和 Shell 语句中读取与合并参数,并初始化需要使用的插件和配置插件等执行环境所需要的参数
  2. 编译构建流程:从Entry发出,针对每个Module串行调用对应的Loader去翻译文件内容,再找到该Moudle依赖的Module,递归地进行编译处理
  3. 输出流程:对编译后的Module组合成Chunk,把Chunk转换成文件,输出到文件系统

1.png

网络和浏览器

介绍一下你对浏览器内核的理解

  1. 主要分成两部分:渲染引擎(layout engineer或 Rendering Engine)和JS引擎。
  2. 渲染引擎:负责取得网页的内容(HTML、XML、图像)、整理讯息(例如加入CSS等),以及计算网页的显示方式,然后渲染到用户的屏幕上。
  3. JS 引擎:解析和执行 javascript 来实现逻辑和控制 DOM 进行交互。
  4. 最开始渲染引擎和 JS 引擎并没有区分的很明确,后来 JS 引擎越来越独立,内核就倾向于只指渲染引擎。

输入url后经历了哪些过程

  1. 解析 URL,判断是否命中缓存(DNS prefetch)
  2. 访问 DNS 服务器,将域名解析获取 IP 地址
  3. 三次握手建立 TCP 连接
  4. 发送 HTTP 请求
  5. 服务器处理请求并返回 HTTP 报文
  6. 浏览器解析渲染页面
  7. 断开连接:TCP 四次挥手

浏览器重绘和回流的区别?

  • 回流: 部分渲染树或整个渲染树需要重新分析且节点尺寸需要重新计算,表现为重新生成布局,重新排列元素
  • 重绘: 由于节点的几何属性发生改变或样式改变,例如元素背景元素,表现为某些元素的外观被改变

重绘不一定导致回流,但回流一定会导致重绘

避免白屏,提高CSS的加载速度

  • 使用CDN(CDN会根据你的网络状况,挑选最近的一个具有缓存内容的节点为你提供资源,因此可以减少加载时间)
  • 对CSS进行压缩,按需加载css
  • 合理使用缓存
  • 减少http请求数,合并css文件

http缓存,强缓存和协商缓存的区别

所谓http缓存其实就是指在本地使用的计算机中开辟一个内存区,同时也开辟一个硬盘作为数据传输的缓冲区,然后用这个缓冲区来暂时保存用户以前访问过的信息。

HTTP缓存通常分为强缓存和协商缓存。强缓存是指浏览器直接从本地缓存中获取响应,而不发送请求到服务器。协商缓存是指浏览器在发送请求到服务器之前,先检查本地缓存是否过期,如果过期则发送请求到服务器更新缓存。

强制缓存的实现主要依赖于HTTP响应头中的Cache-Control和Expires字段。Cache-Control字段用于指定缓存的行为和策略,而Expires字段则指定了资源的过期时间。当浏览器接收到这些字段后,它会根据指令将资源存储在本地缓存中,并在后续请求时检查缓存。

协商缓存的实现主要依赖于HTTP响应头中的Last-Modified和ETag字段。Last-Modified字段用于记录资源的最后修改时间,而ETag字段则是一个由服务器生成的唯一标识符,用于表示资源的特定版本。当浏览器再次请求资源时,它会将这两个字段发送给服务器,服务器会根据这些信息来判断资源是否被修改。

当浏览器访问一个已经访问过的资源是,它的步骤是:

1.先看是否命中强缓存,命中的话直接使用缓存

2.没命中强缓存,则会发送请求到服务器看是否命中 协商缓存

3.如果命中了协商缓存,服务器会返回304告诉浏览器可以使用本地缓存

4.没命中协商缓存,则服务器会返回新的资源给浏览器

前端性能优化方案

可以从DOM层面,CSS样式层面和JS逻辑层面分别入手,大概给出以下几种:
(1) 减少DOM的访问次数,可以将DOM缓存到变量中;
(2) 减少重绘和回流,任何会导致重绘和回流的操作都应减少执行,可将多次操作合并为一次;
(3) 尽量采用事件委托的方式进行事件绑定,避免大量绑定导致内存占用过多;
(4) css层级尽量扁平化,避免过多的层级嵌套,尽量使用特定的选择器来区分;
(5) 动画尽量使用CSS3动画属性来实现,开启GPU硬件加速;
(6) 图片在加载前提前指定宽高或者脱离文档流,可避免加载后的重新计算导致的页面回流;
(7) css文件在<head>标签中引入,js文件在<body>标签中引入,优化关键渲染路径;
(8) 加速或者减少HTTP请求,使用CDN加载静态资源,合理使用浏览器强缓存和协商缓存,小图片可以使用Base64来代替,合理使用浏览器的预取指令prefetch和预加载指令preload;
(9) 压缩混淆代码,删除无用代码,代码拆分来减少文件体积;
(10) 小图片使用雪碧图,图片选择合适的质量、尺寸和格式,避免流量浪费。

设计模式

工厂模式

class Man {
  constructor(name) {
    this.name = name
  }
  alertName() {
    alert(this.name)
  }
}

class Factory {
  static create(name) {
    return new Man(name)
  }
}

Factory.create('yck').alertName()

可以想象一个场景。假设有一份很复杂的代码需要用户去调用,但是用户并不关心这些复杂的代码,只需要你提供给我一个接口去调用,用户只负责传递需要的参数,至于这些参数怎么使用,内部有什么逻辑是不关心的,只需要你最后返回我一个实例。这个构造过程就是工厂。

单例模式

单例模式很常用,比如全局缓存、全局状态管理等等这些只需要一个对象,就可以使用单例模式
单例模式的核心就是保证全局只有一个对象可以访问。因为 JS 是门无类的语言,所以别的语言实现单例的方式并不能套入 JS 中,我们只需要用一个变量确保实例只创建一次就行,以下是如何实现单例模式的例子

class Singleton {
  constructor() {}
}

Singleton.getInstance = (function() {
  let instance
  return function() {
    if (!instance) {
      instance = new Singleton()
    }
    return instance
  }
})()

let s1 = Singleton.getInstance()
let s2 = Singleton.getInstance()
console.log(s1 === s2) // true

发布-订阅模式

发布-订阅模式也叫做观察者模式。通过一对一或者一对多的依赖关系,当对象发生改变时,订阅方都会收到通知。在实际代码中其实发布-订阅模式也很常见
在 Vue 中,如何实现响应式也是使用了该模式。对于需要实现响应式的对象来说,在 get 的时候会进行依赖收集,当改变了对象的属性时,就会触发派发更新。

代理模式

代理是为了控制对象的访问,不让外部直接访问到对象。再现实生活中,也有很多代理的场景。比如国外商品代购。
实际代码:让父节点作为代理去拿到真实点击的子节点。

适配器模式

适配器用来解决两个接口不兼容的情况,不需要改变已有的接口,通过包装一层的方式实现两个接口的正常协作。
在 Vue 中,我们其实经常使用到适配器模式。比如父组件传递给子组件一个时间戳属性,组件内部需要将时间戳转为正常的日期显示,一般会使用 computed 来做转换这件事情,这个过程就使用到了适配器模式。

class Plug {
  getName() {
    return '港版插头'
  }
}

class Target {
  constructor() {
    this.plug = new Plug()
  }
  getName() {
    return this.plug.getName() + ' 适配器转二脚插头'
  }
}

let target = new Target()
target.getName() // 港版插头 适配器转二脚插头

装饰模式

装饰模式不需要改变已有的接口,作用是给对象添加功能。就像我们经常需要给手机戴个保护套防摔一样,不改变手机自身,给手机添加了保护套提供防摔功能。

以下是如何实现装饰模式的例子,使用了 ES7 中的装饰器语法

function readonly(target, key, descriptor) {
  descriptor.writable = false
  return descriptor
}

class Test {
  @readonly
  name = 'yck'
}

let t = new Test()

t.yck = '111' // 不可修改

Hybrid

一套好的 Hybrid 架构方案能让 App 既能拥有 极致的体验和性能,同时也能拥有 Web技术 灵活的开发模式、跨平台能力以及热更新机制。

1.混合方案简析

Hybrid App,俗称混合应用,即混合了Native技术与web技术进行开发的移动应用。现在比较流行的混合方案有三种,主要是在UI渲染机制上的不同:

Webview UI:

  • 通过 JSBridge 完成 H5 与 Native 的双向通讯,并 基于 Webview 进行页面的渲染;
  • 优势: 简单易用,架构门槛/成本较低,适用性与灵活性极强;
  • 劣势: Webview 性能局限,在复杂页面中,表现远不如原生页面;

Native UI:

  • 通过 JSBridge 赋予 H5 原生能力,并进一步将 JS 生成的虚拟节点树(Virtual DOM)传递至 Native 层,并使用 原生系统渲染。
  • 优势: 用户体验基本接近原生,且能发挥 Web技术 开发灵活与易更新的特性;
  • 劣势: 上手/改造门槛较高,最好需要掌握一定程度的客户端技术。相比于常规 Web开发,需要更高的开发调试、问题排查成本;

小程序

  • 通过更加定制化的 JSBridge,赋予了 Web 更大的权限,并使用双 WebView 双线程的模式隔离了 JS逻辑 与 UI渲染,形成了特殊的开发模式,加强了 H5 与 Native 混合程度,属于第一种方案的优化版本;

  • 优势: 用户体验好于常规 Webview 方案,且通常依托的平台也能提供更为友好的开发调试体验以及功能;

  • 劣势: 需要依托于特定的平台的规范限定

其他

输出0-5

方案一:(闭包和自执行函数的理解)

for(var i=0;i<6;i++){
    (function(j){
       setTimeout(()=>{
        console.log(j)
        },1000)
    })(i)
}

方案二:(对setTimeout第二个参数的了解)

for(var i=0;i<6;i++){
   setTimeout((j)=>{
    console.log(j)
    },1000,i)
}

按时间每1秒输出0-5(对异步处理的理解)

方案一:

const tasks = []
  for(var i=0;i<6;i++) {
    ((j)=>{
      tasks.push(new Promise((resolve)=>{
        setTimeout(() => {
          console.log(j)
        }, 1000*j)
        resolve()
      }))  
    })(i)
  }
  Promise.all(tasks)

方案二(最优解):

const sleep = (time)=> new Promise(resolve=>{
    setTimeout(resolve,time)
)
(async () => {  // 声明即执行的 async 函数表达式
    for (var i = 0; i < 6; i++) {
        if (i > 0) {
            await sleep(1000);
        }
        console.log(i);
    }
})();

简单说说怎么实现的接入企业微信的sso登录

  1. 注册应用与授权:

    • 在企业微信后台注册你的应用,并获取到Corp ID、App ID、App Secret等信息。
    • 授权:在企业微信后台为你的应用配置授权信息,包括登录授权和通讯录授权等。
  2. 前端跳转至企业微信授权页面:

    • 前端通过跳转到特定的企业微信授权页面,引导用户进行登录。
    • 页面中通常会包含一个登录按钮,用户点击后,企业微信会验证用户的身份并跳转回你的应用。
  3. 处理跳转回应用的请求:

    • 根据企业微信返回的code,你的应用前端需要发送请求到你的应用服务器。
    • 服务器接收到code后,需要使用App Secret请求企业微信服务器获取access_token。
  4. 使用access_token获取用户信息:

    • 应用服务器使用access_token请求企业微信服务器,获取用户的基本信息。
  5. 用户信息验证:

    • 服务器端验证用户信息,确保用户身份的有效性。
  6. 登录成功后的处理:

    • 服务器端生成登录令牌(如JWT),并返回给前端。
    • 前端保存登录状态,并可以进行页面跳转,展示登录后的界面。

单点登录通常需要一个集中的认证中心,当用户在应用a登录后,认证中心会记录用户的登录状态。
当用户尝试访问应用B时,认证中心直接位应用B提供用户的登录状态验证,从而实现单点登录。
而使用企业微信的sso登录,认证中心就是企业微信的服务器,前端只需要重定向到企业微信的认证地址上,带上在回调地址和appid,跳转到企业微信认证页。用户授权后,企业微信会跳转回你的回调地址,并附带code参数,从url上获取到code并把一些必要的信息传給服务端就行了。

对自己未来的发展规划是什么

  1. 对自身已经掌握的技术持续精进,并通过技术手段去回馈团队和业务(如前端架构、异常监控、性能优化、指标体系等)
  2. 在某一个技术方向上做到突出,能够沉淀出相应方法论,并建设出系统性的平台,在部门及公司内部普及应用
  3. 保持对新技术的热情,持续扩宽技术广度,对团队的技术栈持续迭代,保持团队整体技术的竞争力

具体:(下篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂 - 掘金 (juejin.cn)

参考文献

构建流程 - Webpack Guidebook (tsejx.github.io)

【前端面试指南】计算机网络和浏览器_前端面试题 - 计算机网络与浏览器相关-CSDN博客

(下篇)中高级前端大厂面试秘籍,寒冬中为您保驾护航,直通大厂 - 掘金 (juejin.cn)

阅读全文

NestJS搭建项目(二)nest+socket.io搭建简易聊天室

前端随笔 2024/3/14

socket.io

首先介绍一下socket.io吧。

socket.io是一个用于实现实时通信的JavaScript库。它可以在浏览器和服务器之间实现双向通信,支持多种传输协议,如WebSocket、LongPolling、XHR等。socket.io的主要目的是为了简化实时通信在浏览器和服务器之间的实现,使得开发者可以专注于业务逻辑的实现。

socket.io的主要特点包括:

  1. 支持多种传输协议:socket.io支持WebSocket、LongPolling、XHR等传输协议,可以根据浏览器和服务器的支持情况自动选择合适的传输协议。
  2. 自动重连:socket.io会自动检测连接断开的情况,并在适当的时候尝试重新连接。
  3. 消息压缩:socket.io支持对发送的消息进行压缩,以减少传输的数据量。
  4. 错误处理:socket.io提供了丰富的错误处理机制,可以捕获并处理客户端和服务器之间的错误。
  5. 事件机制:socket.io使用事件机制来处理消息,使得代码更加简洁和易于维护。

总之,socket.io是一款功能强大、易于使用的实时通信库,可以帮助开发者轻松实现浏览器和服务器之间的实时通信。

Nest具体实现

首先需要安装依赖

npm install @nestjs/platform-socket.io

npm install @nestjs/websockets

新建一个文件用来接受广播消息和发送消息

企业微信截图_17092795153263.png

events.gateway.ts

import {
  ConnectedSocket,
  MessageBody,
  SubscribeMessage,
  WebSocketGateway,
  WebSocketServer,
} from '@nestjs/websockets';
import { Server,Socket } from 'socket.io';
// @WebSocketGateway是一个装饰器,用于创建WebSocket网关类。WebSocket网关类是用于处理 WebSocket连接和消息的核心组件之一。
// 它充当WebSocket服务端的中间人,负责处理客户端发起的连接请求,并定义处理不同类型消息的逻辑
@WebSocketGateway({ cors: { origin: '*' } })
export class EventGateway {
  constructor() {}
  @WebSocketServer()
  server: Server;

  @SubscribeMessage('newMessage')
  handleMessage(@MessageBody() body: any, @ConnectedSocket() client: Socket) {
    console.log(body);
    const msg: any = {};
    const { roomId, name,message } =body || {} 
    msg.text = message
    msg.name = name
    msg.roomId = roomId
    this.server.to(roomId).emit('newMessage', msg)
  }
  // 离开房间
  @SubscribeMessage('leave')
  handleLeave(@MessageBody() body: any, @ConnectedSocket() client: Socket) {
    const { roomId, name } =body || {} 
     // 先广播离开消息给房间内其他人
     this.server.to(roomId).emit('leave', `用户:${name}离开了房间 ${roomId}`);
     client.leave(roomId);
  }
  // 创建房间并加入房间
  @SubscribeMessage('join')
  handleJoin(@MessageBody() body: any, @ConnectedSocket() client: Socket) {
    const { roomId, name } =body || {} 
    client.join(roomId);
    // client只能给发送给发起请求的客户端
    // client.emit('join', `用户:${name}加入了房间 ${roomId}`);  
    // 广播消息给除自己以外的所有客户端
    // client.broadcast.emit('join', `用户:${name}加入了房间 ${roomId}`);
    // 使用服务器实例来广播消息给所有客户端
    this.server.to(roomId).emit('join', `用户:${name}加入了房间 ${roomId}`);
  }

  // 获取当前房间的人数
  @SubscribeMessage('getRoomUsers')
  handleGetRoomUsers(@MessageBody() body: any, @ConnectedSocket() client: Socket) {
    const room = this.server.sockets.adapter.rooms.get(body.roomId);
    if (room) {
      this.server.to(body.roomId).emit('getRoomUsers', room.size);
    } else {
      this.server.to(body.roomId).emit('getRoomUsers', 0);
    }
  }
}

在app.module.ts中

import { EventGateway } from './events/events.gateway';

在providers中注入该服务

截图_17092802096500.png
启动项目 yarn start

前端实现逻辑

安装依赖: npm install socket.io-client

先连接上socket.io

import { io } from "socket.io-client";
const URL ='socket.io的地址'
const socket = io(URL);

socket.emit 发送消息

socket.on 监听消息

例如创建或加入房间 (nest代码中join事件,客户端这边连接上了socket.io,就可以通过对应事件实现双向通讯了)

const createOrJoinRoom = () => {
  socket.emit("join", { roomId: props.state.roomId, name: props.state.name });
}

监听加入房间的事件,看看服务端回应广播的消息

  socket.on('join', (e) => {
      console.log(e)
  });

其他的离开房间,发送消息等类似加入房间,具体可看源码

实现效果

前端样式比较简单具体实现效果如下:

1709280685432.png

体验地址

http://zymanage.yaoyaoqiekenao.com/

源码地址

https://github.com/DarknessZY/my-blog-app-nest.git

阅读全文

计算机网络阅读笔记

图书笔记 2024/2/18

前言

了解一下计算机网络相关的,主要是了解一些基础知识,也不会深入学习,所以看的是小林coding的图解网络和菜鸟教程

1. 网络层次划分

为了使不同计算机厂家生产的计算机能够相互通信,以便在更大的范围内建立计算机网络,国际标准化组织(ISO)在1978年提出了”开放系统互联参考模型”,即著名的OSI/RM七层模型(Open System Interconnection/Reference Model)。它将计算机网络体系结构的通信协议划分为七层,自下而上依次为:物理层(Physics Layer)、数据链路层(Data Link Layer)、网络层(Network Layer)、传输层(Transport Layer)、会话层(Session Layer)、表示层(Presentation Layer)、应用层(Application Layer)。其中第四层完成数据传送服务,上面三层面向用户。

除了标准的OSI七层模型以外,常见的网络层次划分还有TCP/IP四层协议以及TCP/IP五层协议,它们之间的对应关系如下图所示:

image.png

2. Tcp/IP 四层网络模型?

应用层

最上层的,也是我们能直接接触到的就是应用层,我们电脑或手机使用的应用软件都是在应用层实现。那么,当两个不同设备的应用需要通信的时候,应用就把应用数据传给下一层,也就是传输层。
所以,应用层只需要专注于为用户提供应用功能,比如 HTTP、FTP、Telnet、DNS、SMTP等。
应用层是不用去关心数据是如何传输的,就类似于,我们寄快递的时候,只需要把包裹交给快递员,由他负责运输快递,我们不需要关心快递是如何被运输的。
而且应用层是工作在操作系统中的用户态,传输层及以下则工作在内核态。

传输层

应用层的数据包会传给传输层,传输层(Transport Layer)是为应用层提供网络支持的。

image.png

在传输层会有两个传输协议,分别是 TCP 和 UDP。

TCP 的全称叫传输控制协议(Transmission Control Protocol),大部分应用使用的正是 TCP 传输层协议,比如 HTTP 应用层协议。TCP 相比 UDP 多了很多特性,比如流量控制、超时重传、拥塞控制等,这些都是为了保证数据包能可靠地传输给对方。

UDP 相对来说就很简单,简单到只负责发送数据包,不保证数据包是否能抵达对方,但它实时性相对更好,传输效率也高。当然,UDP 也可以实现可靠传输,把 TCP 的特性在应用层上实现就可以,不过要实现一个商用的可靠 UDP 传输协议,也不是一件简单的事情。

应用需要传输的数据可能会非常大,如果直接传输就不好控制,因此当传输层的数据包大小超过 MSS(TCP 最大报文段长度) ,就要将数据包分块,这样即使中途有一个分块丢失或损坏了,只需要重新发送这一个分块,而不用重新发送整个数据包。在 TCP 协议中,我们把每个分块称为一个 TCP 段(TCP Segment)。

image.png

当设备作为接收方时,传输层要负责把数据包传个应用,但是一台设备上可能会用很多应用在接收或者传输数据,因此需要一个编号将应用区分开来,这个编号就是端口。
比如80端口通常是web服务器用的,22端口通常是远程登录服务器用的。而对于浏览器(客户端)中的每个标签栏都是一个独立的进程,操作系统会为这些进程分配临时的端口号。
由于传输层的报文会携带端口号,因此接收方可以识别出该报文是发送给哪个应用。

网络层

传输层可能大家刚接触的时候,会认为它负责将数据从一个设备传输到另一个设备,事实上它并不负责。

实际场景中的网络环节是错综复杂的,中间有各种各样的线路和分叉路口,如果一个设备的数据要传输给另一个设备,就需要在各种各样的路径和节点进行选择,而传输层的设计理念是简单、高效、专注,如果传输层还负责这一块功能就有点违背设计原则了。

也就是说,我们不希望传输层协议处理太多的事情,只需要服务好应用即可,让其作为应用间数据传输的媒介,帮助实现应用到应用的通信,而实际的传输功能就交给下一层,也就是网络层(Internet Layer)。

image.png

网络层最常使用的是IP协议(Internet Protocol),IP协议会将传输层的报文作为数据部分,再加上 IP 包头组装成 IP 报文,如果 IP 报文大小超过 MTU(以太网中一般为 1500 字节)就会再次进行分片,得到一个即将发送到网络的 IP 报文。

image.png

网络层负责将数据从一个设备传输到另一个设备,世界上那么多设备,又该如何找到对方呢?因此,网络层需要有区分设备的编号。

我们一般用 IP 地址给设备进行编号,对于 IPv4 协议, IP 地址共 32 位,分成了四段(比如,192.168.100.1),每段是 8 位。只有一个单纯的 IP 地址虽然做到了区分设备,但是寻址起来就特别麻烦,全世界那么多台设备,难道一个一个去匹配?这显然不科学。

因此,需要将 IP 地址分成两种意义:

  • 一个是网络号,负责标识该 IP 地址是属于哪个「子网」的;
  • 一个是主机号,负责标识同一「子网」下的不同主机;

怎么分的呢?这需要配合子网掩码才能算出 IP 地址 的网络号和主机号。
举个例子,比如10.100.122.0/24,后面的/24表示就是 255.255.255.0 子网掩码,255.255.255.0 二进制是「11111111-11111111-11111111-00000000」,大家数数一共多少个1?不用数了,是 24 个1,为了简化子网掩码的表示,用/24代替255.255.255.0。

知道了子网掩码,该怎么计算出网络地址和主机地址呢?

网络号: IP地址转二进制和子网掩码转二进制后做AND运算

主机号: 将子网掩码取反后与ip地址进行按位与运算

那么在寻址的过程中,先匹配到相同的网络号(表示要找到同一个子网),才会去找对应的主机。

除了寻址能力, IP 协议还有另一个重要的能力就是路由。

实际场景中,两台设备并不是用一条网线连接起来的,而是通过很多网关、路由器、交换机等众多网络设备连接起来的,那么就会形成很多条网络的路径,因此当数据包到达一个网络节点,就需要通过路由算法决定下一步走哪条路径。

路由器寻址工作中,就是要找到目标地址的子网,找到后进而把数据包转发给对应的网络内。

image.png
所以,IP 协议的寻址作用是告诉我们去往下一个目的地该朝哪个方向走,路由则是根据「下一个目的地」选择路径。寻址更像在导航,路由更像在操作方向盘

网络接口层

生成了 IP 头部之后,接下来要交给网络接口层(Link Layer)在 IP 头部的前面加上 MAC 头部,并封装成数据帧(Data frame)发送到网络上。

image.png

总结

image.png

参考文章

图解网络介绍 | 小林coding (xiaolincoding.com)

计算机网络基础知识总结 | 菜鸟教程 (runoob.com)

阅读全文

使用LightHouse对前端网站性能分析和优化

LightHouse

网站性能检测有很多,最近学习使用了LightHouse,LightHouse 是 Google 开发的,它提供了一个基于浏览器服务的网站性能分析工具。LightHouse 使用了深度学习技术和大规模语言模型来预测代码中的性能瓶颈,并给出详细的分析报告。

以微软的Edge为例,打开控制台,点击右边的+,就能找到LightHouse,然后对你想要进行分析的网站进行
性能分析.png

Lighthouse 主要监测指标

image.png
| 指标 | 说明 |
| ——————————– | ———————————————————— |
| 性能指标(Performance) | 页面的性能评分,包括:首次内容绘制(First Contentful Paint)、(Largest Contentful Paint)最大内容绘制、可交互时间(Time to Interactive)、速度指标(Speed Index)、(Cumulative Layout Shift)累计布局位移。 |
| 可访问性/无障碍(Accessibility) | 监测页面的可访问性与优化建议。 |
| 最佳做法(Best Practice) | 页面是否符合最佳实践。 |
| 搜索引擎优化(SEO) | 页面是否针对搜索引擎结果排名进行了优化。 |
| PWA(Progressive Web App) | 验证 PWA 的各个方面的性能情况。

性能指标

image.png
我们利用LightHouse做前端网站性能分析主要关注的就是Performance这一块,上面提到Performance页面的性能评分指标分为:**FCP (First Contentful Paint)、LCP(Largest Contentful Paint)、SI (Speed Index)、TBT(Total Blocking Time)、CLS(Cumulative Layout Shift)**,下面将具体的了解这几个东西

1.FCP

FCP(First Contentful Paint)即首次内容绘制。它统计的是从进入页面到首次有 DOM 内容绘制所用的时间。这里的 DOM 内容指的是文本、图片、非空的 canvas 或者 SVG。

FCP 和我们常说的白屏问题相关,它记录了页面首次绘制内容的时间。

下图可以看到为什么导致FCP慢,以及是什么导致FCP慢甚至点击蓝色的链接跳转相关如何解决这块的问题,能帮助我们快速定位到什么原因导致白屏的,已经响应的解决方案,红色的代表影响比较大,橙色是中等。

可以看到下面三条橙色的就是我们老生常谈的首屏优化,不过和红色一样,我们可以看到具体是哪里需要优化:

  • 缩减 JavaScript
  • 启用文本压缩
  • 减少未使用的 CSS
    image.png

2.LCP

LCP(Largest Contentful Paint)即最大内容绘制。它统计的是从页面开始加载到视窗内最大内容绘制的所需时间,这里的内容指文本、图片、视频、非空的 canvas 或者 SVG 等。

注意:FCP关注的是页面上任何内容的首次呈现时间,而LCP关注的是最大的内容元素的呈现时间。

例如:

image.png

这个图片太大了,完全呈现给用户需要1710毫秒,最大的内容渲染时间有点长了,所以我们就可以考虑优化这个块,压缩这个图片之类的。

3.SI

SI(Speed Index)即速度指数。Lighthouse 会在页面加载过程中捕获视频,并通过 speedline 计算帧与帧之间视觉变化的进度,这个指标反映了网页内容填充的速度。页面解析渲染过程中,资源的加载和主线程执行的任务会影响到速度指数的结果。

4.TBT

TBT(Total Blocking Time)是指在页面加载过程中,用户输入或交互受到阻塞的总时间。它衡量了在加载过程中由于JavaScript执行和主线程忙碌而导致用户输入延迟的总时间。 TBT 是 Web 性能指标之一,用于评估页面加载过程中的交互性能。
这个一般是由js引起,如下图:

image.png

TBT 的延迟通常由 JavaScript 的执行和主线程忙碌引起。当页面加载过程中存在大量的 JavaScript 代码执行或者主线程被长时间占用时,会导致用户输入或交互受到阻塞,从而增加 TBT。因此,优化 JavaScript 的加载和执行,以及减少主线程的繁忙程度,可以有助于减少 TBT。
根据上面提示,我们可以优化JavaScript 资源加载

资源加载的优化通常有几个思路:

  • 合理的加载顺序/策略(延迟加载/预先加载)
  • 压缩优化资源的体积
  • 代码分割 & 公共提取 & 按需加载

    5.CLS

CLS(Cumulative Layout Shift)即累计布局位移进行评估。这个指标是通过比较单个元素在帧与帧之间的位置偏移来计算

image.png
看的出来,使用LightHouse后很快可以定位到需要优化哪里

无障碍设计

Accessibility 辅助功能 : 无障碍设计,也称为网站可达性。是指所创建的网站对所有用户都可用/可访问,不管用户的生理/身体能力如何、不管用户是以何种方式访问网站。

比如:

Background and foreground colors do not have a sufficient contrast ratio. 这个意思就是某处文字背景色和文字颜色对比度不够,对于视障用户可能不好区分,展开可以看到具体是哪块元素。

最佳做法

est Practices 最佳做法 : 实践性检测,如网页安全性,如是否开启 HTTPS、网页存在的漏洞等

SEO

SEO(Search Engine Optimization):搜索引擎优化检测,如网页 title 是否符合搜索引擎的优化标准等

总结

总之使用LightHouse对前端网站性能分析后,能帮助我们开发者定位到哪里需要优化,哪个图片太大影响正常资源加载等等,对应开发者来说是个不错的工具,当然也不一定非要按照上面提示的优化,开发者也肯定有自己的考量。我自己的网站性能指标在80左右就行了,懒得继续折腾了(手动狗头)。如果是公司的项目,能跑就行,哈哈!

八股总结

谈到性能优化,跑不掉的就是八股文,也总结一下。

  1. 使用lazy loading:通过将大量数据分成几块,用户只有点击触发某个特定的元素时,才会加载这部分数据。这样可以减少初始化数据时的性能开销。
  2. 使用虚拟scrolllist:通过将大量数据分成小块,并将其渲染为虚拟的滚动列表。这样可以减少渲染开销,提高用户体验。
  3. 使用图片懒加载:通过使用JavaScript或者在HTML属性中设置onload事件,当图片加载完成时再显示,这样可以减少初始化时的性能开销。
  4. 使用预加载:通过在页面加载时预先加载一些数据和资源,当用户访问时可以更快地渲染。
  5. 使用现代前端框架:使用现代前端框架可以简化代码,提高性能,例如React、Vue、Angular等。
  6. 优化列表和数组操作:使用现代前端框架提供的列表和数组操作方法,可以减少代码量,提高性能。
  7. 使用Gzip压缩:通过在服务器端使用Gzip压缩静态资源,可以减少网络传输时的时间开销。
  8. 使用CDN:通过将静态资源部署到CDN服务器上,可以提高用户体验,减少网络传输时的时间开销。
  9. 优化DOM操作:在使用JavaScript操作DOM时,尽量减少操作的次数和复杂度,以提高性能。
  10. 使用事件代理:将事件监听器添加到父元素上,可以减少事件监听器的数量,提高性能。

个人博客

耀耀切克闹 (yaoyaoqiekenao.com)

参考文章

前端性能测试工具 Lighthouse(灯塔)使用 - 掘金 (juejin.cn)

使用 Lighthouse 分析前端性能 - 知乎 (zhihu.com)

阅读全文

WebSocket 从入门到入土

前端随笔 2023/12/8

前言

因新部门需求有一个后台管理需要一个右上角的实时的消息提醒功能,第一时间想到的就是使用WebSocket建立实时通信了,之前没整过,于是只能学习了。和原部门相比现在太忙了,快乐的日子一去不复返了。经典的加量不加薪啊!!!

一.WebSocket 基本概念

1.WebSocket是什么?

WebSocket 是基于 TCP 的一种新的应用层网络协议。它提供了一个全双工的通道,允许服务器和客户端之间实时双向通信。因此,在 WebSocket 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输,客户端和服务器之间的数据交换变得更加简单。
WebSocket

2.与 HTTP 协议的区别

与 HTTP 协议相比,WebSocket 具有以下优点:

  1. 更高的实时性能:WebSocket 允许服务器和客户端之间实时双向通信,从而提高了实时通信场景中的性能。
  2. 更少的网络开销:HTTP 请求和响应之间需要额外的数据传输,而 WebSocket 通过在同一个连接上双向通信,减少了网络开销。
  3. 更灵活的通信方式:HTTP 请求和响应通常是一一对应的,而 WebSocket 允许服务器和客户端之间以多种方式进行通信,例如消息 Push、事件推送等。
  4. 更简洁的 API:WebSocket 提供了简洁的 API,使得客户端开发人员可以更轻松地进行实时通信。

当然肯定有缺点的:

  1. 不支持无连接: WebSocket 是一种持久化的协议,这意味着连接不会在一次请求之后立即断开。这是有利的,因为它消除了建立连接的开销,但是也可能导致一些资源泄漏的问题。
  2. 不支持广泛: WebSocket 是 HTML5 中的一种标准协议,虽然现代浏览器都支持,但是一些旧的浏览器可能不支持 WebSocket。
  3. 需要特殊的服务器支持: WebSocket 需要服务端支持,只有特定的服务器才能够实现 WebSocket 协议。这可能会增加系统的复杂性和部署的难度。
  4. 数据流不兼容: WebSocket 的数据流格式与 HTTP 不同,这意味着在不同的网络环境下,WebSocket 的表现可能会有所不同。

3.WebSocket工作原理

1. 握手阶段

WebSocket在建立连接时需要进行握手阶段。握手阶段包括以下几个步骤:

  • 客户端向服务端发送请求,请求建立WebSocket连接。请求中包含一个Sec-WebSocket-Key参数,用于生成WebSocket的随机密钥。
  • 服务端接收到请求后,生成一个随机密钥,并使用随机密钥生成一个新的Sec-WebSocket-Accept参数。
  • 客户端接收到服务端发送的新的Sec-WebSocket-Accept参数后,使用原来的随机密钥和新的Sec-WebSocket-Accept参数共同生成一个新的Sec-WebSocket-Key参数,用于加密数据传输。
  • 客户端将新的Sec-WebSocket-Key参数发送给服务端,服务端接收到后,使用该参数加密数据传输。

2. 数据传输阶段

建立连接后,客户端和服务端就可以通过WebSocket进行实时双向通信。数据传输阶段包括以下几个步骤:

  • 客户端向服务端发送数据,服务端收到数据后将其转发给其他客户端。
  • 服务端向客户端发送数据,客户端收到数据后进行处理。

双方如何进行相互传输数据的
具体的数据格式是怎么样的呢?WebSocket 的每条消息可能会被切分成多个数据帧(最小单位)。发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息。

发送方 -> 接收方:ping。

接收方 -> 发送方:pong。

ping 、pong 的操作,对应的是 WebSocket 的两个控制帧

3. 关闭阶段

当不再需要WebSocket连接时,需要进行关闭阶段。关闭阶段包括以下几个步骤:

  • 客户端向服务端发送关闭请求,请求中包含一个WebSocket的随机密钥。
  • 服务端接收到关闭请求后,向客户端发送关闭响应,关闭响应中包含服务端生成的随机密钥。
  • 客户端收到关闭响应后,关闭WebSocket连接。

总的来说,WebSocket通过握手阶段、数据传输阶段和关闭阶段实现了服务器和客户端之间的实时双向通信。

二.WebSocket 数据帧结构和控制帧结构。

1. 数据帧结构

WebSocket 数据帧主要包括两个部分:帧头和有效载荷。以下是 WebSocket 数据帧结构的简要介绍:

  • 帧头:帧头包括四个部分:fin、rsv1、rsv2、rsv3、opcode、masked 和 payload_length。其中,fin 表示数据帧的结束标志,rsv1、rsv2、rsv3 表示保留字段,opcode 表示数据帧的类型,masked 表示是否进行掩码处理,payload_length 表示有效载荷的长度。
  • 有效载荷:有效载荷是数据帧中实际的数据部分,它由客户端和服务端进行数据传输。

2. 控制帧结构

除了数据帧之外,WebSocket 协议还包括一些控制帧,主要包括 Ping、Pong 和 Close 帧。以下是 WebSocket 控制帧结构的简要介绍:

  • Ping 帧:Ping 帧用于测试客户端和服务端之间的连接状态,客户端向服务端发送 Ping 帧,服务端收到后需要向客户端发送 Pong 帧进行响应。
  • Pong 帧:Pong 帧用于响应客户端的 Ping 帧,它用于测试客户端和服务端之间的连接状态。
  • Close 帧:Close 帧用于关闭客户端和服务端之间的连接,它包括四个部分:fin、rsv1、rsv2、rsv3、opcode、masked 和 payload_length。其中,opcode 的值为 8,表示 Close 帧。

三. JavaScript 中 WebSocket 对象的属性和方法,以及如何创建和连接 WebSocket。

WebSocket 对象的属性和方法:

  1. WebSocket 对象:WebSocket 对象表示一个新的 WebSocket 连接。

  2. WebSocket.onopen 事件处理程序:当 WebSocket 连接打开时触发。

  3. WebSocket.onmessage 事件处理程序:当接收到来自 WebSocket 的消息时触发。

  4. WebSocket.onerror 事件处理程序:当 WebSocket 发生错误时触发。

  5. WebSocket.onclose 事件处理程序:当 WebSocket 连接关闭时触发。

  6. WebSocket.send 方法:向 WebSocket 发送数据。

  7. WebSocket.close 方法:关闭 WebSocket 连接。

创建和连接 WebSocket:

  1. 创建 WebSocket 对象:
var socket = new WebSocket('ws://example.com');

其中,ws://example.com 是 WebSocket 的 URL,表示要连接的服务器。

  1. 连接 WebSocket:

使用 WebSocket.onopen 事件处理程序检查 WebSocket 是否成功连接。

socket.onopen = function() {
    console.log('WebSocket connected');
};
  1. 接收来自 WebSocket 的消息:

使用 WebSocket.onmessage 事件处理程序接收来自 WebSocket 的消息。

socket.onmessage = function(event) {
    console.log('WebSocket message:', event.data);
};
  1. 向 WebSocket 发送消息:

使用 WebSocket.send 方法向 WebSocket 发送消息。

socket.send('Hello, WebSocket!');
  1. 关闭 WebSocket:

当需要关闭 WebSocket 时,使用 WebSocket.close 方法。

socket.close();

注意:在 WebSocket 连接成功打开和关闭时,会分别触发 WebSocket.onopen 和 WebSocket.onclose 事件。在接收到来自 WebSocket 的消息时,会触发 WebSocket.onmessage 事件。当 WebSocket 发生错误时,会触发 WebSocket.onerror 事件。

四.webSocket简单示例

以下是一个简单的 WebSocket 编程示例,通过 WebSocket 向服务器发送数据,并接收服务器返回的数据:

  1. 首先,创建一个 HTML 文件,添加一个按钮和一个用于显示消息的文本框:
<!DOCTYPE html>
<html>
<head>
   <meta charset="UTF-8">
   <title>WebSocket 示例</title>
</head>
<body>
   <button id="sendBtn">发送消息</button>
   <textarea id="messageBox" readonly></textarea>
   <script src="main.js"></script>
</body>
</html>
  1. 接下来,创建一个 JavaScript 文件(例如 main.js),并在其中编写以下代码:
// 获取按钮和文本框元素
const sendBtn = document.getElementById('sendBtn');
const messageBox = document.getElementById('messageBox');

// 创建 WebSocket 对象
const socket = new WebSocket('ws://echo.websocket.org'); // 使用一个 WebSocket 服务器进行测试

// 设置 WebSocket 连接打开时的回调函数
socket.onopen = function() {
   console.log('WebSocket 连接已打开');
};

// 设置 WebSocket 接收到消息时的回调函数
socket.onmessage = function(event) {
   console.log('WebSocket 接收到消息:', event.data);
   messageBox.value += event.data + '\n';
};

// 设置 WebSocket 发生错误时的回调函数
socket.onerror = function() {
   console.log('WebSocket 发生错误');
};

// 设置 WebSocket 连接关闭时的回调函数
socket.onclose = function() {
   console.log('WebSocket 连接已关闭');
};

// 点击按钮时发送消息
sendBtn.onclick = function() {
   const message = 'Hello, WebSocket!';
   socket.send(message);
   messageBox.value += '发送消息: ' + message + '\n';
};

五.webSocket应用场景

  1. 实时通信:WebSocket 非常适合实时通信场景,例如聊天室、在线游戏、实时数据传输等。通过 WebSocket,客户端和服务器之间可以实时通信,无需依赖轮询,从而提高通信效率和减少网络延迟。

  2. 监控数据传输:WebSocket 可以在监控系统中实现实时数据传输,例如通过 WebSocket,客户端可以实时接收和处理监控数据,而无需等待轮询数据。

  3. 自动化控制:WebSocket 可以在自动化系统中实现远程控制,例如通过 WebSocket,客户端可以远程控制设备或系统,而无需直接操作。

  4. 数据分析:WebSocket 可以在数据分析场景中实现实时数据传输和处理,例如通过 WebSocket,客户端可以实时接收和处理数据,而无需等待数据存储和分析。

  5. 人工智能:WebSocket 可以在人工智能场景中实现实时数据传输和处理,例如通过 WebSocket,客户端可以实时接收和处理数据,而无需等待数据处理和分析。

六.WebSocket 错误处理

WebSocket 的错误处理

  1. WebSocket is not supported:当浏览器不支持 WebSocket 时,会出现此错误。解决方法是在浏览器兼容性列表中检查是否支持 WebSocket。

  2. WebSocket connection closed:当 WebSocket 连接被关闭时,会出现此错误。解决方法是在 WebSocket.onclose 事件处理程序中进行错误处理。

  3. WebSocket error:当 WebSocket 发生错误时,会出现此错误。解决方法是在 WebSocket.onerror 事件处理程序中进行错误处理。

  4. WebSocket timeout:当 WebSocket 连接超时时,会出现此错误。解决方法是在 WebSocket.ontimeout 事件处理程序中进行错误处理。

  5. WebSocket handshake error:当 WebSocket 握手失败时,会出现此错误。解决方法是在 WebSocket.onerror 事件处理程序中进行错误处理。

  6. WebSocket closed by server:当 WebSocket 连接被服务器关闭时,会出现此错误。解决方法是在 WebSocket.onclose 事件处理程序中进行错误处理。

  7. WebSocket closed by protocol:当 WebSocket 连接被协议错误关闭时,会出现此错误。解决方法是在 WebSocket.onclose 事件处理程序中进行错误处理。

  8. WebSocket closed by network:当 WebSocket 连接被网络错误关闭时,会出现此错误。解决方法是在 WebSocket.onclose 事件处理程序中进行错误处理。

  9. WebSocket closed by server:当 WebSocket 连接被服务器错误关闭时,会出现此错误。解决方法是在 WebSocket.onclose 事件处理程序中进行错误处理。

通过为 WebSocket 对象的 onclose、onerror 和 ontimeout 事件添加处理程序,可以及时捕获和处理 WebSocket 错误,从而确保程序的稳定性和可靠性。

七.利用单例模式创建完整的wesocket连接

class webSocketClass {
    constructor(thatVue) {
      this.lockReconnect = false;
      this.localUrl = process.env.NODE_ENV === 'production' ? 你的websocket生产地址' : '测试地址';
      this.globalCallback = null;
      this.userClose = false;
      this.createWebSocket();
      this.webSocketState = false
      this.thatVue = thatVue
    }
  
    createWebSocket() {
      let that = this;
      // console.log('开始创建websocket新的实例', new Date().toLocaleString())
      if( typeof(WebSocket) != "function" ) {
        alert("您的浏览器不支持Websocket通信协议,请更换浏览器为Chrome或者Firefox再次使用!")
      }
      try {
        that.ws = new WebSocket(that.localUrl);
        that.initEventHandle();
        that.startHeartBeat()
      } catch (e) {
        that.reconnect();
      }
    }

    //初始化
    initEventHandle() {
      let that = this;
      // //连接成功建立后响应
      that.ws.onopen = function() {
        console.log("连接成功");
      }; 
      //连接关闭后响应
      that.ws.onclose = function() {
        // console.log('websocket连接断开', new Date().toLocaleString())
        if (!that.userClose) {
          that.reconnect(); //重连
        }
      };
      that.ws.onerror = function() {
        // console.log('websocket连接发生错误', new Date().toLocaleString())
        if (!that.userClose) {
          that.reconnect(); //重连
        }
      };
      that.ws.onmessage = function(event) {
        that.getWebSocketMsg(that.globalCallback);
        // console.log('socket server return '+ event.data);
      };
    }
    startHeartBeat () {
      // console.log('心跳开始建立', new Date().toLocaleString())
      setTimeout(() => {
          let params = {
            request: 'ping',
          }
          this.webSocketSendMsg(JSON.stringify(params))
          this.waitingServer()
      }, 30000)
    }
    //延时等待服务端响应,通过webSocketState判断是否连线成功
    waitingServer () {
      this.webSocketState = false//在线状态
      setTimeout(() => {
          if(this.webSocketState) {
              this.startHeartBeat()
              return
          }
          // console.log('心跳无响应,已断线', new Date().toLocaleString())
          try {
            this.closeSocket()
          } catch(e) {
            console.log('连接已关闭,无需关闭', new Date().toLocaleString())
          }
          this.reconnect()
          //重连操作
      }, 5000)
    }
    reconnect() {
      let that = this;
      if (that.lockReconnect) return;
      that.lockReconnect = true; //没连接上会一直重连,设置延迟避免请求过多
      setTimeout(function() {
        that.createWebSocket();
        that.thatVue.openSuccess(that) //重连之后做一些事情
        that.thatVue.getSocketMsg(that)
        that.lockReconnect = false;
      }, 15000);
    }
  
    webSocketSendMsg(msg) {
      this.ws.send(msg);
    }
  
    getWebSocketMsg(callback) {
      this.ws.onmessage = ev => {
        callback && callback(ev);
      };
    }
    onopenSuccess(callback) {
      this.ws.onopen = () => {
        // console.log("连接成功", new Date().toLocaleString())
        callback && callback()
      }
    }
    closeSocket() {
      let that = this;
      if (that.ws) {
        that.userClose = true;
        that.ws.close();
      }
    }
  }
  export default webSocketClass;
阅读全文

nginx里各配置含义解析

前端随笔 2023/11/10

前言

因封装了个图片上传的组件,需要一个图片上传的接口,用nest.js把接口整完后,把项目部署到服务器后出现了点小问题,发现是nginx配置错了,于是学习一下nginx,巩固一下。

nginx是什么?

Nginx是一个开源的Web服务器和反向代理服务器,它最初由Igor Sikorsky开发,现在由OpenResty项目维护。Nginx的主要用途是处理HTTP请求和HTTPS请求,以及进行URL转发、负载均衡、反向代理等。它具有高性能、高可用性和灵活性,被广泛应用于Web服务器、CDN、反向代理、API网关等场景中。

nginx配置文件概述

可以打开你项目nginx目录下的文件

Nginx的配置文件按以下结构组织:

配置块 功能描述
全局块 与Nginx运行相关的全局设置
events块 与网络连接有关的设置
http块 代理、缓存、日志、虚拟主机等的配置
server块 虚拟主机的参数设置(一个http块可包含多个server块)
location块 定义请求路由及页面处理方式
当你使用 Nginx 时,配置文件按以下结构组织:

image.png

下面是一个示例 Nginx 配置文件,演示了这些块的嵌套关系和用法:

# 全局块
user www-data;
worker_processes auto;

# events 块
events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

# http 块
http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    # server 块
    server {
        listen 80;
        server_name example.com;

        # location 块
        location / {
            root /path/to/website;
            index index.html;
        }
        
        location /api {
            proxy_pass http://backend_server;
        }
    }
}

1. 全局块(global block):

全局块包含与Nginx运行相关的全局设置,如工作目录、错误日志等。以下是一些常见的配置项:

  • worker_processes:设置工作进程的数量,可根据服务器性能进行调整。
  • worker_connections:设置每个工作进程的最大连接数,可根据服务器性能进行调整。
  • user:设置Nginx服务的用户和组。
  • pid:设置Nginx服务的进程ID文件路径。
  • error_log:设置错误日志文件路径。
  • access_log:设置访问日志文件路径。
  • worker_aio_requests:设置异步请求的最大并发数。
  • events { ... }:定义事件处理相关的配置,如工作线程数、事件处理方法等。

2. events块:

events块包含与网络连接有关的设置,如工作线程数、事件处理方法等。以下是一些常见的配置项:

  • worker_connections:设置每个工作进程的最大连接数,可根据服务器性能进行调整。
  • use:指定事件处理模块,如epoll、kqueue等。
  • multi_accept:设置是否允许多个连接同时接受处理。
  • accept_mutex:设置是否启用互斥锁,防止同时处理多个连接。
  • accept_mutex_delay:设置互斥锁延迟时间,防止过度锁定。
  • worker_aio_requests:设置异步请求的最大并发数。

3. http块:

http块包含代理、缓存、日志、虚拟主机等配置。以下是一些常见的配置项:

  • server { ... }:定义一个虚拟主机,如监听端口、server_name、location等。
  • listen:设置监听的IP地址和端口。
  • server_name:设置虚拟主机的域名或IP地址。
  • root:设置静态文件的根目录。
  • index:设置默认主页。
  • charset:设置字符集。
  • proxy_pass:设置代理后端服务器地址。
  • location:定义请求路由及页面处理方式,如URL路径、处理方式(如静态文件、动态内容等)。
  • alias:设置别名,用于访问静态文件。
  • expires:设置静态文件过期时间。
  • log_format:设置日志格式。
  • access_log:设置访问日志文件路径。
  • error_log:设置错误日志文件路径。

4. server块:

server块包含虚拟主机的参数设置,如监听端口、server_name、location等。以下是一些常见的配置项:

  • listen:设置监听的IP地址和端口。
  • server_name:设置虚拟主机的域名或IP地址。
  • root:设置静态文件的根目录。
  • index:设置默认主页。
  • charset:设置字符集。
  • proxy_pass:设置代理后端服务器地址。
  • location:定义请求路由及页面处理方式,如URL路径、处理方式(如静态文件、动态内容等)。
  • alias:设置别名,用于访问静态文件。
  • expires:设置静态文件过期时间。
  • log_format:设置日志格式。
  • access_log:设置访问日志文件路径。
  • error_log:设置错误日志文件路径。

5.location块:

location块定义请求路由及页面处理方式,如URL路径、处理方式(如静态文件、动态内容等)。以下是一些常见的配置项:

  • root:设置静态文件的根目录。
  • alias:设置别名,用于访问静态文件。
  • expires:设置静态文件过期时间。
  • try_files:设置查找静态文件的顺序。
  • default_type:设置默认文件类型。
  • content_by_lua_block:使用Lua脚本处理动态内容。
  • content_by_lua_file:使用Lua文件处理动态内容。

nginx的主要作用

  1. 静态文件服务:Nginx 可以用作静态文件服务器,用于提供静态文件(如 HTML、CSS、JavaScript、图像文件等)的访问。在配置文件的 server 块中,可以使用 root 指令指定静态文件所在的目录。
  2. 反向代理:Nginx 可以作为反向代理服务器,将客户端的请求转发给后端服务器进行处理,并将响应再返回给客户端。在配置文件的 server 块中,可以使用 proxy_pass 指令指定后端服务器的地址。
  3. 负载均衡:Nginx 可以实现负载均衡,将客户端请求分发到多个后端服务器上,实现请求的平衡分配。在配置文件的 server 块中,可以使用 upstream 指令定义后端服务器的集群,并在 location 块中使用 proxy_pass 指令将请求转发给后端服务器集群。
  4. HTTPS 和 SSL/TLS 加密:Nginx 可以配置和管理 HTTPS 服务,并支持 SSL/TLS 协议进行通信加密。在配置文件的 server 块中,可以使用 listen 和 ssl_certificate 等指令配置 SSL/TLS 证书和加密相关的设置。
  5. 虚拟主机:Nginx 支持虚拟主机,可以在一台服务器上配置多个独立的域名或网站。在配置文件的 http 块中,可以定义多个 server 块,每个 server 块对应一个虚拟主机,并根据需要配置不同的域名、监听端口、SSL 设置等。
  6. 日志记录和统计:Nginx 可以记录并统计服务器的访问日志,包括客户端IP、请求时间、请求方法、请求路径等信息。在配置文件的 server 块中,可以使用 access_log 指令指定访问日志的文件路径和格式。

个人博客

耀耀切克闹 (yaoyaoqiekenao.com)

阅读全文

关于我调部门感觉又重新面试一次这件事,做出知识总结

面试 2023/10/20

前言

这篇文章的起因是,当时上周部门调整,要调动到其他部门,最开始我以为就走个流程意思意思,一点准备都没有。没想到,去其他部门还经过了3面,感觉挺正式的,在这期间问的问题有些令我印象深刻,发现了许多不足吧,我是去年毕业的,工作了1年多了,本来以为一些基础知识掌握的差不多了,路还远着,还得学啊!本来那天我还准备一下班就回去玩战地2042,免费周啊!啪的一下兴趣全无,总结一下知识吧,指不定什么时候用上(手动狗头)

节流

节流是指一定时间内只触发一次函数调用,如果在指定时间内多次触发,执行第一次,其他的触发将会被忽略,直到过了设定的时间间隔才触发。

function throttle (fn,delay) {
    let timer;
    retrun function (...args) {
        if(!timer) {
            fn(this,args)
             timer = settimeout(()=>{
                timer=null
            },delay)
        }
    }
}

防抖

防抖是在函数调用后,在指定时间间隔后才触发一次。如果在这个时间间隔内再次触发函数,将重新计时,直到过了设定的时间间隔才会触发最后一次函数调用。

function debounce (fn,delay) {
    let timer;
    retrun function (...args) {
        if(timer) {
          clearTimetout(timer)
        }
         timer = settimeout(()=>{
             fn(this,args)
              timer=null
         },delay) 
    }
}

数据扁平化

数组

function flatter(arr) {
    let result = []
    for(let i =0;i<arr.length;i++) {
        if(Array.isArray(arr[i]) {
            result = result.concat(flatter(arr[i]))
        } esle {
            result.push(arr[i])
        }
    }
    return result
}

去重

const arr1 = [...new Set(arr)]
const arr1 = arr.map((item,index)=>{
    return arr.indexof(item)==index
})

查找字符串中出现最多的字符

当时手写了一半,str.split(item).length应该还要-1才是当前字符出现的次数

  const str = ref<string>('sdfgsgdd');
  const fn = (str: string) => {
    const arr = Array.from(str);
    let maxCount = 0;
    let mostFrequentChar = '';
    const Nsome = [...new Set(arr)];
    Nsome.forEach((item) => {
      const count = str.split(item).length - 1;
      if (count > maxCount) {
        maxCount = count;
        mostFrequentChar = item;
      }
    });
    console.log('出现最多的次数,字符', maxCount, mostFrequentChar);
  };

闭包及其应用场景

我的回答是:
函数里面嵌套函数,并且内部函数引用了外部函数的变量,就是函数能访问其作用域外的变量

应用场景:
我的回答其中之一是:vueX中状态共享是使用了闭包,节流,防抖
但在 Vuex 中,闭包主要用于封装和共享状态,而不是用于访问和操作外部函数的变量。它使用了闭包的概念,但不是严格意义上的闭包。

1.模块化开发 2.回调函数 3.延迟执行(节流,防抖)

原型&原型链及其应用场景

  1. 原型(Prototype):
  • 每个 JavaScript 对象都有一个原型(prototype),它是一个对象。
  • 对象的原型用于共享属性和方法,当我们访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端。
  • 原型可以通过 proto 属性访问,也可以通过 Object.getPrototypeOf() 方法获取。
  1. 原型链(Prototype Chain):
  • 原型链是由对象的原型组成的链式结构,它用于实现对象之间的继承。
  • 当我们访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端。
  • 原型链的顶端是 Object.prototype,它是所有对象的原型。

应用场景:

  • 继承:通过原型链,我们可以实现对象之间的继承,一个对象可以继承另一个对象的属性和方法。这样可以避免重复定义和维护相似的代码,提高代码的重用性和可维护性。
  • 共享属性和方法:通过原型链,我们可以将属性和方法定义在原型上,从而实现对象之间的共享。这样可以节省内存空间,避免重复创建相同的属性和方法。
  • 扩展原生对象:通过修改原型链,我们可以扩展 JavaScript 的原生对象,为其添加新的方法和属性。这样可以为原生对象添加自定义的功能,满足特定的需求。

在没有class之前,js是怎么做面向对象的

没答出来,只知道js可以通过class实现面向对象,然后又被问在没有class之前,js是怎么做面向对象的。这也是原型链的应用场景之一,可能是前面原型链的应用场景没说这个,想给我一个提示。

在没有class关键字之前,JavaScript使用原型继承来实现面向对象编程。
javaScript 中的每个对象都有一个原型(prototype),原型是一个对象,它包含了共享的属性和方法。当我们访问一个对象的属性或方法时,如果对象本身没有该属性或方法,JavaScript 会沿着原型链向上查找,直到找到该属性或方法或者到达原型链的顶端。

通过原型链,我们可以实现对象之间的继承和共享属性和方法。下面是一个使用原型继承的示例:

// 创建一个构造函数
function Person(name, age) {
  this.name = name;
  this.age = age;
}
// 在构造函数的原型上定义方法
Person.prototype.sayHello = function() {
  console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
};
// 创建一个 Person 对象
const person1 = new Person('Alice', 25);
// 调用对象的方法
person1.sayHello(); // 输出 "Hello, my name is Alice and I am 25 years old."

node是什么,express是什么,node服务中的中间件是用来干什么的

Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行时环境,可以用于构建高性能的网络应用程序。它允许使用 JavaScript 在服务器端运行代码,而不仅仅局限于在浏览器中运行。

Express 是一个基于 Node.js 的 Web 应用程序框架,它提供了一组简洁而灵活的功能,用于构建 Web 应用程序和 API。Express 提供了路由、中间件、模板引擎等功能,使得构建 Web 应用程序变得更加简单和高效。

中间件的作用是增强和扩展 Node.js 服务的功能,使得处理请求和响应的过程更加灵活和可定制。通过使用中间件,可以将常见的功能模块化,提高代码的可维护性和可重用性。

Express 提供了一些内置的中间件,同时也支持自定义中间件。您可以使用内置的中间件,如 express.json()、express.urlencoded() 来处理请求体的解析,或者编写自己的中间件来满足特定的需求。

你h5怎么处理兼容性

因为是vite+v3项目,vite官方有推荐的插件库,在插件库中有一个关于浏览器兼容支持的插件:@vitejs/plugin-legacy

插件@vitejs/plugin-legacy的作用是为打包后的文件提供传统浏览器兼容性支持

  1. 首先安装插件:npm i @vitejs/plugin-legacy -D
  2. 然后在vite.config.js中配置
import legacyPlugin from '@vitejs/plugin-legacy'
export default defineConfig( {
    plugins: [
        legacyPlugin({
            targets:['chrome 52'],  // 需要兼容的目标列表,可以设置多个
            additionalLegacyPolyfills:['regenerator-runtime/runtime'] // 面向IE11时需要此插件
        })
    ]
})

rem,px,em这些有什么区别

  1. px(像素):px 是绝对单位,表示屏幕上的一个物理像素点。它是最常用的单位,具有固定的大小,不会根据其他因素而改变。例如,font-size: 16px; 表示字体大小为 16 像素。
  2. rem(根元素字体大小的倍数):rem 是相对单位,相对于根元素(即 元素)的字体大小。如果根元素的字体大小为 16 像素,那么 1rem 就等于 16 像素。如果根元素的字体大小为 20 像素,那么 1rem 就等于 20 像素。通过设置根元素的字体大小,可以方便地调整整个页面的大小。例如,font-size: 1.5rem; 表示字体大小为根元素字体大小的 1.5 倍。
  3. em(相对于父元素字体大小的倍数):em 也是相对单位,相对于父元素的字体大小。如果父元素的字体大小为 16 像素,那么 1em 就等于 16 像素。如果父元素的字体大小为 20 像素,那么 1em 就等于 20 像素。通过设置父元素的字体大小,可以影响其子元素的大小。例如,font-size: 1.2em; 表示字体大小为父元素字体大小的 1.2 倍。

总结来说,px 是绝对单位,不会随其他因素改变;rem 是相对于根元素字体大小的倍数,可以方便地调整整个页面的大小;em 是相对于父元素字体大小的倍数,可以影响子元素的大小。

在实际使用中,可以根据需求选择合适的单位。对于响应式设计,使用 rem 可以方便地调整整个页面的大小;对于局部样式,可以使用 px 或 em 来控制具体的大小。

你工作中遇到了什么坑或者解决什么让自己印象深刻的问题

  • element-plus的el-table表格的二次封装(可以使用tsx)
  • el-table表格的动态合并
  • h5 ios时调起键盘会把整个布局往上推
  • h5调用封装的app JSbrige完成返回
  • 登录的拼图验证
  • h5嵌套在微信小程序中时,由我们h5跳到三方提供的安全验证h5页面,返回时,本地存储的东西没了
  • 利用git hooks+husky+eslint完成前端代码规范和提交规范
  • 银行卡拖拽排序,把排完的顺序返回服务端

上面这些都是我解决了,也不仅仅只有这些,回头想了了下明明自己有很多可以说的,在当时就说了2,3个,然后负责人问我还有吗时,我卡壳了,居然不知道还要说什么。后面我感觉也是根据这个展开来问的

V2混入和V3的hooks,为什么V3要改成hooks的方式

感觉应该是问hooks的好处吧?反正我是答的不太对的,以下是总结:

Vue 3 引入了 Composition API(包括 setup 函数和 hooks),这是一个新的方式来组织和复用代码,与 Vue 2 的混入(mixins)有所不同。
混入在 Vue 2 中被广泛使用,它们允许你在多个组件之间共享行为。然而,混入有一些问题:

  1. 命名冲突:如果混入和组件有相同的方法或数据属性,可能会导致冲突。
  2. 来源不明:当一个组件使用了多个混入时,可能很难确定一个特定的方法或数据属性来自哪个混入。
  3. 复杂性:混入可以包含生命周期钩子、方法、数据等,这可能会增加理解和维护组件的复杂性。
    相比之下,Vue 3 的 Composition API(包括 hooks)提供了一种更灵活、更可控的方式来组织和复用代码:
  4. 更好的逻辑复用和代码组织:你可以将相关的代码(如数据、方法和生命周期钩子)组织在一起,而不是强制按照 Vue 的选项(data、methods、created 等)来组织代码。
  5. 更好的类型推断:对于使用 TypeScript 的项目,Composition API 提供了更好的类型推断。
  6. 更清晰的来源:每个函数和响应式数据的来源都非常明确,因为它们都是从特定的 hook 或 setup 函数返回的。
    因此,虽然 Vue 3 仍然支持混入,但推荐使用 Composition API 来组织和复用代码。

vue3中怎么封装一个自定义指令

  • 通过app.directive()方法注册指令,该方法接受两个参数,第一个参数是指令的名称,第二个参数是一个对象,包含指令的各个生命周期的钩子函数
  • 然后我们就可以在生命周期的钩子函数中定义指令的行为,根据指令的需求,在相应的生命周期钩子函数中编写逻辑代码

什么情况下会使用自定义指令

我的回答是:想要操作dom元素时并且这种类似情况经常出现,如节流和防抖指令,就是给dom加上disabled。按钮权限指令,给当前按钮dom一个显示和隐藏

拖拽排序

拖拽排序的实现原理主要涉及一下几个步骤:

  • 1.监听拖拽事件: 浏览器提供了一系列的拖拽事件,设置draggable=”true”
    1. 开始拖拽:当用户开始拖拽一个元素时,会触发 dragstart 事件。在这个事件的处理函数中,我们可以通过 传入的dragstart(e,index) ,中的index来设置当前被拖拽元素的下标。
    1. 拖拽过程:当用户拖拽元素时,会不断触发 dragover 事件。在这个事件的处理函数中,我们需要调用 event.preventDefault 方法来阻止浏览器的默认行为,否则无法触发 拖拽 事件。
    1. 拖拽到另一个元素区域时:当用户拖拽到另一个元素时,会触发 dragenter 事件。在这个事件的处理函数中,我们可以通过 dragente(e,index)方法来获取拖拽到的元素的下标,然后根据获取的两下标来更新列表的排序。

表格动态合并

element-plus表格合并(例如前两列合并) | 耀耀切克闹 (yaoyaoqiekenao.com)

模拟new实例创建的过程

  • 1.创建了新对象并将._proto_指向构造函数.prototype
  • 2.将this指向新创建的对象
  • 3.返回新对象
function newSimulator() {
    //1.创建新对象
    const obj = new Object()
    //2.设置_proto_为构造函数prototype
    const constructor = [].shift.call(arguments)
    obj._proto_ = constructor.prototype
    //3.this指向新对象,也就是改变this的指向
    const ret = constructor.apply(obj,arguments)
    //4.返回对象或this
    return typeof ret = 'object' ? ret : obj
}

冒泡排序

const arr = [1,7,9,2,3,5]
for(let i=0;i<arr.length;i++){
    for(let j=0;j<arr.length-i-1;j++){
        let a = []
        if(arr[j]<arr[j+1]){
            a =arr[j]
            arr[j]=arr[j+1]
            arr[j+1]=a
        }
    }
}

深拷贝

1.使用 JSON 序列化和反序列化

const obj={
   arr:[1,2]
}
const clone = JSON.parse(JSON.stringify(obj))

2.使用递归完成深拷贝

这种方式通过递归地遍历原始对象,并对该对象的的属性进行逐一的深拷贝,以创建一个原对象的独立副本。

function deepCloneObject(obj) {
       if(obj ===null||typeof object !='object') {
           return obj
       }
       const clone = Array.isArray(obj)?[]:{}
       for(let key in obj) {
           if(object.prototype.hasOwnProperty.call(obj,key))
           clone[key] = deepClone(obj[key])
       }
       retrun clone
} 

函数柯里化

函数柯里化是一种将具有多个参数的函数转换为逐个应用参数的函数序列的技术。通过柯里化,我们可以将一个函数的多个参数转化为一系列嵌套的函数调用。

柯里化的优点是可以创建可复用的函数模板。通过部分应用来生成新的函数。这样可以更灵活地使用函数,并且可以方便的创建更专注于特定功能的函数。
简单的函数柯里化例子:

function add(x) {
  return function(y) {
    return x + y;
  }
}

// 使用柯里化的add函数
var add5 = add(5);
console.log(add5(3)); // 输出 8
console.log(add5(7)); // 输出 12

封装一下

function curry(fn) {
    return function curried(...args) {
        if(args.length>=fn.length) {
            return fn.apply(this,args)
        } else {
            return function(...moreArgs) {
                return curried.apply(this,args.concat(moreArgs))
            }
        }
    }
}

数组API的实现

forEach

Array.portotype.my_forEach = function(callback) {
    for(let i=0;i<this.length;i++) {
        callback(this[i],i,this)
    }
}

map

Array.portotype.my_map = function(callback) {
    let res= []
    for(let i=0;i<this.length;i++) {
     callback(this[i],i,this)&&res.push( callback(this[i],i,this))
    }
    return res
}

filter

Array.portotype.my_filter = function(callback) {
    let res= []
    for(let i=0;i<this.length;i++) {
       callback(this[i], i, this) && res.push(this[i])
    }
    return res
}

前端模块化

问:你讲讲前端模块化吧
答:模块化的开发方式可以提高代码复用率,方便进行代码的管理,通常一个文件就是一个模块,有自己的作用域,只向外暴露特定的变量和函数。

问:模块化有哪几种标准?
答:目前流行的js模块化规范有CommonJS、AMD、CMD以及Es6的模块系统

问:ES Modules 和CommonJS的一些区别
答:
1.使用语法层面,CommonJs是通过modules.exports,exports导出,require导入;ES Modules则是export导出,import导入
2.CommonJs是运行时加载模块,EsModules是在静态编译期间就确定模块的依赖
3.EsModulse在编译期间会将所有import提升到顶部,CommonJs不会提升require
4.CommonJs导出是一个值拷贝,会对加载结果进行缓存,一但内部再修改这个值,则不会同步到外部。ESModule是导出的一个引用,内部修改可以同步到外部
5. CommonJs中顶层的this指向这个模块本身,而ESModule中顶层this指向undefined
6. CommonJS加载的是整个模块,将所有的接口全部加载进来,ESModule可以单独加载其中的某个接口

vue的数据双向绑定的原理

vue的响应式原理是采用‘发布-订阅’的设计模式结合object.defineProperty()劫持各个属性的getter和setter,在数据发生变动时通过调用Deo.notity函数发布订阅给观察者watcher,让其更新响应的视图。

虚拟dom

虚拟dom是用来表现真实dom结果的javaScript对象树,是构建在浏览器真实dom上的抽象层,虚拟dom是可以直接在内存中操作的,可以通过diff算法来新旧dom的差异,将最终变化应用到真实dom上

diff算法

diff算法又称虚拟Dom的周界算法,vue的diff算法是通过深度优先、先序遍历的方式进行的,它将前后两个虚拟Dom树进行逐层比较,当找到某一层不一样的节点时,停止下降,然后比较这些节点的子节点,当所有的子节点都完成比较之后,算法会由下至上进行回溯,此过程被称为执行patch操作。在执行patch操作时,Vue对于不同类型的节点的更新方式也不同,对于元素节点可以更新他的属性和子节点;对于文本节点,只能更新它的文本内容;对于每个子节点,如果key值相同,可以进行复用或者重新排序,或者将其他的节点移动到这个位置。

vue中nextTick的理解及作用

使用场景描述:更改一个数据,导致dom元素的width发生了更改,但又要获取这个更新后的dom元素的width,可以用nextTick
vue2 中的nextTick是在下次Dom更新循环之后执行回调函数,并且是作为vue实例的方法调用的

this.$nextTick(() => { // 组件的DOM已经更新完毕,可以进行相应操作 // ... });

Vue 3的nextTick作为一个独立的函数导入,返回一个Promise,并且可以直接传递回调函数作为参数。这些变化使得Vue 3中的nextTick更加灵活和易于使用。

// Vue 3 
import { nextTick } from 'vue'; 
nextTick(() => { // 在下次DOM更新循环之后执行 });

vue在实例挂载的过程中发生了什么?

  1. 实例化:首先,Vue.js会创建一个新的Vue实例。在这个过程中,Vue.js会设置实例的各种属性和方法,包括数据对象、计算属性、方法、指令等。
  2. 编译模板:Vue.js会将模板编译成渲染函数。模板就是包含Vue特定语法的HTML代码。编译过程中,Vue.js会解析模板中的指令(如v-if、v-for等)和插值表达式(如),并将它们转换为JavaScript代码。
  3. 创建虚拟DOM:渲染函数会被调用,生成一个虚拟DOM树。虚拟DOM是对真实DOM的轻量级表示,它可以更高效地处理DOM的更新。
  4. 挂载:最后,Vue.js会将虚拟DOM渲染为真实DOM,并将其挂载到指定的元素上。这个过程通常在调用vm.$mount()方法或者在实例化Vue时传入el选项后发生。
  5. 更新:当数据变化时,Vue.js会重新执行渲染函数,生成新的虚拟DOM,并与旧的虚拟DOM进行对比(这个过程称为diff)。然后,Vue.js会根据diff结果,以最小的代价更新真实DOM。

这个过程中还会触发一系列的生命周期钩子,如created、mounted等,开发者可以在这些钩子中执行自己的代码。

vue2中data是一个函数而不是对象的原因

data之所以是一个函数,是因为一个组件可能会多处调用,而每一次调用就会执行data函数并返回新的数据对象,这样,可以避免多处调用之间的数据污染。

vue2中给对象添加新属性界面页面不刷新

vue2是用Object.defineProperty实现数据响应式,而后面新增的属性,并没有通过Object.defineProperty设置成响应式数据,所以页面没变化,常用解决方式:

  • Vue.set()
  • Object.assign()
  • $forcecUpdated()

Vue SSR的实现原理

vue.js的ssR是一种在服务器上预渲染Vue.js应用程序的技术。

  1. 服务器接收请求:当服务器接收一个请求时,它会创建一个新的Vue实例。
  2. 创建渲染器:使用vue-server-renderer包创建一个渲染器。
  3. 渲染页面:服务器使用渲染器将Vue实例渲染为Html字符串。
  4. 发送响应:服务器将渲染后的Html字符串作为响应发送给客户端。
  5. 客户端接收响应:客户端接收到服务器的响应后,将HTML字符串解析为DOM并显示给用户。
  6. 激活(Hydration): Vue在客户端创建一个新的Vue实例,将其挂载到服务器收到的Dom上

keep-alive的使用

keep-alive的主要作用是缓存路由组件,以提高性能

<router-view v-slot="{ Component }">  
    <keep-alive :include="permissionStore.keepAliveName">  
        <component :is="Component" :key="$route.path" />  
    </keep-alive>  
</router-view>
  1. router-view是 Vue Router 的一个组件,用于渲染当前路由对应的组件。
  2. v-slot=”{ Component }” 是一个插槽,用于获取当前路由对应的组件。
  3. keep-alive 是 Vue 的一个内置组件,用于缓存组件,避免重复渲染。
  4. :include=”permissionStore.keepAliveName” 是 的一个属性,表示只有名称在 permissionStore.keepAliveName 中的组件会被缓存。
  5. 是一个动态组件,:is=”Component” 表示组件的类型由 Component 决定,:key=”$route.path” 表示每个路由路径对应一个唯一的组件实例。

Vue项目中有封装axios吗?主要是封装哪方面的?

  • 1.封装前需要和后端协商好一些约定,请求头,状态码,请求时间….
  • 2.设置接口请求前缀:根据开发、测试、生产环境的不同,前缀需要加以区分
  • 3.移除重复的请求,如果请求在pending中,提示’操作太频繁,请稍后再试’
  • 4.用map结构根据相应状态码处理错误信息
  • 5.请求拦截,若headers中没有token的,移除请求
  • 6.响应拦截器,例如服务端返回的message中有’message’,提示’请求超时,请刷新网页重试’
  • 7.请求方法的封装,封装get、post请求方法,使用起来更为方便

css预处理器

css预处理器扩充了css语言,增加了诸如变量、混合(mixin)、函数等功能,让css更易维护、方便。本质上。预处理是css的超集。包含一套自定义的语法及一个解析器,根据这些语法定义自己的样式规则,这些规则最终会通过解析器编译生成对应的css文件。

如何实现上拉加载

image.png
触底公式:

scrollTop + clientHeight >= scrollHeight

简单实现:

    let clientHeight = document.documentElement.clientHeight;//浏览器高度
    let scrollHigiht = documnet.body.scrollHeight;//元素内容高度的度量,包括由于溢出导致的视图中不可见内容
    let scrollTop = documnet.body.scrollTop; //滚动视窗的高度距离`window`顶部的距离
    let distance = 50;  //距离视窗还用50的时候,开始触发;

    if ((scrollTop + clientHeight) >= (scrollHeight - distance)) {
        console.log("开始加载数据");
    }

如何实现下拉刷新

关于下拉刷新的原生实现,主要分成三步:

  1. 监听原生touchstart事件,记录其初始位置的值,e.touches[0].pageY;
  2. 监听原生touchmove事件,记录并计算当前滑动的位置值与初始位置值的差值,大于0表示向下拉动,并借助CSS3的3. translateY属性使元素跟随手势向下滑动对应的差值,同时也应设置一个允许滑动的最大值
  3. 监听原生touchend事件,若此时元素滑动达到最大值,则触发callback,同时将translateY重设为0,元素回到初始位置。

封装和使用JSBrige

  1. 定义协议:首先,需要定义一种协议,用于约定H5页面与App之间的通信规则。这可以是一组自定义的URL Scheme或JavaScript函数。
  2. 注册事件监听:在H5页面中,通过JavaScript代码注册事件监听器,用于接收来自App的消息或回调。可以使用window.addEventListener或其他类似的方法来监听特定的事件。
  3. 发送消息给App:在H5页面中,通过调用JSBridge提供的方法,将消息发送给App。这可以是通过修改URL Scheme的方式,或者调用App提供的JavaScript接口。
  4. 处理App的消息或回调:在App原生代码中,通过监听URL Scheme或执行JavaScript代码的方式,接收来自H5页面的消息或回调。根据协议约定,处理相应的逻辑或调用相应的功能。
  5. 回调结果给H5页面:在App原生代码中,根据协议约定,将处理结果或回调信息发送回H5页面。可以通过修改URL Scheme的方式,或者调用H5页面中注册的JavaScript回调函数。

个人博客

耀耀切克闹 (yaoyaoqiekenao.com)

gitHub

DarknessZY (zhangyao) (github.com)

阅读全文
1 ... 3 4 5 ... 9
avatar
耀耀切克闹

欢迎来到耀耀切克闹的博客

不是因为是主角才成功,而是成功了才成为主角

想要的都拥有,得不到的都释怀