Git Product home page Git Product logo

study-notes's People

Contributors

shizhihuaxu avatar

Watchers

 avatar

study-notes's Issues

for...of 和 for ... in 的区别

for... of

  1. 不可遍历对象

  2. 遍历的是键值 value;

  3. 可遍历具有 Symbol.iterator 接口的的数据结构,例如 Set 、Map 、数组、类数组对象、字符串、Generator 对象等;

  4. 它可以与break、continue和return配合使用;

for ... in

  1. 最好不要用来遍历数组,会将数组原型链上的可遍历的属性输出;数组的键名是数字,但是for...in循环是以字符串作为键名“0”、“1”、“2”等等;
  2. 遍历的是键名,数组的索引和对象的 key;
  3. 最好用来遍历对象
let arr = [1,2,3]

for(let item in arr) {console.log(item)} // 0 1 2
for(let item in arr) {console.log(typeof item)} // string

arr.myname = 'haha'
for(let item in arr) {console.log(item)} // 0 1 2 myname

vue 中在 css 中使用 js 变量

  1. 内联声明 css 变量,在元素的 style 属性上绑定 js 变量

    <template>
        <div 
            v-for='(item, index) in list'
            :key='index'
            class='my-web'
            :style='{"--opacity": getBarOpacity(index)}'
        ></div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                list: []
            }
        },
        methods: {
            getBarOpacity(index){
                const colorArr = []
                
                return colorArr[index]
            }
        }
    }
    </script>
    
    
    <style>
    .my-web {
    	background-color: opacity: var(--opacity); // 使用 css 变量
    }
    </style>
  2. vue3 中,在 style 标签上绑定 js 变量

    • 这种方式如何解决循环生成的列表中每个列表项使用颜色值不同的问题呢?
    <template>
    	<div class='my-web'></div>
    </template>
    
    <script>
    export default {
        data() {
            return {
                opacity: 1
            }
        }
    }
    </script>
    
    <style vars="{ color }">
    .my-web {
    	background-color: var(--color)
    }
    </style>

盒模型

标准盒模型

box-sizing:content-box

​ css 设置的 width 宽度只表示内容区域的宽度,不包含内边距,边框

IE 盒模型

box-sizing:border-box

​ css 设置的 width 宽度包含了内容区域、内边距(左右)、边框(左右),高度同理

改变盒模型的 css 属性

​ box-sizing: border-box | content-box

获取盒模型的最终宽高

​ el.offsetWidth/offsetHeight

​ el.getBoundingClientRect().width

​ el.style.width // 只能获取行内样式

持续更新 - vue项目中一些性能优化的点

vue自身功能优化、请求、打包、插件引入

  1. 注意 v-show 与 v-if 的使用场景

  2. 不要同时使用 v-for 和 v-if

    v-for 会循环所有内容,v-for 会比 v-if 优先执行,所以情况会是先由v-for 渲染出来了,然后 v-if又使其隐藏掉了,造成了不必要的计算影响性能。

  3. 为 v-for 遍历中的 item 设置 key 值

    更新机制,减少不必要的重新渲染

  4. 组件 keep-alive

  5. 图标使用 iconfont,减少图片体积

  6. echarts 仅引入使用到的图表

  7. momentjs 仅引入使用到的语言包

  8. ui 组件库的组件的按需引入

  9. 路由懒加载

  10. 可以使用 nuxt 优化首屏渲染

  11. 打包时压缩 js 文件体积

  12. 开启 gzip 压缩,对于支持的浏览器返回 gzip 压缩的文件

  13. 使用缓存:http 缓存或本地缓存

  14. 长列表滚动加载

模块化方案

CommonJS(Nodejs,同步)

  1. CJS 使用不同的算法是因为它从文件系统加载文件,这耗费的时间远远小于从网络上下载。因此 Node 在加载文件的时候可以阻塞主线程,而不造成太大影响。而且既然文件已经加载完成了,那么它就可以直接进行实例化和运行。所以在 CJS 中实例化和运行并不是两个相互独立的阶段,而是连续不间断的。

  2. 在 CJS 中,整个导出对象在导出时都是值拷贝。即,所有的导出值都是拷贝值,而不是引用。所以,如果导出模块内导出的值改变了,导入模块中导入的值也不会改变。

  3. AMD/CMD 是 CommonJS在浏览器端的解决方案。

CommonJS是同步加载(代码在本地,加载时间基本等于硬盘读取时间)。

AMD/CMD是异步加载(浏览器必须这么做,代码在服务端)

UMD 是 AMD 与 CommonJS 的结合,跨平台的解决方案;UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。

ES Module(esm,ECMAScript Nodule,异步)

  1. js 的模块化标准

  2. 解决什么问题

    • 解决变量管理,变量作用域与访问,变量共享的问题,提供一种更好的方式管理变量和函数,将相关的变量和函数放在一个模块中,模块作用域,共享数据。

    • 对外暴露的过程为导出,导入此模块的模块就可以显示的声明依赖此模块的数据,是一种明确的依赖关系

    • 从入口文件,根据依赖关系加载各个依赖文件,但是不能直接使用,而是进行解析,将模块关系记录下来,解析后将代码和状态(变量的实际值)结合起来,形成模块实例。模块加载会从入口文件开始,最终生成完整的模块实例关系图。

  3. 同其它模块化系统有什么区别

  4. ESM 生成模块实例的过程:

  • 加载。从入口开始找出所有文件,下载,把所有文件解析成模块记录
  • 实例化。为模块分配内存空间,然后依据导入导出语句把模块指向对应的内存地址。
  • 运行。运行代码,将内存空间填充为真实值
  1. 为什么说 esm 是异步的?
  • 因为它把整个过程分为了三个不同的阶段:加载、实例化和运行,并且这三个阶段是可以独立进行的。(顺序固定。但不必一个完成立即执行下一步,例如使用 webpack 构建项目,打包后未运行,待部署后再真正执行)
  1. 模块定位符 (import from 'specifier', export)
  2. 动态 import() ,实际上把加载的模块当成了入口文件,会生成一个新的模块依赖关系树,与非 动态import的依赖关系树共有的模块会共享同一个模块实例,因为加载器会缓存模块实例,并且再全局范围内一个模块只会存在一个模块实例。
  3. 加载器会缓存模块,一个模块文件只会被下载一次,即使有多个模块依赖它。使用模块映射,每个全局作用域有一个独立的模块映射,加载文件后会将 url 记录在模块映射中,如果有其它模块也依赖于这个模块,会自动跳过下载。
  4. 解析目标(解析方式),对同一个文件不同的解析目标会生成不同的结果
  5. ESM 则使用称为实时绑定(Live Binding)的方式。导出和导入的模块都指向相同的内存地址(即值引用)。所以,当导出模块内导出的值改变后,导入模块中的值也实时改变了。
  6. 模块导出的值在任何时候都可以能发生改变,但是导入模块却不能改变它所导入的值,因为它是只读的。

面试问题列表

[TOC]

前端面试题列表

1 css基础

1.1 布局

  1. 响应式设计原理

    媒体查询与断点,确保在不同 css 像素范围内显示友好,可以使用不同的排版布局

  2. 适配原理与实现(rem,vw)

    设计稿固定一个尺寸设计,设计稿的尺寸要根据适配的屏幕大小来确定。在使用此方式适配时,屏幕尺寸的范围如果过大的话其实会适配的不是很好,因为它的**是等比缩放,选择一个尺寸设计,比这个尺寸大的屏幕放大,比这个小的屏幕缩小,所以如果尺寸跨度过大的话显示依然不友好。

  3. 水平垂直居中的方案

    • 宽高不固定
      • flex 布局
      • grid
      • text-align 水平居中
      • line-height 垂直居中
      • vertical-align(只对inline-block 有效)
    • 宽高固定时
      • absolute + 负 margin:父元素设置 position:relative;子元素 absolute,left: 50%,margin-left: 负的宽度一半,垂直方向同样处理
      • absolute + transform:父元素 设置 position:relative;子元素 absolute,left: 50%,transformX: -50%,,垂直方向同样处理
  4. 实现元素的高度根据宽度按照固定的比例缩放

依据 padding/margin 的百分比均是以父元素的宽度来计算的

<div class='parent'>
  <div class='child-wraper'>
    
  </div>
</div>

.parent {
	width: 50%; // half of its parent
	postion: relative;
	overflow: hidden; // 防止内容溢出
}
.parent::after {
	content: '';
	display: block;
	margin:  62.5% // width/height = 8/5 height = width/(8/5)
}
.child {
	position: absolute;
	width: 100%;
	height: 100%;
}

1.2 盒模型

盒模型 MDN

box-sizing MDN

  1. 浏览器在渲染一个页面的时候,渲染引擎会将每个元素表示为一个盒子,css 决定这个盒子的大小、位置以及属性。

  2. 盒子分为块级盒子(block box)和内联盒子(inline box)两种类型。

    块级盒子

    • 每个盒子都会独占一行;
    • width 和 height 属性可以发挥作用;
    • 内边距(padding), 外边距(margin) 和 边框(border) 会将其他元素从当前盒子周围“推开”。

    内联盒子

    • 盒子不会产生换行;
    • width 和 height 属性将不起作用;
    • 垂直方向的内边距、外边距以及边框会被应用但是不会把其他处于 inline 状态的盒子推开;
    • 水平方向的内边距、外边距以及边框会被应用且把其他处于 inline 状态的盒子推开;
    • 请注意,除可替换元素外,对于行内元素来说,尽管内容周围存在内边距与边框,但其占用空间(每一行文字的高度)则由 line-height 属性决定,即使边框和内边距仍会显示在内容周围;水平方向上占据的空间会由 margin,padding,border 和元素内容大小决定,也就是说水平方向上的 padding,margin,border 是起作用的,会将其他元素从当前盒子周围“推开”。
  3. 盒模型由四部分组成 content、padding 、border、margin(margin 不计入实际大小 —— 当然,它会影响盒子在页面所占空间,但是影响的是盒子外部空间。盒子的范围到边框为止 —— 不会延伸到margin) ;

  4. box-sizing: border-box(IE) | content-box(标准);

content-box 是默认值。表明 width 属性设置的只是元素内容区域的大小,如过还设置了边框和内边距,盒子的最终渲染出来的大小将会是 width + padding + border;

border-box:设置的边框内边距的值是包含在width内的,最终渲染出来的元素大小依然是 width 属性设置的大小;border-box 是不包含 margin 的。

1.3 行内元素与块级元素的区别 ,display 有哪些值,特点

块盒子

div,p,h1~h6,ul li,form,table,hr

行内盒子

span,img ,i,strong,em,a,b,input,select,br

display:block, inline-block, inline 的区别

相对于 inline 元素,允许设置宽高,垂直方向上的 margin 和 padding 可以生效
相对于 block 元素,不会换行,出现在前一个元素后面

1.4 视口

移动端的两个概念,布局视口和可视视口,对布局视口进行缩放,使之与可视视口同样大小;

1.5 line-height 的百分比、em 与数值的区别

line-height 设置在自身元素上

  • 百分比:乘以自身元素字体的大小,字体大小可能是由父元素继承而来;
  • em:em 单位本身就是以自身元素字体大小计算,同样字体大小可能是由父元素继承而来的;
  • 数值:此时依然是相对于自身元素字体大小计算,字体大小可能是继承而来的;

line-height 设置在父元素上,子元素继承的情况

  • 百分比/em:继承与父元素自身字体大小计算后得出得出的值;
  • 数值:继承数值,再与自身元素的字体大小计算出最终 line-height 值,自身字体大小可能是由继承而来的。(永远都是继承系数,以自身元素字体大小计算实际值)
// ------------------------- demo1-----------------------------------
// html
<div class="p1">
    <div class="s1">1</div>
    <div class="s2">1</div>
</div>
<div class="p2">
    <div class="s5">1</div>
    <div class="s6">1</div>
</div>

// css
.p1 {font-size: 16px; line-height: 32px;}
.s1 {font-size: 2em;}
.s2 {font-size: 2em; line-height: 2em;}

.p2 {font-size: 16px; line-height: 2;}
.s5 {font-size: 2em;}
.s6 {font-size: 2em; line-height: 2em;}

// 输出结果为
.p1:32px
.s1:32px
.s2:64px

.p2:32px
.s5:64px
.s6:64px

// -------------------  demo 2 ---------------------------
// html
<div class="box green">
 <h1>Avoid unexpected results by using unitless line-height.</h1>
  length and percentage line-heights have poor inheritance behavior ...
</div>

<div class="box red">
 <h1>Avoid unexpected results by using unitless line-height.</h1>
  length and percentage line-heights have poor inheritance behavior ...
</div>

// css 
.green {
  line-height: 1.1;
  border: solid limegreen;
}

.red {
  line-height: 1.1em;
  border: solid red;
}

h1 {
  font-size: 30px;
}

.box {
  font-size: 15px;
}

// 输出结果为
.green:16.5px
.green h1:33px

.red:16.5px 
.red h1:16.5px // 继承计算后的值

1.6 BFC

BFC 元素形成了独立的布局环境,其中的元素布局是不受外界的影响,决定了元素如何对其内容进行定位。

特性

  1. BFC的区域不会与float box重叠(左图右文的两栏布局)
  2. 计算BFC元素的高度时,会包括浮动元素(可以借此解决父元素高度塌陷的问题)
  3. 在一个BFC下的块 margin 会发生重叠,不在同一个则不会(可以借此解决外边距重叠的问题)

