面试|复习小册(2022-9)

45,734 阅读19分钟

知识点梳理与复习计划(2022-9)

阅读提示:

① 学识有限,难免错漏,仅供参考。因超字数,会存在裁剪现象,应自行补充。

② 标题前标有【高频】和【重要】的,是笔者根据自身经验建议重点掌握的内容。

③ 长更文,有些遇到了写了个标题,还没时间补充。

④ 如果可以,麻烦点个赞,谢谢。

⑥主要参考资料:mdn文档、《JavaScript高级程序设计》、《JavaScript权威指南》、《图解HTTP》、vueG方系列文档。

一、前端技术图谱梳理

前端技术图谱梳理.png

二、关于简历

关于简历

简历结构

  • ①个人基本信息:手机油箱等,期望薪资可以不写简历上,颜值高的贴贴相片
  • ②经验技能:
业务经验:两三句话,概括自己的工作经验,业务经验,建议不超过三行。

社区贡献:有就写,没有就算了。例如csdn专家博主,掘金lv7博主,贴上主页地址。
开源贡献:有就写,某某开源项目的核心贡献者...

专业技能:编程语言,技术框架,构建工具...
  • ③教育经历:两三行就行。
第一行。校名-学历-专业-在校时间。例如华中科技大学,本科,软件工程,2014-2018。
第二到第三行。获奖信息,重要经历。例如获得国奖,互联网+总决赛金奖,省级优秀学生干部...
  • ④工作经历:如实且时间倒序写,主要写下工作职责,获得s绩效,优秀员工,突出共享奖,不建议太长。
  • ⑤项目经历:
重点部分,找几个代表性项目,重点介绍下即可,最好类型不重复,例如后台管理系统之类的写一个就可以了。
例如小程序写一个,pc写一个,可视化项目写一个。主要自己做的部分,例如做了哪些优化,封装插件组件啊...
项目经历和工作经历,都建议是按照时间倒序来写...
  • ⑥资格证书:有就写写。

简历书写原则

  • 如实写
  • 简明扼要
  • 专业术语

最后,以上纯属经验之谈,请勿轻易深信。

三、常规部分

1.【高频】前端性能优化?

前端性能优化:减少消耗(资源加载优化、编码优化),提高用户体验。

(1)资源加载优化方向

  • ①减少请求数量。 合理规划利用请求,合理利用缓存技术减少不必要的频繁请求。及时性不高的数据可以缓存起来利用, 例如一些基础数据。应用懒加载技术,按需加载,缩短首屏或页面首次加载发起的请求数量,缩短等待和渲染时间。

    再例如,删除列表中的某一条数据的场景,是否每次删除之后立即向服务器发起更新列表的请求? 及时性要求没那么高的场景,其实执行删除接口和前端删除即可,不必每次删除之后都向服务器请求新的列表。

  • ②缩短请求时间。 打包压缩文件,压缩 js 和 css 文件,减小文件体积,缩短加载时间。

  • ③优化脚本加载方式。 script 加载。考虑异步请求资源,合理利用 script 加载方式,script 脚本的执行只在默认的情况下是同步和阻塞的。 script 有 defer 和 async 属性(延迟和异步),这可以改变脚本的执行方式。

    ④优化 css 加载。合理规划 css 的加载引入方式,减少 @import 的使用,页面被加载时,link 会同时被加载, 而 @import 引用的 CSS 会等到页面被加载完再加载。css 尽量放在 head 中会先加载,减少首次渲染时间。

  • ⑤一些用户操作行为用到的资源,可不在初始化时加载,合理延迟或有需要才请求。

  • ⑥静态资源考虑上 cdn

(2)编码优化方向

  • ①减少重排(reflow)重绘(repaint)

减少不必要的 DOM 深度,例如不必要的嵌套结构。 在 DOM 树中的一个级别进行更改可能会致使该树的所有级别(上至根节点,下至所修改节点的子级)都随之变化。 这会导致花费更多的时间来执行重排。

  • ②尽可能减少 CSS 规则的数量,并删除未使用到的 CSS 规则。一些默认就有的 CSS 规则,就不必写了, 具有继承性的样式,也不必每级节点都写。calc() 之类的计算函数没必要的应该少用。

  • ③关于动画效果。如果您想进行复杂的渲染更改(例如动画),请在流程外执行此操作。 您可以使用 position-absolute 或 position-fixed 来实现此目的。

  • ④关于 css 选择器。避免使用不必要且复杂的 CSS 选择器(尤其是后代选择器), 因为此类选择器需要耗用更多的 CPU 处理能力来执行选择器匹配。 总之不必要的深度,不管是 css 还是 dom 都不是好的选择,这对人和机器都是同样的道理,因为读和理解起来都同样的“费力”。

  • ⑤合理使用虚拟列表技术,降低消耗。

更详细请看导致 JS 缓慢的三主因之重排重绘

  • ⑥函数节流(throttle)或函数去抖(debounce)

使用函数节流(throttle)或函数去抖(debounce),限制某些不必要的行为的的频繁触发。

更详细的函数去抖与节流

(3)构建部署方向

开启 gzip 压缩...待完善

(4)其他:api、带宽**

使用性能更好的 api,优化网络连接等

2.【高频】你的职业规划是怎样的?

这个问题,在印象里面似乎 hr 问得比较多。实事求是回答完事, 例如会坚持技术路线,往资深架构方向走,也希望拓阔方向,向大前端靠近。

tips:讲个笑话,跟着 JavaScript 走,当它一t天下的时候,也跟着...

3.【高频】你印象最深刻的bug或技术难题是什么?你怎么解决的?

也有这样问“你最有成就感的项目是哪个?介绍一下?”

结合自身经历,概括用到的知识技术点,以及解决思路。

ps:笔者经验之谈,思路清晰的能自圆其说的回答就行,时长不宜太短,内容没那么重要。主要是能清晰的自圆其说。

4.【高频】从浏览器输入url到页面响应结束,这个过程是怎样的?js会阻塞文档渲染吗?

  • 解析 url 的有效性和合法性;
  • 执行缓存策略,策略内缓存中有则从缓存取并显示;
  • 缓存中没有则发送请求协议,DNS 解析域名,换取映射的 ip 地址;
  • 浏览器和服务器进行TCP连接,进行三次握手;
  • 握手成功正式发起请求,携带请求包,请求获取资源数据包;
  • 服务器接受处理请求包,寻找资源数据,并将资源数据返回给浏览器(当然可能啥也没有,也可能出错);
  • 浏览器接收请求返回的资源数据,尝试解析数据;
  • 浏览器解析返回的资源数据过程: 默认按照排序先后进行解析,加载 head 中的资源等等,例如 css 资源;

加载 script 脚本资源;script 脚本的执行只在默认的情况下是同步和阻塞的。 script 标签可以有 defer 和 async 属性(延迟和异步),这可以改变脚本的执行方式(在支持他们的浏览器)

继续解析后面的标签,文档内容

  • 浏览器渲染页面,同时执行可能的异步请求
  • 响应结束(可能正常显示,也可能异常)

5.TDK 是什么?在前端优化 SEO,应该从哪些方面着手?

①TDK:title 标题标签、description 描述标签、keywords 关键词标签,这三个标签的首字母合体。

②SEO:

  • 合理使用 title、description、keywords: title 标题,description 描述,keywords 关键词的搜索影响权重是逐渐减小的。

title 和 description 的内容应能代表网页内容,不能乱用不被普遍认可的词汇, 不同页面 title、description、keywords应不同, keywords 的关键词,重要的靠前排放。

  • 语义化的 HTML 代码:符合 W3C 规范,语义化代码让搜索引擎容易理解网页。

  • 少用 iframe:搜索引擎不会抓取 iframe 中的内容。

  • 网速:网站网速是搜索引擎排序的一个重要指标

6.看下这道题目都输出了啥?

这是一道笔试题。

for(let i=0;i<4;i++){
  i++
  console.log(i) // 打印了 1 和 3
}

7.【高频】介绍下重排(reflow)重绘(repaint),以及如何进行优化。

  • 重排或回流(reflow): 重排是在网络浏览器中执行的一个流程,用于重新计算文档中各元素的位置和几何形状,以便重新呈现该文档的部分内容或全部内容。 会改变文档布局,会引发元素的位置、尺寸发生改变的行为可称为重排。重排比起重绘,在视觉效果上会更明显, 每当操作 DOM 树、更改影响布局的样式、更改元素的 className 属性或更改浏览器窗口大小时,都会发生重排现象。

  • 重绘(repaint): 在不改变文档布局的情况下,文档元素发生的例如背景颜色等外观改变的行为可称为重绘。 根据 Opera 的说法,重绘的成本也很高,但在处理能力较高的现代设备中,可能感觉不明显。

更多信息

8.【高频】BFC 产生的条件?

  • 根元素()天然就会创建 BFC,iframe 会创建一个 html,所以 iframe 也会天然创建 BFC。
  • 浮动元素(float 值不为 none),float 的默认值是 none。
  • 绝对定位元素(position 值为 absolute 或 fixed),position 的默认值是 static。
  • 行内块元素(display 值为 inline-block)
  • 表格单元格(display 值为 table-cell,HTML表格单元格默认值)
  • 表格标题(display 值为 table-caption,HTML表格标题默认值)
  • 匿名表格单元格元素(display 值为 table、table-row、 table-row-group、table-header-group、 table-footer-group(分别是 HTML table、tr、tbody、thead、tfoot 的默认值)或 inline-table)
  • overflow 值不为 visible、clip 的块元素。overflow 的默认值是 visible。
  • display 值为 flow-root 的元素。flow-root 可以创建一个无副作用的 BFC,就像 html 根元素那样。但需注意 flow-root 的兼容性, 它是一个较新的属性,IE 全系列不支持,Chrome58,Firefox53,Edge79开始支持。
  • contain 值为 layout、content 或 paint 的元素
  • 弹性元素(display 值为 flex 或 inline-flex 元素的直接子元素),如果它们本身既不是flex、grid也不是table容器
  • 网格元素(display 值为 grid 或 inline-grid 元素的直接子元素),如果它们本身既不是flex、grid也不是table容器
  • 多列容器(column-count 或 column-width 值不为 auto,包括column-count 为 1)
  • column-span 值为 all 的元素始终会创建一个新的 BFC,即使该元素没有包裹在一个多列容器中。

更多信息-关于BFC

9.【高频】vue 中组件之间可以怎么样通信。

  • prop 和 emit()可以通过propemit() 可以通过 prop 和 emit() 通信。在子组件通过 prop 接受父组件传递下来的信息。 父组件可以根据约定的事件名,接受子组件通过 $emit(name,data) 向上派发的事件和传递的信息。

  • refsrefref绑定,refs 和 ref。ref 绑定,refs 获取组件实例,进而获取组件的属性、方法等信息。

  • Vuex,可以通过 vuex 进行数据交互,状态管理。

  • Bus (中央事件总线)

10.【高频】vue 指令中 v-for 和 v-if 哪个优先级更高

