shizhihuaxu / study-notes Goto Github PK
View Code? Open in Web Editor NEW学习笔记,见 issues
学习笔记,见 issues
for... of
不可遍历对象;
遍历的是键值 value;
可遍历具有 Symbol.iterator 接口的的数据结构,例如 Set 、Map 、数组、类数组对象、字符串、Generator 对象等;
它可以与break、continue和return配合使用;
for ... in
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
内联声明 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>
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 宽度只表示内容区域的宽度,不包含内边距,边框
box-sizing:border-box
css 设置的 width 宽度包含了内容区域、内边距(左右)、边框(左右),高度同理
box-sizing: border-box | content-box
el.offsetWidth/offsetHeight
el.getBoundingClientRect().width
el.style.width // 只能获取行内样式
vue自身功能优化、请求、打包、插件引入
注意 v-show 与 v-if 的使用场景
不要同时使用 v-for 和 v-if
v-for 会循环所有内容,v-for 会比 v-if 优先执行,所以情况会是先由v-for 渲染出来了,然后 v-if又使其隐藏掉了,造成了不必要的计算影响性能。
为 v-for 遍历中的 item 设置 key 值
更新机制,减少不必要的重新渲染
组件 keep-alive
图标使用 iconfont,减少图片体积
echarts 仅引入使用到的图表
momentjs 仅引入使用到的语言包
ui 组件库的组件的按需引入
路由懒加载
可以使用 nuxt 优化首屏渲染
打包时压缩 js 文件体积
开启 gzip 压缩,对于支持的浏览器返回 gzip 压缩的文件
使用缓存:http 缓存或本地缓存
长列表滚动加载
CJS 使用不同的算法是因为它从文件系统加载文件,这耗费的时间远远小于从网络上下载。因此 Node 在加载文件的时候可以阻塞主线程,而不造成太大影响。而且既然文件已经加载完成了,那么它就可以直接进行实例化和运行。所以在 CJS 中实例化和运行并不是两个相互独立的阶段,而是连续不间断的。
在 CJS 中,整个导出对象在导出时都是值拷贝。即,所有的导出值都是拷贝值,而不是引用。所以,如果导出模块内导出的值改变了,导入模块中导入的值也不会改变。
AMD/CMD 是 CommonJS在浏览器端的解决方案。
CommonJS是同步加载(代码在本地,加载时间基本等于硬盘读取时间)。
AMD/CMD是异步加载(浏览器必须这么做,代码在服务端)
UMD 是 AMD 与 CommonJS 的结合,跨平台的解决方案;UMD先判断是否支持Node.js的模块(exports)是否存在,存在则使用Node.js模块模式。再判断是否支持AMD(define是否存在),存在则使用AMD方式加载模块。
js 的模块化标准
解决什么问题
解决变量管理,变量作用域与访问,变量共享的问题,提供一种更好的方式管理变量和函数,将相关的变量和函数放在一个模块中,模块作用域,共享数据。
对外暴露的过程为导出,导入此模块的模块就可以显示的声明依赖此模块的数据,是一种明确的依赖关系
从入口文件,根据依赖关系加载各个依赖文件,但是不能直接使用,而是进行解析,将模块关系记录下来,解析后将代码和状态(变量的实际值)结合起来,形成模块实例。模块加载会从入口文件开始,最终生成完整的模块实例关系图。
同其它模块化系统有什么区别
ESM 生成模块实例的过程:
[TOC]
响应式设计原理
媒体查询与断点,确保在不同 css 像素范围内显示友好,可以使用不同的排版布局
适配原理与实现(rem,vw)
设计稿固定一个尺寸设计,设计稿的尺寸要根据适配的屏幕大小来确定。在使用此方式适配时,屏幕尺寸的范围如果过大的话其实会适配的不是很好,因为它的**是等比缩放,选择一个尺寸设计,比这个尺寸大的屏幕放大,比这个小的屏幕缩小,所以如果尺寸跨度过大的话显示依然不友好。
水平垂直居中的方案
实现元素的高度根据宽度按照固定的比例缩放
依据 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%;
}
浏览器在渲染一个页面的时候,渲染引擎会将每个元素表示为一个盒子,css 决定这个盒子的大小、位置以及属性。
盒子分为块级盒子(block box)和内联盒子(inline box)两种类型。
块级盒子:
内联盒子:
盒模型由四部分组成 content、padding 、border、margin(margin 不计入实际大小 —— 当然,它会影响盒子在页面所占空间,但是影响的是盒子外部空间。盒子的范围到边框为止 —— 不会延伸到margin) ;
box-sizing: border-box(IE) | content-box(标准);
content-box 是默认值。表明 width 属性设置的只是元素内容区域的大小,如过还设置了边框和内边距,盒子的最终渲染出来的大小将会是 width + padding + border;
border-box:设置的边框和内边距的值是包含在width内的,最终渲染出来的元素大小依然是 width 属性设置的大小;border-box 是不包含 margin 的。
块盒子
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 元素,不会换行,出现在前一个元素后面
移动端的两个概念,布局视口和可视视口,对布局视口进行缩放,使之与可视视口同样大小;
line-height 设置在自身元素上
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 // 继承计算后的值
BFC 元素形成了独立的布局环境,其中的元素布局是不受外界的影响,决定了元素如何对其内容进行定位。
特性
触发BFC
<html>)
float
不是 none
)position
为 absolute
或 fixed
)display
为 inline-block
)overflow
计算值(Computed)不为 visible
的块元素display
为 table-cell
,HTML表格单元格默认为该值)display
为 table-caption
,HTML表格标题默认为该值)display
为 table
、table-row
、 table-row-group
、table-header-group
、table-footer-group
(分别是HTML table、row、tbody、thead、tfoot 的默认属性)或 inline-table
)display
值为 flow-root
的元素contain
值为 layout
、content
或 paint 的元素display
为 flex
或 inline-flex
元素的直接子元素)display
为 grid
或 inline-grid
元素的直接子元素)column-count
不为 auto,包括 ``column-count
为 1
)column-span
为 all
的元素始终会创建一个新的BFC,即使该元素没有包裹在一个多列容器中(标准变更,Chrome bug)。使用场景
基本选择器
通配(*)
标签(如 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 、属性选择器、伪类 > 伪元素、元素(类型也就是标签)选择器
情景
父元素与第一个子元素的 margin-top(无法将两者的 margin-top 隔开)
父元素与最后一个子元素 margin-bottom
相邻元素之间左右边距(除非最后一个元素清除前一个元素分浮动)
空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)
计算方式
浏览器的渲染机制:参考文档
HTML 解析器将 HTML 解析生成 DOM 树;
CSS 解析器将 CSS 解析生成 CSSOM 树;
将 CSSOM 树 和 DOM 树结合生成 Render Tree(渲染树仅包含可见的节点);
布局(回流):计算它们在设备视口内的确切位置和大小;
绘制(重绘):将渲染树中的每个节点转换成屏幕上的实际像素。
什么是不可见节点呢?
优化关键渲染路径就是指最大限度缩短执行上述第 1 步至第 5 步耗费的总时间
执行渲染树构建、布局和绘制所需的时间将取决于文档大小、应用的样式,以及运行文档的设备: 文档越大,浏览器需要完成的工作就越多;样式越复杂,绘制需要的时间就越长。如果 DOM 或 CSSOM 被修改,您只能再执行一遍以上所有步骤,以确定哪些像素需要在屏幕上进行重新渲染。
回流(reflow):
...
重绘(repaint):
1、字体颜色
...
回流一定会触发重绘,重绘不一定触发回流。
什么情况下浏览器会强制触发回流和重绘?
现代的浏览器都是很聪明的,由于每次重排都会造成额外的计算消耗,因此大多数浏览器都会通过队列化修改并批量执行来优化重排过程。浏览器会将修改操作放入到队列里,直到过了一段时间或者操作达到了一个阈值,才清空队列。但是!当你获取布局信息的操作的时候,会强制队列刷新,比如当你访问以下属性或者使用以下方法:
offsetTop、offsetLeft、offsetWidth、offsetHeight
scrollTop、scrollLeft、scrollWidth、scrollHeight
clientTop、clientLeft、clientWidth、clientHeight
getComputedStyle()
getBoundingClientRect
具体可以访问这个网站:https://gist.github.com/paulirish/5d52fb081b3570c81e3a
以上属性和方法都需要返回最新的布局信息,因此浏览器不得不清空队列,触发回流重绘来返回正确的值。因此,我们在修改样式的时候,**最好避免使用上面列出的属性,他们都会刷新渲染队列。**如果要使用它们,最好将值缓存起来。author: chenjigeng
减少回流和重绘的方法:
display 有哪些属性值,区别
块元素
块级元素占据其父元素(容器)的整个空间,因此创建了一个“块”。
默认情况下,块级元素会新起一行;一般块级元素可以包含行内元素和其他块级元素;
常见的块级元素有 div、p、ul、ol、address、article、audio、vedio、canvas、figure、form、h1...、pre、table 等。
行内元素
一个行内元素只占据它对应标签的边框所包含的空间。
一般情况下,行内元素只能包含数据和其他行内元素。
常见的行内元素有
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
块级和行内的区别
css modules 本身不是浏览器的特性,也不是 css 的标准,而是借助 webpack 等构建工具,对 css 类名和选择器限定作用域。(css-loader中实现)
css modules 仅支持 id 和 class 选择器
优点:
默认布局 static
该关键字指定元素使用正常的布局行为,即元素在文档常规流中当前的布局位置。
此时 top, right, bottom, left 和 z-index 属性无效。
相对定位 relative
对 table-*-group, table-row, table-column, table-cell, table-caption 元素无效。
绝对定位 absolute,fixed
大多数情况下,height和width 被设定为auto的绝对定位元素,按其内容大小调整尺寸。但是,被绝对定位的元素可以通过指定top和bottom ,保留height未指定(即auto),来填充可用的垂直空间。它们同样可以通过指定left 和 right并将width 指定为auto来填充可用的水平空间。
absolute: 相对于最近的非 static 定位祖先元素的偏移,来确定元素位置;可以创建 BFC
,不会出现外边距合并
fixed: 相对于屏幕视口定位。当元素祖先的 transform
, perspective
或 filter
属性非 none
时,容器由视口改为该祖先。
粘性定位 sticky
元素根据正常文档流进行定位,然后相对它的最近滚动祖先和最近块级祖先,包括table-related元素,基于top
, right
, bottom
, 和 left
的值进行偏移。偏移值不会影响任何其他元素的位置。
注意,一个sticky元素会“固定”在离它最近的一个拥有“滚动机制”的祖先上(当该祖先的overflow
是 hidden
, scroll
, auto
, 或 overlay
时),即便这个祖先不是最近的真实可滚动祖先。
粘性定位可以被认为是相对定位和固定定位的混合。元素在跨越特定阈值前为相对定位,之后为固定定位。
参考资料:
Philip Roberts的演讲《Help, I'm stuck in an event-loop》
阮一峰老师 再谈Event Loop
基本过程:
常见的宏、微任务
浏览器的与nodejs 的有什么区别?
这样不是违背了 promise 的设计初衷?它的目的不就是希望能提前到所有异步任务之前么
cors
Options 预检请求
// 构造函数
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
setInterval 时间间隔一定,不管上次有没有执行完,到时间后都会去执行第二次
使用 setTimeout 可以保证上次任务执行完再去执行下一次,但是不能保证时间间隔是所设置的那个
Set:
WeakSet :
Map:
WeakMap:
Object:
Promise.all
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))
})
})
}
1 逻辑语句中的数据类型转换
2 ==
相等判断的数据类型转换
3 加减乘除
加
减乘除
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
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
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
优点:
缺点:
diff 算法只会进行同级节点对比,复用已有的元素而不是从头渲染,加快渲染速度
1、新老开始
2、新老结束
3、老开始,新结束
4、老结束,新开始
5、没有节点可以复用,去老节点中找key 和当前新节点的 key 值一样的节点,如果找到了,判断为相同节点说明可以复用,直接移动他的位置,节点不相同仍然不可复用;如果没找到,说明没有节点可以复用,需要重新创建节点dom,插入
问题:
key 的作用:
key 的作用是是相同类型的节点进行区分,让 vue 知道该更新哪个,以及在哪个位置更新。
最终目的是尽可能的复用 dom,当渲染列表或使用if else 渲染两个相同标签的内容时,如果创建的结果只是内容顺序变了,如果没有 key ,还是会一个个的更新内容,如果有key 了,只需去看对应key 的节点是否一致就好了,不一致就更新,一致说明可以复用,只是原来的那个 key 的标签位置不同了,也是不需要再重新修改的。
例如:下一次数据更新后节点没有新增或者减少,内容也没有变化,仅仅是列表的顺序换了,这时候如果没有 key,那么从开始到结束的标签内容都需要重新更新一遍的;有了key以后,不论标签的位置在哪,如果key 相同,说明还是原来的那个标签,然后再去看他内容是否变了,不变可以直接复用,变了再去修改,这样就达到了一种减少操作dom 的目的。
为什么不推荐将 index 作为 key?
从上面的描述中可以知道,如果使用 index 作为 key 就没有意义了,列表创建的新vnode key 顺序不会变化,但是并没有对应上旧的 vnode,此时跟不绑定本质key 没什么区别。
作用:缓存动态组件/router view状态,可以避免组件重复创建和渲染,提升系统性能
实现:缓存节点,首次生成后不再重新执行组件的创建过程,可以在缓存中直接拿到渲染的节点,只会有 activated/deactivated 两个生命周期了
mounted (初次渲染,后续不会再执行)/activated/deactivated
不会渲染实体节点,是一个抽象组件
在内存中缓存
缓存最大值的处理方式,再缓存新的节点时,判断是否超过缓存最大值,超过则采取 LRU 最近最少使用的原则,删除最早的节点;要注意,在页面需要渲染的组件重新命中缓存时,需要重新将其加入缓存以更新缓存;清理缓存时正在使用的组件不可删除;
作用:将回调函数延迟到下次 DOM 更新循环之后执行,保证在回调函数中,获取的dom 是最新更新完成的 dom。
原理:
Vue 在更新 DOM 时是异步执行的。只要侦听到数据变化,Vue 将开启一个队列,并缓冲在同一事件循环中发生的所有数据变更。如果同一个 watcher 被多次触发,只会被推入到队列中一次。利用事件循环机制,在同步任务,也就是对数据的修改全部执行完毕后,再进行dom更新 。
当修改数据时,dom 不会立即重新渲染,如果需要对更新后的 dom 做点什么就需要使用nextTick处理
同时传入多个 mixins,mixins 的优先级,从左到右的顺序从先到后。
data,props,methods watch,computed等对象选项合并时,键名冲突时以组件数据优先。
生命周期钩子函数合并时,合并组件自身和 mixins 中的同名生命周期钩子函数为一个数组,混入对象的钩子函数先执行,组件中的后执行。
可以自定义选项合并策略
watch 和 computed 的区别
computed
Dep.target
这个全局变量。watcher.update()
去更新computed 的值。不能检测到数据更新的情况有:
beforeMounted 先父后子
mounted 先子后父
下面这个需要重新确认一下
beforeDestory 先子后父
destoryed
作用:为 scoped style 的模块在 dom 及 css 样式上增加了唯一的标记,保证样式私有化,不影响全局。
缺点:
具体是怎么实现的呢?借助了什么工具?
vue-loader 为 vue 文件添加唯一标记,css-loader 为 css 添加唯一标记,
使用全局 mixins 在 beforeCreate 生命周期,执行 vuexInit 方法,Vue.mixin({ beforeCreate: vuexInit })
mutation 是唯一改变 state 中数据的方法,是同步的;action 提交的也是 mutation,而不是直接修改状态
静态模块打包器,高效的管理和维护项目中的资源。
代码转化:typescript 转换为 js,scss 转换为 css,vue 转换为 html,css,js (借助 loader)
文件优化:压缩代码,压缩图片,混淆代码等
代码分割:提取公共文件,文件懒加载,避免文件体积过大分包
模块合并:在采用模块化的项目有很多模块和文件,需要构建功能把模块分类合并成一个文件
webpack 在 loader 配置中的加载 loader 顺序是相同优先级的 loader 从右向左,从下到上的,这是因为 webpack 选择了 compose 方式(从右到左)去组合函数而不是 pipe 方式(从左到右)。
几种 loader 分类
前置 pre
普通 normal
内联 inline
后置 post
优先级 pre > normal > inline > post
使用 node 做中间层,与后端 api 请求数据,拿到数据后借助 vue 预先在服务器上生成 html 结构,返回到前端渲染
优点是 seo,首屏渲染性能(不需要先下载一堆 js 和 css 解析后才能看到页面)
webpack
图片
请求
dns
vue
缓存
tcp 是面向字节流的,但传输的数据单元是报文段(首部+数据)
每个tcp 连接的两个端点是 socket ip:port
SYN = 1 表示这是一个连接请求或连接接受报文
ACK = 1 表示确认号 ack 有效
三次握手:
seq = x,SYN=1
;seq = y,SYN = 1,ack = x+ 1, ACK = 1
seq = y + 1,ACK = 1
四次挥手:
为什么要进行三次握手而不是两次?
to do 这里需要明确以下所指的多个连接 是指 tcp 和 http ,区分清tcp连接和http请求
随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,越来越多的请求导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。
并没有更改核心概念及语义,只是更改了数据的格式化方式以及如何在 server 和 client 之间传输的方式,主要解决一些性能方面的问题。
新增的二进制帧层与 1.x 不兼容,所以版本增加至 2.x。
HTTP 2 将数据报首部字段和消息主体部分分成小的消息片段和帧,并且每一部分都是由二进制格式编码。把 tcp 数据报的头部和数据部分分成了 header frame 和 data frame。也就是头部帧和数据体帧。帧的传输最终在流中进行,头部(header)帧 和 data 帧可以分为多个片段帧。
首部字段压缩:它允许通过一个静态Huffman代码对传输的报头字段进行编码,这减少了它们各自的传输大小。client 与 server 维护一份相同的静态表(Static Table),包含常见的头部名称,以及特别常见的头部名称与值的组合。client 与 server 维护一份相同的动态表(Dynamic Table),可以动态地添加内容。在传输时使用表的 key 值代表,并对 key 进行哈夫曼编码压缩,减少传输数据。
数据流优先级:重要请求优先获得响应,根据首部字段的权重为每个请求分配资源。
server push:在某次请求中,server 判断哪些资源也会被需要,就会在此次响应中一并返回,不需要 client 再明确的一个个去请求这些文件,从某种程度上来说减少了延迟。
流控制:
防止发送的数据,接收方不希望或不能够处理。发送方调节发送的数据量或者接收方调节接收的数据量。
与 tcp 流控制不同,TCP流控制的粒度不够细,并且没有提供必要的应用程序级api来规范各个流的交付。
定向:每个 receiver 可以选择为每个流和整个连接设置它想要的任何窗口大小。
基于信用的:每个 receiver 通知初始连接和 stream 流控制窗口大小(bytes)
无法禁用流控制:一旦连接建立,client 和 server 交换 SETTINGS 帧后,就无法禁用。当client 发送数据,server 接收数据后会发送一个 WINDOW_UPDATE 帧更新可用窗口的大小。
逐段的,不是端到端的。也就是说,中介者可以使用它来控制资源使用,并根据自己的标准和启发来实现资源分配机制。
控制算法由 client 和 server 自己实现,http 2 并没有实现,使终端实现自己的调节资源的分配与使用策略。
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(比较旧):缓存的过期时间
HTTP1.0 Header 属性之 Prama(比较旧):
max-age 和 expires 什么区别
max-age: 距离上次请求更新内容后的时间,可以存活的时长,单位秒,
expires: 缓存的过期日期,截至日期
强缓存才会存储在 硬盘 或 内存 中;
状态码为灰色的请求则代表使用了强制缓存,请求对应的Size值则代表该缓存存放的位置,分别为from memory cache 和 from disk cache。
from memory cache代表使用内存中的缓存,from disk cache则代表使用的是硬盘中的缓存,浏览器读取缓存的顺序为memory –> disk。
内存缓存(from memory cache):内存缓存具有两个特点,分别是快速读取和时效性:
快速读取:内存缓存会将编译解析后的文件,直接存入该进程的内存中,占据该进程一定的内存资源,以方便下次运行使用时的快速读取。
时效性:一旦该进程关闭,则该进程的内存则会清空。
硬盘缓存(from disk cache):硬盘缓存则是直接将缓存写入硬盘文件中,读取缓存需要对该缓存存放的硬盘文件进行I/O操作,然后重新解析该缓存内容,读取复杂,速度比内存缓存慢。
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。
websocket 与 http 长连接,多路复用的对比
停止等待:A 向 B 发送给数据,每发送一个分组,都需要等待 B 返回确认的消息才会传输下一个;
超时重传:如果在传输过程中数据丢失 或 B在接收数据检测出现了差错,此分组就会被丢弃,等到发送方 A 在一段时间内(超时计时器)未接收到来自接收方 B 发来的确认消息时就会重传这个分组,A在发送分组后会保存一份分组的副本,直到接收到确认消息后再清空。
确认消息的丢失或迟到:由于来自接收方的确认消息迟到或丢失,发送方A会在超时计时器到期后重传分组;此时B收到了重复的数据,丢掉这个重传的数据,并且仍然要向 A 发送确认消息,否则A会不断重传。
连续ARQ(自动重传请求)协议
ARQ 表示重传的请求是自动进行的,接收方不需要向发送方请求重传某个分组。
滑动窗口协议
git 常用命令,git flow 工作流
** 原理 **
攻击者基于服务器对访问用户的信任,窃取浏览器中存储(一般是 cookie)的已授权的用户信息,伪造正常请求对服务器做一些恶意操作
用户C打开浏览器,访问受信任网站A,输入用户名和密码请求登录网站A;
在用户信息通过验证后,网站A产生Cookie信息并返回给浏览器,此时用户登录网站A成功,可以正常发送请求到网站A;
用户未退出网站A之前,在同一浏览器中,打开一个TAB页访问网站B;
网站B接收到用户请求后,返回一些攻击性代码,并发出一个请求要求访问第三方站点A;
浏览器在接收到这些攻击性代码后,根据网站B的请求,在用户不知情的情况下携带Cookie信息,向网站A发出请求。网站A并不知道该请求其实是由B发起的,所以会根据用户C的Cookie信息以C的权限处理该请求,导致来自网站B的恶意代码被执行。
** 解决方案 **
验证 HTTP 头中的 refer 字段,判断请求来源(用户可能未提供 refer 或 refer 值被篡改)
在请求地址中添加 token 并验证(关键在于在请求中放入黑客所不能伪造的信息)
入侵者 C 可以从网络上截获 A 发送给 B 的报文,C 不需要破译这个报文,而是直接把这个由 A 加密的报文发送给 B,使 B 误认为 C 就是 A,然后 B 就向伪装成 A 的 C 发送本应该发送给 A 的报文。
解决方案:时间戳 或 随机数
(这几种应该都是位图吧,矢量图是 svg)
从以下几点进行对比:
是否支持无损压缩
色彩丰富度
是否支持透明(做 icon的时候有用,背景透明)
是否支持动态效果
文件体积
首先明确几个概念:
色彩丰富度:跟图片格式的位数有关系,位数表示的是一个像素点,支持显示的图片颜色数量,比如8位格式的图片,一个像素点可以支持 2 的8次方中颜色的显示,那么图片格式位数越多,图片色彩就可以更丰富。
**透明通道:**1个8位的灰度通道(256位,也就是rgba 的 a );黑表示不透明,白表示透明,灰是指半透明;指每个像素点的颜色是否支持使用透明度;是否支持透明很重要,在某些情况下需要部分区域透明的图片,此时只能使用支持透明度的格式。
有损/无损压缩:
为减小文件体积对文件进行压缩,什么时候需要进行压缩,是存储时自动压缩的吗,怎么压缩
格式 | jpg/jpeg | png | apng | gif | webp |
---|---|---|---|---|---|
压缩 | 有损 | 无损 | 无损 | 无损/有损均支持 | |
色彩 | 8,24位 | 8位 | |||
透明 | 不支持 | 支持 | 支持 | ||
动态 | 支持 | 支持 | 支持 | ||
体积 |
采用 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-height ,vertical-align ,letter-spacing ,word-spacing ,display , 寻找对不齐的原因2. 可能会出现锯齿 |
1、vue 是如何实现响应式的?(数据变化触发视图更新)
2、哪些情况下不能在数据变化后触发视图更新?为什么?
3、如何解决上述问题 2 ,原理又是什么?
4、为什么数组push 、shift 等一些操作可以触发视图更新?
一、情景:
父子元素
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
相邻元素之间左右边距(除非最后一个元素清除前一个元素浮动)
空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)
块级元素没有 height 或 min-height
块级元素没有 border-top 或 border-bottom
块级元素没有 padding-top 或 padding-bottom
块级元素没有内容
二、计算方式:
两正值取最大
一正一负相加
两负值取绝对值最大的负值
强缓存: 如果命中不会与服务器端进行交互
HTTP1.1 Expires(比较旧):缓存的过期时间
HTTP1.0 Header 属性之 Prama(比较旧) :与 cache-control:no-cache 作用相同,同时存在时,prama > cache-control > expires
两者同时存在时 cache-contol 的优先级高于 expires
max-age 和 expires 什么区别
max-age: 距离上次请求更新内容后的时间,可以存活的时长,单位秒,
expires: 缓存的过期日期,截至日期
两者同时存在时 max-age > expires
协商缓存: 无论是否命中都需要与服务器端交互,判断资源是否可用,如果可用之返回状态304,不返回实体,可节省一些带宽,如果不可以再返回200,并返回最新资源实体。
关于 last-modified 和 Etag 区别:
同一个逻辑关注点相关的代码配置在一起;
setup 参数为 (props, context),在组件创建前执行,由于在执行 setup 时尚未创建组件实例,因此在 setup 选项中没有 this。这意味着,除了 props 之外,你将无法访问组件中声明的任何属性——本地状态、计算属性或方法;
可以在其中注册生命周期钩子方法,钩子方法需要加 on 前缀;
可以使用 provide 和 inject 方法;
从 setup 返回的所有内容都将暴露给组件的其余部分 (计算属性、方法、生命周期钩子等等) 以及组件的模板;
还可以返回一个渲染函数
toRefs
创建对prop的 user
property 的响应式引用 demo ,
const { user } = toRefs(props)
但是,因为 props 是响应式的,你不能使用 ES6 解构,因为它会消除 prop 的响应性;所以使用 toRef 创建对 props 的引用,并且能保证它的响应式
reactive 方法,定义一个新的响应式属性(和 ref 创建有什么不同?)
Teleport :想要把某些关注点逻辑相同的ui组件放在一起,但是会需要使用css 改变定位及层级问题,当嵌套过深或比较复杂时会难以处理,Teleport 提供了一种方法,可以将组件的某一部分渲染到指定标签或css 选择器指定的元素上。
片段:支持多个根节点,但是,这要求开发者显式定义 attribute 应该分布在哪里;
<template v-for>
设置 key 的位置变化;三者的作用
改变调用此方法的函数的this 指向
call
参数列表
apply
参数传一个数组
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
自己实现
/**
* 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 // 处理精度
}
一、情景:
父元素与第一个子元素的 margin-top(无法将两者的 margin-top 隔开):
父元素与最后一个子元素 margin-bottom
父元素不存在 border-bottom
父元素不存在 padding-bottom
父元素与子元素之间不存在内容
父元素不存在 height 或 min-height 或 max-height
子元素不存在 height 或 min-height 或 max-height
相邻元素之间左右边距(除非最后一个元素清除前一个元素分浮动)
空的块级元素的上下边距(无法将 margin-top 和 margin-bottom 隔开)
块级元素没有 height 或 min-height
块级元素没有 border-top 或 border-bottom
块级元素没有 padding-top 或 padding-bottom
块级元素没有内容
二、计算方式:
两正值取最大
一正一负相加
两负值取绝对值最大的负值
// 消息提示
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
不绑定 arguments ,但是可以访问到最近的非箭头父级函数的 arguments ;
不能作为构造函数使用,不能使用 new 关键字调用,不存在 prototype 属性。
不会创建自己的 this,只会从自己的作用域链的上一层继承 this;
无法使用 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
[TOC]
class A {}
typeof A // function
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
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
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
源代码
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);
从以下几点进行对比:
map:
返回一个新数组;
不会影响原数组(除非在 map 中手动修改);
按顺序调用;
只会在有值的索引上调用 callback,不存在值的索引不会调用;
在 map 循环中需要在回调函数中 return 才会形成新数组;
不可在循环中使用 return 和 break 退出;
forEach:
for of
interator
接口的 value 值
for in
reduce
initialValue
,会抛出 TypeError
;因此提供初始值会比较安全;
for 循环
indexOf
使用严格相等的模式判断
arr 是特殊的 object,可以添加非数字属性;
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)优先级:
流依赖项和权重的组合允许客户端构建和通信一个“优先级树”,该树表示它希望如何接收响应。反过来,服务器可以通过控制CPU、内存和其他资源的分配来使用这些信息对流处理进行优先级排序,并且一旦响应数据可用,就分配带宽以确保向客户端提供最优的高优先级响应。
HTTP/2中的流依赖项是通过引用另一个流的唯一标识符作为其父流来声明的,如果省略该流,则表示该流依赖于“根流”。声明一个流依赖项表明,如果可能的话,应该在父流的依赖项之前分配资源。
共享同一父进程的流。应该根据他们的体重比例分配资源。
可以根据用户交互和其他信号改变依赖关系并重新分配权重。
只是一种倾向,不能真正的保证传输顺序。也就是说客户端不能使用流优先级强制服务器以特定的顺序处理流。
import type { PropType } from 'vue'
type mYFunction = () => any
export default defineComponent({
props: {
action: [String, Function] as PropType<string | mYFunction>,
}
})
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,
},
})
const props = withDefaults(defineProps<{
type: OpsBatchEnum | string[] // 这里可以使用其他文件导入的类型的
visible: boolean,
ids: number[]
}>(), {
type: OpsBatchEnum.RESOURCE,
visible: true,
ids: () => [],
})
// 错误写法,类型声明来源于另一个文件
import type { MyProps } from './typing'
const props = defineProps<MyProps>()
// 错误写法 非类型字面量
type Props = {
visible: boolean
};
const props = defineProps<Props>({
visible: Boolean
})
模块的引用方式:相对引用(../../)和非相对引用(其它形式如 直接名称或 路径别名);
相对引用方式:
// root/src/moduleA.js
var x = require("./moduleB.js"); // 相对引入同级目录的 moduleB
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
数据对象 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" }
}
})
同名的钩子函数
将两者中钩子函数的内容合并成为一个数组,并且混入的钩子函数优先于组件自身的钩子函数执行
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" }
}
})
值为对象的选项,例如 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"
vue.extend 也使用以上的策略合并
可以自定义选项合并策略
BFC是一个独立的布局环境,其中的元素布局是不受外界的影响,决定了元素如何对其内容进行定位
特性:
触发 bfc:
使用场景:
可选链
字符串枚举
类型断言
<string>foo
foo as string // 推荐写法
动态的为对象添加属性
let obj = {}
obj.a = 1 // 报错 obj 上没有属性 a
一般情况下是为对象提前预留而外的属性
interface LooseObject {
[key: string]: any
}
let obj: LooseObject = {}
obj.a = 1
内置工具
Record
Partial
Pick
泛型
K(Key):表示对象中的键类型;
V(Value):表示对象中的值类型;
E(Element):表示元素类型。
交集与合集
A & B
A | B
字面量类型
限定的不再是一个类似 string 的一个范围,而是具体的单值
type
和 interface
有什么区别
[TOC]
目的:
等比缩放,在不同屏幕宽度的设备上,以 iphone 6 为基准设置元素尺寸,更大的屏幕放大,更小的屏幕缩小。
操作思路:
1. 找一个基准值,基准值可以随着屏幕宽度的变化而变化 (使用 rem)
2. 根据屏幕宽度变化,设置基准值的大小 1rem = doucment.documentElement.clientWidth /( 10,7.5等)
3. 以基准值来设置页面上元素的 css 尺寸(屏幕宽度稍变大,基准值就会稍变大,css 尺寸就会略增大,实现缩放)
字体:
字体的适配目标一般是:不同宽度, dpr 的设备上的字体大小看起来一致;不排除控制一行显示字体数量的目标。
根据第操作思路第 3 点,假如我们在 iphone6 上设置了字体 16 px,某一行会显示6个字;那么在比 iphone 6 更宽的屏幕上字体会变大,一行也是显示6个字;我们的目的一般不是为了控制每行字的显示数量,而是希望字体的大小在不同宽度的屏幕上看起来一致,一行完全可显示更多数量的字体,显示的多少不是控制的目标。
这个跟 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
图片:
什么时候需要使用 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 时,缩小图片,这个方法带来的问题就是缩小图片锐度会减少,另外在普通屏幕上使用大分辨率图片会造成资源浪费。
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)
设计稿:750px
https://aotu.io/notes/2017/04/28/2017-4-28-CSS-viewport-units/index.html
width=device-width,scale = 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);
}
单个请求频繁提交的问题
请求开始前设置保存按钮禁用,请求成功后再次解禁
<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
取消请求
this.$refs.myForm.fields.forEach((e) => {
if (e.prop === 'abcd') {
e.resetField()
}
})
js中面向对象编程是基于构造函数(constructor)和原型链(prototype)的;
Function 是最顶层的构造器,Object 是最顶层的对象;
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 运算符发生的事情
Object 是最顶层的对象,所有的对象都将继承 Object 的原型;Object 也是由 Function 构造出来的;
let obj = {}
obj.__proto__ === Object.prototype // true
var Foo= function(){}
Foo.prototype.__proto__ === Object.prototype // true
Function 创建了自己;
Function 创建了Object;
然后所有对象又继承 Object 的原型;
做个题试试
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);
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.