触发BFC

  • 根元素(<html>)
  • 浮动元素(元素的 float不是 none
  • 绝对定位元素(元素的 positionabsolutefixed
  • 行内块元素(元素的 displayinline-block
  • overflow 计算值(Computed)不为 visible 的块元素
  • 表格单元格(元素的 displaytable-cell,HTML表格单元格默认为该值)
  • 表格标题(元素的 displaytable-caption,HTML表格标题默认为该值)
  • 匿名表格单元格元素(元素的 displaytabletable-rowtable-row-grouptable-header-grouptable-footer-group(分别是HTML table、row、tbody、thead、tfoot 的默认属性)或 inline-table
  • display值为 flow-root 的元素
  • contain 值为 layoutcontent 或 paint 的元素
  • 弹性元素(displayflexinline-flex 元素的直接子元素)
  • 网格元素(displaygridinline-grid 元素的直接子元素)
  • 多列容器(元素的 column-count不为 auto,包括 ``column-count1
  • column-spanall 的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更Chrome bug)。

使用场景

  • 清除浮动
  • 左右两栏自适应布局
  • 解决外边距合并的问题

1.7 css 选择器

选择器类型

基本选择器

通配(*)

标签(如 div,p)

类(.classname

id( #idname)

属性选择器([attr = val])

分组选择器( 逗号分隔 div , p)

关系选择器

后代 (空格分隔)

直接子代(>)

一般兄弟元素( ~ )

紧邻兄弟元素( + )

伪类、伪元素

伪类:(:hover, :active, :focus, :visited :first-child, :last-child :before, :after) 等等

伪元素:(:first-line,:first-letter)

选择器权重

  • !important 的权重最高,但也会被权重高的 !important 覆盖;

  • 行内样式总会覆盖外部样式表的样式(除 !important);

  • 两个不同权重的选择器作用在同一个元素上,权重高的 css 规则生效;

  • 两个相同权重的选择器作用在同一元素上,后定义的选择器规则生效;

  • 单独使用一个选择器的时候,不能跨等级使css规则生效;

    无论多少个class组成的选择器,都没有一个ID选择器权重高。类似的,无论多少个元素组成的选择器,都没有一个class选择器权重高、无论多少个ID组成的选择器,都没有行内样式权重高。

  • !important > inline > id > class 、属性选择器、伪类 > 伪元素、元素(类型也就是标签)选择器

css权重

1.8 外边距合并

情景

  1. 父子元素

父元素与第一个子元素的 margin-top(无法将两者的 margin-top 隔开)

  • 父元素不存在 border-top
  • 父元素不存在 padding-top
  • 父元素与子元素间不存在内容
  • 父元素未创建 BFC
  • 子元素未设置浮动

父元素与最后一个子元素 margin-bottom

  • 父元素不存在 border-bottom
  • 父元素不存在 padding-bottom
  • 父元素与子元素之间不存在内容
  • 父元素不存在 height 或 min-height 或 max-height
  • 子元素不存在 height 或 min-height 或 max-height
  1. 相邻元素之间左右边距(除非最后一个元素清除前一个元素分浮动)

  2. 空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)

    • 块级元素没有 height 或 min-height
    • 块级元素没有 border-top 或 border-bottom
    • 块级元素没有 padding-top 或 padding-bottom
    • 块级元素没有内容

计算方式

  • 两正值取最大
  • 一正一负相加
  • 两负值取绝对值最大的负值

1.9 浏览器的渲染机制、回流和重绘、优化

浏览器的渲染机制参考文档

browser-render

  1. HTML 解析器将 HTML 解析生成 DOM 树;

  2. CSS 解析器将 CSS 解析生成 CSSOM 树;

  3. 将 CSSOM 树 和 DOM 树结合生成 Render Tree(渲染树仅包含可见的节点);

    • 从 DOM 树的根节点遍历每一个可见节点(display:none 不可见节点此时不会出现在渲染树中);
    • 对于每个可见节点,为其找到适配的 CSSOM 规则并应用它们;
    • 生成 Render Tree (渲染树),连同节点内容及样式。
  4. 布局(回流):计算它们在设备视口内的确切位置和大小;

    • 从渲染树的根节点遍历,计算节点的几何信息,输出每个节点的“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸: 所有相对测量值都转换为屏幕上的绝对像素。
  5. 绘制(重绘):将渲染树中的每个节点转换成屏幕上的实际像素。

什么是不可见节点呢?

  1. 某些节点不可见(例如script、link等),因为它们不会体现在渲染输出中,所以会被忽略。
  2. 某些节点通过 CSS 隐藏,因此在渲染树中也会被忽略,例如在该节点上设置了“display: none”属性。
  3. visiblility: hidden 和 display:none 不同,前者隐藏元素,但元素仍占据着布局空间(即将其渲染成一个空框),而后者 (display: none) 将元素从渲染树中完全移除,元素既不可见,也不是布局的组成部分。

优化关键渲染路径就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间

​ 执行渲染树构建、布局和绘制所需的时间将取决于文档大小、应用的样式,以及运行文档的设备: 文档越大,浏览器需要完成的工作就越多;样式越复杂,绘制需要的时间就越长。如果 DOM 或 CSSOM 被修改,您只能再执行一遍以上所有步骤,以确定哪些像素需要在屏幕上进行重新渲染。

回流和重绘属性参考文档

回流(reflow):

  1. 元素位置发生变化
  2. 元素的尺寸发生变化
  3. 添加或删除元素
  4. 元素内容发生变化
  5. 页面首次渲染
  6. 浏览器窗口尺寸发生变化

...

重绘(repaint):

1、字体颜色

...

回流一定会触发重绘,重绘不一定触发回流。

什么情况下浏览器会强制触发回流和重绘?

参考文档

现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:

offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
getBoundingClientRect
具体可以访问这个网站:https://gist.github.com/paulirish/5d52fb081b3570c81e3a
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,**最好避免使用上面列出的属性,他们都会刷新渲染队列。**如果要使用它们,最好将值缓存起来。

author: chenjigeng

减少回流和重绘的方法

  1. 多次对元素的修改尽量合并成一次

1.10 块级元素和行内元素的区别

  1. display 有哪些属性值,区别

  2. 块元素

    • 块级元素占据其父元素(容器)的整个空间,因此创建了一个“块”。

    • 默认情况下,块级元素会新起一行;一般块级元素可以包含行内元素和其他块级元素;

    • 常见的块级元素有 div、p、ul、ol、address、article、audio、vedio、canvas、figure、form、h1...、pre、table 等。

  3. 行内元素

    • 一个行内元素只占据它对应标签的边框所包含的空间。

    • 一般情况下,行内元素只能包含数据和其他行内元素。

    • 常见的行内元素有

      b, big, i, small, tt
      abbr, acronym, cite, code, dfn, em, kbd, strong, samp, var
      a, bdo, br, img, map, object, q, script, span, sub, sup
      button, input, label, select, textarea
      
  4. 块级和行内的区别

    • 块级元素可以设置宽高,行内元素设置会不起作用,行内元素高度可以通过 line-height 来设置;
    • 每个块级元素默认会占满父元素的宽度,从上到下排列;行内元素默认只占据自身元
      素大小的空间,从左到右排列。
    • 行内元素设置 padding-top/bottom,margin-top/bottom 无效(但是 padding-left/right,margin-left/right 有效)

1.11 CSS Modules 和 vue scoped 的原理

css modules 本身不是浏览器的特性,也不是 css 的标准,而是借助 webpack 等构建工具,对 css 类名和选择器限定作用域。(css-loader中实现)

css modules 仅支持 id 和 class 选择器

优点:

  1. 局部作用域,避免全局污染和名称冲突的问题

1.12 定位元素

  1. 默认布局 static
    该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。

    ​ 此时 top, right, bottom, left 和 z-index 属性无效。

  2. 相对定位 relative

    ​ 对 table-*-group, table-row, table-column, table-cell, table-caption 元素无效。

  3. 绝对定位 absolute,fixed

    ​ 大多数情况下,height和width 被设定为auto的绝对定位元素,按其内容大小调整尺寸。但是,被绝对定位的元素可以通过指定top和bottom ,保留height未指定(即auto),来填充可用的垂直空间。它们同样可以通过指定left 和 right并将width 指定为auto来填充可用的水平空间。

    ​ absolute: 相对于最近的非 static 定位祖先元素的偏移,来确定元素位置;可以创建 BFC,不会出现外边距合并

    ​ fixed: 相对于屏幕视口定位。当元素祖先的 transform, perspectivefilter 属性非 none 时,容器由视口改为该祖先。

  4. 粘性定位 sticky

    ​ 元素根据正常文档流进行定位,然后相对它的最近滚动祖先和最近块级祖先,包括table-related元素,基于top, right, bottom, 和 left的值进行偏移。偏移值不会影响任何其他元素的位置。

    ​ 注意,一个sticky元素会“固定”在离它最近的一个拥有“滚动机制”的祖先上(当该祖先的overflowhidden, scroll, auto, 或 overlay时),即便这个祖先不是最近的真实可滚动祖先。

    ​ 粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。

2 js 基础

2.1 Event Loop

参考资料:

Philip Roberts的演讲《Help, I'm stuck in an event-loop》

阮一峰老师 再谈Event Loop

基本过程:

  1. js 是单线程的,同一时间只能做一件事,js 设计为单线程的原因一部分是由于会操作 dom、与用户交互,如果多线程操作会出现问题。
  2. 单线程带来的问题是,任务必须排队,一个任务执行完才能进行下一个任务,如果一个任务执行时间很长,就会阻塞下一个任务的运行,有些任务不需要立即知道它的返回结果,可以先执行其它的,等到有结果了再回来处理。
  3. 将任务类型分为了两种,同步任务和异步任务。同步任务是指在主线程上执行的任务(形成调用栈),异步任务是有执行结果后加进任务队列;待主线程调用栈的同步任务被执行完毕后,才会去检查、执行任务队列中的任务,直到任务队列被清空,进入下一轮事件循环。

常见的宏、微任务

  • task(宏任务) :setTimeout、setInterval、script(整体代码)、 I/O 操作、UI 渲染等。
  • micro-task(微任务) : new Promise().then(回调)、MutationObserver(html5新特性) 等。

浏览器的与nodejs 的有什么区别?

  • 在 node v11.0 之前,node 是先执行完此轮事件循环的所有宏任务再去执行此轮的微任务,而浏览器中是先执行完一个宏任务的所有微任务,再去执行下一个宏任务
  • 在 node v11.0 之后,两者均为先执行完一个宏任务里的所有微任务,再去执行下一个宏任务。

这样不是违背了 promise 的设计初衷?它的目的不就是希望能提前到所有异步任务之前么

2.2 事件绑定的方式,捕获和冒泡触发顺序

2.3 原型,原型链,原型继承

2.4 跨域,原因及解决方案

cors

Options 预检请求

2.5 数据类型及获取数据类型的方式、原理,存在什么问题、隐式类型转换

2.6 闭包,使用场景,特点,如何清除

2.7 本地存储方案 cookie 和 storage 区别

2.8 this 指向情况,如何改变,箭头函数中的this

  1. apply,call区别;当第一个参数传 null 的时候 this 指向(意图是不提供this,如果非要说明指向应该是执行时所在作用域的那个this)
  2. bind 不能改变箭头函数的this指向,bind 和以上方法的区别

2.9 作用域

2.10 new 运算符发生了什么

// 构造函数
function Test(name) {
	this.name = name
}

// 实例
const test = new Test('zhangsan')

以上代码中 test 是由 Test 构造器创建而来的一个实例,内部的实现类似于

// 1. 创建一个普通对象
const test = {}

// 2. 继承构造器的原型
if(Test.prototype !== null) {
    test.__proto__ = Test.prototype 
}

// 3. 将新创建的对象作为构造函数的执行上下文
// 这也是为什么虽然继承的是 prototype 但是却可以访问到构造器中 this.xx 属性的原因
const func = Test.apply(test, [])

// 4.  如果上下文创建成功
if(typeof func === 'object' || typeof func === 'function') && func !== null){
    return func
}

// 5. 如果上下文创建不成功,直接返回普通对象
return test

解释以下来自 MDN 的代码

function Car() {}
car1 = new Car();
car2 = new Car();

console.log(car1.color);    // undefined

car1.color = 'black'; // 定义在 car1 这个 {} 上了,所以只能在 car1 上访问到
console.log(car1.color);   // black
console.log(car2.color); // undefined 访问不到

Car.prototype.color = "original color";
console.log(car1.color); // original color 这里可以访问到的原因是 car1.__proto__ = Car.prototype
console.log(car2.color); // original color 这里可以访问到的原因是 car2.__proto__ = Car.prototype


console.log(car1.__proto__.color) //original color
console.log(car2.__proto__.color) //original color
console.log(car1.color)  // black
console.log(car2.color) // original color

2.11 null 和 undefined 的区别

2.12 为什么 var 会有声明提升这样的设计

2.13 es 模块化与 commonjs 有什么样的区别

2.14 使用setInterval 去定时执行一个异步任务和使用setTimeout 重复执行有什么样的区别?

​ setInterval 时间间隔一定,不管上次有没有执行完,到时间后都会去执行第二次

​ 使用 setTimeout 可以保证上次任务执行完再去执行下一次,但是不能保证时间间隔是所设置的那个

2.15 防抖和节流,实现方法和使用场景

2.16 深浅拷贝手写

2.17 let cont var 区别

2.18 for of 和 for in 区别

2.19 数组去重方式

2.20 模块化

2.21 Set,Map数据结构和 Object

Set

  1. 无重复值的集合,值既可以是基本数据类型也可以是引用数据类型;内部判断值是否相同的算法叫做 'Same-value equality',类似于全等运算符,主要区别是此算法中 NaN 会被认为是相等的;
  2. 可以接受具有 Iterable 接口的数据格式作为参数初始化;
  3. 可以使用 for...of 进行遍历。

WeakSet

  1. 成员 只能是引用数据类型,并且是弱引用(垃圾回收机制不考虑 WeakSet 本身对该对象的引用,只要其它地方没有再引用此对象,就会回收自动回收所占的内存),常用于 防止引用结束后忘记取消引用而导致内存泄漏 的问题;
  2. 由于成员是弱引用,不确定何时会被垃圾回收机制回收,所以不可遍历。

Map

  1. 键值对的集合,解决传统对象只能用字符串作为对象键名的问题,可以使用各类型的值作为键名;
  2. 键值跟内存地址绑定

WeakMap

  1. 仅接受 引用类型 作为键名,并且是弱引用;

Object

  1. 只能使用字符串作为键名;
  2. 键名在某些情况下是会有顺序的(数字)

2.22 Promise

Promise.all

  1. 接收一个具有 iterable 接口的参数(Array,Map,Set),只返回一个 Promise 实例
1.如何实现一个promise.all 
function promiseAll(promiseList) {
    return new Promise((resolve, reject) => {
        let count = 0
        let valueList = new Array(promiseList.length)

        promiseList.forEach(promise => {
            Promise.resolve(promise).then(result => {
                count++
                valueList[count] = result  
                if (count === promiseList.length) {
                    resolve(valueList)
                }
            }, err => reject(err))
        })
    })
}

2.23 隐式数据类型转换(Implicit Type Conversion)

1 逻辑语句中的数据类型转换

2 == 相等判断的数据类型转换

3 加减乘除

    • 只要有一个是 string 类型的,均会被转换为 string 类型;
  • 减乘除

3 vue

3.1 vue 响应式原理

​ vue 进行初始化时,会在beforeCreate 和 created 两个生命周期之间调用 initState 对数据进行初始化,这些数据包含(data、props、watch、computed、methods 选项),在 initProps 和 initData 两个方法中对 data 和 props 选项的数据添加了响应式处理。

数据劫持:借助 Object.defineProperty 修改 data 和 props 对象的现有属性,为其添加存取拦截器,并且将 this.property 代理到this._props[property] this._data[property] 去,可以直接访问属性,到这里我们就可以拦截到数据的存取操作了;

依赖收集:拦截数据的访问,每个组件实例都会对应一个渲染 watcher 实例,每个数据定义为响应式数据时都会创建一个 dep 实例,组件渲染过程中访问此数据时,将此数据收集为此 watcher 的依赖项;

派发更新:当触发数据更新时,通知所有依赖此数据的 watcher 进行更新, dep.notify,也就是调用了watcher.update 更新视图;

到底谁作为谁的依赖呢 watcher依赖 dep

vue响应式-1

vue响应式-2

3.2 虚拟dom(VNode树)有什么优缺点

React由于只触发更新,而不能知道精确变化的数据,所以需要diff来找出差异然后patch差异队列.

Vue采用数据劫持的手段可以精准拿到变化的数据,为什么还要用虚拟DOM,仅仅是为了跨平台?

虚拟 dom 的最终结果也需要去操作真实 dom ,那和直接操作真实 dom 有什么区别呢?

作用:

​ 通过建立虚拟 DOM 来追踪自己要如何改变真实 DOM。VNode 是对真实 dom 结构的一种抽象描述,用于数据更新后修改 dom时,做一个集中化的处理,避免繁琐的手动处理;并且配合 diff 算法,可以在某些情况下,对节点复用,减少dom 更新,以达到提高渲染性能的目的。

原生 DOM 操作 vs. 通过框架封装操作。

这是一个性能 vs. 可维护性的取舍。框架的意义在于为你掩盖底层的 DOM 操作,让你用更声明式的方式来描述你的目的,从而让你的代码更容易维护。没有任何框架可以比纯手动的优化 DOM 操作更快,因为框架的 DOM 操作层需要应对任何上层 API 可能产生的操作,它的实现必须是普适的。针对任何一个 benchmark,我都可以写出比任何框架更快的手动优化,但是那有什么意义呢?在构建一个实际应用的时候,你难道为每一个地方都去做手动优化吗?出于可维护性的考虑,这显然不可能。框架给你的保证是,你在不需要手动优化的情况下,我依然可以给你提供过得去的性能。

作者:尤雨溪
链接:https://www.zhihu.com/question/31809713/answer/53544875
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

优点:

  1. 组件和节点的抽象能力。
  2. 提供了跨平台的能力。服务器渲染、weex 开发。
  3. 保证性能下限,普适多种操作场景。在某些情况下,通过 diff 算法找出可以复用的节点,减少操作 dom,提升性能
  4. 可维护性。封装底层操作,避免大量的手动处理。

缺点:

  1. 性能未必是最优的。 在任何情况下,重新渲染之前都会走一遍 diff 的逻辑,并且最终依然要操作真实的 dom。

3.3 diff 算法, key 的作用,为什么不推荐 index 作为key

diff 算法解析

diff 算法只会进行同级节点对比,复用已有的元素而不是从头渲染,加快渲染速度

1、新老开始
2、新老结束
3、老开始,新结束
4、老结束,新开始
5、没有节点可以复用,去老节点中找key 和当前新节点的 key 值一样的节点,如果找到了,判断为相同节点说明可以复用,直接移动他的位置,节点不相同仍然不可复用;如果没找到,说明没有节点可以复用,需要重新创建节点dom,插入

问题:

  1. 什么情况算是 sameVnode
  • 新旧节点的 key 值相同、并且节点内容完全一致
  1. 节点是怎么被复用的
    • 完全相同的节点,克隆节点,直接在旧节点上移动位置,避免重新创建 dom 节点
    • 最小化更新。如果标签相同,内容数据不同,仅更新数据;子节点不同,仅更新子节点,省去移除原节点,创建新节点的开销。

key 的作用:

​ key 的作用是是相同类型的节点进行区分,让 vue 知道该更新哪个,以及在哪个位置更新。

最终目的是尽可能的复用 dom,当渲染列表或使用if else 渲染两个相同标签的内容时,如果创建的结果只是内容顺序变了,如果没有 key ,还是会一个个的更新内容,如果有key 了,只需去看对应key 的节点是否一致就好了,不一致就更新,一致说明可以复用,只是原来的那个 key 的标签位置不同了,也是不需要再重新修改的。

例如:下一次数据更新后节点没有新增或者减少,内容也没有变化,仅仅是列表的顺序换了,这时候如果没有 key,那么从开始到结束的标签内容都需要重新更新一遍的;有了key以后,不论标签的位置在哪,如果key 相同,说明还是原来的那个标签,然后再去看他内容是否变了,不变可以直接复用,变了再去修改,这样就达到了一种减少操作dom 的目的。

为什么不推荐将 index 作为 key?

​ 从上面的描述中可以知道,如果使用 index 作为 key 就没有意义了,列表创建的新vnode key 顺序不会变化,但是并没有对应上旧的 vnode,此时跟不绑定本质key 没什么区别。

3.4 keep-alive 作用,怎么实现的

作用:缓存动态组件/router view状态,可以避免组件重复创建和渲染,提升系统性能

实现:缓存节点,首次生成后不再重新执行组件的创建过程,可以在缓存中直接拿到渲染的节点,只会有 activated/deactivated 两个生命周期了

  • mounted (初次渲染,后续不会再执行)/activated/deactivated

  • 不会渲染实体节点,是一个抽象组件

  • 在内存中缓存

  • 缓存最大值的处理方式,再缓存新的节点时,判断是否超过缓存最大值,超过则采取 LRU 最近最少使用的原则,删除最早的节点;要注意,在页面需要渲染的组件重新命中缓存时,需要重新将其加入缓存以更新缓存;清理缓存时正在使用的组件不可删除;

3.5 nextTick 作用,原理

作用:将回调函数延迟到下次 DOM 更新循环之后执行,保证在回调函数中,获取的dom 是最新更新完成的 dom。

原理:

Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。利用事件循环机制,在同步任务,也就是对数据的修改全部执行完毕后,再进行dom更新 。

当修改数据时,dom 不会立即重新渲染,如果需要对更新后的 dom 做点什么就需要使用nextTick处理

3.6 vue 生命周期做了哪些事情

3.7 插件机制

3.8 混入策略

  1. 同时传入多个 mixins,mixins 的优先级,从左到右的顺序从先到后。

  2. data,props,methods watch,computed等对象选项合并时,键名冲突时以组件数据优先。

  3. 生命周期钩子函数合并时,合并组件自身和 mixins 中的同名生命周期钩子函数为一个数组,混入对象的钩子函数先执行,组件中的后执行。

  4. 可以自定义选项合并策略

3.9 watch 和 computed 的区别,computed 的依赖缓存是指什么,怎么缓存的,什么时候失效?

watch 和 computed 的区别

computed

  1. 初始化时为 computed 的每个属性生成一个watcher 对象(computedWatcher)
  2. 在computed求值的时候,它会先把自身也就是 watcher 本身赋值给 Dep.target 这个全局变量。
  3. 然后求值的过程中,会读取到响应式数据,那么响应式数据的 dep 就会收集到这个 watcher 作为依赖。
  4. 下次computed 依赖的响应式数据更新了,就会从 dep 中找出它收集到的 watcher,触发 watcher.update() 去更新computed 的值。
  5. 那么缓存是指什么呢?其实是指computed 第一次求值后,会将 dirty 标志位置为 false,在下次 get computed 属性的时候如果为 false 不再重新计算属性的值;
  6. 那么缓存什么时候会失效呢或者说如何更新这个缓存?我们刚刚注意到computed 会生成一个watcher,并且依赖了响应式数据,当依赖的响应式数据变化后,会notify 相关 wathcer,notify 中会调用 watcher 的 update 方法,而 update 方法中会将 dirty 这个标志位重新置为 true,然后调用 get 触发 getter重新求 computed 属性的值,此时标志位为true,会重新计算;如果页面中有使用到这个 computed 属性,computed 属性 getter 方法将此属性作为进组件渲染 watcher 的依赖,computed 的属性变化又会去通知视图更新

3.10 vue2.x 不能检测到哪些情况的更新,为什么,proxy 为什么可以,object.defineproperty 不能根据数组索引设置get/setter 吗,如果能又是处于什么样的原因不做处理呢?

不能检测到数据更新的情况有:

3.11 挂载时生命周期函数的执行顺序

beforeMounted 先父后子

mounted 先子后父

下面这个需要重新确认一下

beforeDestory 先子后父

destoryed

3.12 vue style scoped 的用途和原理

作用:为 scoped style 的模块在 dom 及 css 样式上增加了唯一的标记,保证样式私有化,不影响全局。

缺点:

  1. 其它模块的同名css 选择器如果为全局样式,仍然会对此模块的样式产生影响,只是此模块设置的样式不会影响到其它模块了。
  2. 由于为选择器添加了[v-data-] 选择器,会影响优先级。

具体是怎么实现的呢?借助了什么工具?

vue-loader 为 vue 文件添加唯一标记,css-loader 为 css 添加唯一标记,

3.7 vue-router 原理

3.8 vuex原理

使用全局 mixins 在 beforeCreate 生命周期,执行 vuexInit 方法,Vue.mixin({ beforeCreate: vuexInit })

3.9 vuex 为什么推荐使用 Mutations 修改数据,而不是直接修改 state 数据

mutation 是唯一改变 state 中数据的方法,是同步的;action 提交的也是 mutation,而不是直接修改状态

4 前端工程化

4.1 loader 和 plugin 的区别,实现原理?plugin 调用过程

  1. loader 它是一个转换器,用于编译指定类型的代码,plugins 是一个扩展器,扩展 webpack 的功能,能够被用于执行更广泛的任务比如打包优化、文件管理、环境注入等

4.2 按需引用的原理

4.3 分包的方式

4.4 如何分离 css 和 js 文件

4.5 webpack 是做什么的,解决了什么样的问题?

静态模块打包器,高效的管理和维护项目中的资源。

代码转化:typescript 转换为 js,scss 转换为 css,vue 转换为 html,css,js (借助 loader)

文件优化:压缩代码,压缩图片,混淆代码等

代码分割:提取公共文件,文件懒加载,避免文件体积过大分包

模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件

4.6 webpack 的构建流程是什么样的?

4.7 webpack loader 加载顺序

  1. webpack 在 loader 配置中的加载 loader 顺序是相同优先级的 loader 从右向左,从下到上的,这是因为 webpack 选择了 compose 方式(从右到左)去组合函数而不是 pipe 方式(从左到右)。

  2. 几种 loader 分类

    前置 pre

    普通 normal

    内联 inline

    后置 post

    优先级 pre > normal > inline > post

5 性能优化

5.1 ssr 原理,有什么样的优点

使用 node 做中间层,与后端 api 请求数据,拿到数据后借助 vue 预先在服务器上生成 html 结构,返回到前端渲染

优点是 seo,首屏渲染性能(不需要先下载一堆 js 和 css 解析后才能看到页面)

webpack

图片

请求

dns

vue

缓存

6 计算机网络

6.1 https/ TLS handshake

6.2 TCP 三次握手,四次挥手

tcp 是面向字节流的,但传输的数据单元是报文段(首部+数据)

每个tcp 连接的两个端点是 socket ip:port

SYN = 1 表示这是一个连接请求或连接接受报文

ACK = 1 表示确认号 ack 有效

三次握手

  1. Client 主动发起一个请求,seq = x,SYN=1
  2. Server 返回确认信息 seq = y,SYN = 1,ack = x+ 1, ACK = 1
  3. Client 再次确认 seq = y + 1,ACK = 1

四次挥手

为什么要进行三次握手而不是两次?

6.3 http 1.0/1.1/2.0/3.0 存在的问题及改进方法

to do 这里需要明确以下所指的多个连接 是指 tcp 和 http ,区分清tcp连接和http请求

一篇比较不错的文章

HTTP 1.0

  1. 连接无法复用:HTTP 1.0 时,一个 tcp 连接只能进行一次请求响应,下一次请求需要重新建立 tcp 连接。
  2. 队头阻塞(head-of-line blocking):只有前一个连接结束后才能进行下一次连接。

HTTP 1.1

  1. keep-alive:解决连接复用的问题。在同一个 tcp 连接上可以进行多个 http 请求,可以设置tcp 最大连接时间和最大的 http 连接数量,但是每个请求仍然是依次响应的,只有前一个 http 请求响应结束才能进行下一个。
  2. pipelining:解决队头阻塞的问题。可以将多个 http 请求发送到服务器,但依然是依次响应。并且此解决方案存在一些限制和问题:
  • 如果希望能够多个请求并行执行,就要建立多个连接,但是浏览器对于每个域名会限定连接数量,不同浏览器会有所不同,并且多个连接建立与关闭非常浪费时间;
  • 只有幂等的请求(GET,HEAD)能使用 pipelining,非幂等请求比如POST不能使用,因为请求之间可能会存在先后依赖关系;
  • 服务器的响应只能够一个接着一个的按请求的顺序返回响应 (但各大浏览器有些不支持 / 默认关闭) ;
  • 初次创建连接时不应启动此机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议;
  • HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可。
  1. 首部字段未压缩导致不必要的网络流量:在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。

随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。

  1. 不支持资源优先级,导致底层 tcp 连接使用不当:重要的请求不能优先接收响应。

HTTP 2.0

​ 并没有更改核心概念及语义,只是更改了数据的格式化方式以及如何在 server 和 client 之间传输的方式,主要解决一些性能方面的问题。

​ 新增的二进制帧层与 1.x 不兼容,所以版本增加至 2.x。

  1. 单一连接多个请求,多路复用(流技术)

    仅使用一次连接,即可同时进行多个请求与响应数据流的交错传输。

    http2-stream

  2. 二进制 分帧(相对于 plaintext 更快更正确的解析)

​ HTTP 2 将数据报首部字段和消息主体部分分成小的消息片段和帧,并且每一部分都是由二进制格式编码。把 tcp 数据报的头部和数据部分分成了 header frame 和 data frame。也就是头部帧和数据体帧。帧的传输最终在流中进行,头部(header)帧 和 data 帧可以分为多个片段帧。

http2-frame

  1. 首部字段压缩:它允许通过一个静态Huffman代码对传输的报头字段进行编码,这减少了它们各自的传输大小。client 与 server 维护一份相同的静态表(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合。client 与 server 维护一份相同的动态表(Dynamic Table),可以动态地添加内容。在传输时使用表的 key 值代表,并对 key 进行哈夫曼编码压缩,减少传输数据。

  2. 数据流优先级:重要请求优先获得响应,根据首部字段的权重为每个请求分配资源。

  3. server push:在某次请求中,server 判断哪些资源也会被需要,就会在此次响应中一并返回,不需要 client 再明确的一个个去请求这些文件,从某种程度上来说减少了延迟。

  4. 流控制

    • 防止发送的数据,接收方不希望或不能够处理。发送方调节发送的数据量或者接收方调节接收的数据量。

    • 与 tcp 流控制不同,TCP流控制的粒度不够细,并且没有提供必要的应用程序级api来规范各个流的交付。

    • 定向:每个 receiver 可以选择为每个流和整个连接设置它想要的任何窗口大小。

    • 基于信用的:每个 receiver 通知初始连接和 stream 流控制窗口大小(bytes)

    • 无法禁用流控制:一旦连接建立,client 和 server 交换 SETTINGS 帧后,就无法禁用。当client 发送数据,server 接收数据后会发送一个 WINDOW_UPDATE 帧更新可用窗口的大小。

    • 逐段的,不是端到端的。也就是说,中介者可以使用它来控制资源使用,并根据自己的标准和启发来实现资源分配机制。

    • 控制算法由 client 和 server 自己实现,http 2 并没有实现,使终端实现自己的调节资源的分配与使用策略。

6.4 http 缓存,强缓存与协商缓存

强缓存

  1. 如果命中不会与服务器端进行交互, 如果命中,返回 200 状态码 (from disk or from memory)

HTTP1.1 Cache-Control:

  • max-age: 资源能够被缓存的最大时间,单位秒

  • no-cache:强制确认缓存。每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。

  • no-store:禁止缓存,每次请求都要向服务器重新获取数据。

  • public:响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。

  • private:浏览器缓存。响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。

  • s-maxage:同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。

  • must-revalidate:意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。

HTTP1.1 Expires(比较旧):缓存的过期时间

  • 两者同时存在时 cache-contol 的优先级高于 expires

HTTP1.0 Header 属性之 Prama(比较旧):

  • 与 cache-control:no-cache 作用相同,同时存在时,prama > cache-control > expires

max-age 和 expires 什么区别

​ max-age: 距离上次请求更新内容后的时间,可以存活的时长,单位秒,

​ expires: 缓存的过期日期,截至日期

  1. 强缓存存储位置
  • 强缓存才会存储在 硬盘 或 内存 中;

  • 状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。

  • from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。

内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:

  • 快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。

  • 时效性:一旦该进程关闭,则该进程的内存则会清空。

硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。

协商缓存

  1. 无论是否命中都需要与服务器端交互,判断资源是否可用,如果可用之返回状态304,不返回实体,可节省一些带宽,如果不可以再返回200,并返回最新资源实体。

Last-modified/If-Modified-Since:服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。

注意:但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag。

Etag/If-None-Match:由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd,服务器检查 ETag,返回 304 或 200。

关于 last-modified 和 Etag 区别

  • 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。

  • Last-modified 只能精确到秒。

  • 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变。

  • Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。

6.5 tcp 与 udp 的区别

  1. tcp 面向连接,udp 无连接
  2. tcp 只能一对一通信,udp 可以一对一,一对多,多对多(单播,多播,广播)
  3. tcp 面向字节流,udp 面向报文
  4. tcp 可靠传输(拥塞控制,错误重传),udp 不可靠传输
  5. tcp 适用于需要数据可靠的情况,udp 适合实时性高的情况,不考虑数据丢失情况(视频会议,直播等)

6.6 http 与 https 区别

6.7 Websocket 原理,对比

websocket 与 http 长连接,多路复用的对比

6.8 TCP 可靠传输的原理

差错重传

  1. 停止等待协议
  • 停止等待:A 向 B 发送给数据,每发送一个分组,都需要等待 B 返回确认的消息才会传输下一个;

  • 超时重传:如果在传输过程中数据丢失 或 B在接收数据检测出现了差错,此分组就会被丢弃,等到发送方 A 在一段时间内(超时计时器)未接收到来自接收方 B 发来的确认消息时就会重传这个分组,A在发送分组后会保存一份分组的副本,直到接收到确认消息后再清空。

  • 确认消息的丢失或迟到:由于来自接收方的确认消息迟到或丢失,发送方A会在超时计时器到期后重传分组;此时B收到了重复的数据,丢掉这个重传的数据,并且仍然要向 A 发送确认消息,否则A会不断重传。

拥塞控制

  1. 连续ARQ(自动重传请求)协议

    ARQ 表示重传的请求是自动进行的,接收方不需要向发送方请求重传某个分组。

  2. 滑动窗口协议

7 其它工具

7.1 git

git 常用命令,git flow 工作流

8 Web 安全

8.1 SYN 泛洪攻击

8.2 CSRF 跨站请求伪造

** 原理 **

    攻击者基于服务器对访问用户的信任,窃取浏览器中存储(一般是 cookie)的已授权的用户信息,伪造正常请求对服务器做一些恶意操作
  1. 用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;

  2. 在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;

  3. 用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;

  4. 网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;

  5. 浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。

** 解决方案 **

  1. 验证 HTTP 头中的 refer 字段,判断请求来源(用户可能未提供 refer 或 refer 值被篡改)

  2. 在请求地址中添加 token 并验证(关键在于在请求中放入黑客所不能伪造的信息)

8.3 XSS 跨站脚本攻击

8.4 重放攻击

​ 入侵者 C 可以从网络上截获 A 发送给 B 的报文,C 不需要破译这个报文,而是直接把这个由 A 加密的报文发送给 B,使 B 误认为 C 就是 A,然后 B 就向伪装成 A 的 C 发送本应该发送给 A 的报文。

解决方案:时间戳 或 随机数

9 图片

9.1 jpg, png ,svg,webp 图片的区别*

(这几种应该都是位图吧,矢量图是 svg)

从以下几点进行对比:

  • 是否支持无损压缩

  • 色彩丰富度

  • 是否支持透明(做 icon的时候有用,背景透明)

  • 是否支持动态效果

  • 文件体积

首先明确几个概念:

色彩丰富度:跟图片格式的位数有关系,位数表示的是一个像素点,支持显示的图片颜色数量,比如8位格式的图片,一个像素点可以支持 2 的8次方中颜色的显示,那么图片格式位数越多,图片色彩就可以更丰富。

**透明通道:**1个8位的灰度通道(256位,也就是rgba 的 a );黑表示不透明,白表示透明,灰是指半透明;指每个像素点的颜色是否支持使用透明度;是否支持透明很重要,在某些情况下需要部分区域透明的图片,此时只能使用支持透明度的格式。

有损/无损压缩:

为减小文件体积对文件进行压缩,什么时候需要进行压缩,是存储时自动压缩的吗,怎么压缩

  • 有损压缩。指在压缩文件大小的过程中,损失了一部分图片的信息,也即降低了图片的质量,并且这种损失是不可逆的,我们不可能从有一个有损压缩过的图片中恢复出全来的图片。常见的有损压缩手段,是按照一定的算法将临近的像素点进行合并。
  • 无损压缩。只在压缩文件大小的过程中,图片的质量没有任何损耗。我们任何时候都可以从无损压缩过的图片中恢复出原来的信息。
格式 jpg/jpeg png apng gif webp
压缩 有损 无损 无损 无损/有损均支持
色彩 8,24位 8位
透明 不支持 支持 支持
动态 支持 支持 支持
体积

9.2 字体图标和 svg 的区别

​ 采用 Iconfont 字体图标替换项目中图片图标的使用,以达到缩减体积、风格统一、提高开发效率等目的。若配合设计师使用,设计师可在平台上管理图标,复用图标,减少设计图标耗费的时间,而开发只负责使用设计师维护好的图标库,减少了与设计师的交流成本。

共同优点:

矢量图可缩放,不会失真

相对于切图能够缩减体积、风格统一、提高开发效率

区别:

​ svg 如果是使用设计师导出的文件的话,需要在每次使用时引入,此时不支持调整颜色

​ iconfont 可以通过设置字体颜色属性设置颜色

优点 缺点
svg 1. 一个图标可以拥有多种色彩
2. 不会有锯齿
1. 如果是svg 文件的话本身带的颜色无法覆盖
2. 如果是 svg 文件的话需要在每次使用的时候引入,产生额外的请求
iconfont 1. 本身是一个字符
2. 可通过 css 修改颜色、大小
3. 体积更小
4. 易于集中维护
5. unicode 、font-class 方式使用时只支持单色,symbol 使用方式时支持多色
6. 只需要导入一次,到处使用
1. 定位不准:这个大多数前端开发应该都有感触,典型就是复选框和文字标签对不齐,要检查图标的 CSS line-heightvertical-alignletter-spacingword-spacingdisplay, 寻找对不齐的原因
2. 可能会出现锯齿

10 可视化

10.1 canvas 动效,svg 动效

vue 数据变化检测的问题

1、vue 是如何实现响应式的?(数据变化触发视图更新)
2、哪些情况下不能在数据变化后触发视图更新?为什么?
3、如何解决上述问题 2 ,原理又是什么?
4、为什么数组push 、shift 等一些操作可以触发视图更新?

外边距合并

一、情景:

  1. 父子元素

    1.1 父元素与第一个子元素的 margin-top(无法将两者的 margin-top 隔开):

    父元素不存在 border-top

    父元素不存在 padding-top

    父元素与子元素间不存在内容

    父元素未创建 BFC

    子元素未设置浮动

    1.2 父元素与最后一个子元素 margin-bottom

    父元素不存在 border-bottom

    父元素不存在 padding-bottom

    父元素与子元素之间不存在内容

    父元素不存在 height 或 min-height 或 max-height

    子元素不存在 height 或 min-height 或 max-height

  2. 相邻元素之间左右边距(除非最后一个元素清除前一个元素浮动)

  3. 空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)

    块级元素没有 height 或 min-height

    块级元素没有 border-top 或 border-bottom

    块级元素没有 padding-top 或 padding-bottom

    块级元素没有内容

二、计算方式:

​ 两正值取最大

​ 一正一负相加

​ 两负值取绝对值最大的负值

http 缓存

强缓存: 如果命中不会与服务器端进行交互

  1. HTTP1.1 Cache-Control:
  • max-age: 资源能够被缓存的最大时间,单位秒
  • **no-cache:**强制确认缓存。每次有请求发出时,缓存会将此请求发到服务器(译者注:该请求应该会带有与本地缓存相关的验证字段),服务器端会验证请求中所描述的缓存是否过期,若未过期(注:实际就是返回304),则缓存才使用本地缓存副本。
  • no-store:禁止缓存,每次请求都要向服务器重新获取数据。
  • public:响应可以被任何对象(发送请求的客户端、代理服务器等等)缓存。
  • private:浏览器缓存。响应只能被单个用户(可能是操作系统用户、浏览器用户)缓存,是非共享的,不能被代理服务器缓存。
  • s-maxage:同 max-age,覆盖 max-age、Expires,但仅适用于共享缓存,在私有缓存中被忽略。
  • must-revalidate:意味着缓存在考虑使用一个陈旧的资源时,必须先验证它的状态,已过期的缓存将不被使用。
  1. HTTP1.1 Expires(比较旧):缓存的过期时间

  2. HTTP1.0 Header 属性之 Prama(比较旧) :与 cache-control:no-cache 作用相同,同时存在时,prama > cache-control > expires

  3. 两者同时存在时 cache-contol 的优先级高于 expires

max-age 和 expires 什么区别

  • max-age: 距离上次请求更新内容后的时间,可以存活的时长,单位秒,

  • expires: 缓存的过期日期,截至日期

  • 两者同时存在时 max-age > expires

协商缓存: 无论是否命中都需要与服务器端交互,判断资源是否可用,如果可用之返回状态304,不返回实体,可节省一些带宽,如果不可以再返回200,并返回最新资源实体。

  1. Last-modified/If-Modified-Since:服务器端资源的最后修改时间,响应头部会带上这个标识。第一次请求之后,浏览器记录这个时间,再次请求时,请求头部带上 If-Modified-Since 即为之前记录下的时间。服务器端收到带 If-Modified-Since 的请求后会去和资源的最后修改时间对比。若修改过就返回最新资源,状态码 200,若没有修改过则返回 304。
  2. 但是如果在本地打开缓存文件,就会造成 Last-Modified 被修改,所以在 HTTP / 1.1 出现了 ETag
  3. Etag/If-None-Match:由服务器端上生成的一段 hash 字符串,第一次请求时响应头带上 ETag: abcd,之后的请求中带上 If-None-Match: abcd,服务器检查 ETag,返回 304 或 200。

关于 last-modified 和 Etag 区别

  • 某些服务器不能精确得到资源的最后修改时间,这样就无法通过最后修改时间判断资源是否更新。
  • Last-modified 只能精确到秒。
  • 一些资源的最后修改时间改变了,但是内容没改变,使用 Last-modified 看不出内容没有改变。
  • Etag 的精度比 Last-modified 高,属于强验证,要求资源字节级别的一致,优先级高。如果服务器端有提供 ETag 的话,必须先对 ETag 进行 Conditional Request。

vue 3 变化

New Features

  1. setup 选项,组合 API
  • 同一个逻辑关注点相关的代码配置在一起

  • setup 参数为 (props, context),在组件创建前执行,由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法;

  • 可以在其中注册生命周期钩子方法,钩子方法需要加 on 前缀;

  • 可以使用 provide 和 inject 方法;

  • 从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板;

  • 还可以返回一个渲染函数

  1. 新的 ref 函数,使任何响应式变量在任何地方起作用,依然可以用 watch 监听变化

toRefs 创建对prop的 user property 的响应式引用 demo ,

const { user } = toRefs(props)

但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性;所以使用 toRef 创建对 props 的引用,并且能保证它的响应式
  1. reactive 方法,定义一个新的响应式属性(和 ref 创建有什么不同?)

  2. Teleport :想要把某些关注点逻辑相同的ui组件放在一起,但是会需要使用css 改变定位及层级问题,当嵌套过深或比较复杂时会难以处理,Teleport 提供了一种方法,可以将组件的某一部分渲染到指定标签或css 选择器指定的元素上。

  3. 片段:支持多个根节点,但是,这要求开发者显式定义 attribute 应该分布在哪里;

Breaking Changes

  1. 移除了 filters,使用 computed 代替,全局filters 可以替换为定义在全局上的方法,globalProperties
  2. data 选项的数据在合并时是浅复制,直接覆盖;data 定义现在必须是一个返回object 的函数,不可以是纯 object;
  3. v-for 中绑定ref 将不再自动生成数组,vue3 中可以将 ref 绑定到函数上,在函数中处理
  4. 异步组件需要使用 defineAsyncComponent 助手方法创建;
  5. $on,$off 和 $once 实例方法已被移除;
  6. v-if/v-else/v-else-if 的各分支项 key 将不再是必须的,vue 会自动生成;<template v-for> 设置 key 的位置变化;
  7. v-on 的 .native 修饰符已被移除。同时,新增的 emits 选项允许子组件定义真正会被触发的事件;(inheritAttrs 设置);
  8. 两者作用于同一个元素上时,v-if 会拥有比 v-for 更高的优先级。(2.x 是 v-for 优先级更高);
  9. 当使用 watch 选项侦听数组时,只有在数组被替换时才会触发回调。换句话说,在数组改变时 watch 回调将不再被触发。要想在数组改变时触发 watch 回调,必须指定 deep 选项。

call apply bind 的区别

  1. 三者的作用

    改变调用此方法的函数的this 指向

  2. call

    参数列表

  3. apply

    参数传一个数组

  4. bind

    参数列表

  5. 三者区别

  • call 和 apply 的主要区别是参数形式不同
  • call,apply 和 bind 的主要区别是bind 仅仅是改变目标 this 指向,需要手动调用目标函数执行,而前两者在调用call,apply 后会立即执行。
  • call,apply 被调用后返回的值是目标函数被改变this 指向后的运行结果,而 bind 返回的是一个改变了目标函数指向的新的函数(拷贝),需要手动调用新生成的这个函数才会生成运行结果

事件处理模型

<div class='parent'>
    <div class='child'>
	</div>
</div>

// 形式一:均在捕获
parent.addEventListener('click', e => {
   console.log(1);
})
child.addEventListener('click', e => {
   console.log(2);
})

// 形式二:parent 在捕获,child 在冒泡
parent.addEventListener('click', e => {
   console.log(1);
}, true)
child.addEventListener('click', e => {
   console.log(2);
}, false)

// 形式三:均在冒泡
parent.onclick = function() {
	console.log(1)
}
child.onclick = function() {
	console.log(2)
}

// 形式四:parent、child 均在冒泡,child 阻止了事件冒泡
parent.addEventListener('click', e => {
   console.log(1);
})
child.addEventListener('click', e => {
   console.log(2);
    e.stopPropagation()
})

// 形式五:parent 在捕获,child 在冒泡,child 阻止了事件冒泡
parent.addEventListener('click', e => {
   console.log(1);
}, true)
child.addEventListener('click', e => {
   console.log(2);
    e.stopPropagation()
})

上面的事件,当点击 child 是,输出顺序是怎样的呢?

形式一:冒泡阶段执行,输出 2,1

形式二:parent 在捕获阶段,child 在冒泡阶段,输出 1,2

形式三:均在冒泡阶段执行,输出 2,1

形式四:均在冒泡阶段执行,但子元素阻止了事件冒泡,所以只输出 2

形式五:parent 在捕获,child 在冒泡,child 阻止了事件冒泡,依然输出 1,2

两种事件模型:捕获和冒泡

捕获(capture):从祖先元素开始向触发的目标元素开始触发事件,执行绑定在捕获阶段的事件

冒泡(bubbling):先从触发的目标元素开始触发事件,向祖先元素冒泡执行,执行绑定在冒泡阶段的事件

绑定事件的方式

w3c: addEventListener 第三个参数为 useCapture 是否使用捕获,默认 false,使用冒泡

IE 8.0 以下: 使用 attachEvent 仅支持冒泡

onclick: 仅支持冒泡

​ w3c 都是先捕获直到到达触发事件的目标元素,然后再从此元素向祖先冒泡;看父子元素初始化事件时,指定的是在捕获阶段执行还是在冒泡阶段执行,从而可以得出执行的先后顺序。

阻止事件冒泡和捕获

w3c的方法是e.stopPropagation(),IE则是使用e.cancelBubble = true

阻止默认事件
e.preventDefault
获取事件触发的目标元素
e.target/srcElement

获取当前执行事件的元素

e.currentTarget

new 运算符发生了什么?

自己实现

/**
 * constructor 一个指定对象实例的类型的类或函数
 * arguments  一个被用于 constructor 调用的参数列表
 */
function create(constructor,arguments){
    let obj = {} // 1.创建一个空的 js 对象,即 {}
    
    // 2.继承构造器的原型
    if(constructor.prototype !== null) {
        obj.__proto__ = constructor.prototype
   }
    
    // 3.将步骤1新创建的对象作为 this 的上下文
    let obj1 = constructor.apply(obj, Array.prototype.slice.call(arguments, 1))
    
    // 4.由构造器返回的对象就是 new 表达式的结果
    if((typeof obj1 === 'object' || typeof obj1 === 'function') && obj1 !== null){
        return obj1
    }
    
    // 5.如果构造器没有显示返回一个对象,而是基本类型或其它,则使用步骤1中创建的对象
    return obj
}

所以:

function Test() {
    this.name = 'myname'
}

let test = new Test()

//  每个对象 实例 都有一个构造器
test.__proto__ === test.constructor.prototype // true
test.__proto__ === Test.prototype // true

最大余额法计算百分比

计算规则:

​ 几个人根据选票数分配议席;

​ 假设 3 个人,争夺 100 个议席;
​ A 的票数 为 127,
​ B 的票数为 231,
C 的票数为 143,

​ 那么每个席位占有的选票的数目为 (127 + 231 + 143)/100 = 5.01

​ 那么 A 确定已经拥有的席位数为 127/5 = 25.349301...... 25
​ B 确定拥有的席位数为 231/5 .01= 46.107784.... 46
​ C 确定拥有的席位数为 143/5.01 = 28.542914.... 28;

​ 此时坐席还有 100 - 25 - 46 - 28 = 1 个;

​ 此时
​ A 的剩余票数 0.349301
​ B 的剩余票数 0.107784
​ C 的剩余票数 0.5429114;

​ 然后根据剩余选票多的人依次分配剩余席位;结果为 C 获得最终的 1 个席位;

Echarts 中的饼状图算法实现:

/**
 * @param {Array.<number>} valueList a list of all data
 * @param {number} idx index of the data to be processed in valueList
 * @param {number} precision integer number showing digits of precision
 * @return {number} percent ranging from 0 to 100
 */
const getPercentWithPrecision = (valueList, idx, precision) => {
    if (!valueList[idx]) {
        return 0
    }

    // 计算出总票数
    const sum = valueList.reduce((acc, val) => {
        return acc + (isNaN(val) ? 0 : val)
    }, 0)
    if (sum === 0) {
        return 0
    }

	// 精度,方便计算
    const digits = Math.pow(10, precision) 
	
    // 计算每个人至少确定拥有的席位;乘以 100 因为最后计算出来的是带百分比的结果
    const votesPerQuota = valueList.map((val) => {
        return (isNaN(val) ? 0 : val) / sum * digits * 100 
    })
    
    // 总的席位,带精度
    const targetSeats = digits * 100 
	
    // 设置至少确定拥有的席位(整数个的,向下取整)
    const seats = votesPerQuota.map((votes) => {
        // Assign automatic seats.
        return Math.floor(votes) 
    })

    // 计算出目前已经被占用的总席位
    let currentSum = seats.reduce((acc, val) => {
        return acc + val
    }, 0)

    // 计算出每个人剩余选票数
    const remainder = votesPerQuota.map((votes, idx) => {
        return votes - seats[idx]
    })


    // 如果当前剩余席位小于目标所有席位,进行分配
    // Has remainding votes.
    while (currentSum < targetSeats) {
        // Find next largest remainder.
        let max = Number.NEGATIVE_INFINITY // 最大的负数
        let maxId = null // 剩余票数最多的人的在数组中的 id

        for (let i = 0, len = remainder.length; i < len; ++i) { // 去检测每个人的剩余票数
            if (remainder[i] > max) { // 找出剩余票数最多的人
                max = remainder[i] 
                maxId = i // 找出剩余票数最多的人的 id
            }
        }

        // Add a vote to max remainder.
        ++seats[maxId] // 给这个人加上一个席位
        remainder[maxId] = 0  // 设置给加过票的这个人的票数最小,剩余的人中选出票数最多的
        ++currentSum // 将目前占用的席位加1
    }

    return seats[idx] / digits // 处理精度
}

外边距合并

一、情景:

  1. 父子元素
  • 父元素与第一个子元素的 margin-top(无法将两者的 margin-top 隔开):

    • 父元素不存在 border-top
    • 父元素不存在 padding-top
    • 父元素与子元素间不存在内容
    • 父元素未创建 BFC
    • 子元素未设置浮动
  • 父元素与最后一个子元素 margin-bottom

    • 父元素不存在 border-bottom

    • 父元素不存在 padding-bottom

    • 父元素与子元素之间不存在内容

    • 父元素不存在 height 或 min-height 或 max-height

    • 子元素不存在 height 或 min-height 或 max-height

  1. 相邻元素之间左右边距(除非最后一个元素清除前一个元素分浮动)

  2. 空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)

  • 块级元素没有 height 或 min-height

  • 块级元素没有 border-top 或 border-bottom

  • 块级元素没有 padding-top 或 padding-bottom

  • 块级元素没有内容

二、计算方式:

两正值取最大

一正一负相加

两负值取绝对值最大的负值

Websocket 封装

// 消息提示
import { Message } from 'view-design'
import { getType } from '@/scripts/utils'

const heartTimeout = 10 * 60 * 1000 // 10 min,心跳检测间隔时间
const reopenTimeout = 10 * 1000 // 重连间隔 10s

let urlPrefix = '' // weboscket 请求前缀

// 设置 weboscket 请求前缀
function setUrlPrefix () {
    const socketUrl = process.env.VUE_APP_SOCKET_URL

    if (!socketUrl) {
        const loc = window.location
        const protocol = loc.protocol
        const host = loc.host

        urlPrefix = `${protocol === 'https:' ? 'wss' : 'ws'}://${host}`
    } else {
        urlPrefix = process.env.VUE_APP_SOCKET_URL
    }
}

setUrlPrefix()

// socket 构造函数
function Socket (urlSuffix, msgCallback) {
    if (!urlSuffix || !msgCallback || getType(msgCallback) !== 'function') return

    this.conn = null // 连接实例
    this.urlSuffix = urlSuffix // url 前缀
    this.msgCallback = msgCallback // 接收到消息的回调函数
    this.heartCheckTimer = null // 心跳检测定时器
    this.reopenTimer = null // 重连定时器
    this.disconnectInitiative = false // 是否主动断开
}

Socket.prototype = {
    /**
     * 建立连接
     * @param { string } urlSuffix 连接的 url 后缀
     * @param { function } msgCallback  接收到消息后执行的回调函数
     */
    connect () {
        if (this.conn && this.conn.readyState === 1) { // 判断是否已经成功连接
            return
        }

        // 拼接请求 url
        const url = `${urlPrefix}${this.urlSuffix}`

        // 创建 Websocket 实例
        if (window.WebSocket) {
            this.conn = new WebSocket(url)
        } else {
            return Message.error('您的浏览器不支持WebSocket。请选择其他的浏览器再尝试连接服务器')
        }

        // 成功建立连接,检测连接状态
        this.conn.onopen = () => {
            this.heartCheck()
        }

        // 从服务器接受到信息
        this.conn.onmessage = (evt) => {
            if (!evt || !evt.data) { // 事件本身的 data字段
                return
            }

            const data = getType(evt.data) === 'string' ? JSON.parse(evt.data) : evt.data

            // 接收到的是 ack 表示确认心跳检测,不做处理
            if (data.data === 'ack') return

            // 执行回调函数
            this.msgCallback(data) // 传输内容的data字段
        }

        // 连接关闭
        this.conn.onclose = evt => {
            // 清空心跳的定时器,不需要再检测了
            this.heartCheckTimer && clearInterval(this.heartCheckTimer)

            // 非人为关闭需要重新建立连接
            if (!this.disconnectInitiative) {
                this.reconnect()
            }

            this.disconnectInitiative = false
        }

        // 连接错误,会同时触发 close 事件
        this.conn.onerror = () => {}

        return this
    },
    // 检测连接是否还存在
    heartCheck () {
        this.heartCheckTimer && clearInterval(this.heartCheckTimer)

        this.heartCheckTimer = setInterval(() => {
            this.send({ message: 'heartbeat', data: null })
        }, heartTimeout)
    },
    // 重连
    reconnect () {
        this.reopenTimer && clearTimeout(this.reopenTimer)

        // 避免持续重连持续触发错误
        this.reopenTimer = setTimeout(() => {
            this.connect()
        }, reopenTimeout)
    },
    // 发送数据
    send (data) {
        if (this.conn && this.conn.readyState === 1) { // 已连接状态可发送消息
            this.conn.send(getType(data) === 'object' ? JSON.stringify(data) : data)
        }
    },
    // 手动关闭连接
    disconnect () {
        if (this.conn) {
            this.conn.close()
        }

        this.disconnectInitiative = true
    },
}

export default Socket

箭头函数

  1. 不绑定 arguments ,但是可以访问到最近的非箭头父级函数的 arguments ;

  2. 不能作为构造函数使用,不能使用 new 关键字调用,不存在 prototype 属性。

  3. 不会创建自己的 this,只会从自己的作用域链的上一层继承 this;

  4. 无法使用 call,apply 改变 this 的指向,使用 apply,、call 调用传入的第一个参数会被忽略;

任务执行顺序测试

// ---------------------------------------  demo 1:promise 中 有 setTimeout 的执行顺序  -----------------------------
new Promise(function (resolve, reject) {
    console.log(1)

    Promise.resolve().then(function () {
        console.log(2)

        setTimeout(function () {
            console.log(3)
        }, 0)
    })

    setTimeout(function () {
        console.log(4)
    }, 0)
})

setTimeout(function () {
    console.log(5)
}, 0)

console.log(6)

/** 
 * 注意:一个事件循环只有一个 microtask Queue,可以有很多个 Task Queue
 *       microtask Queue 会在本轮事件循环结束执行,Task Queue 会在下一轮事件循环开始执行,所以才会有先执行宏任务这种说法
 *       setTimeout 的时间表示,延迟将回调函数添加进事件队列的时间
 *       使用await时,会从右往左执行,当遇到 await 时,会阻塞函数内部处于它后面的代码
 * 
 * //  第一次事件循环
 * 1.执行同步代码 new Promise 中的代码,首先输出 1
 * 2.遇到 Promise.resolve() 一个异步操作,将异步执行结果也就是 then 中的回调函数 放入 microtask Queue
 * 3.遇到输出4的 setTimeout 异步操作,主线程计时,到达时间后,将回调函数放入 Task Queue 中
 * 4.遇到输出5的 setTimeout 异步操作,主线程计时,到达时间后,将回调函数放入 Task Queue 中 
 * 5.输出 6
 * 
 * 6.此时此次事件循环结束,会按顺序(先进先出)执行 microtask 队列,执行所有 microtask Queue 中的任务
 * 7.取出输出 2 的那个 promise 回调函数执行,输出 2
 * 8.继续执行此 promise,遇到一个 setTimeout 为一个 Task,主线程计时,到达时间后,放入 Task Queue 中
 * 
 * // 第二次事件循环
 * 9.检查 Task Queue 中是否有需要执行的函数,按顺序(先进先出)执行
 * 10.步骤 3 的 setTimeout 回调最先被放入队列,先执行 输出 4
 * 11.取 setTimout Task Queue 下一个,执行步骤 4 的 setTimout 回调 同步代码,输出 5 ;若此 Task Queue 中 microtask Queue不为空则执行所有,若无,执行下一个 Task 
 * 12.取 setTimout Task Queue 下一个,执行步骤 8 的 setTimout 回调 同步代码,输出 3;若此 Task Queue 中 microtask Queue不为空则执行所有,若无,执行下一个 Task 
 * 
 * 
 * 第一轮事件循环 1,6 结束同步任务,执行 micro task 队列 2 (promse 在本轮事件循环结束后执行)
 * 第二轮事件循环 4,5,3(task 在下一轮事件循环开始执行) 
 */

//  -------------------------------demo2:内外均存在 promise -------------------------------
new Promise(function (resolve, reject) {
    resolve()
    console.log(1)

    Promise.resolve().then(function () {
        console.log(2)

        setTimeout(function () {
            console.log(3)
        }, 0)
    })

    setTimeout(function () {
        console.log(4)
    }, 0)
}).then(function () {
    console.log(7)
})

setTimeout(function () {
    console.log(5)
}, 0)

console.log(6)

/**
 * 第一轮事件循环 1,6(同步完成);(2,7) microtask Queue 在本轮结束执行
 * 第二轮事件循环  4,5,3(Task Queue)
 */

//  ---------------------------------  demo3:在 demo2 基础上修改定时器执行时长,看输出顺序  --------------------------------------
// 测试 setTimeout 定时器的时间对输出顺序的影响
new Promise(function (resolve, reject) {
    resolve()
    console.log(1)

    Promise.resolve().then(function () {
        console.log(2)

        setTimeout(function () {
            console.log(3)
        }, 0) // 最短
    })

    setTimeout(function () {
        console.log(4)
    }, 1000) // 适中
}).then(function () {
    console.log(7)
})

setTimeout(function () {
    console.log(5)
}, 2000) // 最长

console.log(6)

/**
 * 第一轮事件循环 1,6(同步完成);(2,7) microtask Queue 在本轮结束执行
 * 第二轮事件循环  3,4,5(Task Queue)
 * 
 * Question:为什不是按照放入队列的顺序执行的,而结果是按照事件执行完成的先后输出的
 *      这是因为执行 setTimeout 时,不是直接将异步操作回调函数直接放入 Task Queue 中,而是计时,等待时间到了,异步操作有结果了,才放入
 *      所以表现为 
 */

//  ------------------------------- demo4:在 demo2 上添加链式调用,测试 promise 执行顺序-----------------------------------
new Promise(function (resolve, reject) {
    resolve()
    console.log(1)

    Promise.resolve().then(function () {
        console.log(2)

        setTimeout(function () {
            console.log(3)
        }, 0)
    }).then(function () {
        console.log(8) // essential 看 8 输出的位置
    })

    setTimeout(function () {
        console.log(4)
    }, 0)
}).then(function () {
    console.log(7)
}).then(function () {
    console.log(9) // essential 看 9 输出的位置
})

setTimeout(function () {
    console.log(5)
}, 0)

console.log(6)

/**
 * 第一轮事件循环 1,6(同步完成);(2,7) microtask Queue 在本轮结束执行 (8,9)下一轮 microtask
 * 第二轮事件循环  4,5,3(Task Queue)
 */

// -------------------------------------------  demo5:promise 链式调用的执行顺序-------------------------
// promise then 方法会返回一个新的 promise 实例,那么这个实例的 then 方法是在下一个事件循环执行吗,还是立即执行(答,下一轮)
new Promise((resolve) => {
    resolve();
})
.then(() => console.log(1))
.then(() => console.log(2))
.then(() => console.log(3))

new Promise((resolve) => {
    resolve();
})
.then(() => console.log(4))
.then(() => console.log(5))
.then(() => console.log(6))

/**
 * 输出结果 1 4 2 5 3 6
 * 
 */

//  -------------------------------------------  demo6:带有 async await 的情况,await 命令后是异步操作-------------------------
console.log(1)

// 函数声明
async function async1() {
    await async2()
    console.log(2)
}
async function async2() {
    console.log(3)
    return Promise.resolve().then(() => { // essensial
        console.log(4)
    })
}
async1() // 函数调用

setTimeout(function () {
    console.log(5)
}, 0)

new Promise(resolve => {
        console.log(6)
        resolve()
    })
    .then(function () {
        console.log(7)
    })
    .then(function () {
        console.log(8)
    })

console.log(9)

/**
 * 注意:
 *      用 await 时,会从右往左执行;
 *      当遇到 await 时,会阻塞函数内部处于它后面的代码,去执行该函数外部的同步代码;
 *      当外部同步代码执行完毕,再回到该函数内部执行剩余的代码;
 *      当 await 执行完毕之后,会先处理微任务队列的代码,(如果执行完 await 之前存在微任务)
 * 执行结果为 
 *  1,3,6,9(同步任务)
 *  4,7,8,2(microtask) // 8 2 ? why not 2 8
 *  5 (Task Queue)
 */

//  -------------------------------------------  demo7:带有 async await 的情况 ,await 命令后是同步操作-------------------------
console.log(1)

function sync() {
    console.log(4)
}

// 函数声明
async function async1() {
    await async2()
    console.log(2)
}
async function async2() {
    console.log(3)
    return sync() // essensial
}
async1() // 函数调用

setTimeout(function () {
    console.log(5)
}, 0)

new Promise(resolve => {
        console.log(6)
        resolve()
    })
    .then(function () {
        console.log(7)
    })
    .then(function () {
        console.log(8)
    })

console.log(9)

/** 
 * await 命令后是同步操作,自动转成立即 resolved 的 Promise 对象
 * 输出结果为:
 *      1,3,4,6,9
 *      2,7,8
 *      5
 */

//  -----------------------------------------  demo 8:await 返回的是一个 setTimeout
console.log(1)

// 函数声明
async function async1() {
    await async2()
    console.log(2)
}
async function async2() {
    console.log(3)

    return setTimeout(function () { // essensial
        console.log(4)
    }, 0)
}
async1() // 函数调用

setTimeout(function () {
    console.log(5)
}, 0)

new Promise(resolve => {
        console.log(6)
        resolve()
    })
    .then(function () {
        console.log(7)
    })
    .then(function () {
        console.log(8)
    })

console.log(9)

// 1,3,6,9
// 2,7,8
// 4,5

class 实现原理

class 实现原理

[TOC]

class A {}  
typeof A // function

Class 的底层实现要素

  1. 只能使用 new 操作符调用 class ;
  2. Class 可以定义实例属性方法和静态属性方法;
  3. 子 Class 的实例可以继承父 Class 上的实例属性方法;
  4. 子 Class 可以继承父Class 上的静态属性方法;

1. 如何实现只使用 new 操作符调用 class

function _classCallCheck(instance, Constructor) {   
	if (!(instance instanceof Constructor)) {     
		throw new TypeError('Cannot call a class as a function')   
	} 
} 
	
// 例子 
function Test() {}  

let test1 = new Test()  
let test2 = Test()  

test1 instanceof Test // true 
test2 instanceof Test // false 

2. 定义实例属性方法和静态属性方法

  • 在构造器 原型 上定义实例属性方法
  • 在构造器 本身 上定义静态属性方法
  • 静态属性方法是使用类名调用的,实例属性方法需要创建实例调用;
  • 父类的静态属性和方法可以被子类继承,但是不可以被重写;
  • 父类的实例属性和方法可以被子类继承,可以被重写;
function _createClass(Constructor, protoProps = [], staticProps = []) {
    // 在构造函数的原型上定义实例属性方法,定义在原型上才可被创建的实例访问到
    _defineProperties(Constructor.prototype, protoProps)
    // 在构造函数本身定义静态属性方法,定义在本身上只能通过构造函数名访问
    _defineProperties(Constructor, staticProps)
}

// 实现公用的批量给对象添加属性方法的方法
function _defineProperties(target, props) {
    props.forEach(prop => {
        Object.defineProperty(target, prop.key, prop)
    })
}
function Test(){}

// enumerable默认为 false

// 属性定义在构造器上,不可通过实例访问,只能通过构造器名访问
Object.defineProperty(Test, 'myname', {value: 'zhangsan', enumerable: true}) 
Test.myname // zhangsan 
Object.keys(Test) // ['myname']

let test = new Test()
test.myname // undefined

// 属性定义在构造器原型上,可以通过实例访问,不可以通过构造器名访问
Object.defineProperty(Test.prototype, 'age', {value: 12, enumerable: true}) 
Test.age // undefined

let test = new Test()
test.age // 12

3. 继承实例属性方法 和 静态属性方法

  • Object.create 将创建的对象的 proto 指向 源对象;使用这种继承方法而不是直接将父类原型直接赋值给子类是保证子类重写了父类的方法属性不会影响到父类;
  • Object.setPrototypeOf(obj, prototype) 设置某个对象的原型
function _inherits(subClass, superClass) {
    // 子类实例继承父类的实例属性方法
    subClass.prototype = Object.create(superClass.prototype)
    // 修正constructor属性
    subClass.prototype.constructor = subClass

    // 子类继承父类的静态属性方法
    Object.setPrototypeOf(subClass, superClass)
}

// 例子
class A {}
class B extends A {}

B.prototype.__proto__ === A.prototype
b.__proto__.__proto__ === A.prototype

B.prototype === A
B.__proto__ === A

4. 为什么子类继承父类要调用 super 方法

Class 的 ES5 实现方式

  • 源代码

    class Person {
        constructor(options) {
            this.name = options.name
            this.age = options.age
        }
        eat() {
            return 'eating'
        }
        static isPerson(instance) {
            return instance instanceof Person
        }
    }
    
    class Student extends Person {
        constructor(options) {
            super(options)
            this.grade = options.grade
        }
        study() {
            return 'studying'
        }
        static isStudent(instance) {
            return instance instanceof Student
        }
    }
  • babel 编译后

    var Person = (function() {
        function Person(options) {
            // 确保使用new调用
            _classCallCheck(this, Person)
            
            this.name = options.name
            this.age = options.age
        }
    
        _createClass(
            Person,
            // 实例属性方法
            [{
                key: 'eat',
                value: function eat() {
                    return 'eating'
                }
            }],
            // 静态属性方法
            [{
                key: 'isPerson',
                value: function isPerson(instance) {
                    return instance instanceof Person
                }
            }]
        )
        return Person
    })();
    
    var Student = (function(_Person) {
        // 继承 父类实例属性方法和静态属性方法
        _inherits(Student, _Person)
    
        function Student(options) {
            // 确保使用new调用
            _classCallCheck(this, Student)
    
            // 执行父类构造函数
            _Person.call(this, options)
    
            this.grade = options.grade
        }
    
        _createClass(Student,
            // 实例属性方法
            [{
                key: 'study',
                value: function study() {
                    return 'studying'
                }
            }],
            // 静态属性方法
            [{
                key: 'isStudent',
                value: function isStudent(instance) {
                    return instance instanceof Student
                }
            }]
        )
    
        return Student
    })(Person);

待整理-数组遍历的几种方式对比

从以下几点进行对比:

  1. 是否修改原数组
  2. 是否会忽略数组中未定义的值
  3. 返回值形式
  4. 是否可以通过 break 或 return 退出数组
  5. 主要应用场景

map:

  • 返回一个新数组;

  • 不会影响原数组(除非在 map 中手动修改);

  • 按顺序调用;

  • 只会在有值的索引上调用 callback,不存在值的索引不会调用;

  • 在 map 循环中需要在回调函数中 return 才会形成新数组;

  • 不可在循环中使用 return 和 break 退出;

forEach:

  • 返回值为 undefined;
  • 单纯的为数组的每个元素执行一次操作,会忽略掉未初始化的值;
  • 不会在迭代前创建数组的副本;

for of

  • 遍历实现了 interator 接口的 value 值
  • 可以使用 break 跳出循环;


for in

  • 只有此遍历方法不会忽略数组上的非数字属性;

reduce

  • reduce 的回调函数的返回值会分配给累加器参数(记得给回调函数添加返回值);
  • 对数组的每个元素执行回调函数,不包含已删除的和从未被赋值的;
  • 如果数组为空且没有提供 initialValue,会抛出 TypeError;因此提供初始值会比较安全;
  • 返回值是最后一次执行回调的返回值;


for 循环

  • 可以使用 break 跳出循环;
  • 可以控制循环起点;

indexOf 使用严格相等的模式判断

arr 是特殊的 object,可以添加非数字属性;

http 2 的改进与 http 1.x 的缺陷

http 1.0

1. 连接无法复用;head-of-line blocking:

HTTP1.0 只允许一条 tcp 链接上处理一个 request

head-of-line blocking:只有前一个请求返回后才能进行下一个请求


2. HTTP 1.1 解决连接无法复用的问题 keep-alive**:HTTP 1.1 keep-alive 能解决连接复用的问题,但是每个请求与响应还是依次的,只有上一个请求响应后才能发起下一个请求。可设置连接时间和每个连接最大请求个数。 pipelining 是 keep-alive 的升级,可以一次多个请求。


3. HTTP 1.1 解决 head-of-line-blocking 的问题的方案:pipelining(流水线工作)(未能根本解决问题):浏览器的多个请求可以同时发到服务器。

  • 如果希望能够多个请求并行执行,就要建立多个连接,但是浏览器对于每个域名会限定连接数量,不同浏览器会有所不同,并且多个连接建立与关闭非常浪费时间;

  • 只有幂等的请求(GET,HEAD)能使用pipelining,非幂等请求比如POST不能使用,因为请求之间可能会存在先后依赖关系;

  • 服务器的响应只能够一个接着一个的按请求的顺序返回响应 (但各大浏览器有些不支持 / 默认关闭)

  • 初次创建连接时不应启动管线机制,因为对方(服务器)不一定支持 HTTP/1.1 版本的协议

  • HTTP /1.1 要求服务器端支持管线化,但并不要求服务器端也对响应进行管线化处理,只是要求对于管线化的请求不失败即可


4. 首部字段未压缩导致不必要的网络流量

在 HTTP/1 中,HTTP 请求和响应都是由「状态行、请求 / 响应头部、消息主体」三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件(例如图片、音频),但状态行和头部却没有经过任何压缩,直接以纯文本传输。

随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。


5. 不支持资源优先级,导致底层 tcp 连接使用不当:重要的请求不能优先接收响应


http 2

并没有更改核心概念及语义,只是更改了数据的格式化方式以及如何在 server 和 client 之间传输的方式,主要解决一些性能方面的问题

新增的二进制帧层与 1.x 不兼容,所以版本增加至 2.x


1. 单一连接多个请求,多路复用(流技术):

仅使用一次连接,即可同时进行多个请求与响应数据流的交错传输


2. 二进制 分帧(相对于 plaintext 更快更正确的解析)

HTTP 1.x 的数据报形如:

POST /upload HTTP /1.1 Host: www.example.com {"msg": "hello"}

HTTP 2 的数据报为 将数据报首部字段和消息主体部分分成小的消息片段和帧,并且每一部分都是由二进制格式编码。

把 tcp 数据报的头部和数据部分分成了 header frame 和 data frame。也就是头部帧和数据体帧。帧的传输最终在流中进行,流中的帧,头部(header)帧 和 data 帧可以分为多个片段帧,例如data帧即是可以 data = data_1 + data_2 + ... + data_n。


3. 首部字段压缩:HPACK压缩来压缩头部

它允许通过一个静态Huffman代码对传输的报头字段进行编码,这减少了它们各自的传输大小。

client 与 server 维护一份相同的静态表(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合.

client 与 server 维护一份相同的动态表(Dynamic Table),可以动态地添加内容。


在传输时使用表的key 值代表,并对 key 进行哈夫曼编码压缩,减少传输数据


4. 数据流优先级:重要请求优先获得响应,根据首部字段的权重为每个请求分配资源


5. server push:在某次请求中,server 判断哪些资源也会被需要,就会在此次响应中一并返回,不需要 client 再明确的一个个去请求这些文件,从某种程度上来说减少了延迟


6. 流控制:防止发送的数据,接收方不希望或不能够处理。发送方调节发送的数据量或者接收方调节接收的数据量

与 tcp 流控制不同,TCP流控制的粒度不够细,并且没有提供必要的应用程序级api来规范各个流的交付。

定向:每个 receiver 可以选择为每个流和整个连接设置它想要的任何窗口大小。

基于信用的:每个 receiver 通知初始连接和 stream 流控制窗口大小(bytes)

无法禁用流控制:一旦连接建立,client 和 server 交换 SETTINGS 帧后,就无法禁用。当client 发送数据,server 接收数据后会发送一个 WINDOW_UPDATE 帧更新可用窗口的大小。

逐段的,不是端到端的。也就是说,中介者可以使用它来控制资源使用,并根据自己的标准和启发来实现资源分配机制。

控制算法由 client 和 server 自己实现,http 2 并没有实现,使终端实现自己的调节资源的分配与使用策略


流(stream)、消息(message)、帧(frame):

流:在已建立的连接内的双向字节流,携带一个或更多的消息

消息:映射到逻辑请求或响应消息的完整帧序列

帧:http2 最小的通信单元,每个帧都有一个 header,标识帧属于哪一个流


所有通信都在一个TCP连接上执行,该连接可以承载任意数量的双向流。

每个流都有唯一的标识符和可选的优先级信息,用于携带双向消息(一次请求与响应的组合)。

每个消息都是一个逻辑 HTTP消息,比如一个请求或响应,它由一个或多个帧组成

帧是承载特定类型数据的最小通信单元,HTTP 报头、消息负载等等。来自不同流的帧可以交错,然后通过在每一帧的报头中嵌入的流标识符重新组装。


数据流(stream)优先级:

  • 每个流可以分配1到256之间的整数权值
  • 可以给每个流添加对另一个流的显式依赖

流依赖项和权重的组合允许客户端构建和通信一个“优先级树”,该树表示它希望如何接收响应。反过来,服务器可以通过控制CPU、内存和其他资源的分配来使用这些信息对流处理进行优先级排序,并且一旦响应数据可用,就分配带宽以确保向客户端提供最优的高优先级响应。

HTTP/2中的流依赖项是通过引用另一个流的唯一标识符作为其父流来声明的,如果省略该流,则表示该流依赖于“根流”。声明一个流依赖项表明,如果可能的话,应该在父流的依赖项之前分配资源。

共享同一父进程的流。应该根据他们的体重比例分配资源。

可以根据用户交互和其他信号改变依赖关系并重新分配权重。

只是一种倾向,不能真正的保证传输顺序。也就是说客户端不能使用流优先级强制服务器以特定的顺序处理流。

vue3 props

  1. props 多类型
import type { PropType } from 'vue'

type mYFunction = () => any

export default defineComponent({
  props: {
    action: [String, Function] as PropType<string | mYFunction>,
  }
})
  1. script setup 中 props 写法,运行时声明
import { defineProps } from 'vue'
import type { PropType } from 'vue'

const props = defineProps({
    visible: { 
        type: Boolean,
        required: true,
    },
    type: {
        type: String as PropType<OpsBatchEnum>,
        required: true,
    },
    ids: {
        type: Array as PropType<number[]>,
        retuired: true,
    },
})
  1. script setup 中 props 写法,类型声明,ts
const props = withDefaults(defineProps<{
    type: OpsBatchEnum | string[] // 这里可以使用其他文件导入的类型的
    visible: boolean,
    ids: number[]
}>(), {
    type: OpsBatchEnum.RESOURCE,
    visible: true,
    ids: () => [],
})
  1. 类型声明参数限制
    类型字面量(boolea, number, string)
    在同一文件中的接口或类型字面量的引用
// 错误写法,类型声明来源于另一个文件
import type { MyProps } from './typing'

const props = defineProps<MyProps>()

// 错误写法 非类型字面量
  1. 运行时声明和类型声明的形式不可同时使用
  type Props = {
    visible: boolean
  };
  const props = defineProps<Props>({
    visible: Boolean
  })

nodejs 模块解析方式

模块的引用方式:相对引用(../../)和非相对引用(其它形式如 直接名称或 路径别名);

相对引用方式

// root/src/moduleA.js
var x = require("./moduleB.js"); // 相对引入同级目录的 moduleB
  1. 检查 root/src/moduleB.js 文件是否存在,如果不存在转到步骤2;
  2. 检查 root/src/moduleB目录 是否包含一个package.json文件,且package.json文件指定了一个"main"模块。 如果 node 发现文件 root/src/moduleB/package.json 包含了{ "main": "lib/mainModule.js" },那么会引用 root/src/moduleB/lib/mainModule.js。如果不存在转到步骤 3;
  3. 检查 root/src/moduleB目录 是否包含一个 index.js 文件。 这个文件会被隐式地当作那个文件夹下的"m ain"模块。
1. /root/src/moduleB.js
2. /root/src/moduleB/package.json (如果指定了"main"属性)
3. /root/src/moduleB/index.js

非相对引用方式

// root/src/moduleA.js
var x = require("moduleB.js"); // 非相对引入 moduleB

​ node 会在一个特殊的文件夹 node_modules 里查找你的模块。 node_modules可能与当前文件在同一级目录下,或者在上层目录里。 node会向上级目录遍历,查找每个 node_modules 直到它找到要加载的模块。

​ node 则会以下面的顺序去解析 moduleB.js,直到有一个匹配上。

1. /root/src/node_modules/moduleB.js
2. /root/src/node_modules/moduleB/package.json (如果指定了"main"属性)
3. /root/src/node_modules/moduleB/index.js

1. /root/node_modules/moduleB.js
2. /root/node_modules/moduleB/package.json (如果指定了"main"属性)
3. /root/node_modules/moduleB/index.js

1. /node_modules/moduleB.js
2. /node_modules/moduleB/package.json (如果指定了"main"属性)
3. /node_modules/moduleB/index.js

vue 混入合并策略

  1. 数据对象 data 、props 属性同名时,组件选项优先

    var mixin = {
       data: function () {
           return {
               message: 'hello',
               foo: 'abc'
           }
        }
     }
    
     new Vue({
       mixins: [mixin],
       data: function () {
         return {
             message: 'goodbye',
             bar: 'def'
         }
       },
       created: function () {
         console.log(this.$data)
         // => { message: "goodbye", foo: "abc", bar: "def" }
       }
     })
  2. 同名的钩子函数

    将两者中钩子函数的内容合并成为一个数组,并且混入的钩子函数优先于组件自身的钩子函数执行

    var mixin = {
      data: function () {
        return {
          message: 'hello',
          foo: 'abc'
        }
      }
    }
    
    new Vue({
      mixins: [mixin],
      data: function () {
        return {
          message: 'goodbye',
          bar: 'def'
        }
      },
      created: function () {
        console.log(this.$data)
        // => { message: "goodbye", foo: "abc", bar: "def" }
      }
    })
  3. 值为对象的选项,例如 methods, components, computed, watch, directives ,将会被合并成为一个对象,当有同名的情况时,取组件自身的键值对。

    var mixin = {
      methods: {
        foo: function () {
          console.log('foo')
        },
        conflicting: function () {
          console.log('from mixin')
        }
      }
    }
    
    var vm = new Vue({
      mixins: [mixin],
      methods: {
        bar: function () {
          console.log('bar')
        },
        conflicting: function () {
          console.log('from self')
        }
      }
    })
    
    vm.foo() // => "foo"
    vm.bar() // => "bar"
    vm.conflicting() // => "from self"
  4. vue.extend 也使用以上的策略合并

  5. 可以自定义选项合并策略

待完成-BFC

BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,决定了元素如何对其内容进行定位

特性:

  1. BFC的区域不会与float box重叠(左图右文的两栏布局)
  2. 计算BFC元素的高度时,会包括浮动元素(可以借此解决父元素高度塌陷的问题)
  3. 在一个BFC下的块 margin 会发生重叠,不在同一个则不会(可以借此解决外边距重叠的问题)

触发 bfc:

  1. float 不为 none
  2. overflow 不为 visiable
  3. position 不为 relative 和 static的
  4. html 元素本身
  5. display的值为table-cell, table-caption, inline-block中的任何一个
  6. 弹性元素 flex,inline-flex
  7. 网格元素 grid,inline-grid

使用场景:

  1. 清除浮动
  2. 左右两栏自适应布局
  3. 解决外边距合并的问题

ts 基础

  1. 可选链

  2. 字符串枚举

  3. 类型断言

    <string>foo
    foo as string // 推荐写法
    
  4. 动态的为对象添加属性

    let obj = {}
    obj.a = 1 // 报错 obj 上没有属性 a

    一般情况下是为对象提前预留而外的属性

    interface LooseObject {
      [key: string]: any
    }
    
    let obj: LooseObject = {}
    obj.a = 1
  5. 内置工具

    Record
    Partial
    Pick
    
  6. 泛型

    K(Key):表示对象中的键类型;
    V(Value):表示对象中的值类型;
    E(Element):表示元素类型。
    
  7. 交集与合集

    A & B
    A | B
    
  8. 字面量类型

    限定的不再是一个类似 string 的一个范围,而是具体的单值

  9. typeinterface 有什么区别

响应式与适配

[TOC]

移动端适配目的与操作思路

目的:

​ 等比缩放,在不同屏幕宽度的设备上,以 iphone 6 为基准设置元素尺寸,更大的屏幕放大,更小的屏幕缩小。

操作思路:

1.  找一个基准值,基准值可以随着屏幕宽度的变化而变化 (使用 rem)
2.  根据屏幕宽度变化,设置基准值的大小 1rem = doucment.documentElement.clientWidth /( 10,7.5等)
3.  以基准值来设置页面上元素的 css 尺寸(屏幕宽度稍变大,基准值就会稍变大,css 尺寸就会略增大,实现缩放)

常见技术点

字体:

​ 字体的适配目标一般是:不同宽度, dpr 的设备上的字体大小看起来一致;不排除控制一行显示字体数量的目标。

  1. 字体为什么一般不用 rem ?

​ 根据第操作思路第 3 点,假如我们在 iphone6 上设置了字体 16 px,某一行会显示6个字;那么在比 iphone 6 更宽的屏幕上字体会变大,一行也是显示6个字;我们的目的一般不是为了控制每行字的显示数量,而是希望字体的大小在不同宽度的屏幕上看起来一致,一行完全可显示更多数量的字体,显示的多少不是控制的目标。

  1. 什么时候字体需要根据 dpr 进行调整?

​ 这个跟 meta 的设置有关系,如果页面没有进行缩放(scale),也就是没改变 1 css 代表的物理像素数,就不需要调整,因为本身 dpr 就是浏览器用来保证不同分辨率的设备上元素看起来大小一致的问题的(1css 代表的物理像素数的, dpr 高的设备, 1css 会占据更多物理像素,会看起来和 dpr =1 的设备上大小一致)。

<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no">

​ 但如果设置了 meta scale,dpr =2 的设备上,本来 1css 用 2 个物理像素表示,但设置 scale =0.5 缩小(相当于手动缩小页面),就会使 1 个物理像素 表示 1 css 像素,这个时候就需要根据 dpr 调大 css font-size 的 值,才能看起来和 dpr =1 的设备上看起来大小一致。

<meta name="viewport" content="width=clientWidth * dpr,initial-scale=1/dpr,minimum-scale=1/dpr,maximum-scale=1/dpr,user-scalable=no">

// dpr  = 1
.title {
    font-size: 12px; 
}
[data-dpr="2"] .title {
    font-size: 12 * dpr px;
}
[data-dpr="3"] .title {
    font-size: 12 * dpr px;
}

边框:

​ 边框的适配目的是将 750px 设计稿的 1px 边框(实际上设计稿是物理像素)还原为物理像素。我们给边框设置的是css 像素,在 dpr =2 的设备上,1px 边框就会占据 2 * 2 个物理像素,就会比设计稿上的 1px 看起来粗一些,那实际上在 dpr =2 的设备上应该设置 css 像素 0.5 px。

// 方法一:未兼容,有些浏览器不识别 0.5 px,会解析成 0
.border { border: 1px solid #999 }  // dpr = 1

@media screen and (-webkit-min-device-pixel-ratio: 2) {
    .border { border: 0.5px solid #999 }
}
@media screen and (-webkit-min-device-pixel-ratio: 3) {
    .border { border: 0.333333px solid #999 }
}

// 方法二:借助 box-shadow
div {
    box-shadow: 0px 0px 1px 0px red inset;  // 四周边框
    box-shadow: inset 0px -1px 1px -1px red;  // 仅下边框
}

// 方法三:兼容方案,伪类 + tranform:scale 

图片:

  1. 什么时候需要使用 2 倍图、3 倍图?

    ​ 图片本身的大小是位图像素;理论上,1个位图像素对应于1个物理像素,图片才能得到完美清晰的展示。

    ​ 未设置 meta scale,当我们在 css 中使用 px 来限定一个图片的大小为 200px * 200px ,在 dpr =1 的设备上1 css 像素用 dpr 个物理像素来显示,用的图片分辨率为 200 * 200 像素,刚好正常显示;但是如果用同一个图片到 dpr = 2 的设备上,css 的这些像素就需要 400 * 400 个物理像素来填满,就会将图片拉伸填充,图片就会变得模糊不清,所以此时就需要 @2x 倍图。

    ​ 设置了 meta scale = 1/dpr, 使1px 像素用1个物理像素表示,直接使用设计稿上的图片。

    ​ 解决方案有两种,

    ​ 一是在不同 dpr 时引入不同的图片,这个方法需要管理更多的图片;

    ​ 二是直接使用 最大分辨率的图片,在 dpr = 1 时,缩小图片,这个方法带来的问题就是缩小图片锐度会减少,另外在普通屏幕上使用大分辨率图片会造成资源浪费。

使用 rem 做移动端的适配

planA: 使用 750px 的页面布局,再缩放

​ 设计稿尺寸:750px

​ meta: width = document.documentElement.clientWidth * dpr

​ meta: scale = 1/dpr

​ rem(html font-size) = (document.documentElement.clientWidth / 7.5) px,把clientWidth 分成多少份,每份为 1rem,自行决定,使 rem 成为一个好计算的值

​ 字体:字体不使用 rem 转换,可能会出现通过rem计算,最终呈现到页面上是 23.335px 这样的奇葩的字体大小,可能还会因此出现锯齿、模糊不清等问题;在 body 中设置字体大小,会被所有页面内容继承,body font-size: 12(假设 dpr =1 时字体大小) * dpr px。

​ 图片:仍按设计稿尺寸写,按设计稿写(假设设计稿宽为 200px , 设为 rem 单位为 200px / html font size = x rem)

​ 边框:仍按 1px 写,按设计稿写(假设设计稿为 1px , 设为 rem 单位为 1px / html font size = x rem)

planB: 开发以 iphone 6 为基准( dpr =1时, css 像素 375px)

​ 设计稿尺寸:750px, dpr = 3,出 375*3 分辨率的图

​ meta: width = device-width

​ meta: scale = 1

​ rem(html font-size) = document.documentElement.clientWidth /10 , (为计算 rem 转换方便可除 10),相当于把页面分成了10份,假设 clientWidth = 375px,每份是 37.5px,1 rem = 37.5px,j假设页面上的元素 0.2rem 就是0.2 * 37.5 = 7.5px(依旧是 css 像素),那么如何将设计稿上元素的尺寸转为 rem 呢?

​ 注意:设置一个元素为 2px css 像素高度,不同 dpr 所呈现的物理尺寸是一致的,不同的是所占据的物理像素数不同。

​ 字体:因为本身就是 css 像素,在不同 dpr 上所呈现的物理尺寸是一样的;

​ 图片,需使用 n 倍图:假设图片宽高设为 200px*200px,在 dpr =1的屏幕上用了一张 @1x 图片,200 * 200 图片分辨率(显示器点距),可以完美显示;到 dpr =2 的屏幕上后,图片是 200 * 200 分辨率,而 css 200px * 200px表示的物理像素就是 400 * 400 分辨率,会把图片拉伸,占满这 400 * 400 个点距,图片就会变得模糊;所有设定图片的 css 尺寸后,要在不同 dpr 设备上,使用 @(dpr * 设定的 css 尺寸) 分辨率的图片,也就是 dpr 倍图

​ 同理:若在 dpr= 1 的设备上,设定一个图片 css 尺寸为 200px * 200px 却使用了400 * 400 分辨率的图,就会压缩图片,图片锐度。

​ 边框:适配的目标是将边框转换为 1 个物理像素高度,写成 0.5 px 边框或使用兼容方案设置为1px height然后 transform:scale(0.5)

使用 vw,vh 做适配

设计稿:750px

https://aotu.io/notes/2017/04/28/2017-4-28-CSS-viewport-units/index.html

响应式中的媒体查询

  1. meta 设置
width=device-width,scale = 1
  1. 断点与内容宽度

    // 断点类型
    $bp-xs: 						350px;
    $bp-sm: 						576px;
    $bp-md: 						768px;
    $bp-lg: 						992px;
    $bp-xl: 						1200px;
    
    
    .container {
        margin: 0 auto;
    
        @media (max-width: 575px) {
            padding: 0 10px;
        }
    
        @media (min-width: 576px) {
            max-width: 540px;
        }
    
        @media (min-width: 768px) {
            max-width: 720px;
        }
    
        @media (min-width: 992px) {
            max-width: 960px;
        }
    
        @media (min-width: 1200px) {
            max-width: 1140px;
        }
    }
    

响应式中的移动端处理

​ 使用 planB ,处理图片

参考链接:

显示器的分辨率是怎么来的

显示器和电脑的连接接口也变得纷繁复杂,我们常见的有VGA,DVI,Displayport(DP)和HDMI。

谁决定了最终的显示分别率呢?实际上是由显卡和显示器共同决定的。

A. 显示器存储了EDID信息,里面有它可用的所有显示分辨率的列表。

B. 高低端显卡本身有自己可以支持的分辨率列表。

C. 显卡驱动程序用DDC从显示器那里提取过来EDID信息,得到显示器分辨率集合。同时知道自己显卡的分辨率集合。它对两个集合做一个交集,报告给操作系统。

D. 操作系统在分辨率设置界面显示出这个集合供用户挑选。

闭包

作用域链

访问词法作用域(定义时而非执行时)的所有内容;

function fn1() { var a = 1; fn2(); }
function fn2() { return a; }

fn1() // a is not defined

虽然在 fn1 中调用了 fn2,但是 fn2 并不能访问到变量a,因为 fn2 被定义时,变量 a 是不可见的,和 fn1 一样只能访问自身作用域和全局作用域的内容


// 改写成
function fn1() { 
    var a = 1; 
    
    function fn2() {
        console.log(a)  // 定义时 fn2 可以访问到 fn1 的作用域
    }
    
    return fn2
}

let b = fn1()
b()  // 1 

​ 函数被定义时会记录自身所在的环境和相关的作用域链(只记能访问到哪些作用域),但是这并不意味着函数也会对其作用域中的变量进行记录;所以在函数所能访问到的作用域中任意添加、删除、修改,函数都能访问到这些改变;

利用闭包突破作用域链

​ 一般情况下,在函数外部是无法访问在函数内部定义的内容的,闭包可以创建一个可以访问到内部内容的外部函数

// 形式一:
function fn1() { 
    var a = 1; 
    
    return function fn2() {
        console.log(a)  // 定义时 fn2 可以访问到 fn1 的作用域
    }
}

let n = fn1()
n()  // 1 


// 形式二:
let n; 

function fn1() { 
    var a = 1; 
    
    n = function fn2() {
        console.log(a)  // 定义时 fn2 可以访问到 fn1 的作用域
    }
}

fn1()
n() // 1

循环中的闭包

function fn() {
	var a = []
    var i;
    
    for(i=0;i<3;i++){
        a[i] = function() {
            return i
        }
    }
    
    return a
}

let arr = fn()

arr[0]() // 3
arr[1]() // 3
arr[2]() // 3

每个 a[i] 都是一个可以访问到 i 的函数,形成了闭包;但是闭包不会记录 i 的值,只是保持了对 i 的引用,所以循环结束时 i 的值为3,此时访问 i 值为 3;解决方案是在每次循环时将变量“本地化”;


解决方案一:使 a[i]  function 立即执行
function fn() {
	var a = []
    var i;
    
    for(i=0;i<3;i++){
        a[i] = (function(x) {
            return x
        })(i)
    }
    
    return a
}

解决方案二:在每次迭代操作中将 i 的值 “本地化”
function fn() {
	var a = []
    var i;
    
    function makeClosure(x) {
        a[x] = function() {
            return x
        }
    }
    
    for(i=0;i<3;i++){
        makeClosure(i)
    }
    
    return a
}


解决方案三:使用 let
function fn() {
	var a = []
    
    for(let i=0;i<3;i++){
        a[i] = function() {
            return i
        }
    }
    
    return a
}

// let 相当于

var a = [];

function makeClosure(i) {
  a[i] = function () {
    console.log(i);
  };
};

for (var i = 0; i < 3; i++) {
  makeClosure(i);
}

后台管理类系统请求优化

  1. 单个请求频繁提交的问题

    • 请求开始前设置保存按钮禁用,请求成功后再次解禁

      <template>
          <Button
              :icon='isBtnLoading ? "ios-loading": ""'
              :disabled='isBtnLoading'
              @click='confirm'
          >
              确认
          </Button>
      </template>
      
      <script>
          export default {
              data() {
                  return {
                      isBtnLoading: false
                  }
              },
              methods: {
                  confirm () {
                      this.$refs.form.validate(valid => {
                          if (!valid) return
      
                          const params = {}
                          this.isBtnLoading = true
      
                          this.$api.addScene(params).then(res => {
                              ....
                              this.isBtnLoading = false
                          })
                      })
                  }
              }
          }
      </script>
    • (没试过)在 axios 请求拦截器,使用 cancel token API 取消请求

view-ui 脱坑实录

  1. 重置表单单个字段,清空校验提示
this.$refs.myForm.fields.forEach((e) => {
    if (e.prop === 'abcd') {
        e.resetField()
    }
})

vue Proxy 与 Object.defineProperty 对比

  1. Proxy 更多拦截方法,不仅限于 get,set,还有has.....
  2. Proxy 能够代理整个对象,拦截对整个对象的操作;而defineProperty 只能对对象的属性遍历设置 getter,setter,不能代理整个对象,某些对数组的操作需要hack ,场景有限;
  3. Proxy有兼容性问题, IE不支持

Function 和 Object 的关系 - 😵

Function 和 Object的关系

js中面向对象编程是基于构造函数(constructor)和原型链(prototype)的;

  1. Function 是最顶层的构造器,Object 是最顶层的对象;

  2. Function 是最顶层的构造器,它构造了系统中所有的对象,包括用户自定义对象,系统内置对象,甚至包括它自已;所有的函数对象是被 Function 这个函数对象构造出来的,都是 Function 的实例;

    // 创建用户自定义对象
    function func() {}  // 函数声明
    let func = function () {} // 函数表达式
    let func = new Function()  // Function 构造器 
    
    func.constructor === Function
    
    // 系统内置对象
    Object.constructor // Function() { [native code] }
    Number.constructor
    String.constructor
    
    // Function 自身
    Function = new Function()
    
    Function.constructor === Function
    Function.__proto__ === Function.prototype // new 运算符发生的事情
  3. Object 是最顶层的对象,所有的对象都将继承 Object 的原型;Object 也是由 Function 构造出来的;

let obj = {}
obj.__proto__ === Object.prototype  // true

var Foo= function(){} 
Foo.prototype.__proto__ === Object.prototype // true
  1. Object对象直接继承自Function对象,一切对象(包括Function对象)直接继承或最终继承自Object对象。

Function 创建了自己;

Function 创建了Object;

然后所有对象又继承 Object 的原型;

  1. 做个题试试

    var Foo= function(){}  
    
    var f1 = new Foo();  
    
    console.log(f1.__proto__ === Foo.prototype);  
    
    console.log(Foo.prototype.constructor === Foo);  
    
    var o1 =new Object();  
    
    console.log(o1.__proto__ === Object.prototype);  
    
    console.log(Object.prototype.constructor === Object);  
    
    console.log(Foo.prototype.__proto__ === Object.prototype);  
    
    //Function and Object  
    
    console.log(Function.__proto__ === Function.prototype);  
    
    console.log(Object.__proto__ === Function.prototype);  
    
    console.log(Object.prototype.__proto__);  
    
    console.log(Object.__proto__ === Function.prototype);  
    

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.