gw明确提示,不推荐在同一元素上使用 v-if 和 v-for。

在 Vue 2 中,v-for 优先级比 v-if 高,Vue 3 中则相反,v-if 的优先级高于 v-for。

vue3 gw关于二者优先级的描述

当它们同时存在于一个节点上时,v-if 比 v-for 的优先级更高。这意味着 v-if 的条件将无法访问到 v-for 作用域内定义的变量别名:

<!--
 这会抛出一个错误,因为属性 todo 此时
 没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

vue2 gw关于二者优先级的描述

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。 当你只想为部分项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo }}
</li>

11.【高频】.你对 es6 的了解?

es6 又称 es2015,是 js 的标准,它包含了许多新的语言特性和库,是比较大的一次升级。

新增了:

  • “class“
  • "let和const"
  • “箭头函数“
  • “解构赋值“
  • “字符串模板“
  • “promise“
  • “async/await“(es7)
  • “引入 module 模块“
  • “generators(生成器)“
  • “symbol“
  • "Map和Set"
  • "Proxy"
  • "class"

数据结构上set 属于集合,map属于字典。Set 是不包含重复值的列表,map 是键值对形式的,map 的键值都可以是任意类型。

async/await,是es7出的基于promise的异步处理方案。

proxy,vue3 实现数据响应式的关键技术点之一。

ES6支持class语法,但ES6的class只是原型链的一种语法糖,不是新的对象继承模型。

12.【高频】说一说深拷贝?你平时是怎么实现深拷贝的?

  • 浅拷贝:就是将一个对象的内存地址的复制给另一个对象。

  • 深拷贝:先新建一个空对象,内存中创建一个新的地址, 然后把被复制对象的所有可枚举的属性方法一一复制过来。

  • 实现拷贝的方法

①深拷贝。JSON.parse(JSON.stringify(data)),但对于 function 有缺陷。 (原本我是不太喜欢啥都用第三方库,比如一个日期格式化,一个深拷贝, 但后来一想技术员和研究员终究是不同,读者推荐 lodash 的 clonedeep 实现深拷贝。)

②浅拷贝。Object.assign() 实现一层深拷贝。

③浅拷贝。解构赋值法实现一层拷贝

④深拷贝。创建新对象 for 循环拷贝,有需要可结合递归。

大多数场景,数据是符合json格式的,还是 JSON.parse(JSON.stringify(data)) 做深拷贝实用, 有的人说有性能担忧,但讲真,每个字节所到之处都涉及性能问题吧?

13.【高频】你对防抖和节流有了解吗?

函数防抖动(debounce):防止在短时间内过于频繁的执行相同的任务。 当短时间内的频繁是不必要的时候,就可以考虑去抖动,避免资源浪费,或造成不友好的体验。

函数防抖动的原理,主要是利用一次性定时器,延迟任务的执行,在延迟这段时间内, 如果任务再次被触发,则通过 clearTimeout 销毁上一次产生的定时器, 因为定时器的被销毁,之前被延迟执行的任务也会随之被取消执行。 这样就实现了在一定时间内,只执行一次任务。这一次的执行通常是最后一次的触发, 因为此前的触发因为定时器的销毁而被取消了。

多次触发只执行最后一次或许就是和“节流”概念的区别?它两在作用上挺像的,在具体实现上略有不同。 函数防抖(debounce)是短时间内连续多次触发,但只执行最后一次,即是说将多次执行变成了只执行最后一次,执行次数减少。 而节流(throttle)是将短时间的多次执行,变成每隔一段时间执行一次。

更详细的函数防抖(debounce)

14.【高频】css 的相对单位有哪些?px 和 em 有什么不同?

  • ① 14种相对单位
序号单位相对于
1em元素的字号,在 font-size 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width
2ex字体的X字高(x-height)
3cap字体 中 大 写 的 标 称 高度
4ch数字“0”的宽度,元素字体中窄字形的平均字符前进,由“0”(零,U+0030)字形表示。
5ic元素字体中全宽字形的平均字符前进,由“水”(CJK 水象形文字,U+6C34)字形表示。
6rem根元素的字体大小。
7lh元素的行高line-height 。
8rlh根元素的行高。
9vw视口宽度的 1%。
10vh视口高度的 1%。
11vi根元素内联轴上视口大小的 1%。
12vb根元素块轴上视口大小的 1%。
13vmin视口较小尺寸的 1%。
14vmax视口较大尺寸的 1%。
  • ② px、em、rem和vw
px 是 css 中的绝对单位,也是web前端最为常见的单位,它表示像素,他的值不会受其他元素或其他值的影响,
我们可以理解为10px所表示的意义,总是相同的。

em 是相对单位,相对于元素的字号,在 font-size 中使用是相对于父元素的字体大小,
在其他属性中使用是相对于自身的字体大小,如 width。

相对单位的值的表示的大小,受其他东西的影响,比如1em,可能相当于10px的大小,
也可能相当于100px,甚至更高,因为em的实际表示大小是根据父元素而定,
1em表示的是父元素字体大小的一倍,要注意的是虽然只是受父元素的影响,
但父元素也会受它的父元素的影响。

rem是相对单位,相对于根元素的字体大小。一般浏览器默认根元素的大小为16px。

vh 和 vw 都是相对于视口,即设备屏幕可视区域的,这里视口宽高分别被划分为100,
vh 是相对于视口的高度,vw 是相对于视口的宽度。例如一个视口宽高为 1024 * 768,
则1vw,表示视口宽度的1%,即是102.41vh表示视口高度的1%,即是76.8。
所以在不同视口大小的设备,1vh所表示的意义是不同的。
  • ③额外的rpx
rpx是微信小程序中的WXSS尺寸单位,WXSS (WeiXin Style Sheets)是一套样式语言,
WXSS 具有 CSS 大部分特性。
rpx可以根据屏幕宽度进行自适应。规定屏幕宽为750rpx。如在 iPhone6 上,屏幕宽度为375px,
共有750个物理像素,则750rpx = 375px = 750物理像素,1rpx = 0.5px = 1物理像素。

更多信息-好奇|CSS长度单位竟然有21种

15.【高频】用过闭包吗?你对闭包的认识是什么?

Mozilla 上这样解释闭包:一个函数和对其周围状态(lexical environment,词法环境) 的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。

我对闭包的理解:

  • 闭包就是函数内部嵌套了函数。
  • 闭包使得函数可以模拟私有项,
  • 可以使得内部函数可以访问外部函数的属性。
  • 非必要不用闭包。

更多信息-学闭包(closure),应该注意什么?

16.【高频】js 中的继承与原型链?

a.继承

笔者随笔注释:mdn 文档中提到继承往往是和原型链放在一块提的 《JavaScript权威指南》第六版和第七版基本也是在这样, 并没有单独的章节提到继承分类为:原型链、盗用构造函数、组合继承、原型式继承、寄生式继承和寄生式组合继承。 不过这6种继承的提法,在《JavaScript高级程序设计》第四版的第八章第三节倒是正儿八经的提到了, 至于书中这种继承分类提法的由来是否来自 ecma-262 标准不清楚,又或者说是来自作者的经验总结。

ecma-262中有没有提及不清楚, 在我的印象中面向对象的编程语言 Java 中可以实现的继承类型有:单一继承,多重继承,多级继承、分层继承和混合继承。这种分类倒是好理解一些。

你可能会好奇我为什么会纠结这个,因为这个继承分类的提法感觉有些“不正经”, 所以我试图寻找这种分类提法的根源来路是否合理。来源于标准 ecma-262 当然是“来路正经”的, 来源于个人的实践经验总结也可以是一种“正经的来路”。就好像设计模式不就是各种“最佳实践经验的总结”吗?

所以把js中的继承分类上述六类足够科学合理吗?还是像 mdn 文档这样解释[继承和原型链]比较合理?

也有人说,继承的本质是复制(拷贝),也就是通过某种手段实现了从“我无”到“我有”,如果这样看,复制(拷贝)和继承的核心区别在哪里呢?

本段文字,单纯的是笔者的随笔记录,可能有问题,可能未必有答案,读者权当看一段无聊的自言自语。ecma-262 pdf版本

继承是面向对象编程中讨论最多的话题。很多面向对象语言都支持两种继承:接口继承和实现继承。 前者只继承方法签名,后者继承实际的方法。接口继承在 ECMAScript 中是不可能的,因为函数没有签名。 实现继承是 ECMAScript 唯一支持的继承方式,而这主要是通过原型链实现的。

class的extend关键字、原型链、盗用构造函数、组合继承、原型式继承、寄生式继承和寄生式组合继承。

b.原型链

javaScript 对象有一个指向一个原型对象的链。当试图访问一个对象的属性时,它不仅仅在该对象的自有属性上搜寻, 还会搜寻该对象的原型,以及该对象的原型的原型, 依次层层向上搜索,直到找到一个名字匹配的属性或到达原型链的末尾。

这种依次层层向上搜索中,遇到的每层的原型所组成的链条就是原型链。

下面通过一个示例来理解继承与原型链

    // 使用语法结构创建的对象
    let obj = {
      a: 'hello',
      fc: function () {
        return this.a + 1
      }
    }
    // obj 这个对象继承了 Object.prototype 上面的所有属性
    // obj 自身没有名为 hasOwnProperty 的属性
    // hasOwnProperty 是 Object.prototype 的属性
    // 因此 obj 继承了 Object.prototype 的 hasOwnProperty
    // Object.prototype 的原型为 null

    // 原型链如下:
    // obj ---> Object.prototype ---> null
    console.log(obj)
    console.log(obj.__proto__)
    console.log(obj.__proto__.__proto__) // null,这就是原型链的终点

    let subObj = Object.create(obj); // 创建的 subObj 本身没有自身的属性,但它继承有 obj 的属性。
    console.log(subObj.fc()); // 3,fc() 继承自 obj
    // 那么此时,我们就说 subObj 是一个继承自 obj 的对象,因为继承,obj 的一些属性被传递给了 subObj,
    // 例如 fc() 就继承自 obj
    // subObj 的原型链是 // subObj ---> obj ---> Object.prototype ---> null
    console.log(subObj)

这是一张打印上面 obj 对象的原型链截图, 打印结果也符合上面关于 obj 对象原型链的结论:obj ---> Object.prototype ---> null

image.png

下面是一张打印 subObj 的原型连截图: 打印结果也符合上面关于 subObj 对象原型链的结论:subObj ---> obj ---> Object.prototype ---> null

image.png

更多信息-面不面试都要会的继承与原型链:原型链的尽头是null?

17.【重要】改变盒子模型的 css 属性啥?介绍下盒子模型

①通过 box-sizing 来改变盒子模型,它有两个选项 border-box 和 content-box。

②盒模型:分为内容(content)、填充(padding)、边框(border)、边界(margin)四个部分

③与 css 盒模型有关的一个值得注意属性是 box-sizing。通常 box-sizing 的默认值是 content-box, 而他还有另一个值 border-box。content-box 中,属性 width,height 只包含内容content, 不包含 border 和 padding。border-box 中,属性 width,height 则包含 content、border和padding。

