第一回: 要坚持一件事,除非出于爱好,本就枯乏无味的。但是,一个人或许很难坚持下去,两个人的话就能互相扶持,坚持下去了。本文简单记录,我和我的小伙伴,每天分享学习的知识点。(2021/09/06)
第二回:上面的大概两个人就坚持了一个月不到吧,其实群里每天就只是我在说而已,另外个小伙伴老是各种问题吧,当然我也没有在意,学习本就是一个人的事,再到后面这次,和之前一个阿里前端训练营的五个小伙伴的,结业之后我又提议每日分享,开始是很积极的,大概就一个月吧~~~就不在见大家写了,现在回头补上每次的分享吧,坚持学习真的很有意思的事,你不会一下子见到效果,但是某一天回顾,你会发现这个点,你了解的更多,更深入了~(2021/11/23)
第三回:我呢,一直觉得自己不算很自律吧~~(谦虚点哈,哈哈哈哈)所以我喜欢找伴一起学习,这回我加了一个社群般的学习组织,哈哈哈,真的不错,一堆学习的人,学习有氛围多舒服呀。这一回合继续每天的学习和分享。(2022/01/12)
备注说明:关于分享我也会尽量从为什么,是什么,怎么做来说,现在的排版的话是没有对内容分类的,每天是想到啥就说啥,后面会对他们分类整理的,我也会尽量对每个知识点去深入,所以有些点下面会有相关文章的链接~同时第二回合的内容有部分是一起每日分享的小伙伴分享的噢,感谢他们的付出!
因为什么而诞生的:
首先http是无状态的,那么就没办法记录用户的状态,比如是否登录。举个例子我们登录淘宝网页后,当我们打开商品的详细页,我们也还是登录的,而不会是变为未登录。这里就用到了cookie
如何实现,解决了什么:
当我们第一次向服务端请求时,服务端就会创建cookie,该cookie会包含用户信息等,并且返回给客户端。客户端存储在本地,当客户端再次访问该服务端时,就会把该cookie一起添加到http请求中,发送到服务器,就可以识别当前用户是否登录
同类的存在,区别
类似的浏览器存储还有session,localStorage,sessionStorage需要了解的。比较多,这里就不做深入了,但是是需要明白他们的区别的和使用场景的。
一篇介绍的文章:浏览器存储中的存储 cookie, session, localStorage, sessionStorage
因为什么而诞生的:
可以说是经常会在面试中问到的一个知识点,并且在实际业务中也会频繁用到
防抖:,比如我们的输入框中,表单验证,按键提交等。当我们打开淘宝的输入框搜索时,每输入完下面就会出现相关的提示,这里就用到了防抖。
具体是什么呢?防抖就是当持续触发事件时,会等到停止后一段时间才开始执行。为什么需要这样呢?比如,要是我们点击一个按键,我们是不是只需要提交一次,要是我们疯狂点击,是不是就会疯狂触发。还有输入搜索,我们是不是应该输入结束才开始调用接口搜索,而不是每输入一个字就调用一次。使用防抖函数后,就使得只会触发一次,避免了无意义的触发。
节流:和防抖相比,节流是持续触发事件,会每隔一段时间,才执行执行一次,比如在DOM元素的拖拽功能中,射击游戏,计算鼠标移动距离中。当我们计算鼠标的移动距离,要是没有使用节流函数,那么每移动1px,就会调用计算。我们可想而知,当我们轻轻滑动。就会调用触发无数次了,会给服务器带来极大的压力。但我们使用后,比如设置间隔时间,持续触发那么他就会每隔这个时间段触发一次。大大减少服务端压力。
如何实现,解决了什么:
具体掌握程度,不仅要了解两者区别和作用,同时还需要能手写实现,
看该篇文章:节流和防抖函数 以及如何解决请求有防抖但是需要马上响应或获取防抖状态的情况
var是最开始的js关键词之一,首先一个变量在js中,分为声明和初始化。
var变量提升可以说是他的缺点了,美其名曰 js特性。但实际上这个特性带来很多问题。变量提升会把无论声明在哪里,都会提升到顶部,你在任何地方都会访问的到。另外他是函数作用域,而且可以多次声明,就会造成你自己啥时候覆盖了都不知道
console.log(a) //undefien
var a = 1
console.log(a) //1
var a = 2
console.log(a) //2
另外
var a = 1 等价于 var a = undefined ,a = 1 两条语句合成,所以上面两行代码等价于下面
var a = undefined
console.log(a) //undefien
var a = 1
let和const就是为了解决var的问题
具有块级作用域
当遇到变量提升的情况,会有暂时性锁区。
不可重复声明,会报错
const的最大区别是不可重新赋值,其他类似let,但有个地方,当const的值为引用类型时,是可以重新赋值的,因为在栈空间报存的是地址,真正的值在堆空间(那么如何让他不可更改呢?)这个问题看第二回里面的第二点吧~~
整理文章:JavaScript基础知识-变量
因为什么而诞生的:
首先我们应该知道,除了引用型数据是存储在堆中的,其他类型都是存储在栈中的,而引用型的数据会在栈中保留一个指针,指向在堆中的位置
所以前拷贝只是复制,指向某个对象的指针,也就是它的引用地址,浅拷贝的结果,是指向同一内存的,修改会影响
深拷贝的话会将其完全复制,不会指向共同的内存,修改不会互相影响
实现的方法
浅拷贝
深拷贝
// 我的深拷贝
//数据
const obj1 = {
age : 20,
name : 'aasfa',
address : {
city : 'beijing',
county : 'china'
},
arr: ['s','g' , 'f']
}
//我的深拷贝
function myDeepCope (obj = []) {
//判断是否为对象或null,
if (typeof obj !== 'object' || obj == null ) {
return obj
}
// 用来开辟新的地址,保存复制过来得到值
let result;
// 判断类型
if(obj instanceof Array) {
result = []
} else {
result = {}
}
for (let key in obj) {
// 避免继承以外的属性,只能当前的
if ( obj.hasOwnProperty(key)) {
result[key] = myDeepCope(obj[key])
}
}
return result
}
//测试
let aa = myDeepCope(obj1);
console.log(aa.name);
aa.name = 'fasdgasdag'
console.log(aa.name);
console.log(obj1.name);
说到作用域链,我们不得不先从作用域开始。首先我们得知道在js中有全局作用域和函数作用域。顾名思义:
作用域就是变量与函数的可访问范围,即作用域控制着变量和函数的可见性和生命周期
全局作用域的变量,函数在整个全局中都能被访问到,它的生命周期和页面的等同
函数作用域的,只能在当前函数内被访问到,生命周期随函数结束而结束销毁。
所以每一个变量或函数都会有自己的作用域范围,而作用域链简单看来说就是当前作用域范围(自身内部)中找不到时,就会往他的上一级寻找有没有,直到全局都没有的,返回 undefined。要小心的是,有些时候,不要相信我们第一眼看到的就以为是他的上一级。如何判断他的上一级需要根据词法作用域来判断。
参考文章:JavaScript基础知识-执行上下文/作用域/this(作用域链/LHS/RHS/实现块作用域/闭包)
这个是个非常重要的点了,而且涉及到的知识点很多,可扩展性很大,想一棵树的大主干,可以有很多的分支,而认证去分析里面的每一步,可以很好的体系化连接每一个知识。
接下来我们就慢慢分析一波
扩展文章:深入了解输入网址到展示网站发生什么
1. 重排
2. 重绘
重绘就是布局计算完毕后,页面会重新绘制,这时浏览器会遍历渲染树,绘制每个节点,当元素外观变化但没有改变布局的时候,重新把元素绘制的过程。
重绘不一定出发重排,但重排一定会出发重绘
如:vidibility、outline、背景色等属性的改变
举个生动的例子来说就是,我们可以理解重排为一个人的身体,而重绘为一个人的外观,显而易见,当你长胖或者长高了,都会引起身体的变化
但是比如你化个妆,涂个口红啥的,就只是改变你的外表,是重绘,不会说因此你的身体就改变了。
我们常常说HTML是网页的结构,CSS是网页的外观,JS是网页的动作,那么一般涉及到网页的HTMl改变的(也即是DOM元素改变)的就是重排,而涉及到CSS的比如改变颜色等就是重绘(对于会影响到DOM的不算,比如使用了display:flex)
3. 如何减少或避免
1.有无连接
2.通信方式
3.对应用层报文的处理
发送方应用进程将应用层报文交付给应用层 UDP,UDP 直接给应用层报文添加一个首部,使之成为应用层用户数据报,然后进行发送。接收方 UDP 接收到该报文以后,只需将首部去掉,然后将报文交付给接收方的应用进程就行了。UDP 不会对报文进行拆分,因此它是面向报文的。
TCP 则会将发送方的数据块仅仅看作一连串无结构的字节流,将它们编号并存储在缓存中,然后根据自己的发送策略,提取一定量的字节,加上首部构建成 TCP 报文段进行发送。最后接收方的 TCP 一方面将 TCP 报文中提取出数据并存储在缓存,另一方面将接收缓存中的一些字节交付给接收方的应用进程。
4.是否提供可靠传输服务
对于 UDP,发送方会一门心思给接收方不断地发送数据报,没有可靠性保证、顺序保证和流量控制字段等,可靠性较差。但是正因为UDP协议的控制选项较少,在数据传输过程中延迟小、数据传输效率高,适合对可靠性要求不高的应用程序如果发送过程中出现了误码、丢包的情况,接收方不会做任何处理,只管接收就行了。保证了实时性,所以网络直播、视频会议等使用 UDP 的传输方式。
TCP 收到报文准确无误后,会向发送方发送一个确认的报文,这样一来,如果收到了误码或者遇到丢包的情况,由于发送端没有收到确认消息,会进行超时重发,直到收到接收端的确认报文。下载文件、浏览网页时,我们希望数据没有出现丢失,因此它们使用 TCP 协议进行数据传输。
通过滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制
使用校验和,确认和重传机制来保证可靠传输
TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制
归纳总结
类别
UDP
TCP
是否连接
无连接
面向连接
是否可靠
不可靠传输,不使用流量控制和拥塞控制
可靠传输(数据顺序和正确性),使用流量控制和拥塞控制
连接对象个数
支持一对一,一对多,多对一,多对多交互通信
只能是一对一通信
传输方式
面向报文
面向字节流
首部开销
首部开销小,仅8字节
首部最小20字节,最大60字节
适用场景
适用于实时应用,如视频会议、直播
适用于要求可靠传输的应用,如文件传输
扩展文章:计算机网络-运输层(UDP/TCP协议)
开始:A(TCP客户端进程)主动打开,离开close状态,B(TCP服务端进程)被迫离开close状态,进入listen状态
第一次握手:首先A向B发送连接请求报文段,这时首部中的同步位SYN = 1(主机B由SYN=1知道,A要求建立联机),同时选择一个初始序号seq = x。A进入SYN-SENT(同步已发送)状态。该报文不包含应用层数据
第二次握手:首先B收到A的请求报文段后,如果同意建立连接,则向A发送确认。在确认报文段中把SYN位和ACK位都置1(主机A由SYN=1知道,B要求建立联机,并且返回ACK=1,表示收到上一个SYN的确认),确认号 ack = x+ 1,同时也为自己选择一个初始序号seq = y。这时B进入SYN-RCVD(同步收到)状态。该报文不包含应用层数据
第三次握手:A收到后,再次给B发送确认,确认报文段的ACK置1,确认号ack = y + 1,而自己的序号seq = x + 1。这时,TCP连接已经建立,A进入ESTABLISHED(已建立连接)状态。该报文可以包含应用层数据
补充:tcp标志位: SYN(synchronous建立联机) ACK(acknowledgement 确认联机) seq(Sequence number 顺序号码) ack(Acknowledge number 确认号码)
扩展文章:计算机网络-运输层(UDP/TCP协议)
absolute:定位是相对于离元素最近的设置了绝对或相对定位的父元素决定的,如果没有父元素设置绝对或相对定位,则元素相对于根元素即html元素定位。设置了absolute的元素脱了了文档流,元素在没有设置宽度的情况下,宽度由元素里面的内容决定。脱离后原来的位置相当于是空的,下面的元素会来占据位置。
relative:定位是相对于自身位置定位(设置偏移量的时候,会相对于自身所在的位置偏移)。设置了relative的元素仍然处在文档流中,元素的宽高不变,设置偏移量也不会影响其他元素的位置。最外层容器设置为relative定位,在没有设置宽度的情况下,宽度是整个浏览器的宽度。
问:想对子元素启用绝对定位来的,但没有生效
解答:是因为没有注意到绝对定位的使用条件:
- 绝对定位使用通常是父级定义position:relative定位
- 子级定义position:absolute绝对定位属性
- 并且子级使用left或right和top或bottom进行绝对定位。
当时没有给父元素设置 position:relative 导致没有生效。
1. 浏览器的组成:
浏览器的组成。简单的说可以分为两部分,外壳+内核。外壳的种类相对较多,内核较少。外壳指菜单、工具栏等,主要是为用户界面操作、参数设置等提供的。它调用内核来实现各种功能。内核是浏览器的核心。内核是基于标记语言显示内容的程序或模块。(我们可以写插件的,就是能对外壳进行定义,以及调用一些内核API,感觉挺好玩的,还没试过~~~)
2. 浏览器作用:
向服务器发出请求,在浏览器窗口中展示您选择的网络资源。这里所说的资源一般是指 HTML 文档,也可以是 PDF、图片或其他的类型。资源的位置由用户使用 URI指定。浏览器根据HTML规范进行解释
3. 浏览器内核:
浏览器的核心部分是“渲染引擎”也会简称“浏览器内核”。负责解释页面语法(HTML、CSS 解析、页面布局)和渲染(显示)页面。但是现在一般我们提到的大部分“浏览器内核”都包含了 JavaScript 引擎,复制处理一些动作,动态效果,所以我们可以一般认为浏览器内核包含渲染引擎和JavaScript引擎。因为浏览器的引擎不同,对我们的网页语法的解析就会产生一些不同。所以我们写CSS的时候,一般会对全局进行一些初始化,以及我们需要对页面做兼容性处理。
4. 浏览器使用的内核分类
Trident 内核:IE、MaxThon、TT、The World、360、搜狗浏览器等(当年的大哥了,没落后,IE都被淘汰了,不过国内一些老的机构的页面还是基于IE的,比如教师资格考试就要在IE上报名)
Gecko 内核:Netscape6 及以上、FF、MozillaSuite/SeaMonkey 等(什么鬼东西,要不是搜了,都没听过)
Presto 内核:Opera7 及以上
Webkit 内核:Safari、Chrome 等(大哥大了~~~edga用了后,明显用户增加了)
1. 进程:
为了表达程序的并发过程的变化,但程序的静态性和顺序性无法描述该过程,所以有了进程这个定义
进程是系统资源分配的最小单位,是程序的一次运行,有自己的独立空间
2. 线程:
为了提高进程的并发性,进一步提高系统的吞吐量和效率
3. 线程和进程的区别:
1.一个程序至少有一个进程,一个进程至少有一个线程.
2.进程是拥有资源的基本单位,线程是调度和分派的基本单位,共享进程的资源
3.都具有并发性,但线程的划分小于进程,有效的提高了效率
4.多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。而是归属于的进程的下面。
5.每一个进程都有对应的接口,但线程必须依赖于进程,不能独立存在。
6.线程的意外终止会影响整个进程的正常运行,但是一个进程的意外终止不会影响其他的进程的运行。
7.进程切换开销大,效率低,线程切换开销小,效率高
该文章:计算机基础-进程与线程以及携程
在 Web 安全领域中,XSS 和 CSRF 是最常见的攻击方式
XSS,即 Cross Site Script,译做跨站脚本攻击,XSS 攻击是指攻击者在网站上注入恶意的客户端代码,通过恶意脚本对客户端网页进行篡改,从而在用户浏览网页时,对用户浏览器进行控制或者获取用户隐私数据的一种攻击方式。哪些部分会引起XSS攻击简单来说,任何可以输入的地方都有可能引起,包括URL!
一般有反射型(js链接),存储型(服务器),基于Dom(浏览器)三类方式(自己百度去了解详细哈)
防范方式:
1.HttpOnly 防止劫取 Cookie 、
2.输入检查,对于用户的任何输入要进行检查、过滤和转义。建立可信任的字符和 HTML 标签白名单,对于不在白名单之列的字符或者标签进行过滤或编码。
3.在变量输出到 HTML 页面时,可以使用编码或转义的方式来防御 XSS 攻击
CSRF,即 Cross Site Request Forgery,译是跨站请求伪造,是一种劫持受信任用户向服务器发送非预期请求的攻击方式。通常情况下,CSRF 攻击是攻击者借助受害者的 Cookie 骗取服务器的信任,可以在受害者毫不知情的情况下以受害者名义伪造请求发送给受攻击服务器,从而在并未授权的情况下执行在权限保护之下的操作。
防范方式:
(比如 Cookie是我们的身份身份证,而Cookie对于任何请求都可以认为是本人,而坏人盗用了你的Cookie在你不知道的情况下,去把你银行的钱去了,在银行看起来也会是你本人取的,而你还不知道~~)
1.验证码:CSRF 攻击往往是在用户不知情的情况下构造了网络请求。而验证码会强制用户必须与应用进行交互,才能完成最终请求。因为通常情况下,验证码能够很好地遏制 CSRF 攻击
2.Referer Check:根据 HTTP 协议,在 HTTP 头中有一个字段叫 Referer,它记录了该 HTTP 请求的来源地址。通过 Referer Check,可以检查请求是否来自合法的”源”。
3.添加 token 验证:CSRF 攻击之所以能够成功,是因为攻击者可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于 Cookie 中,因此攻击者可以在不知道这些验证信息的情况下直接利用用户自己的 Cookie 来通过安全验证
什么是事件流:
我们点击一个按键,那么他是如何传递的呢,是从文档顶部一层层传入到这个按键,还是从这个按键传出去呢?
在曾经IE就是从里面往外(确定的逐步到不确定的),叫做事件冒泡
Netscapte就是从外面往里(不确定的逐步到确定的),叫做事件捕捉
后面W3C的定义规范中,就把两者都包含了~~~
所以DOM事件模型会分为捕获和冒泡。一个事件发生后,会在子元素和父元素之间传播(propagation)。这种传播分成三个阶段。
(1)捕获阶段:事件从window对象自上而下向目标节点传播的阶段;
(2)目标阶段:真正的目标节点正在处理事件的阶段;
(3)冒泡阶段:事件从目标节点自下而上向window对象传播的阶段。
那么为什么会有事件捕获和事件冒泡呢 这就涉及到事件委托
那么什么是事件委托呢,事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件
这里里面涉及的捕获阶段,冒泡阶段,事件委托建议好好看一些代码的实现~
参考文章
https://juejin.cn/post/6844903781969166349#heading-5
https://www.jianshu.com/p/6512139d1d9e
javascript从诞生之日起就是一门单线程的非阻塞的脚本语言。这是由其最初的用途来决定的:与浏览器交互。
作为浏览器脚本语言,JavaScript的主要用途是与用户互动,以及操作DOM。这决定了它只能是单线程,否则会带来很复杂的同步问题。比如,假定JavaScript同时有两个线程,一个线程在某个DOM节点上添加内容,另一个线程删除了这个节点,这时浏览器应该以哪个线程为准?
另外个原因大概是不想让浏览器变得太复杂,因为多线程需要共享资源、且有可能修改彼此的运行结果,对于一种网页脚本语言来说,这就太复杂了。后来就约定俗成,
JavaScript为一种单线程语言。(Worker API可以实现多线程,但是JavaScript本身始终是单线程的。)
但是,单线程很多时候会造成资源的浪费,JavaScript如何解决的呢?这个可以了解:对列和事件循环,同步任务和异步任务机制,以及回调函数等
1. MVC
首先我们了解一下这几个字母分别代表什么?
MVC全名是Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写
在这其中:
2. MVVM
首先我们了解一下这几个字母分别代表什么?
MVVM是Model-View-ViewModel的简写,是模型(model)-视图(view)-视图模型(viewmodel)的缩写
在别处看到这张图,讲的很不错
生命周期
描述
作用
beforeCreate
组件实例被创建之初
在加载实例时触发。而data和menthod都没有初始化,不能在这个阶段使用。我们可以在这个阶段加个loading事件。
created
组件实例已经完全创建
初始化完成时的事件写在这里,如在这结束loading事件,异步请求也适宜在这里调用。而data和menthod都已经初始化,可以在这个阶段使用。
beforeMount
组件挂载之前
在内存中已经编译好了模板了,但是还没有挂载到页面中,此时,页面还是旧的
mounted
组件挂载到实例上去之后
已挂载 Vue实例已经初始化完成了。此时组件脱离了创建阶段,进入到了运行阶段。 如果我们想要通过插件操作页面上的DOM节点,最早可以在和这个阶段中进行
beforeUpdate
组件数据发生变化,更新之前
更新前 页面中的显示的数据还是旧的,data中的数据是更新后的, 页面还没有和最新的数据保持同步
updated
组件数据更新之后
如果对数据统一处理,在这里写上相应函数,更新页面显示的数据和data中的数据同步了,都是最新的
beforeDestroy
组件实例销毁之前
Vue实例从运行阶段进入到了销毁阶段,这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于可用状态。但还没有真正被销毁,可以做类似确认停止事件的确认框事件。
destroyed
组件实例销毁之后
这个时候上所有的 data 和 methods , 指令, 过滤器 ……都是处于不可用状态。组件已经被销毁了。
activated
activated keep-alive 缓存的组件激活时
deactivated
keep-alive 缓存的组件停用时调用
errorCaptured
捕获一个来自子孙组件的错误时被调用
nextTick
更新数据后立即操作dom
用来处理我们数据更新了,但是页面数据没有更新的情况
别处看到的一张很好的图!(要是侵权麻烦联系哈!马上删,谢谢啦)
event loop
也就是事件循环,为了解决堵塞问题的,我们知道js是一门,单线程,非堵塞的语言。为什么单线程呢,是因为JavaScript定位与服务器的应用,当我门对一个DOM元素同时添加,删除操作时,那么如果为多线程会如何呢,单线程的机制保证了按顺序执行。同时一个任务在执行中往往有很大一部分时间在等待I/O的响应,多线程并不会带来太多的效率提升
那么在事件循环前的任务都是同步代码的,响应时间很慢,浪费了很多的时间,js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
js里实现异步有哪几种方式
HTTP/0.9
HTTP/1.0
HTTP/1.1
HTTP/2.0
其中,1.0和1.1最常用,0.9几乎不用(旧),2.0比较少用(更新代价大)
BFC是网页的一块区域,里面的元素都基于这块区域布局。虽然BFC本身是环绕文档流的一部分,但它将内部的内容与外部的上下文隔离开。这种隔离为创建BFC的元素做出了以下3件事情。
(1) 包含了内部所有元素的上下外边距。它们不会跟BFC外面的元素产生外边距折叠。
(2) 包含了内部所有的浮动元素。
(3) 不会跟BFC外面的浮动元素重叠。
简而言之,BFC里的内容不会跟外部的元素重叠或者相互影响。如果给元素增加clear属性,它只会清除自身所在BFC内的浮动。如果强制给一个元素生成一个新的BFC,它不会跟其他BFC重叠。
给元素添加以下的任意属性值都会创建BFC。
float: left或right,不为none即可。
overflow:hidden、auto或scroll,不为visible即可。
display:inline-block、table-cell、table-caption、flex、inline-flex、grid或inline-grid。拥有这些属性的元素称为块级容器(block container)。
position:absolute或position: fixed。
备注:网页的根元素也创建了一个顶级的BFC
为什么出现:
css 引入伪类和伪元素概念是为了格式化文档树以外的信息。也就是说,伪类和伪元素是用来修饰不在文档树中的部分。
伪类:
用于定义元素的特殊状态。
常见的有:
:focus / :hover / :empty / :active
伪元素:
用于设置元素指定部分的样式。比如我们需要对一段文字的第一行设置颜色为红,但是其他部分为黑就能用到::first-line(其实这里会创建一个span把这一行包起来,但是我们在文档流中又是看不到的)
常见的有:
::after / ::before / ::selection / ::first-line / ::first-letter
区别:
伪类是操作文档中已有的元素,而伪元素是创建了一个文档外的元素,两者最关键的区别就是这点。一般伪类是单冒号,如:hover,而伪元素是双冒号::before
突然想起当年面试问过的一个问题是关于 const 的,这里回顾一下吧~~
我们都知道 const 是一个必须声明的时候必须同时初始化,且赋值,并且这个值不能修改。
那么要是我们对他的引用是一个对象呢?我们知道对象的话,这时候 const 实际保持的是这个对象在栈中的引用,实际的数据在堆里面,所以这时候修改对象的值是不会违反 const 的限制的。那么问题来了~
要是我们需要对这个变量不可修改呢?尽管他是对象?
ref: 用来辅助开发者在不依赖 jQuery 的情况下,获取 DOM 元素或组件的引用,也常用于在父子组件中获取对方的某个元素进行取值,调用方法等。
每个vue 的组件实例上, 都包含一个 $refs 对象, 里面存储着对应的 DOM 元素或组件的引用
1.默认情况下,组件的 $refs 指向一个空对象
2.如果想要使用 ref 引用页面上的组件实例,则可以按照如下方式:
3.使用ref属性,为对应的组件添加引用名称
methods: {
getRef(){
// 通过 this.$refs. 引用的名称,可以引用组件的实例
console.log(this.$refs.counterRef)
// 引用到组件的实例之后,就可以调用组件上的methods方法
this.$refs.counterRef.add()
}
}
这个方法可以说很便利,但是不要太依赖了,往往在不能通过其他方法获取的时候回才比较建议使用,毕竟我们因该尽量减少添加,而是复用可以复用的部分。
$refs 只会在组件渲染完成之后生效,并且不是响应式的。这仅作为一个用于直接操作子组件的“逃生舱”——你应该避免在模板或计算属性中访问
tip:如果获取不到的时候,可以试一试使用nextTick
官方解释:在下次 DOM 更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法,获取更新后的 DOM。
为啥需要这个呢?因为 vue 中不是数据一发生改变就马上更新视图层的,如果这样会带来比较差的性能优化,在 vue 中会添加到任务队列中,待执行栈的任务处理完才执行。所以 vue 中我们改变数据时不会立即触发视图,但是如果我们需要实时获取到最新的DOM,这个时候可以手动调用 nextTick。
在这里我们最好去认识一下事件循环机制,才能更好的了解 $nextTick 的机制。
曾经,刚开始吃过大亏,当时就是因为还没有获取到更新(v-show)后状态的 DOM 元素,导致事件没能绑定到对应的元素上,测试了半天都没效果,后面才发现是这个问题。引以为戒!!!!
为什么
往往我们在网页开发中,常常会引入 script 标签,同时我们知道,在 Http网页中会按顺序之上往下的执行,这也就是为什么我们需要把 mate 标签放在最上面,而把 script 放在最下面,前者是便于网络爬虫爬取,便于优化网站的排名,后者是避免脚本加载的过程造成的阻塞,影响网页的渲染.,针对这个问题,所以就提出了两种解决的方案 defer 和 async,在了解这两个解决方案前我们先看看。
页面的加载和渲染过程是如何的
○ 首先浏览器发送 HTTP请求获取到HTTP文档,获取到后会自上往下顺序执行,构建DOM
○ 构建中,如果遇到外联声明或者脚本声明,会暂停文本构建,创建新的网络请求,获取外联样式或者脚本文件
○ 获取到后,会执行外联样式文件或脚本,之后才会继续文档解析
○ 最后完成文档解析后,将DOM和CSSDOM进行关联和映射,最后将视图渲染到浏览器窗口
所以说这个过程中,外联文件的下载执行和文档的构建是同步进行的,如果当我们外联的文件遇到问题,一直没有下载下来,而同时文档的构建也没法进行,就会很大程度上影响用户的体验,也就是阻塞了文档的的解析。所以我们看下面的办法是如何解决的。
defer
这个布尔属性被设定用来通知浏览器该脚本将在文档完成解析后,触发 DOMContentLoaded 事件前执行。
带有该属性会告诉浏览器马上下载该脚本,但是并不会马上执行脚本,而是在文档解析完成后才进行,同时对于多个脚本的话,发送的网络请求是同步请求的。
async
带有该属性,会告诉浏览器马上下载该脚本,同时继续文档解析,当下载完成后会马上解析执行脚本。而遇到多个脚本的时候,发送的网络请求是异步的,所以可能湖出现后面的脚本比前面的脚本更加前进行解析执行。所以如果使用该属性,要注意脚本之间不存在依赖关系。
区别
script标签
js执行顺序
是否阻塞解析HTTP
网络请求
script
在HTTP中的执行顺序
阻塞
网络请求同步
script defer
在HTTP中的执行顺序
不阻塞
网络请求同步
script async
网络请求的返回顺序
可能阻塞也可能不阻塞
网络请求异步
所以基于上面两者的有特点,如果你的脚本依赖于文档内容是否解析完,以及脚本之间是否依赖,那么使用 defer,反之就是async。
补充: 一般执行过程
文档解析->脚本加载->脚本执行->DOMContentLoadeed
写法:
// 函数声明:
function foo() {
alert( “foo” );
}
//函数表达式:
const foo = function() {
alert( “foo” );
}
代表的含义:
○ 上面这两段示例代码的意思都是“创建一个名为foo的函数”(函数也是对象)。
创建时机:
○ 函数声明:解析器在向执行环境中加载数据时会率先读取函数声明,并使其在执行任何代码之前可用(可访问),即函数声明提升。函数声明的另外一个特殊的功能是它们的块级作用域。严格模式下,当一个函数声明在一个代码块内时,它在该代码块内的任何位置都是可见的。但在代码块外不可见。
○ 函数表达式:在代码执行到表达式时被创建,被创建之前是调用不了的。
● SVG:SVG可缩放矢量图形,通过XML描述的2D图形,SVG基于XML就意味着SVG DOM中的每个元素都是可用的,可以为某个元素附加Javascript事件处理器。在SVG中,每个被绘制的图形均被视为对象。如果SVG对象的属性发生变化,那么浏览器能够自动重现图形。
● 其特点如下:
● Canvas:Canvas是画布,通过Javascript来绘制2D图形,基于像素,通过画布和绘制的API实现,是逐像素进行渲染的。其位置发生改变,就会重新进行绘制。
● 其特点如下:
● 区别
Canvans
SVG
历史
较新,由Apple私有技术发展而来
历史悠久,2003年成为W3C标准
功能
简单,2D绘图API
功能丰富,各种图像,动画等
特点
基于像素,只能脚本驱动
矢量,XML,CSS,元素操作
对象
基于像素
基于图像对象
驱动
单个HTML元素
多个图形元素(Path,Line等)
性能
适合小面积,大数量
适合大面积,小数量
模糊
基于像素,也就是分辨率,放大会模糊失真
矢量,改变大小不会失真
● 使用选择
JS 数据类型有:
数字(number)、字符串(string)、布尔(bool)、符号(symbol)、空(undefined)、空(null)、对象(object)、大整型(bigint)。
JS 数据类型又可以分为两种
基本数据类型:包括Undefined、Null、Boolean、Number、String、Symbol、BigInt七种基本数据类型。
引用数据类型:Object。常见的有对象、数组和函数等。
基本数据类型和引用数据类型有什么区别?
概念:
● 基本数据类型:简单的数据段,表示不能再细分下去的基本类型。
● 引用数据类型:有多个值构成的对象。对象在逻辑上是属性的无序集合,是存放各种值的容器。对象值存储的是引用地址,所以和基本类型值不可变的特性不同,对象值是可变的。
存放位置
● 基本数据类型:原始数据类型直接存储在栈(stack)中的简单数据段,占据空间小、大小固定,属于被频繁使用数据,所以放入栈中存储;
● 引用数据类型:引用数据类型存储在堆(heap)中的对象,占据空间大、大小不固定。如果存储在栈中,将会影响程序运行的性能;引用数据类型在栈中存储了指针,该指针指向堆中该实体的起始地址。当解释器寻找引用值时,会首先检索其在栈中的地址,取得地址后从堆中获得实体。
扩展文章:
JavaScript基础知识-数据类型以及数据的三种判断方式
Vue的作用域样式 Scoped CSS 的实现思路如下:
为每个组件实例(注意:是组件的实例,不是组件类)生成一个能唯一标识组件实例的标识符,我称它为组件实例标识,简称实例标识,记作 InstanceID;
给组件模板中的每一个标签对应的Dom元素(组件标签对应的Dom元素是该组件的根元素)添加一个标签属性,格式为 data-v-实例标识,示例:
;
给组件的作用域样式
特点
1.将组件的样式的作用范围限制在了组件自身的标签,即:组件内部,包含子组件的根标签,但不包含子组件的除根标签之外的其它标签;所以 组件的css选择器也不能选择到子组件及后代组件的中的元素(子组件的根元素除外);
因为它给选择器的最后一个选择器单元增加了属性选择器 [data-v-实例标识] ,而该属性选择器只能选中当前组件模板中的标签;而对于子组件,只有根元素 即有 能代表子组件的标签属性 data-v-子实例标识,又有能代表当前组件(父组件)的 签属性 data-v-父实例标识,子组件的其它非根元素,仅有能代表子组件的标签属性 data-v-子实例标识;
2.如果递归组件有后代选择器,则该选择器会打破特性1中所说的子组件限制,从而选中递归子组件的中元素;
原因:假设递归组件A的作用域样式中有选择器有后代选择器 div p ,则在每次递归中都会为本次递归创建新的组件实例,同时也会为该实例生成对应的选择器 div p[data-v-当前递归组件实例的实例标识],对于递归组件的除了第一个递归实例之外的所有递归实例来说,虽然 div p[data-v-当前递归组件实例的实例标识] 不会选中子组件实例(递归子组件的实例)中的 p 元素(具体原因已在特性1中讲解),但是它会选中当前组件实例中所有的 p 元素,因为 父组件实例(递归父组件的实例)中有匹配的 div 元素;
选择器
格式
优先级权重
id选择器
#id
100
类选择器
.classname
10
属性选择器
a[ref = “eee”]
10
伪类选择器
li:last-child
10
标签选择器
div
1
为元素选择器
li:after
1
相邻兄弟选择器
h1 + p
0
子选择器
ul > li
0
后代选择器
li a
0
通配符选择器
*
0
注意事项:
● !important声明的样式的优先级最高;
● 如果优先级相同,则最后出现的样式生效;
● 继承得到的样式的优先级最低;
● 通用选择器(*)、子选择器(>)和相邻同胞选择器(+)并不在这四个等级中,所以它们的权值都为0;
● 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
一、跨源资源共享(CORS)
在说简单请求前我们先来了解一下我们往往在学习前端之初和后端对接接口的时候遇到的一个常见的问题,那就是跨域!
那么了解啥是跨域之前,我们看看一什么是同源?因为只有当不是同源的时候,才会涉及到跨域了!
二、同源的MDN定义:
如果两个 URL 的 protocol、port(en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。
作用就是:不允许不同的ip、端口、协议的应用在浏览器内进行互相资源共享、请求调用。避免出现一些安全问题!
判断是否同源:
因为出于安全性,浏览器限制脚本内发起的跨源HTTP请求。也就是来自其他域的请求,这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求HTTP资源,除非响应报文包含了正确CORS响应头,也就是我们的CORS。
三、那么CORS是如何工作的呢?
那么我们要讲的简单请求和复杂请求所需要了解的基本知识就简单介绍完了!
四、简单请求:某些请求不会触发 CORS 预检请求(触发options方法)。本文称这样的请求为“简单请求”
1、使用下列方法之一:GET、POST、HEAD。
2、不得人为设置该集合之外的其他首部字段。该集合为:Accept,Accept-Language,Content-Language,Content-Type
3、Content-Type 的值仅限于下列三者之一:text/plain,multipart/form-data,application/x-www-form-urlencoded
4、请求中的任意XMLHttpRequestUpload 对象均没有注册任何事件监听器;XMLHttpRequestUpload 对象可以使用 XMLHttpRequest.upload 属性访问
5、请求中没有使用 ReadableStream 对象
五、复杂请求:不是简单请求的都是复杂请求,也可以看做在实际进行请求之前,需要发起预检请求的请求。
六、关于options方法
这次的太多了,下次补充
箭头函数
普通函数
写法
使用箭头定义,省去 function 关键词( => )
function 关键词
可否具名
只能是匿名函数
可以具名也可匿名
构造函数
不能用于构造函数,不能使用new
可以用于构造函数,以此创建对象实例
argument
不绑定arguments,使用rest参数…来访问参数列表
具有一个arguments对象,用来存储实际传递的参数
this指向
没有 prototype ,所以本身没有this,一般指向其所在的上下文,任何方法都改变不了其指向,如 call() , bind() , apply()
this一般指向它的调用者
1. 文档声明(Doctype)和(!Doctype html)有何作用
2. 严格模式与混杂模式的区分:
总之,严格模式让各个浏览器统一执行一套规范兼容模式保证了旧网站的正常运行。
1. call的作用
● 使用 call() 方法,您可以编写能够在不同对象上使用的方法。就是改变它的this指向,
● call(第一个参数:想让函数中this指向谁,就传谁进来,后面的参数:本身函数需要传递实参,需要几个实参,就一个一个的传递即可);call的作用: 1. 调用函数 2.指定函数中this指向
2. apply的作用
● 通过 apply() 方法,您能够编写用于不同对象的方法。
● Function.apply(obj,args)方法能接收两个参数obj:这个对象将代替Function类里this对象args:这个是数组,它将作为参数传给Function(args–>arguments)
3. bind的作用
● bind()方法创建一个新的函数,在bind()被调用时,这个新函数的this被bind的第一个参数指定,其余的参数将作为新函数的参数供调用时使用。
三者异同
相同:都能改变 this 的指向,都是挂载在 Function. prototype 上
不同:call 和 apply 是使用后马上执行,而 bind 是返回一个新的函数,调用显函数才会执行目标函数
格式
优点
缺点
适用场景
GIF
文件小,支持动画,透明,无兼容性问题
只支持256种颜色
logo、icon、动图
JPG
色彩丰富、文件小
有损压缩
色彩丰富的图
PNG
无损压缩、支持透明、简单图片尺寸小
不支持动画、色彩丰富的图片尺寸大
logo、icon、透明图
SVG
随意伸缩不牺牲质量、支持动画、比前三者小
复杂度高减慢渲染速度
图标
全局属性 NaN 的值表示不是一个数字(Not-A-Number)
NaN 属性是一个不可配置(non-configurable),不可写(non-writable)的属性。但在ES3中,这个属性的值是可以被更改的,但是也应该避免覆盖。
编码中很少直接使用到 NaN。通常都是在计算失败时,作为 Math 的某个方法的返回值出现的(例如:Math.sqrt(-1))或者尝试将一个字符串解析成数字但失败了的时候(例如:parseInt(“blabla”))。
typeof NaN; // “number”
NaN是一个特殊值,它和自身不相等,是唯一一个非自反(自反,reflexive,即x === x不成立)的值。而NaN !== NaN 为 true。
● display: none:渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件。
● visibility: hidden:元素在页面中仍占据空间,但是不会响应绑定的监听事件。
● opacity: 0:将元素的透明度设置为0,以此来实现元素的隐藏。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件。
● position: absolute:通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏。
● z-index: 负值:来使其他元素遮盖住该元素,以此来实现隐藏。
● clip/clip-path:使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。
● transform: scale(0,0):将元素缩放为0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件。
在VUE中还有 v-if 和 v-show 这两个指令,在目录你能看到关于这两个的使用区别,同时原生中我们使用 display: none与visibility: hidden 比较多,你也能看看这两者的区别。
window.onload和DOMContentLoaded的区别
他们的区别是按照执行顺序来的:
我们再来看一下DOM的执行顺序:
我们可以看到在第4步的时候DOMContentLoaded事件会被触发。在第6步的时候onload事件会被触发。那么为什么需要分为两个阶段呢?
因为当我们需要给元素添加某些触发方法时,但是这时元素还没有渲染出来,那么是没有效果的,而这两个回调就能避免这些情况。同理的我们在vue中也会遇到这种问题,这时候使用的是一个叫 $nextTick 的函数。
这两个属性都是让元素隐藏,不可见。两者主要分两点:
关于跨域上面有介绍到,这里总结下面两点:
解决方案一:jsonp
这种方式,实际我们只需要了解一下可以这样处理就好了,实际中并不怎么使用:
解决方案二:中间服务器代理
这种往往我们需要写一个配置文件,配置vue.config.js文件,在里面思想是创建虚拟的本地服务器,当发起请求时,axios向本地服务器请求,本地服务器再向目的服务器请求,这样就不存在跨域问题了。
前端部署的地址->中间服务器->目标服务器,如同下面的请求端口:
127.0.0.1:8000->127.0.0.1:8000->127.0.0.1:8888
具体实现如图:
解决方案三:CORS跨域资源共享
提个问题?是不是在cmd里所有的操作 git bash 都可以执行
答案是:大部分是可以的。
git在安装的时候也安装了一个轻量级的bash环境,然后启动这个"git bash",出来的命令窗口就带有这个bash环境的环境变量。bash是基于shell的命令库,本身是unix下的命令脚本。你甚至可以在"git bash"中使用一个轻量的vi编辑器(神器!不过鄙人不会用>_<!!)。
因此本质上来说:git bash是一个封装过的cmd命令行,并在其中加入了指向bash环境的环境变量。因此cmd命令行本身的环境变量它也有,自然就能使用dos命令了。但是在封装的过程中,windows可能对调用自己cmd命令行控件的第三方应用设了限制,所以说是大部分可以用。
● absolute 生成绝对定位的元素,相对于static定位以外的第一个父元素进行定位
● relative 生成相对定位的元素,相对于其正常位置进行定位
● fixed 生成绝对定位的元素,相对于浏览器窗口进行定位
● static 元素出现在正常的流中
● inherit 规定应该从父元素继承position属性的值
补充:不过实际企业开发中好像使用BEM的也比较多,因为这样更加便于阅读和可维护
三者的区别:
● px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
● em和rem相对于px更具有灵活性,他们是相对长度单位,其长度不是固定的,更适用于响应式布局。
● em是相对于其父元素来设置字体大小,这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而rem是相对于根元素,这样就意味着,只需要在根元素确定一个参考值。
使用场景:
● 对于只需要适配少部分移动设备,且分辨率对页面影响不大的,使用px即可。
● 对于需要适配各种移动设备,使用rem,例如需要适配iPhone和iPad等分辨率差别比较挺大的设备。
px、em、rem的区别与转换 ?
px像素相对长度单位
em是相对于当前对象内文本的字体尺寸,默认16px,值不是固定的,会继承父级元素的字体大小
rem 是 CSS3 新增的一个相对单位,相对的是HTML 根元素,1rem等于HTML根元素设定的font-size的px值
补充个问题,那么我们一般最小可设置的像素是12,如何实现更小的呢?看目录哈!
静态语言
● 在声明变量之前需要先定义变量类型。我们把这种在使用之前就需要确认其变量数据类型的称为静态语言。
动态语言
● 在声明变量之前不需要先定义变量类型。我们把这种在使用之前不需要确认其变量数据类型的称为动态语言。
弱/强类型语言
● 通常把会偷偷转换的操作称为隐式类型转换。而支持隐式类型转换的语言称为弱类型语言,不支持隐式类型转换的语言称为强类型语言。
JavaScript
JavaScript 是一种弱类型的、动态的语言。那这些特点意味着什么呢?
● 弱类型,意味着你不需要告诉 JavaScript 引擎这个或那个变量是什么数据类型,JavaScript 引擎在运行代码的时候自己会计算出来。
● 动态,意味着你可以使用同一个变量保存不同类型的数据
我们知道在谷歌浏览器中,字体的最小值为12px,当你设置字体为10px的时候,结果显示的还是12px,这是因为Chrome浏览器做了如下限制:
那么设计给你的小于12px,该如何处理呢?
提刀找设计,简简单单,告诉他(男设计)低于12是不可的~~
要是设计是个妹子呢?那么我们肯定是有求必应的,怎么能说不行呢?
首先我们的CSS3有个新的属性是:transform:scale(0.8)
-webkit-transform-origin-x: 0; /*缩小后文字居左*/
-webkit-transform: scale(0.80); /*关键*/
transform:scale(0.8);
注意点:
transform:scale()这个属性只可以缩放可以定义宽高的元素,而行内元素是没有宽高的,我们可以加上一个display:inline-block;
class Heap {
var a[]; // 数组,从下标1开始存储数据
var n; // 堆可以存储的最大数据个数
var count; // 堆中已经存储的数据个数
Heap(capacity) {
a = new Array[capacity + 1];
n = capacity;
count = 0;
}
//插入元素
insert(data) {
if (count >= n) return; // 堆满了
++count;
a[count] = data; // 最后一位插入
const i = count;
swim(a, count,i)
}
}
//从下往上堆化
swim(a,n,i) {
while (i/2 > 0 && a[i] > a[i/2]) { // 存在父节点,且子节点大于父节点
swap(a, i, i/2); // swap():交换下标为i和i/2的两个元素
i = i/2;
}
}
}
麻了,每次都是要用的时候才去查,这里整理总结一下,可以实现div居中的几种方式
方式一
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
方式二
可以给父元素添加下面的属性,利用flex布局来实现
display: flex;
align-items: center;
flex-direction: column
方式三
通过定位和变形来实现
给父元素添加 position: relative;相对定位。
给自身元素添加position: absolute;绝对定位。
top: 50%;使自身元素距离上方“父元素的50%高度”的高度。
left: 50%;使自身元素距离上方“父元素的50%宽度”的宽度。
transform: translate(-50%,-50%);使自身元素再往左,往上平移自身元素的50%宽度和高度。
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
方式四
这个是实现内容文本居中的,坑死了,之前没留意在一个全局的文件加了,后面很多组件里面的内容都居中了,还一时没发现,虽然想到会不会是全局文件的问题,但一下子眼拙没看到,结果捣鼓半天
body{ text-align:center}
我们在现在的开发中,往往会使用到各种的UI框架,但是有时候框架的样式并不是我们想要的,而且这时候我们去直接修改这个样式的时候,往往还会不生效,这是为什么呢?下面我们一一道来~~
首先我们说一个 scoped 属性:
我们在写组件的时候,往往会加上这个属性,它的作用是不污染全局样式,只在当前组件的范围内有效。它具体是如何实现的呢?
我么可以看到每一个元素都加上了一个标识符,这个的实现基于PostCss。所以我们在使用第三方的组件库的时候,就无法简单的覆盖了,我们无法读取到组件里的。所以这里就到了我们这次的主题:样式穿透
用法:
父元素 ::v-deep 内部元素(通过f12查看元素类名)
是不是简简单单,其实除了上面这种方式外,还有下面两种,但是上面这种基本可以解决我们遇到的问题了~~
//如果使用的是css,可以用下面这种
外层容器 >>> 组件 {}
//但在css预处理器中用上面这种是无法生效的,类似在scss和less中,我们可以用下面这种。
外层容器 /deep/ 组件 {}
1. 为什么使用缓存呢?
首先有如下优先:
可以说缓存就是用内存换时间
2. 缓存过程:
3. 强缓存
强缓存中最重要的两个参数,强缓存就是通过这两个参数来控制缓存在本地的有效期。
4. 协商缓存
那么为什么有了强缓存还需要协商缓存呢?
我们可以知道强缓存,给资源添加了一个过期时间,客户端通过判断是否过期来直接使用缓存或者请求服务端。
而当超过这个过期时间时,客户端向服务端发起请求就是协商缓存,服务端告诉之前的缓存是否还有效,有效就返回304让客户端继续直接使用,并且更新缓存参数。要是无效了,就返回200,告诉客户端使用最新返回的资源。
协商缓存中只要有这两个参数 Last-Modifed/If-Modified-Since 和 Etag/If-None-Match
5. 缓存的特点
前几天用到的获取时间的一些基本方法,这里简单介绍,举个获取当天时间的例子:
//获取当前时间
var date = new Date();
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
//这两个是针对小于十的时候,会为个位数
if (month < 10) {
month = "0" + month;
}
if (day < 10) {
day = "0" + day;
}
var nowDate = year + "-" + month + "-" + day;
//输出结果格式:2021-11-08
基本的函数:
getDate() 返回一个月中的某一天(1-31)
getMonth() 返回月份 (0 ~ 11)
getFullYear() 以四位数字返回年份(2011)
getHours() 返回小时 (0 ~ 23)
getMinutes() 返回分钟 (0 ~ 59)
getSeconds() 返回秒数 (0 ~ 59)
getTime() 返回 1970 年 1 月 1 日至今的毫秒数(1566715528024)
推荐阅读的关于时间的一些时间方法获取:
src 于替换当前元素,href 于在当前档和引资源之间确联系
● src
src 是 source 的缩写,指向外部资源的位置,指向的内容将会嵌到档中当前标签所在位置;在请求 src 资源时会将其指向的资源下载并应到档内,例如 js 脚本,img 图和 frame 等元素。
当浏览器解析到该元素时,会暂停其他资源的下载和处理,直到将该资源加载、编译、执完毕,图和框架等元素也如此,类似于将所指向资源嵌当前标签内。这也是为什么将js 脚本放在底部不是头部。
● href
href 是 Hypertext Reference 的缩写,指向络资源所在位置,建和当前元素(锚点)或当前档(链接)之间的链接,如果在档中添加那么浏览器会设别该文档为 css 件,就会并下载资源并且不会停对当前档的处理。 这也是为什么建议使 link 式来加载 css,不是使@import 式。
前两天做个项目的时候,发现一急起来,命名也乱七八糟了,这里整理一下
首先几点:
1.考虑为元素命名其本身的作用或”用意”,达到语义化。不要使用表面形式的命名,命名的方式能描述出正在做的事情
如:red/left/big等,而是比如一个搜索的按键:searchButton
2.组合命名规则,[元素类型]-[元素作用/内容]
如:搜索按钮: btn-search
登录表单:form-login
新闻列表:list-news
3.涉及到交互行为的元素命名:,凡涉及交互行为的元素通常会有正常、悬停、点击和已浏览等不同样式,命名可参考以下规则:
鼠标悬停::hover 点击:click 已浏览:visited
如:搜索按钮: btn-search、btn-search-hover、btn-search-visited
4.组件的命名要大写
前几天做个业务的时候用到插槽,才发现对这个还不太熟,这里来回顾一下基本的使用。
我们在开发中,往往会把一个复杂的页面分为多个组件去组合,或者当复用性比较高的也会提取为一个组件,但是当我们设计的组件还不能在某些特殊的情况满足我们的需求的时候,我们还需要添加额外的时候,那么该怎么办呢?总不可能再写多一个组件吧?这样的话,复用性也太低了。所以插槽就出现了。
当实际使用的组件不能完全的满足我们的需求的时候,我们就可以用插槽来分发内容,往我们的这个组件中添加一点东西。
基本的使用就是在子组件里面我们需要添加内容的地方加上
那么我们在父组件中使用该子组件的时候,就能直接在这个子组件中添加进入内容。这就是最基本的使用。
那么要是我们想在父组件中传递多个内容呢?这时候就有我们的对应使用的具名插槽了,我们可以对插槽命名狮子对应在子组件中的位置。
//我们可以在子组件的插槽中写入内容,也就是默认值,当我们在父组件中没有传递值的时候,就会使用默认值
我是默认的插槽值
//具名插槽
如下,我们在子组件中的插槽可以命名,这样我们在父组件中可以通过名字来确定位置
//子组件B
const BBorder = {
template: `我是儿子B
这里是头部
这里是默认
这里是尾部 `,
}
const Parents = {
methods: {
},
//实例化BBorder这个子组件
template: `我是父组件:{{msg}}
头部
A 9999999999999
尾部
`,
//注册两个子组件
components: {
BBorder
}
}
同样的,也能出传递值给到父组件中,但是我么要注意,父子组件的编译作用域是不同的,父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
从子组件中传递值到父组件
//子组件中
{{ user.lastName }}
//父组件中
{{ slotProps.user.firstName }}
基本的使用级就这样啦~~
1.typeof
● 这个方法很常见,一般用来判断基本数据类型,如:string,number,boolean,symbol,bigint(es10新增一种基本数据类型bigint),undefined等。
typeof 目前能返回string,number,boolean,symbol,bigint,unfined,object,function这八种判断类型
面试题
为什么typeof null是Object
答:因为在JavaScript中,不同的对象都是使用二进制存储的,如果二进制前三位都是0的话,系统会判断为是Object类型,而null的二进制全是0,自然也就判断为Object
这个bug是初版本的JavaScript中留下的,扩展一下其他五种标识位:
000 对象
1 整型
010 双精度类型
100字符串
110布尔类型
2. instanceof
● 一般用来判断引用数据类型的判断,如:Object,Function,Array,Date,RegExp等
instanceof 主要的作用就是判断一个实例是否属于某种类型
● instanceof 也可以判断一个实例是否是其父类型或者祖先类型
● instanceof原理实际上就是查找目标对象的原型链
手写实现一个:
//手写实现
function myInstance(L, R) {//L代表instanceof左边,R代表右边
var RP = R.prototype
var LP = L.__proto__
while (true) {
if(LP == null) {
return false
}
if(LP == RP) {
return true
}
LP = LP.__proto__
}
}
console.log(myInstance({},Object));
3. Object.prototype.toString.call(这个是判断类型最准的方法)
toString是Object原型对象上的一个方法,该方法默认返回其调用者的具体类型,更严格的讲,是 toString运行时this指向的对象类型, 返回的类型格式为[object,xxx],xxx是具体的数据类型,其中包括:String,Number,Boolean,Undefined,Null,Function,Date,Array,RegExp,Error,HTMLDocument,… 基本上所有对象的类型都可以通过这个方法获取到。
必须通过Object.prototype.toString.call来获取,而不能直接 new Date().toString(), 从原型链的角度讲,所有对象的原型链最终都指向了Object, 按照JS变量查找规则,其他对象应该也可以直接访问到Object的toString方法,而事实上,大部分的对象都实现了自身的toString方法,这样就可能会导致Object的toString被终止查找,因此要用call来强制执行Object的toString方法。
缺点:不能细分为谁谁的实例
先回顾两个命令的定义
● git fetch 命令用于从另一个存储库下载对象和引用
● git pull 命令用于从另一个存储库或本地分支获取并集成(整合)
git的工作流程
可以看出
git fetch是将远程主机的最新内容拉到本地,用户在检查了以后决定是否合并到工作本机分支中
而
git pull 则是将远程主机的最新内容拉下来后直接合并,即:git pull = git fetch + git merge,这样可能会产生冲突,需要手动解决
区别:
相同点:
不同点:
今天回顾才发现对vuex的掌握,很多地方都忘了,太气人了,所以这里简单回顾一下vuex
首先我们肯定得看看vuex是做什么的
简单来说,vuex是基于vue框架的一个状态管理库。那么什么是状态管理呢?
我们在做大型的开发的时候的话,往往会涉及到很多的数据,但如果我们仅仅在该文件的处理,就会导致代码变得臃肿难以维护及复用,举个例子,比如某个数据会在多个组件中展示,或修改,那么我们就可以很清晰的明白来源和处理。或者复杂应用的数据状态,比如兄弟组件的通信、多层嵌套的组件的传值等等。统一的管理,可以使得数据便于
那么我们知道为什么需要vuex之后,我们来简单回顾一下他的使用吧:
vuex有五个核心的概念 state getters mutations action modules
1. state
computed: {
count () {
return store.state.count
}
}
//下面这种是全局注册了store的
computed: {
count () {
return this.$store.state.count
}
}
每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。
辅助函数mapState:当一个组件需要获取多个状态的时候,将这些状态都声明为计算属性会有些重复和冗余
computed: {
// 使用对象展开运算符将此对象混入到外部对象中
...mapState({
// ...
})
}
2. getters
可以简单理解为state的计算属性,依赖于state的值,state发生改变就会同时改变
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
}
}
})
3. mutations
待续
4. action
待续
5. modules
待续
GET和POST,两者是HTTP协议中发送请求的方法
GET
GET方法请求一个指定资源的表示形式,使用GET的请求应该只被用于获取数据
POST
POST方法用于将实体提交到指定的资源,通常导致在服务器上的状态变化或副作用
本质上都是TCP链接,并无差别
W3C上讲述的区别
具体post和get的区别分析
get把请求的数据放在url上,即HTTP协议头上,其格式为:以分割URL和传输数据,参数之间以&相连;post把数据放在HTTP的包体内(requrest body
get提交的数据最大是2k(原则上url长度无限制,那么get提交的数据也没有限制咯?限制实际上取决于浏览器,浏览器通常都会限制url长度在2K个字节,即使(大多数)服务器最多处理64K大小的url,也没有卵用);
post理论上没有限制。实际上IIS4中最大量为80KB,IIS5中为100KB**
GET产生一个TCP数据包,浏览器会把http header和data一并发送出去,服务器响应200(返回数据);
POST产生两个TCP数据包,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据)**
GET在浏览器回退时是无影响的,POST会再次提交请求
GET请求会被浏览器主动cache,而POST不会,除非手动设置
安全性:
都不安全,原则上post肯定要比get安全,毕竟传输参数时url不可见,但也挡不住部分人闲的没事在那抓包玩,浏览器还会缓存get请求的数据。安全性个人觉得是没多大区别的,防君子不防小人就是这个道理。对传递的参数进行加密,其实都一样
编码方式
GET请求只能进行url编码,而POST支持多种编码方式
是否有历史记录
GET请求参数会被完整保留在浏览器历史记录里,而POST中的参数不会被保留
接收类型
GET只接受ASCII字符的参数的数据类型,而POST没有限制
那么,post那么好为什么还用get?get效率高!
麻了,今天看一段代码没看明白扎回事,才发现这里有个知识点都忘的一干二净了,这里来回顾一下
定义过滤器有两种常见的方式: 双花括号插值和 v-bind 表达式
{{ message | capitalize }}
那么过滤器有什么用呢?其实一般用来格式文本,比如我们往往拿到的时间是时间戳,那么就可以很简单的通过过滤器来格式成我们常见的格式,这个非常便捷。
使用上的我们先要定义过滤的方法,这个可以全局定义或者组件内定义
//组件内定义
filters: {
capitalize: function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
}
}
//全局定义
Vue.filter('capitalize', function (value) {
if (!value) return ''
value = value.toString()
return value.charAt(0).toUpperCase() + value.slice(1)
})
new Vue({
// ...
})
基本简单的使用这样就够了,还想了解更多可以去看官方文档
简单来说,我们知道Vue中有多种的方式来父子组件传值
父传子:props
子传父:this.$emit(‘事件’ , ‘数据’)
兄弟组件:中间事件总线思想(也叫事件巴士)
多层级:父组件中使用 provide ,在子组件中使用 inject
今天我们要来讲的就是事件总线这个,简单来说,它的使用就是首先创建事件总线,也就是我们的大巴
//两种方式,一种是创建一个单独的文件如下面的的bus.js在用到的地方导入,
//还有中就是在项目的main中实例化
// bus.js
import Vue from 'vue'
export const EventBus = new Vue()
// main.js
Vue.prototype.$EventBus = new Vue()
创建好之后我们就很简单啦
//在需要发送事件的地方用通过 this.$emit(‘事件’ , ‘数据’)来发送
//在需要接收的地方用xxx.$on('事件',(数据)=>{ })来接收
//下面是一个完整的例子
Document
是不是发现太好用了,特别是当我们的项目中还没有复杂到使用vuex的时候,那么为什么没有被大范围使用呢,这里我们就要注意了。写的时候是非常爽,但是当你修改维护的时候,特别别人看的时候,那得叫苦连天了,使用事件总线的话,就好像一量飞机,载着数据满天飞,多了的话,你就很难维护。那时候真是叫天天不应,叫地地不灵了~~·
前些天,公司里的大佬们就有同事强烈认为应该完全禁止使用,哈哈哈,其实工具没错,但不应该滥用。
前段时间在上班的时候,发现有个路由跳转还携带了参数,突然才意识到当年自己vue学又多烂还忘了很多东西,有空真的应该完整的去回顾一次的~~~
也回想当年做过一个项目,当时的做法多傻,当时是一个视频网站,需要点击这个视频就有相关的跳转到该视频的播放页面,当时我居然把那个id存储到localstoram里面,在那个页面再去读取,妈呀,现在回想有好几种方式实现,扎做都不会沦落到用localstorage来~~~
回到正题~
传参方式可划分为 params 传参和 query 传参,而 params 传参又可分为在 url 中显示参数和不显示参数两种方式
方式A:这种需要在路由配置好可以传递参数XXX的,不是最方便的
路由配置
{
path: '/child/:XXX',
component: Child
}
父组件
子组件读取
this.num = this.$route.params.XXX
方式B:这种同样需要在路由配置好可以传递参数XXX的,不过是用到push方法的
路由配置
{
path: '/child/:XXX',
component: Child
}
父组件
this.$router.push({
path: `/child/${XXX}`
})
子组件读取
this.num = this.$route.params.XXX
上面两种方式都会在地址显示出传递的参数,类似get请求~~
方式C:这种不需要在路由配置好根据路由的名称,需要保持一致
路由配置
不需要配置,但是子组件的name必须与父组件传递的路由一致
父组件
this.$router.push({
name: 'B',
params: {
XXX: '妈呀'
}
})
子组件读取
this.num = this.$route.params.XXX //妈呀
方式D:这种不需要在路由配置好根据路由的名称,通过query来传递
路由配置
不需要配置,但是子组件的name必须与父组件传递的路由一致{
父组件
this.$router.push({
path: '/child',
query: {
XXX: '妈呀'
}
})
子组件读取
this.num = this.$route.query.XXX
总的来说使用方式C和D最为多,毕竟不需要对路由配置做修改
不得不说,解构赋值真的好用,但是今天,万能的解构赋值居然报错了,看了眼看出问题是啥,但是居然不知道扎解决,过去这种问题都是用可选连和判断空值的,这好像不能和我高大上的解构赋值一起搞呀,问了问大佬,妙及了,马上给我丢了几个案例,这里分享一波~~
基本的结构赋值我就不细说了,大家随便都能查到,这里主要想说的是多层嵌套对象该如何解构
我们看一个例子
我们需要解构出第三个箭头的数组出来,我们可以下面的写法(为什么 _statistic 是中括号的的实际关系到一个属性访问和键访问的知识点,区别在于是否符合标识符的命名规范~)
上面的这种写法是可以读取出来我们解构的值的,但是会报错
类似这个错,说读取不到project
那么这是为什么呢?这是因为我们的是对象嵌套对象,的那个我们遇到对象嵌套对象的时候就要小心了,经常会出现各种的问题,往往会有:读取到值,功能正常,但是会报错,或者值传递进去了但我们对其赋值操作不成功。(这个可以用监听并且加上 immediate: true,和 deep: true)
只是因为当我们一个值为null的时候,他也是对象,所以在一开始这个值还没有的时候是为null的,那么也就没有他嵌套的属性,所以会报cannot read
这时候往往要做的是空值判断,我们可以使用可选链或者判断空值来解决
空值判断
可选链
//比如上图那个:通过加问号就好了
data.owner?.avatar
回到主题,那么我们上面解构报错的问题就知道了~就是出现空值的情况,那么如何解决呢?
那就是设置缺省值 ,设置缺省值为{}
开头的那段代码,改为这样就不会报错了~
简单的那些我就不说了,今天我们来过一过比较少见,但不得不会的那些~~毕竟会的话,方便不少了~
1.标签属性选择器
//CSS 属性选择器通过已经存在的属性名或属性值匹配元素。
/* 存在title属性的 元素 */
?
a[title] {
color: purple;
}
/* 存在href属性并且属性值匹配"https://example.org"的 元素 */
a[href="https://example.org"] {
color: green;
}
[attr]
表示带有以 attr 命名的属性的元素。
[attr=value]
表示带有以 attr 命名的属性,且属性值为 value 的元素。
[attr~=value]
表示带有以 attr 命名的属性的元素,并且该属性是一个以空格作为分隔的值列表,其中至少有一个值为 value。
[attr|=value]
表示带有以 attr 命名的属性的元素,属性值为“value”或是以“value-”为前缀("-"为连字符,Unicode 编码为 U+002D)开头。典型的应用场景是用来匹配语言简写代码(如 zh-CN,zh-TW 可以用 zh 作为 value)。
[attr^=value]
表示带有以 attr 命名的属性,且属性值是以 value 开头的元素。
[attr$=value]
表示带有以 attr 命名的属性,且属性值是以 value 结尾的元素。
_[attr=value]_*
表示带有以 attr 命名的属性,且属性值至少包含一个 value 值的元素。
[attr operator value i]
在属性选择器的右方括号前添加一个用空格隔开的字母 i(或 I),可以在匹配属性值时忽略大小写(支持 ASCII 字符范围之内的字母)。
2. 子代选择器
当使用 > 选择符分隔两个元素时,它只会匹配那些作为第一个元素的直接后代(子元素)的第二元素. 与之相比, 当两个元素由 后代选择器相连时, 它表示匹配存在的所有由第一个元素作为祖先元素(但不一定是父元素)的第二个元素, 无论它在 DOM 中"跳跃" 多少次.
3. 相邻兄弟选择器
相邻兄弟选择器 (+) 介于两个选择器之间,当第二个元素紧跟在第一个元素之后,并且两个元素都是属于同一个父元素的子元素,则第二个元素将被选中。
/* 图片后面紧跟着的段落将被选中 */
img + p {
font-style: bold;
}
1. Set
简介
最大特点:
方法
2. Map
简介
方法
console.log("AAAA");
setTimeout(() => console.log("BBBB"), 1000);
const start = new Date();
while (new Date() - start < 3000) {}
console.log("CCCC");
setTimeout(() => console.log("DDDD"), 0);
new Promise((resolve, reject) => {
console.log("EEEE");
foo.bar(100);
})
.then(() => console.log("FFFF"))
.then(() => console.log("GGGG"))
.catch(() => console.log("HHHH"));
console.log("IIII");
//输出结果:
AAAA
CCCC
EEEE
IIII
HHHH
BBBB
DDDD
分析
事件循环
事件循环前的任务都是同步代码的,响应时间很慢,浪费了很多的时间,js引擎遇到一个异步事件后并不会一直等待其返回结果,而是会将这个事件挂起,继续执行执行栈中的其他任务。当一个异步事件返回结果后,js会将这个事件加入与当前执行栈不同的另一个队列,我们称之为事件队列。被放入事件队列不会立刻执行其回调,而是等待当前执行栈中的所有任务都执行完毕, 主线程处于闲置状态时,主线程会去查找事件队列是否有任务。如果有,那么主线程会从中取出排在第一位的事件,并把这个事件对应的回调放入执行栈中,然后执行其中的同步代码…,如此反复,这样就形成了一个无限的循环。这就是这个过程被称为“事件循环(Event Loop)”的原因。
宏任务和微任务的区别
请问下面这个结果是多少呢?
123['toString'].length + 123 = ?
1. 形参个数
咱们来看看下面这个例子
function fn1 () {}
function fn2 (name) {}
function fn3 (name, age) {}
console.log(fn1.length) // 0
console.log(fn2.length) // 1
console.log(fn3.length) // 2
可以看出,function有多少个形参,length就是多少。但是事实真是这样吗?继续往下看
2. 默认参数
如果有默认参数的话,函数的length会是多少呢?
function fn1 (name) {}
function fn2 (name = '林三心') {}
function fn3 (name, age = 22) {}
function fn4 (name, aaa, age = 22, gender) {}
function fn5(name = '林三心', age, gender, aaa) {}
console.log(fn1.length) // 1
console.log(fn2.length) // 0
console.log(fn3.length) // 1
console.log(fn4.length) // 2
console.log(fn5.length) // 0
上面可以看出,function的length,就是第一个具有默认值之前的参数个数
3. 剩余参数
在函数的形参中,还有剩余参数这个东西,那如果具有剩余参数,会是怎么算呢?
function fn1(name, ...args) {}
console.log(fn1.length) // 1
可以看出,剩余参数是不算进length的计算之中的
4.总结
总结之前,先公布123[‘toString’].length + 123 = 的答案是124
总结就是:length 是函数对象的一个属性值,指该函数有多少个必须要传入的参数,即形参的个数。形参的数量不包括剩余参数个数,仅包括第一个具有默认值之前的参数个数。
编译语言和解释语言
我们写的代码一般不能被电脑直接识别,所以就有了编译器和解释器,也对应不同的语言
编译语言一般是 C / C++, Go等,这类语言首次执行会通过编译器编译出机器能读懂的二进制文件,每次运行的时候会直接运行这个二进制文件。
解释语言一般是Python,JavaScript等,每次执行都需要解释器进行动态解释和执行。
那么编译一般来说有六个步骤
编译语言和解释语言的主要区别在于词义分析后生成的类型不同,他们都会生成AST这一步,3,4,5,6可以合并称之为代码生成
我们前端开发用的JavaScript就是解释型语言,其实一开始是没有字节码的,是直接将AST编译成机器码,所以效率是很高的,但是机器码占用的内存过大,所以又有了字节码的出现。这里又涉及到一门新的技术 JIT (即使编译)的出现。
所以我们的V8引擎使用的是 字节码 + JIT 的技术
首先变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(之前没有声明),然后运行时引擎会在该作用域中查找该变量,如果能找到就会对他赋值
那么引擎查找中就涉及到我们这里要讲到的 LHS查询 和 RHS查询
这里我们不能简单的认为是左右来判断
而是
LHS:赋值操作的目标是谁
RHS:谁是赋值操作的源头
也可以说查找的目的是对变量进行赋值,那么就会使用LHS 查询;如果目的是获取变量的值,就会使用RHS 查询
考虑以下代码:
console.log(a);
其中对a 的引用是一个RHS 引用,因为这里a 并没有赋予任何值。相应地,需要查找并取得a 的值,这样才能将值传递给console.log(…)
相比之下,例如:
a = 2;
这里对a 的引用则是LHS 引用,因为实际上我们并不关心当前的值是什么,只是想要为=2 这个赋值操作找到一个目标。
看个例子
function foo(a) { //LhS
console.log(a) //RHS
}
foo(2) //RHS
这里面就涉及到两种查询了
同时 LHS 和 RHS 会现在当前的作用域查询,没有找到的话就会到上一层查找,最后到全局作用域
简单来说二分法不难,但是有两个需要注意的地方防止踩坑
一种是 [ left , right ]
另一种是 [ left , right )
虽然看起来仅仅是右边是否闭合,但是带来的结果是不一样的,也对应了两种二分法的写法
另一个·就是中间值的,也就是 middle 的取值,这里的要注意是否溢出的问题
首先我们有:left < right
所以有: right - left > 0 ; 又有 : left + (right - left) = right
所以 left + (right - left)/2 <= right
所以不会产生溢出吗,因为每一步都限制了 right 的范围而要是 middle = ( left+ right ) / 2
就会有 left+ right >= right
就可能导致溢出的问题出现
为什么
当我们浏览商品时,我们点击了商品进入了商品的详细页,当我们返回的时候,我们需要的是返回到之前点击前的位置继续,而不是重新到了开头或者刷新了。
那么这里就是需要用到了 keep-alive 。
用 keep-alive 包裹组件时,会缓存不活动的组件实例,也就是我们上面的商品列表,而不是销毁,使得我们返回的时候能重新激活。keep-alive 主要用于保存组件
状态或避免重复创建。避免重复渲染导致的性能问题。
大家可以看这个实例,可以很明显感受到 keep-alive 的作用,他使得我们返回的时候,还能看到之前的内容。
具体的一些实现可以去看看一些文章的详细讲解
常见场景
页面的缓存,如上面的,保存浏览商品页的滚动条位置,筛选信息等
实现数据绑定的只要有下面几种
发布者-订阅者模式(backbone.js)
脏值检查(angular.js)
数据劫持(vue.js)
这里我们看看 vue中的实现。
主要核心来说是通过 Object.definProperty ()来对 data 中的每一个属性 进行 get , set 的拦截。下面是一个基于 Object.definProperty () 的简单实现。
//极简版的双向绑定
效果如图:
可以说我们上面完成简单的双向绑定,但是这里是直接操作dom的,要是我们有一千,一万个dom,那就炸裂了,所以说是简单版本。
而在vue.js 则是采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的setter,getter,在数据变动时发布消息给订阅者,触发相应的监听回调。
思路上是首先有一个数据的监听器 Observer 劫持并且监听所有属性,当发生变化时,就会通知订阅者 Watcher 是否需要更新视图(因为订阅者不止一个所以有一个消息订阅者 Dep 来收集订阅者,在监听器和订阅者之间统一管理)。
同时还有一个指令解析器Compile,,Compile主要做的事情是解析模板指令,将模板中的变量替换成数据,然后初始化渲染页面视图,并将每个指令对应的节点绑定更新函数,添加监听数据的订阅者,一旦数据有变动,当订阅者Watcher接收到,就会执行对应的更新函数,从而更新视图。
参考文章:剖析Vue原理&实现双向绑定MVVM
先来回顾一下这两个指令是分别做什么的。
v-if:根据表达式的真值来有条件的渲染元素,在切换时元素及它的数据绑定 / 组件被销毁并重建。
v-for:基于源数据多次渲染元素或模板块。此指令之值,必须使用特定语法 alias in expression,为当前遍历的元素提供别名,同时设置 独一无二的 key 。
那是因为 v-for 的优先级比 v-if 高的。
我们有时候会写出下面的例子:
{{ item.title }}
而实际上这段代码会先循环然后再进行判断,每次v-for都会执行v-if,造成不必要的计算,这会带来性能上的浪费。避免这种使用在同一个元素的情况,我们可以在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项。
{{ user.name }}
data () {
return {
users,: [{
name: '1',
isShow: true
}, {
name: '2',
isShow: true
},{
name: '3',
isShow: false
}]
}
}
computed: {
activeUsers: function () {
return this.users.filter(function (user) {
return user.isShow;//返回isShow=true的项,添加到activeUsers数组
})
}
}
# 总结 内容开始的初心是和朋友互相学习分享,内容来源有原创,有书籍,有引用。如有侵权,务必联系会,马上修正,非常感谢。也欢迎互相交流的小伙伴哦!