有关计算公式

/** content-box 的宽高计算 **/
box-sizing:content-box
width = width(内容宽度);
height = height(内容高度)
/**  border-box 的宽高计算  **/
box-sizing:border-box
width = width(内容宽度) + padding + border;
height = height(内容高度)  + padding + border

更多信息-box-sizing是干啥的?css盒模型与border-sizing在响应式布局的妙用

18.【高频】什么是外边距折叠(margin collapsing)?发生折叠会怎样?

①块的上外边距(margin-top)和下外边距(margin-bottom)有时合并(折叠)为单个边距, 其大小为单个边距的最大值(或如果它们相等,则仅为其中一个),这种行为称为外边距折叠

②外边距折叠(也被翻译为重叠或塌陷)有关的计算:折叠后其大小为单个边距的最大值(或如果它们相等,则仅为其中一个)。

tips:这里说的单个边距的最大值,不是指数学比较上的最大,而是指单个边距距离范围的大小。

  • 同正或同负:选绝对值最大的。

比如 -50px 和 -70px 发生折叠,那么折叠后的值是 70px,因为 -70 的绝对值最大嘛,要是 -50px 和 -50px, 当然此时绝对值最大也是 50px 嘛,所以你只有一个选择 50px。

比如 50px 和 70px 发生折叠,那么折叠后的值是 70px,因为 70 的绝对值最大嘛, 要是 50px 和 50px,当然也选绝对值最大的 50px。

  • 有正有负:最大的正边距与最小的负边距的和。

例如 70px、-50px、20px 发生折叠,那么边界范围就是 70px + (-50px)= 20px。

更多信息-复习|外边距折叠(塌陷)这事

19.【高频】css 中如何引入特殊的外部字体?

@font-face 的 CSS 规则 ,它允许网页开发者为其网页指定在线字体或加载指定的本地字体。 例如某一种漂亮的艺术字体,你的操作系统并没有,然而你希望使用它。 那么,你就可以通过引入该艺术字体的字体文件,然后利用@font-face规则注册字体, 之后你就可以通过 font-family: [注册的字体名称] 来使用引入的字体了。

使用外部字体的三步:①引入字体资源即下载例如ttf的字体文件到本地或使用在线资源, ②@font-face规则注册字体,③根据注册名称和font-family关键字使用字体。

更多信息-探究|客户端表现一致的关键:@font-face

20.【高频】CSS 样式选择器有哪些?优先级是怎样计算的?

11 种样式选择器

  • 通配符选择器,* 是通配符,表示其下的样式对所有元素生效,但通配符的优先级较低。
  • id选择器,例如 #container
  • 类选择器,例如 .box
  • 标签选择器(类型选择器),例如 div span p 等元素标签,直接以标签名称作为样式选择器
  • 后代选择器,有层级关系的叠加样式选择器,是样式选择器的一种组合使用,例如 div p
  • 子选择器,例如 ul>li 或 div>p,对 ul 直接的子元素 li 设置样式,对 div 的直接子元素 p 设置样式。
  • 伪类选择器,例如 a:hover,当鼠标悬浮于 a 标签时的样式。
  • 伪元素选择器,例如常见的 ::before 和 ::after,单冒号写法也被现代浏览器支持(那是css2的语法)。
  • 相邻兄弟选择器,例如 img + p,样式将对 img 图片后面紧跟着的 p 段落生效。
  • 兄弟选择器,A~B 作用于 A 元素之后所有同层级 B 元素。
  • 属性选择器,通过已经存在的属性名或属性值匹配元素,例如 a[href="example.org"] 或 a[title="一级标题"]。

优先级是如何计算的?

当同一个元素有多个声明的时候,优先级才会有意义。因为每一个直接作用于元素的 CSS 规则总是会接管/覆盖(take over)该元素从祖先元素继承而来的规则。

下面列表中,选择器类型的优先级是递增的:

  • 1.类型选择器(标签选择器)(例如,h1)和伪元素(例如,::before)

  • 2.类选择器 (例如,.example),属性选择器(例如,[type="radio"])和伪类(例如,:hover)

  • 3.ID 选择器(例如,#example)。

  • 4.嵌套组合选择器 多种选择器嵌套组合的优先级,往往比单一的样式选择器优先级更高。 例如 #id > div > p{} 的优先级会比 p{} 和 div > p{} 的优先级更高。

  • 5.内联样式 给元素添加的内联样式 (例如,style="font-weight:bold") 几乎总会覆盖外部样式表的任何样式 , 因此可看作是具有更高的优先级。

  • 6.!important 当在一个样式声明中使用一个 !important 规则时,此声明将覆盖任何其他声明。 当然 !important 可以覆盖 !important,此时需要比较权重。

  • 具体的优先级权重规则略显复杂: 一个选择器的优先级可以说是由四个部分相加 (分量),可以认为是个十百千 — 四位数的四个位数:

千位: 如果声明在 style 的属性(内联样式)则该位得一分。这样的声明没有选择器,所以它得分总是1000。 百位: 选择器中包含ID选择器则该位得一分。 十位: 选择器中包含类选择器、属性选择器或者伪类则该位得一分。 个位: 选择器中包含元素、伪元素选择器则该位得一分。

下面是一个示例表:

选择器千位百位十位个位优先级
h100010001
h1 + p::first-letter00030003
li > a[href*="en-US"] > .inline-warning00220022
#identifier01000100
内联样式10001000

tips:可比条件下,写在后面优先级高于写在前面的

tips:可比条件下,同为载入样式,后面载入的优先级更高。

更多信息-11种样式选择器与样式优先级计算

21.【高频】var、let 和 const的认识?

var 关键字用于声明全局变量,存在”变量提升“现象,不会抛出异常,默认值会被设置为 undefined。 “变量提升”的存在,使得变量像是声明提前,声明语句被移动到环境的顶部似得。

在 ECMAScript 6 中,let 和 const 同样会发生“变量提升”现象,但它们仅创建提升,赋值未提升。 在变量声明之前引用这个变量,将抛出引用错误(ReferenceError)。 这个变量将从代码块一开始的时候就处在一个“暂时性死区”,直到这个变量被声明为止。

①作用域:var 声明全局变量会被添加到 window 对象中;let 和 const 用于声明局部变量且存在块级作用域,不会被添加到 window 中。

②“变量提升”:三者声明的变量存在“变量提升”现象,但 var 声明的会被设置默认值 undefined; 而 let 和 const 则是存于“暂时性死区”不会设置默认值,且提前使用会报错。

③声明重复:重复声明 let 和 const 声明的变量不被允许,而 var 声明同名变量则不会报错。

④可修改性:const 声明的原始值类型数据不可以被修改,引用类型则可以修改其属性值但不能重新赋值。var 和 let 声明的变量则可以被重新赋值。

更多信息-变量提升与函数提升

22.【高频】css 属性中 height 可以被继承吗?

①height 默认不会被继承(但“继承”是可控制的)。

②可继承与不可继承的样式小结

可继承:

// 文本系列属性
color:文本颜色
direction:控制文本的书写方向     
text-align:文本水平对齐  
line-height:行高        
text-indent:文本缩进              
word-spacing:字间隔距离        
letter-spacing:字符间隔距离       
text-transform:控制文本大小写        

// 字体系列属性
font:字体简写属性,可配置多项字体参数  
font-size:字体的尺寸     
font-style:字体的样式风格              
font-family:元素的字体     
font-weight:字体的粗细       

光标属性:cursor
元素可见性:visibility
... 

不可继承:

// display:设置元素的内部和外部显示类型

// 盒子模型属性系列
width:宽度
height:高度
margin:外边距
border:边框
padding:内边距

// 背景属性系列
background(简写属性)
background-color
background-image
background-repeat
background-position

// 定位属性相关:
floatposition
toprightbottomleft
min-widthmin-heightmax-widthmax-height
clip
z-index

// 轮廓样式属性系列
outline(简写属性)
outline-style
outline-width
outline-color

③控制继承

CSS 为控制继承提供了四个特殊的通用属性值。每个css属性都接收这些值。 这意味着,就像 css 盒子模型可以通过改变 box-sizing 的值 (border-box 和 content-box)来改变。 css 属性的继承情况也是可以控制的,例如通过设置值 inherit、initial、unset 和 revert 来控制。

inherit:设置该属性会使子元素属性和父元素相同。实际上,就是 "开启继承".

initial:设置属性值和浏览器默认样式相同。如果浏览器默认样式中未设置且该属性是自然继承的,那么会设置为 inherit。

unset:将属性重置为自然值,也就是如果属性是自然继承那么就是 inherit,否则和 initial一样。

revert:目前仅被很少浏览器支持。

④重设几乎所有属性值

CSS 的 简写(shorthand) 属性 all 可以用于同时将这些继承值中的一个应用于(几乎)所有属性。 它的值可以是其中任意一个(inherit, initial, unset, or revert)。 这是一种撤销对样式所做更改的简便方法,以便回到之前已知的起点。

例如像下面这样:例子中有两个 div,第一个 div 应用了样式选择器 div 的样式, 第二个通过 类选择器 unset-all 设置 all 为 unset,重置了几乎所有样式, 例子中的红色背景和绿色边框都被重置为浏览器默认的白色了。

<div>
    <p>这是一段文字</p>
</div>
<div class="unset-all">
    <p>这是另一段文字,它将被充值样式</p>
</div>
div {
    background-color: red;
    border: 2px solid green;
}

.unset-all {
    all: unset;
}

下面是一张效果图

image.png

23.css 样式的匹配顺序是从左到右吗?

不是。 浏览器会从最右边的样式选择器开始,依次向左匹配。最右边的选择器相当于关键选择器(key selector), 浏览器会根据关键选择器从 dom 中筛选出对应的元素,然后再向上遍历相关的父元素,判断是否匹配。

所以组合嵌套选择器时,匹配语句越短越简单,浏览器消耗的时间越短, 同时也应该减少像标签选择器,这样的大范围命中的通配选择器出现在组合嵌套选择器链中, 因为那样会让浏览器做大量的筛选,从而去判断选出匹配的元素。

24.【高频】vue-router 路由的两种模式

  • hash 模式

来自 Vue Router gw

hash 模式是用 createWebHashHistory() 创建的,它在内部传递的实际 URL 之前使用了一个哈希字符(#)。 由于这部分 URL 从未被发送到服务器,所以它不需要在服务器层面上进行任何特殊处理。 不过,它在 SEO 中确实有不好的影响。如果你担心这个问题,可以使用 HTML5 模式。

  • history 模式

来自 Vue Router gw

当使用这种历史模式时,URL 会看起来很 "正常",例如 example.com/user/id。漂亮!

不过,问题来了。由于我们的应用是一个单页的客户端应用,如果没有适当的服务器配置, 用户在浏览器中直接访问 example.com/user/id,就会得… 404 错误。这就丑了。

不用担心:要解决这个问题,你需要做的就是在你的服务器上添加一个简单的回退路由。 如果 URL 不匹配任何静态资源,它应提供与你的应用程序中的 index.html 相同的页面。漂亮依旧!

hash 路由模式及其特性:

  • hash 模式是一种把前端路由的路径用井号 # 拼接在真实 url 后面的模式
  • 通过 js 对 location.hash 进行赋值,会改变 url 的 hash 值
  • 可以通过 hashchange 事件监听 hash 值的变化,来控制页面跳转(渲染)
  • 当井号 # 后面的路径发生变化时,会触发 onhashchange 事件
  • url 中的 hash 值只是客户端的内容,当使用包含 hash 值的路由发送请求时,hash 部分不会被发送
  • hash 值的改变会在浏览器访问历史中新增记录,所以可以使用浏览器的前进后退功能
// 监听 hash 变化的两种方式
// ①监听 hashchange 事件,就像监听 click 事件那样
window.addEventListener('hashchange', function() {
  console.log('The hash has changed!')
}, false);

// ② 利用 window.onhashchange 事件处理程序
window.onhashchange = function locationHashChanged() {
  if (location.hash === '#cool-feature') {
    console.log("You're visiting a cool feature!");
  }
};

history 路由模式及其特性:

  • html5 提供了 window.history API 来控制 url 变化,主要是 history.pushState() 和 history.replaceState() 它们可以在不刷新浏览器的场景下,操作浏览器历史记录。前者是增加一个历史记录,后者是替换当前历史记录。
  • 通过 popstate 事件可以监听 url 变化,来控制页面跳转(渲染)
  • history.pushState() 和 history.replaceState() 不会触发 popstate 事件

每当激活同一文档中不同的历史记录条目时,popstate 事件就会在对应的 window 对象上触发。 调用 history.pushState() 或者 history.replaceState() 不会触发 popstate 事件。 popstate 事件只会在浏览器某些行为下触发, 比如点击后退按钮(或者在 JavaScript 中调用 history.back() 方法),又如 history.go()。 即,在同一文档的两个历史记录条目之间导航会触发该事件。

window.onpopstate = function(event) {
  console.log("location: " + document.location + ", state: " + JSON.stringify(event.state));
};

补充:关于 location 跳转的三种方式

location.href='跳转的地址' 跳转页面
location.reload('') 刷新页面
location.replace('') 替换当前页面

hash 模式的缺点是长得“不好看”,history 模式的缺点是,对于单页应用 history 打包部署需要注意配置支持, 当找不到资源的时候应指向 index.html(单页应用的html页面)。

25.vue.js 的两个核心是什么?

数据驱动和组件化思想

  • 数据驱动

数据驱动是vuejs的核心思想之一,有人称其为vuejs最大的特点。所谓数据驱动就是指数据状态变化时, 自主得去更新与其有依赖的视图。数据驱动的关键在于驱动,这里的驱动是自主的, 生活中有一个词叫做自动化,我简单的认为数据驱动思想,是自动化思想在前端编程自动化上的一种应用, 数据驱动是将对dom的手动操作,自动化了,它根据数据状态的变化,自动更新操作视图。 使用过jQuery的同学应该有体会,数据驱动带来的“自动化”,其实省去了许多操作dom的工作,维护也更加方便。

我对数据驱动的理解,简单总结为以下三点:

①数据驱动是指根据数据状态变化,自主更新视图。

②数据驱动是自动化思想,在前端编程自动化上的应用。

③数据驱动使得操作dom,从手动操作,变成了自动操作,是一种自动化表现。

  • 组件化

vue组件是一种拓展HTML元素,将要展示的内容分成相对独立的拓展HTML元素, 即分成不同组件的过程,或在设计构建视图的编码时,将相对独立的可视区域, 以独立组件的形式构建的过程,就是组件化。

组件化的优点: 每一个组件,可以对应一个viewModel(简写vm,可以是vue实例)。视图页面是组件的容器, 组件化之后,我们可以任意根据需求自由嵌套组合组件,最后形成一个个完整页面。 组件具有高内聚低耦合的特性,那么复用性更好,维护成本更低,提高开发效率,这些优点就呼之欲出了。

更多信息-Vue源码解读:01核心思想篇

26.【高频】vue 的双向数据绑定的原理

Vue2 实现双向数据绑定,object 类型数据是通过 Object 的 defineProperty() 实现的, array 类型数据,是通过拦截重写数组的 7 个可操作且会改变数组自身的方法实现的。

Vue3 则是使用 Proxy 代替 Object.defineProperty 和 重写数组。

更多信息-Vue源码解读:02变化侦测篇

27.【高频】谈谈 vue 实例的生命周期,生命周期钩子都有哪些?

tips:vue2

从gw的生命周期图和源码看,可以大致将vue的生命周期分为4个阶段,分别是初始化阶段,模板编译阶段,挂载阶段,销毁阶段。

初始化阶段:为vue实例初始化属性、事件、数据观测等。

模板编译阶段:将模板字符串编译成渲染函数。(该阶段在runtime版本中不存在,因为已经编译好了。)

挂载阶段:将vue实例挂载到指定dom上,即将模板渲染到真实dom中。

销毁阶段:解绑指令,移除事件监听器,销毁子实例。

与vue生命周期密切相关的钩子函数有beforeCreate,created,beforeMount,mounted,beforeUpdate,updated,beforeDestroy,destroyed。 vue的api文档上,一共有11个和生命周期有关的钩子函数,另外三个分别是activated,deactivated,最后一个是vue2.5.0新增的,叫errorCaptured。

更多信息-Vue源码解读:05生命周期篇

28.【高频】请问 v-if 和 v-show 有什么区别?

v-show 是通过改变 css display 属性值实现切换效果, v-if 则是通过直接销毁或创建 dom 元素来达到显示和隐藏的效果。 v-if是真正的条件渲染,当一开始的值为true时才会编译渲染,而v-show不管怎样都会编译,只是简单地css属性切换。

v-if适合条件不经常改变的场景,因为它的切换会重新编译渲染,会创建或销毁 dom 节点,开销较大。 v-show适合切换较为频繁的场景,开销较小。

29.【高频】你对 vue 中 computed 和 watch 了解?

computed 是计算属性,对于任何复杂逻辑,你都应当使用计算属性,这就是它的应用场景, 虽然通过 methods 或 watch 也能完成, 但是 computed 在处理复杂逻辑时更有优势:计算属性是基于它们的响应式依赖进行缓存的。

watch 是侦听属性,虽然计算属性在大多数情况下更合适,但有时也需要一个自定义的侦听器。这就是为什么 Vue 通过 watch 选项提供了一个更通用的方法, 来响应数据的变化。当需要在数据变化时执行异步或开销较大的操作时,这个方式是最有用的。

Vue 提供了一种更通用的方式来观察和响应 Vue 实例上的数据变动:侦听属性。当你有一些数据需要随着其它数据变动而变动时, 你很容易滥用 watch——特别是如果你之前使用过 AngularJS。然而,通常更好的做法是使用计算属性而不是命令式的 watch 回调。

30.【高频】移动端混合开发中的 webview 中,h5 和 原生(IOS 或 安卓)是如何通信的?

主要是利用 window 这个全局对象,在上面注册全局方法(函数),并监听来实现的,当然安卓和iOS略有不同。 下面是一个 h5 通知原生支付,并接受原生支付结果回调的示例。具体上是结合 postMessage 实现的通信。

// 通知原生发起支付
function notifyPay(payType, data) {
  const params = JSON.stringify(data);
  window.acceptPayInfo = function (payResult) {//接收原生支付结果
    if (payResult) {
      store.commit("setPayResult", JSON.parse(payResult));
      localStorage.setItem('setPayResult', JSON.parse(payResult));
      Toast.clear();
      Toast('支付结果:' + payResult)
    }
  };
  // 安卓
  if (window.PKAndroid) {
    // 支付宝
    if (payType === 1) {
      window.PKAndroid.alipay(params);
      return
    }
    // 微信
    window.PKAndroid.wxpay(params);
            return
  }
  // ios
  if (window.webkit) {
    //支付宝
    if (payType === 1) {
      window.webkit.messageHandlers.alipay.postMessage(params);
      return
    }

    //微信
    window.webkit.messageHandlers.wxpay.postMessage(params)
  }
}

31.【高频】说一下 vue 中 key 的作用?

key 的特殊 attribute 主要作用在于给节点做唯一标识,以便高效的更新虚拟 DOM。 在新旧 nodes 对比时辨识 VNodes。如果不使用 key, Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。 而使用 key 时,它会基于 key 的变化重新排列元素顺序,并且会移除 key 不存在的元素。

有相同父元素的子元素必须有独特的 key。重复的 key 会造成渲染错误。

vue中为什么不建议使用index作为v-forkey?

首先,这个不建议应该有具体场景才更有说服力。例如在vue2.x中的可变列表场景,不建议一说才勉强有点依据。
静态列表,使用 index 作为key并无不妥,相反更简便,性能也不会更差。
甚至如果不报警告错误,不写key也没问题。此外,vue3中对index作为key的弊端做了优化。

vue2 中可变列表场景,不建议index作为key的原因可能有:
①index 作为key,因为differ比较算法的原因,可能会发生dom更新开销更大。
②可能会发生复用错误的旧子节点,导致一些差错,例如可变的 checkbox 列表中。

32.【高频】Vue2中,vue 的父组件的 mounted 和 子组件中的 mounted,哪个先执行?created呢?

父子组件中几个钩子的执行顺序。父组件是子组件的容器,从这角度理解,那么应当是父组件先创建,然后才能容纳子组件创建。 而挂载渲染,子组件作为父组件一部分,父组件的挂载渲染完成,应当以子组件为前提。(特例除外)

所以父created 先于 子created执行,子mounted 先于父mounted执行。

加载渲染:
父beforeCreate -> 父created -> 父beforeMount -> 子beforeCreate 
-> 子created -> 子beforeMount -> 子mounted -> 父mounted
 
子组件更新:
父beforeUpdate -> 子beforeUpdate -> 子updated -> 父updated
 
父组件更新:
父beforeUpdate -> 父updated
 
销毁:
父beforeDestroy -> 子beforeDestroy -> 子destroyed -> 父destroyed

更多信息-Vue源码解读:05生命周期篇

33.【高频】清除浮动的方式

①使用clear属性清除浮动。

②创建BFC环境,利用BFC的特性,BFC具有 排除外部浮动和包含内部浮动的特性。 例如,通过 display: flow-root, 为元素创建 BFC,从而实现清除元素外部浮动覆盖的影响。

BFC 的产生条件:

  • overflow 值不为 visible、clip 的块元素。overflow 的默认值是 visible。
  • 绝对定位元素(position 值为 absolute 或 fixed),position 的默认值是 static。
  • 弹性元素(display 值为 flex 或 inline-flex 元素的直接子元素),如果它们本身既不是flex、grid也不是table容器。
  • 行内块元素(display 值为 inline-block)
  • display 值为 flow-root 的元素。flow-root 可以创建一个无副作用的 BFC,就像 html 根元素那样。 但需注意 flow-root 的兼容性, 它是一个较新的属性,IE 全系列不支持,Chrome58,Firefox53,Edge79开始支持。

笔者注:网上,例如利用overflow清除浮动等很多具体的清除浮动方式,大多是利用的第二点“BFC特性”实现的。

更多信息-关于BFC

34.【高频】img 标签是块级元素吗?行内元素和块级元素有哪些?

行内元素的 padding 和 margin 设置会生效吗?

行内元素:

span, a, i, br, img, button, input, label, select, textarea

b, big, small, tt, abbr, acronym, cite, code, dfn, em, kbd,

strong, samp, var, bdo, map, object, q, script, sub, sup

以下是 HTML 中所有的块级元素列表(虽然”块级“在新的 HTML5 元素中没有明确定义):

p, div, form, h1-h6, header, footer, ul, ol, table

address, article, aside, blockquote, dd, dl, fieldset, figcaption, figure, hgroup, hr, pre, section,

所以 img 是行业元素,也是典型的可替换元素(replaced element)。

以 span 为例的行内元素的宽高边距有效性: width:无效。 height:无效。 margin:left 和 right 有效,top 和 bottom无效。 padding:left 和 right 有效,top 会覆盖前面的元素,bottom 会和后面的元素重叠。

因为 img 还是可替换元素(replaced element)又称置换元素,所以与 span 不同可设置宽高。

35.【高频】用过 Vue.nextTick 吗?你是怎么理解 Vue.nextTick 的?

文档:当你在 Vue 中更改响应式状态时,最终的 DOM 更新并不是同步生效的,
而是由 Vue 将它们缓存在一个队列中,直到下一个“tick”才一起执行。
这样是为了确保每个组件无论发生多少状态改变,都仅执行一次更新。

理解:写在$nextTick当中的代码不会立即执行,而是等数据更新、DOM更新完成之后再执行,
如果你需要的操作,需要在数据和dom更新后才能进行,那么可以考虑使用$nextTick。
我简单的认为它是一个在最佳时机执行其回调的setTimeout函数。
这个最佳时机就是数据更新、DOM更新完成时刻。

应用场景:因为 vue 是异步执行 dom 更新的,所以当你希望在更新数据之后,操作新的视图,
那么你的操作逻辑应写在 Vue.nextTick(callback) 的回调中,而这个回调会在dom 更新循环结束之后执行。
否则,因为异步更新 dom 的原因,如果你不是在 Vue.nextTick(callback) 的回调中执行操作新视图,
那么可能会发生意外。例如你在 created()钩子 是不能操作 dom 的,但你可以在此调接口更新数据,
如果你此时希望接口更新完毕数据后,接着调用操作 dom 的逻辑,那么最后将这部分操作 dom 的逻辑,
放置在Vue.nextTick(callback) 的回调函数中。

$nextTick 的原理:分析源码看,利用到 event loop机制。
Promise.then 会开启微任务
setTimeout 会开启宏任务,
MutationObserver html5 接口监听dom变化,会开启宏任务
setImmediate 会开启宏任务

36.用过 Nginx 吗?对 Nginx 有哪些了解?

Nginx (engine x) 是一个高性能的HTTP和反向代理web服务器,同时也提供了IMAP/POP3/SMTP服务。 Nginx是由伊戈尔·赛索耶夫为俄罗斯访问量第二的Rambler.ru站点(俄文:Рамблер)开发的.

  • 反向代理,负载均衡。 有时单台服务器,无法满足大量用户同时访问,需要服务集群以支持,保证应用的可用性。 nginx 做反向代理,可以代理分发,可以使得多台服务器能够平均分担负载。

我用到 nginx 的地方是:

  • 一是作为 http 服务器,为我做测试实验,以及一些静态资源提供 http 服务。

  • 二通过 nginx 代理实现跨域。

  • 三是利用 nginx 的代理转发,实现同一域名下部署多个项目。

  • 四是配置黑白名单,限制ip访问 这主要是利用 allow 和 deny 实现

allow xxx.xxx.xxx.xxx; # 允许指定的IP访问,白名单
deny xxx.xxx.xxx.xxx; # 禁止指定的IP访问,黑名单

当白名单ip或黑名单ip数量比较多的时候, 可以集中写在 .conf 的文件中,然后通过 include 指令引入

http{
    # 禁止 denyip.conf 文件中的ip访问
    include /ip/denyip.conf; 
 server{
    location /my-app {
        # /my-app 下的资源访问白名单: allowip.conf 中的ip
        include /ip/allowip.conf; 
    }
 }
}
  • 五是Nginx配置SLL证书

  • 六是做一些压缩配置

http{
    # 开启压缩 on/off
    gzip on;
    # 设置要压缩的文件类型
    gzip_types image/gif image/png;
    # 指定压缩级别,值越大压缩程度越高
    gzip_comp_level 3;
    # 指定开启压缩的最小阈值
    gzip_min_length 4k;
    # 指定压缩请求的缓冲数量和大小
    gzip_buffers 10 5k;
    # 可以指定不开启压缩的客户端类型,例如一些低版本的ie本身不支持压缩  
    gzip_disable "MSIE [1-5]\.";
    # 指定压缩处理支持的最低 http 版本
    gzip_http_version 1.1;
}

更多信息-Nginx配置-vue项目打包部署篇

37.【高频】介绍下 vuex

gw:Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 + 库。 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。

vuex 核心:

  • State。共享的的全局的的属性或状态,主要在 state 中声明存放。

  • Getter。Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。 注意从 Vue 3.0 开始,getter 的结果不再像计算属性一样会被缓存起来。

  • Mutation。更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。 这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数

  • Action。 Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态。Action 可以包含任意异步操作。

  • Module。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。 每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割。

    取值方式:

// $store 是 挂载到 vue 实例的 Vuex.Store()对象
// new new Vuex.Store({ state,getters,mutations,actions });
// 取 state 中的电话号码:this.$store.state.phone
// 取 getters 中的方法:this.$store.getters.getTabBar

// 操作 mutations 中定义的方法 setPayPassword
this.$store.commit('setPayPassword', '1');

// map 结合分模块的取法。namespaced: true,
// 获取 state 中的数据 ...mapState("auth", ["userInfo"]), 或 this.$store.state.auth.userInfo
// 获取 getters ...mapGetters("auth", ["username"]),
// 获取 mutations:...mapMutations("userCenter", ["addPage"]),
// 获取 action 的方法:...mapActions("auth", ["logout"]),

vuex 的原理:大概就是利用了全局混入 Mixin,将所创建的 store 对象,混入到每一个Vue实例中。(可阅读 vuex.js 源码查看)

38.【高频】数据类型与赋值

JavaScript 语言中类型集合由原始值和对象组成,数据类型有哪些?

mdn文档-数据结构和类型

最新的 ECMAScript 标准定义了 8 种数据类型: 7七种基本数据类型:Undefined、 Null、 Boolean、 Number、 String、 Symbol(es6)、 BigInt(es2020)。

  • 布尔值(Boolean),有 2 个值分别是:true 和 false.
  • null , 一个表明 null 值的特殊关键字。 JavaScript 是大小写敏感的,因此 null 与 Null、NULL或变体完全不同。
  • undefined ,和 null 一样是一个特殊的关键字,undefined 表示变量未赋值时的属性。
  • 数字(Number),整数或浮点数,例如: 42 或者 3.14159。
  • 任意精度的整数 (BigInt) ,可以安全地存储和操作大整数,甚至可以超过数字的安全整数限制。
  • 字符串(String),字符串是一串表示文本值的字符序列,例如:"Howdy" 。
  • 代表(Symbol)( 在 ECMAScript 6 中新添加的类型).。一种实例是唯一且不可改变的数据类型。

以及对象:Object。

Symbol es6(ECMA2015) 新增的数据类型。 Symbol 的值具有唯一性,常用用法是定义对象的唯一属性名。 BigInt 可以表示任意大小的整数。BigInt,ECMAScript 2020 新增。

不同数据类型赋值情况

数据类型的判断方法?

typeof:能判断原始值类型(Number,String,Boolean)。不能用于精确判断 null、对象、数组,因为都返回 object 。

var value = null;
console.log(typeof value, typeof value === 'object'); // object true

value = "hello";
console.log(typeof value, typeof value === 'string'); // string true

value = 23;
console.log(typeof value, typeof value === 'number'); // number true

value = false;
console.log(typeof value, typeof value === 'boolean'); // boolean true

value = [2]
console.log(typeof value, typeof value === 'array'); // object false

instanceof:能判断对象类型,不能判断原始值数据类型,其内部运行机制是判断在其原型链中能否找到该类型的原型。

var value = "hello";
console.log(value instanceof String); // false, 不能用于判断原始值类型的数据类型

var value = null;
console.log(value instanceof Object); // false, 不能用于判断原始值类型的数据类型

value = [11]
console.log(value instanceof Array); // true

value = {}
console.log(value instanceof Object); // true

Object.prototype.toString.call():所有原始数据类型都是能判断的,还有 Error 对象,Date 对象等。

判断一个变量是否为数组?

// 方法一:变量 instanceof Array
var arr = [1, 2]
console.log(arr instanceof Array); // true

// 方法二:变量.constructor === Array
console.log(arr.constructor === Array) // true

// 方法三:Array.isArray(变量)
console.log(Array.isArray(arr)) // true

// 方法四:利用 isPrototypeOf(变量)
console.log(Array.prototype.isPrototypeOf(arr))

39.【高频】数组去重

方法一:new Set()结合 拓展符... 简单的数组,去重可以可以利用 es6 的 set 关键字结合拓展符号... 实现

var arr = [1, 2, 'hello', 34, 2, 5, 34, 'hello']
function unique(arr) {
  return [...new Set(arr)];
}
console.log(unique(arr)) // 去重后:[1, 2, 'hello', 34, 5]

方法二:数组的 filter 结合 indexOf

var arr = [1, 2, 'hello', 34, 2, 5, 34, 'hello', 2]
function unique(arr) {
  // filter 返回表达式为 true 的数据项
  return arr.filter((item, index, array) => {
    // indexOf 返回的是数组第一个匹配的数据项的下标
    // array 是原数组,保持不变。
    // 当前下标 index 和当前数据项 item 在原数组 array 中的下标(indexOf 返回的)相同,则保留;不同说明有数据项和当前数据项相同,则过滤掉。
    console.log(array.indexOf(item), index);
    // 0 0
    // 1 1
    // 2 2
    // 3 3
    // 1 4
    // 5 5
    // 3 6
    // 2 7
    // 1 8
    return array.indexOf(item) === index;
  });
}
console.log(unique(arr)) // 去重后:[1, 2, 'hello', 34, 5]

对于复杂数组的去重,需要确定重复的根据,例如id重复算重复,还是某个或某些key重复才算重复。

/**
 * 根据数组对象的某个字段值去重
 * key 字段的键名,例如[{name:1}] 根据每条数据的name值来去重
 * */
function unique (arr, key) {
  const res = new Map()
  return arr.filter(item => !res.has(item[key]) && res.set(item[key], 1))
}

40.高阶函数

高阶函数英文叫 Higher-order function。高阶函数是对其他函数进行操作的函数,操作可以是将它们作为参数, 或者返回它们。简单总结为高阶函数是一个接收函数作为参数或者将函数作为返回输出的函数

js内置的高阶函数:Array.prototype.map,Array.prototype.filter,Array.prototype.reduce 和 Array.prototype.sort

vue 中 的 nextTick

41.【高频】vue2 data 为啥是函数而不是对象?

vue 中 data 必须是函数是为了保证组件的独立性和可复用性, 组件中实例化的,组件的 data 是函数,函数返回一个对象,计算机会给这个对象分配一个内存地址。 这样,每次实例化 data 返回的对象,就会被分配不同的内存地址。而不是指向同一个内存地址。 这样数据就具备了独立性,不同实例的 data 状态改变也就不会有相互影响, 从而保证了组件的独立性和可复用性。

42.【高频】js 中 new 关键字背后的操作

new 运算符创建一个用户定义的对象类型的实例或具有构造函数的内置对象的实例。

new 关键字会进行如下的操作:

  • 1.创建一个空的js 对象(即**{}**);
  • 2.为步骤 1 新创建的对象添加属性proto,将该属性链接至构造函数的原型对象;
  • 3.将步骤 1 新创建的对象作为this的上下文 ;
  • 4.如果构造函数没有显式返回一个对象,则返刚创建的对象(即绑定的this)。

---摘自mdn文档 文档给出了很好的解释

简洁版就是:创建一个空对象,并添加__proto__属性链接到构造函数的原型对象,绑定创建的新对象为this,返回新对象。

43.【高频】cookie、sessionStorage、localStorage的区别

  • 1.所属对象:cookie 是 document 的属性,storage 是 window 对象的属性。

  • 2.作用域:localStorage 同源下有效,sessionStorage 同源且不能跨会话窗口。

  • 3.生存期: cookie:默认是关闭浏览器会被清除,可自主设置有效期限。 sessionStorage:当前会话窗口有效,关闭当前会话窗口或浏览器会被清除。 localStorage:理论上除非被手动清除,否则将会永久保存。

  • 4.容量限制:Storage 5M左右,cookies 4K左右。

44.【高频】你对 promise和 Async/await 的理解

Promises对象是CommonJS工作组提出的一种规范,目的是为异步编程提供统一接口。

async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的

promise 的三种状态: Pending(进行中)、Resolved(已完成,又称 Fulfilled)、Rejected(已失败)

promise 原理

...todo

Async/await 原理

...todo

45.Canvas和SVG的区别与认识

①svg绘制的是矢量图,canvas绘制的是位图。
②svg主要是李永波标签绘制矢量图,canvas则是利用笔刷绘制位图。
③svg不依赖于分辨率,canvas依赖于分辨率。
④svg绘制的图形存于dom结构中,canvas则不会出现在dom结构中。
⑤svg支持分层和事件,canvas则不支持,当然canvas可以通过一些库实现。
⑥在游戏应用领域canvas具有优势,canvas适合图像密集型的游戏,其中的许多对象会被频繁重绘。

46.【高频】apply、call、bind、this 的认识

apply、call、bind

// 语法
function.apply(thisArg, argsArray) // 包含多个参数的数组

function.call(thisArg, arg1, arg2, ...) // 参数列表

function.bind(thisArg[, arg1[, arg2[, ...]]])

apply、call、bind 都可以改变 this 的指向。

apply、call 类似,区别是参数不同。该方法的语法和作用与 apply() 方法类似,只有一个区别, 就是 call() 方法接受的是一个参数列表,而 apply() 方法接受的是一个包含多个参数的数组。

mdn 的说明:

apply() 方法调用一个函数, 其具有一个指定的this值,以及作为一个数组(或类似数组的对象)提供的参数。

call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

bind()方法创建一个新的函数, 当被调用时,将其this关键字设置为提供的值,在调用新函数时,在任何提供之前提供一个给定的参数序列。

this

在 ES5 中,普通函数的 this 永远指向最后调用它的那个对象。this 的指向并不是在创建的时候就可以确定的。

另,可以简单的认为匿名函数的 this 永远指向 window。

箭头函数没有自己的this对象,其内部的this就是定义时上层作用域中的this(例如指向window)。

47.箭头函数和普通函数的区别

①普通函数具有原型prototype,箭头函数没有。

②普通函数可以作为构造函数使用,箭头函数则不可以。

③this指向不同。箭头函数内部的this指向是固定的,在一开始就确定了的。
而普通函数内部的this指向,并非在创建时就确定的,它的指向是可变的。
可以简单的认为,普通函数的 this 永远指向最后调用它的那个对象。

④声明方式不同。箭头函数使用箭头声明,不需要funcation关键字,普通函数则需要function关键字。
箭头函数只能声明成匿名函数,但可以通过表达式的方式让箭头函数具名。
普通函数则可以通过function声明具名或匿名函数。

额外的信息:es6开始支持箭头函数。call、apply、bind也无法改变箭头函数内部的this指向,
但他们三个可以改变普通函数内部的this指向。

48.es6 的 set 和 map

Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构。et和map是es6新增的数据结构。

set 集合

set,类似于数组,一般称为集合,该集合的值具有唯一性,会自动去重。

const s = new Set(); // 
s.add(1).add(2) // 返回 Set 结构本身,添加某个值
console.log(s);
s.delete(1) // 返回一个布尔值,表示删除是否成功
s.has(2) // 返回一个布尔值,判断该值是否为Set的成员
s.clear() // 清除所有成员,没有返回值
console.log(s);

// 常用于数组去重
const arr = [1,2,1,5,1];
console.log([...new Set(arr)]); // [1,2,5]

// 字符串去重
const str = "12151hkkh";
console.log([...new Set(str)].join("")) // "125hk"

实现并集、交集、差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);

// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}

// 交集
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}

// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

map 字典,键值对有序列表

map:Map类型是键值对的有序列表,而键和值都可以是任意类型。

const m = new Map();
m.set("name", "张三");
m.set("age", "18");
m.set(1, '测试').set(null, '空') // 链式操作
console.log(m.size) // 返回 Map 结构的成员总数
m.get(name)
m.has()
m.delete()
m.clear(); // 

49.【高频】js 中的作用域

作用域的定义:

百科的解释。作用域(scope),程序设计概念,通常来说,一段程序代码中所用到的名字并不总是有效/可用的, 而限定这个名字的可用性的代码范围就是这个名字的作用域。

我的理解:作用域(scope)即可使用的区域范围,作用域确定了代码中变量的可访问性和可用性的范围。

作用域的分类:全局作用域、函数作用域、块级作用域。

作用域的作用:具有隔离变量的作用,所以不同作用域的同名变量不会冲突。

50.手写一个获取 url 中参数的函数

/**
 * 根据参数的 key 获取 url 上对应参数的 value 值。如需要兼容ie低版本浏览器,需注意检测兼容性。
 * @param key
 * @returns {string}
 */
function getQueryStr(key){
  const urlStr = "https://www.lbb.com/test.html?age=18&name=李四" // window.location.href;
  const url = new URL(urlStr);
  return url.searchParams.get(key);
}

console.log(getQueryStr("age")); // 18
console.log(getQueryStr("name")); // 李四

其他方案

正则和普通方式
...todo

51.【高频】居中的几种实现方式

flex布局结合center

.parent {
  display: flex;
  align-items: center; /* 纵轴对齐方式,默认是纵轴 子元素垂直居中*/
  justify-content: center; /*纵轴对齐方式,默认是纵轴*/
}

flex布局结合margin:auto。

.parent {
  display: flex;
}
.child {
  margin: auto; /* 水平垂直居中*/
}

绝对定位的结合transform

.child {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
}  

绝对定位结合margin:auto

.child {
  position: absolute;
  left: 0;
  top: 0;
  right: 0;
  bottom: 0;
  margin: auto;
}

网格布局grid 结合center

/*写法类似flex布局*/
.parent {
  display: grid;
  align-items: center; /* 纵轴对齐方式,默认是纵轴 子元素垂直居中*/
  justify-content: center; /*纵轴对齐方式,默认是纵轴*/
}

网格布局grid 结合子项margin:auto

.parent {
 display: grid;
}
.child {
 margin: auto;
}  

**表格布局tabel-cell: **。

.parent {
  display: table-cell;
  vertical-align: middle;
  text-align: center;
}

.child {
  display: inline-block;
}

还有一种不常用的方法实现垂直居中。 最后还有一种奇葩的方法。容器设置position: relative。孩子设置 top、left、bottom、right都设置为0

52.【重要】内存管理与垃圾回收机制

mdn 内存管理

mdn 中关于js内存管理的解释非常棒,下面是一些要点引用。

内存管理与垃圾回收机制小结: ①内存生命周期的三个阶段:分配你所需要的内存、使用分配到的内存(读、写)、不需要时将其释放\归还。 ②垃圾回收算法主要依赖于引用的概念。 ③引用计数垃圾收集算法 ④标记 - 清除算法

内存的生命周期

不管什么程序语言,内存生命周期基本是一致的:

  1. 分配你所需要的内存
  2. 使用分配到的内存(读、写)
  3. 不需要时将其释放\归还

所有语言第二部分(使用分配到的内存(读、写))都是明确的。第一(分配你所需要的内存)和 第三部分(不需要时将其释放\归还)底层语言中是明确的, 但在像 JavaScript 这些高级语言中,大部分都是隐含的。

像 C 语言这样的底层语言一般都有底层的内存管理接口,比如 malloc()和free()。
相反,JavaScript 是在创建变量(对象,字符串等)时自动进行了分配内存,并且在不使用它们时“自动”释放。
释放的过程称为垃圾回收。这个“自动”是混乱的根源,
并让 JavaScript(和其他高级语言)开发者错误的感觉他们可以不关心内存管理。
 
最艰难的任务是找到“哪些被分配的内存确实已经不再需要了”。它往往要求开发人员来确定在程序中哪一块内存不再需要并且释放它。

JS 中的内存回收

引用

垃圾回收算法主要依赖于引用的概念。在内存管理的环境中,
一个对象如果有访问另一个对象的权限(隐式或者显式),叫做一个对象引用另一个对象。
例如,一个 Javascript 对象具有对它原型的引用(隐式引用)和对它属性的引用(显式引用)。

引用计数垃圾收集

这是最初级的垃圾收集算法。此算法把“对象是否不再需要”简化定义为“对象有没有其他对象引用到它”。
如果没有引用指向该对象(零引用),对象将被垃圾回收机制回收。

限制:循环引用

该算法(引用计数垃圾收集)有个限制:无法处理循环引用的事例。
在下面的例子中,两个对象被创建,并互相引用,形成了一个循环。
它们被调用之后会离开函数作用域,所以它们已经没有用了,可以被回收了。
然而,引用计数算法考虑到它们互相都有至少一次引用,所以它们不会被回收。
function f(){
  var o = {};
  var o2 = {};
  o.a = o2; // o 引用 o2
  o2.a = o; // o2 引用 o

  return "azerty";
}

f();

标记 - 清除算法

这个算法把“对象是否不再需要”简化定义为“对象是否可以获得”。

这个算法假定设置一个叫做根(root)的对象(在 Javascript 里,根是全局对象)。垃圾回收器将定期从根开始,
找所有从根开始引用的对象,然后找这些对象引用的对象……从根开始,垃圾回收器将找到所有可以获得的对象和收集所有不能获得的对象。

这个算法比前一个要好,因为“有零引用的对象”总是不可获得的,但是相反却不一定,参考“循环引用”。

 2012 年起,所有现代浏览器都使用了标记 - 清除垃圾回收算法。
所有对 JavaScript 垃圾回收算法的改进都是基于标记 - 清除算法的改进,
并没有改进标记 - 清除算法本身和它对“对象是否不再需要”的简化定义。

这个算法引发的限制就是:那些无法从根对象查询到的对象都将被清除。

内存泄露:内存泄露通常是指不再被需要的内存, 由于某种原因, 无法被释放。

内存泄漏常见情形: ①意外创建全局变量的情况。 ②未销毁的定时器和回调函数。 ③不当使用的闭包。 ④未被释放的dom资源。

53.怎样处理 移动端 1px 被 渲染成 2px问题

局部处理

利用meta标签中的viewport属性,设置网页初始缩放系数 initial-scale 为 1,
并结合利用transfrome的scale(0.5) 缩小一倍,其他值设置正常按照设计稿的写,即可达到2px变成1px的目的。

全局处理

利用meta标签中的viewport属性,设置网页初始缩放系数 initial-scale 为 0.5。
其他值设置正常按照设计稿的写即可。

54.OSI七层模型、TCP/IP层

OSI七层模型

  • 7应用层
  • 6表示层
  • 5会话层
  • 4传输层
  • 3网络层
  • 2数据链路层
  • 1物理层

TCP/IP参考模型

TCP/IP是一组用于实现网络互连的通信协议。Internet网络体系结构以TCP/IP为核心。 基于TCP/IP的参考模型将协议分成四个层次,它们分别是:网络访问层、网际互联层(主机到主机)、传输层、和应用层

55.【高频】js 中常见的内存泄露

js 内存泄漏通常是由于一些不再需要的引用没有及时清理,以及死循环等逻辑缺陷形成的堆栈溢出导致的。 例如已经不用的全局变量、定时器、闭包等未能及时解除引用,一些类似死循环或没必要的高频执行等逻辑缺陷。

常见情形:

  • 意外的全局变量
  • 被遗忘的定时器和回调函数
  • 弃用的DOM节点资源
  • 不当使用的闭包
  • 堆栈溢出

56.微前端框架现状和分类

几种微前端框架

iframe:可认为是最原始最简单的微前端解决方案。一个html标签。

Single-spa:是一个将多个单页面应用聚合为一个整体应用的 JS 微前端框架。

Qiankun:阿里出品,基于single-spa的微前端框架,旨在帮助大家能更简单、无痛的构建一个生产可用微前端架构系统。

Micro App:京东出品,基于 Web Component 原生组件进行渲染的微前端框架,
不同于目前流行的开源框架,它从组件化的思维实现微前端,旨在降低上手难度、提升工作效率。

EMP:欢聚时代出品。基于 Webpack5 Module Federation 的微前端框架。

Garfish:字节跳动出品,主要用于解决现代 web 应用在前端生态繁荣和 web 应用日益复杂化两大背景下带来的
跨团队协作、技术体系多样化、应用日益复杂化等问题。

微前端中的一些问题

1.应用隔离
2.css样式隔离
3.js沙箱隔离
4.微应用之间的通信问题

57.【重要】js执行机制那些事(事件循环、事件流、事件委托)

事件循环(Event Loop)

mdn 关于event loop的描述

Event Loop 是 jS的执行机制。

js是单线程的脚本语言,在同一时间,只能做同一件事, 为了协调事件、用户交互、脚本、UI渲染和网络处理等行为, 防止主线程阻塞,Event Loop方案应运而生...

js代码的执行过程中,主要是通过函数调用栈来处理函数的执行顺序,此外,
还通过任务队列来处理另外一些代码的执行。整个执行过程,称之事件循环过程。
一个线程中,事件循环是唯一的,但是任务队列可以拥有多个。
任务队列又分为macro-task(宏任务)与micro-task(微任务),在新标准中,它们被分别称为task与jobs

宏任务: I/O script UI render setTimeout setInterval postMessage MessageChannel setImmediate(Node.js 环境)

微任务: Promise Async/Await(基于promise) process.nextTick(Node.js环境) MutationObserver(html5新特性,MutationObserver 是一个可以监听DOM结构变化的接口。)

// 开始执行宏任务 script 中的内容
console.log('script start')

async function async1() {
  // await,实际上会产生一个promise返回,async/await 是es7的内容,是基于es6的promise优化而来,
  // 主要是解决.then嵌套太深的问题。await 后面的内容相当于开启一个微任务,加入任务队列中。
  // 队列是先进先出的。
  await async2()
  console.log('async1')
}
async function async2() {
  console.log('async2')
}

// 开启一个宏任务,setTimeout回调函数中的内容,
// 会在当前script 宏任务以及该宏任务的微任务,执行完毕之后才执行。
// 在本script(一个宏任务)中是最后执行的。
setTimeout(function() {
  console.log('setTimeout')
  // 零延迟并不意味着回调会立即执行。以 0 为第二参数调用 setTimeout 并不表示在 0 毫秒后就立即调用回调函数。
  // 其等待的时间取决于队列里待处理的消息数量。
}, 0)

new Promise(resolve => {
  console.log('promise')
  resolve()
})
.then(function() {
  // 产生一个基于当前宏任务的微任务,添加到到任务队列中,当前宏任务执行完毕之后,
  // 会优先执行当前会任务的微任务,再执行其他宏任务(setTimeout中的回调)。
  // 但是由于前面先遇到 async/await开启微任务,队列先进先出,所以会先执行前者。
  console.log('promise-then1')
})
.then(function() {
  // 又开启一个微任务,加入任务队列中
  console.log('promise-then2')
})

// 执行 async1,调用 async2,执行async2的内容,
// 遇到 await,await后面的内容开启微任务,加入到任务队列中,等待执行
async1()

console.log('script end')

// chrome98 的输出:
// script start => promise => async2 => script end => promise-then1 => async1=> promise-then2 => setTimeout

网传历史资料显示,关于 promise 和 async/await 的执行顺序的问题, 网传 chrome 旧版中 promise 先于 async/await,新版本则相反。 据说是新版chrome优化了,使得await变得更快。

笔者关于上述观点未能找到相关依据。但可以说的是在 chrome 98中, 它们的执行先后,取决于编码时的排版先后,也就是谁先产生微任务加入任务队列。 谁就会依据队列先进先出原则,率先被执行。(个人理解,未必对。)

要点小结

  • 宏任务、微任务
  • 消息队列
  • 零延迟

DOM级别与事件流

参考资料:JavaScript 高级程序设计(第四版)

关于DOM级别与事件流

DOM(Document Object Model——文档对象模型),
DOM 是载入到浏览器中的文档模型,是节点树,DOM就是一组API(接口)。

DOM级别(不同的迭代版本,类似es6,es7):DOM0级、DOM1级、DOM2级和DOM3级。

DOM事件级别:DOM0级事件处理,DOM2级事件处理和DOM3级事件处理。

DOM0:把一个函数赋值给(DOM元素的)一个事件处理属性(onclick),这样的事件方法就是DOM0级。
DOM1:没有事件相关内容。
DOM2:基于DOM0的处理事件上再新增了一些处理程序。支持同时绑定多个事件处理函数,
定义了 addEventListener 和 removeEventListener 两个方法。
DOM3:DOM3级处理事件是基于DOM2级处理事件上再新增了事件类型的支持。如dbclick、mouseup、keydown、keypress。

DOM2 Events 规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。
事件流兼容性:所有现代浏览器都支持DOM事件流,只有IE8以及更早版本不支持。

element.addEventListener(eventName, fn, useCapture)
// 第三个参数 useCapture:指定事件是否在捕获或冒泡阶段执行。布尔值,可选,默认false
// 可能值:true - 事件句柄fn在捕获阶段执行;false- 默认。事件句柄fn在冒泡阶段执行

事件流执行顺序:事件捕获阶段 -> 目标阶段 -> 事件冒泡阶段,
且当事件处于目标阶段时,事件调用顺序取决于绑定事件的书写顺序。
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>事件冒泡</title>
    <style>
        div{
            margin: 30px;
            padding: 30px
        }
    </style>
</head>
<body>
<div id="box1" style="background: #42b983;">
    box1
    <div id="box2" style="background: #1e87f0;">
        box2
        <div id="box3" style="background: #ff9642;">box3</div>
    </div>
</div>
</body>
<script type="text/javascript">
  let box1 = document.getElementById("box1");
  let box2 = document.getElementById("box2");
  let box3 = document.getElementById("box3");

  box1.addEventListener('click', function(){
    console.log('box1 捕获阶段');
  },true);
  box1.addEventListener('click', function(){
    console.log('box1 冒泡阶段');
  },false);
  box2.addEventListener('click', function(){
    console.log('box2 冒泡阶段');
  },false);
  box3.addEventListener('click', function(){
    console.log('box3 冒泡阶段');
  },false);
  box2.addEventListener('click', function(){
    console.log('box2 捕获阶段');
  },true);
  box3.addEventListener('click', function(){
    console.log('box3 捕获阶段');
  },true);  // 将box3的捕获阶段放到box3的冒泡阶段后面

  // box1 捕获阶段
  // box2 捕获阶段
  // box3 捕获阶段
  // box3 冒泡阶段
  // box2 冒泡阶段
  // box1 冒泡阶段
  
  // 网传资料显示,在早期版本中调换目标元素的捕获事件和冒泡事件,会导致执行顺序被改变。
  // 但这点在chrome 98中未能体现。
  // chrome 98:随意调换目标元素box3的捕获事件和冒泡事件顺序,其结果都是一致的:总是先捕获再冒泡。
</script>
</html>

小结

DOM0级只支持事件冒泡,DOM1没有事件相关的内容, DOM2支持同时绑定多个事件处理程序,DOM3增加了双击等事件类型。

事件流:事件流描述了页面接收事件的顺序。 DOM2 Events 规范规定事件流分为3个阶段:事件捕获、到达目标和事件冒泡。

事件冒泡:IE事件流被称为事件冒泡。事件冒泡从最具体的元素开始触发, 然后向上传播至没那么具体的元素,直至document和window。 例如,依次触发的顺序为:目标节点=》父节点=》body=》html=》document=》window。

事件捕获:Netscape Communicator 团队提出了另一种名为事件捕获的事件流。 事件捕获的触发(接收)顺序与事件冒泡相反。 事件捕获是最不具体的节点应该最先接收到事件,而最具体的节点应该最后接收到事件。 例如,依次触发的顺序为:window=》document=》html=》body=》父节点=》目标节点

事件传播的最上层对象是window。

捕获是不可取消的,但冒泡可以取消。e.stopPropagation() 可以中断冒泡,浏览器不再向上触发。

事件流的执行顺序是:事件捕获阶段(从window到具体元素) => 到达目标 => 事件冒泡阶段(从具体元素到window)。

事件委托

事件委托(delegation)是利用事件冒泡会向上传播的特性,实现把事件处理程序定义在目标元素的父元素或祖先元素上, 代替分别重复定义在目标元素上的一种方法。也称事件代理。例如把事件定义在ul上代替定义在其子元素每个li上, 这样做的好处是避免重复定义过多事件处理程序。

58.【重要】递归

mdn 上这样解释: 一种函数调用自身的操作。递归被用于处理包含有更小的子问题的一类问题。 一个递归函数可以接受两个输入参数:一个最终状态(终止递归)或一个递归状态(继续递归)。

我的理解是:递归就是有条件的自己调用自己。 所谓有条件就是可以终止,不会出现死循环,符合条件就继续自己调用自己,不符合调用就到此为止。

递归的一些应用场景:
数据:处理一些具有层级递进特征的数据,例如 tree 数据,有时会比较方便。
数学: 8皇后问题,汉诺塔,阶乘问题,迷宫问题。
算法:比如快排,归并排序,二分查找,分治算法等。

更多关于递归的信息

59.【重要】mvvm 和 mvc

MVVM是Model-View-ViewModel的简写。它本质上就是MVC 的改进版。 MVVM就是将其中的View 的状态和行为抽象化,让我们将视图 UI 和业务逻辑分开。

MVVM中数据视图实现数据视图响应式的关键:数据劫持+发布订阅模式

MVC:经典MVC模式中,M是指业务模型,V是指用户界面,C则是控制器, 使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。其中,View的定义比较清晰,就是用户界面。

----本段引自百度百科

// todo

60.【高频】vue2 和 vue3 的区别

1.由 vue2 的 Options API 改变为 vue3 的 Composition API + < script setup>。 (笔者注:无论如何辩论,这都是一个巨大的变更,既然如此为什么不是React而是Vue?) vue2 options api(选项式api)时,规定了数据定义在data中,方法写在methods中; vue3中数据和⽅法都定义在setup中。

2.vue3 弃用 Events API,Vue 实例再也不能作为事件总线做事件通信,onon,off,$once被彻底移除。

3.双向绑定。 vue2 主要是采用数据劫持+发布订阅模式的方式,通过 es5 的 Object.defineProperty() 来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应监听回调。以及重写数组的方式实现的。 vue3 则使⽤了 es6 的 ProxyAPI 对数据代理,通过 reactive() 函数给每⼀个对象都包⼀层 Proxy, 通过 Proxy 监听属性的变化,从⽽实现对数据的监听。

4.vue2 要求模板中必须有一个根元素,而vue3则不再要求(其实是退回到vue1,vue1也是不要求的)。

5.在 Vue 2 中,v-for 优先级比 v-if 高,Vue 3 中则相反,v-if 的优先级高于 v-for。

6.生命周期函数的不同。

Vue1Vue2(选项式API)Vue3(组合式API+setup)描述
beforeCompilebeforeCreatesetup组件创建前
compiledcreatedsetup组件创建完成
attachedbeforeMountonBeforeMount组件被挂载之前被调用
detachedmountedonMounted组件挂载完成后执行
initbeforeUpdateonBeforeUpdate组件即将因为响应式状态变更,而更新其 DOM 树之前调用
readyupdatedonUpdated组件因为响应式状态变更,而更新其 DOM 树之后调用
beforeDestroyonBeforeUnmount组件实例被卸载之前调用
destroyedonUnmounted组件实例被卸载之后调用

(笔者注:4和5两点变化都挺有意思)

7.props 和 $emit

// props 的使用
// vue2 挂载到vue实例上,可以直接通过 this 获取
vue2:this.xxx 

// vue3 需要通过 setup 获取
vue3:setup(props,context){ console.log(props) } 

// emit 的使用
vue2:this.$emit()
vue3:setup(props,context){context.emit()}

8.attrs 和 listeners

vue2:$attrs可以获得父组件除了props传递的属性和特性绑定属性 (class和 style)之外的所有属性。
$listeners则可用来获取父组件v-on事件监听器。

Vue3$attrs不仅可以获得父组件传来的属性也可以获得父组件v-on事件监听器,
vue3 中不再使用$listeners

9.ref 的用法也不同,vue2很简单,vue3还需要结合 setup并return出去。

【高频】webpack优化与配置

函数柯里化(Currying)

尾调用优化

普通函数实现箭头函数的写法

【重要】diff vue 中差异算法

关于 css 动画 和 GPU

手写深拷贝

javascript:this

模块化、服务端渲染、用原型链处理过的实际问题

npm 和 yarn 的区别与联系

webpack 和 vite 的区别

webpack 热更新原理、babel 抽象语法树原理

关于响应式设计

四、小程序篇

小程序生命周期函数、页面生命周期函数、优化方向

五、高阶部分

关于设计模式

创建型:工厂模式,抽象工厂模式,建造者模式,单例模式,原型模式 结构型:适配器模式,装饰器模式,代理模式,外观模式,桥接模式,组合模式,享元模式 行为型:发布订阅模式,模板方法模式,职责链模式,策略模式,命令模式,备忘录模式,状态模式,访问者模式,中介者模式,解释器模式,迭代器模式。

关于设计原则

单一职责:一个接口只做一件事,只承担一种职责。业务复杂的该拆分就拆分。

开放封闭原则:对扩展开放,对修改封闭。

里氏置换原则:子类能覆盖父类,父类能出现的地方子类也能出现。

接口独立原则:保持接口的单一独立,避免出现“胖接口”。这点目前在TS中运用到。

依赖倒置原则:编程面向抽象考虑设计,而不是具体的实现。接口使用者,只专注接口而不用关注具体类的实现。俗称“鸭子类型”

迪米特法则:也叫做最少知道原则。

...todo

关于算法

常见算法:

排序算法:冒泡排序、快速排序、选择排序、插入排序、归并排序

搜索算法:顺序搜索、二分搜索

算法思想:

分而治之:算法设计中的一种思想,将一个问题分成多个子问题,递归解决子问题,然后将子问题的解合并成最终的解。

动态规划:是算法设计中的一种思想,将一个问题分解为相互重叠的子问题,通过反复求解子问题来解决原来的问题。

贪心算法是算法设计中的一种思想,期盼通过每个阶段的局部最优选择,从而达到全局的最优,但 结果并不一定是最优。

回溯算法:是算法设计中的一种思想,一种渐进式寻找并构建问题解决方式的策略, 会先从一个可能的动作开始解决问题,如不行,就回溯选择另外一个动作,直到找到一个解。

关于数据结构

① 栈:后进先出的数据结构(js 中可用数组模拟栈)

② 队列:和栈相反,先进先出的一个数据结构(数组模拟队列)

③ 链表:多个元素组成的列表,元素存储不连续,通过 next 指针来链接, 最底层为 null(object模拟链表)

④ 集合:一种无序且唯一的数据结构。ES6中有集合 Set类型。

⑤ 字典:与集合类似,一个存储唯一值的结构,以键值对的形式存储。js中有字典数据结构 就是 Map 类型(map的键值都可以是任意数据类型)。

⑥ 树:一种分层数据的抽象模型, 比如DOM树、树形控件等。js中没有树 但是可以用 Object 和 Array 构建树。

⑦ 图:图是网络结构的抽象模型, 是一组由边连接的节点。js中可以利用Object和Array构建图。

⑧ 堆:一种特殊的完全二叉树, 所有的节点都大于等于最大堆,或者小于等于最小堆的子节点。js通常使用数组来表示堆。

六、关于网络安全

CIA三元组:机密性(Confidentiality) 完整性(Integrity) 可用性(Availability)

XSS攻击

XSS 攻击(Cross Site Script Attack,跨站脚本攻击)。 黑客在你的浏览器中插入一段恶意 JavaScript 脚本,窃取你的隐私信息、冒充你的身份进行操作。这就是XSS攻击。

XSS有哪几种类型?

  • 反射型 XSS (也叫非持久型)。一般发生在前后端一体的应用中。
  • 基于 DOM 的 XSS
  • 存储型 XSS (也叫持久型 XSS)

它们的区别?

反射型的 XSS 的恶意脚本存在 URL 里,存储型 XSS 的恶意代码存在数据库里。 而基于DOM型的XSS 攻击中,取出和执行恶意代码由浏览器端完成, 属于前端 JS 自身的安全漏洞,其他两种 XSS 都属于服务端的安全漏洞。

黑客可以通过XSS攻击做哪些事儿?

  • 未授权操作
  • 修改 DOM
  • 盗取用户 Cookie
  • 刷浮窗广告
  • 发动 XSS 蠕虫攻击
  • 劫持用户行为,进一步渗透内网

XSS攻击如何进行防护?

  • 一切用户输入皆不可信,在输出时进行验证
  • 将 HTML 元素内容、属性以及 URL 请求参数、CSS 值进行编码
  • 当编码影响业务时,使用白名单规则进行检测和过滤
  • 使用 W3C 提出的 CSP (Content Security Policy,内容安全策略),定义域名白名单
  • 设置 Cookie 的 HttpOnly 属性

XSS攻击案例

2005年,年仅19岁的 Samy Kamkar 发起了对 MySpace.com 的 XSS Worm 攻击。 Samy Kamkar 的蠕虫在短短几小时内就感染了100万用户——它在每个用户的自我简介后边加了一句话: “but most of all, Samy is my hero.”(Samy是我的偶像)。 这是 Web 安全史上第一个重量级的 XSS Worm,具有里程碑意义。

某Q邮箱 m.exmail.jq.com 域名被发现反射型 XSS 漏洞。

2007年12月,某度空间受到蠕虫攻击,用户之间开始转发垃圾短消息。

2011年某微博曾被黑客 XSS 攻击,黑客诱导用户点击一个带有诱惑性的链接, 便会自动发送一条带有同样诱惑性链接微博。攻击范围层层扩大,也是一种蠕虫攻击。

CSRF

英文全称是 Cross-site request forgery,又称为“跨站请求伪造”。

关于Web密码学

七、写在后面

tips:学识有限,难免错漏,仅供参考。

“尽管八股的意义重牢笼而轻选材,尽管直接编织牢笼的大多非是资本而正是打工人,然而现实如此, 一时又难以改变,因为我们没有有力的工会。八小时工作制很早就旗帜鲜明的提出,很遗憾先人不再,后人少有智勇...终是思想未达,仍需努力”

我信条是:“一个普通程序员,努力上进,也想,活在当下。” 抱怨这也不行,那也不行,不如一起加油,在梦里做一个改变世界的“天下第一等程序员”,相信新一代的程序员会带来新的风气。

祝愿我们都能马到功成,可以开心的过好每一个今天,无论风雪。

...持续完善ing...

9efa117d77d93d98e90e86f99ca9114.jpg