初衷:要是回到开始做这件事的时候,其实吧是没有什么目的的,就是为了保持学习,每一天无论多忙,多多少少学点吧,所以会看到下面很多东西既没有分类也没有太深入,总的来说是扩展知识的广度,肩付保持学习的习惯。对于学习,我一直觉得不是一蹴而就的。往往是先学,然后是用,再来是变,这样才能说是掌握了的。所以这里主要是学为主,而部分深入学习的,进入下一阶段的就会对他另开博文啦。
第一回: 要坚持一件事,除非出于爱好,本就枯乏无味的。但是,一个人或许很难坚持下去,两个人的话就能互相扶持,坚持下去了。本文简单记录,我和我的小伙伴,每天分享学习的知识点。(2021/09/06)
第二回:上面的大概两个人就坚持了一个月不到吧,再到后面这次,和之前一个阿里前端训练营的五个小伙伴的,结业之后我又提议每日分享,开始是很积极的,大概就一个月吧~~~就不在见大家写了,现在回头补上每次的分享吧,坚持学习真的很有意思的事,你不会一下子见到效果,但是某一天回顾,你会发现这个点,你了解的更多,更深入了~(2021/11/23)
第三回:我呢,一直觉得自己不算很自律吧~~(谦虚点哈,哈哈哈哈)所以我喜欢找伴一起学习,这回我加了一个社群般的学习组织,哈哈哈,真的不错,一堆学习的人,学习有氛围多舒服呀。这一回合继续每天的学习和分享。(2022/01/12)
感慨:坚持真的是最简单又最难的事情呀,不知不觉就已经有近100个点了,分到每一天来说都不是很难,每天半小时左右就好了,但是日积月累就是非常的有成就感,也收获很多,真不积跬步无以至千里!无论做什么,就算慢,只要方向是对的,那么我们终会得到回报的,也或许会超过曾经领先我们的人!(2022/02/24)
备注说明:关于分享我也会尽量从为什么,是什么,怎么做来说,现在的排版的话是没有对内容分类的,每天是想到啥就说啥,后面会对他们分类整理的,我也会尽量对每个知识点去深入,所以有些点下面会有相关文章的链接~同时第二回合的内容有部分是一起每日分享的小伙伴分享的噢,感谢他们的付出!
因为什么而诞生的:
首先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属性,为对应的组件添加引用名称
<my-counter ref="counterRef"></my-counter>
<button @click="getRef">获取 $refs 引用</button>
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
上次被面到这个东东,突然不知道怎么回答了,虽然平时一直用东西,但是要说好像就说不出啥~这里回顾一下 vue-router 包含什么吧!
一、路由常见的属性
● 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 的实现思路如下:
特点
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;
● 样式表的来源不同时,优先级顺序为:内联样式 > 内部样式 > 外部样式 > 浏览器用户自定义样式 > 浏览器默认样式。
这两个属性都是让元素隐藏,不可见。两者主要分两点:
这几个函数有些时候傻傻分不清啥是啥,这里来一次全面的了解他们吧!
一、首先 JS函数有多少种定义的方式?
何为函数呢?简单来说就是重复执行的代码块,这样的一段 JavaScript 代码,它只定义一次,但可能被执行或调用任意次。
常见的定义方式有下面这几种
ps:第一种和第二种函数的定义的方式其实是第四种构造函数的语法糖,当我们定义函数时候都会通过 new Function 来创建一个函数,只是前两种为我们进行了封装,我们看不见了而已,js 中任意函数都是Function 的实例,比如下面:
- var arr = []; 为 var arr = new Array(); 的语法糖。
- var obj = {} 为 var obj = new Object(); 的语法糖
二、箭头函数和普通函数的区别
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this,arguments,super或new.target等,也不能用作构造函数。更适用于那些本来需要匿名函数的地方。
普通函数就万金油啦~~
箭头函数 | 普通函数 | |
---|---|---|
写法 | 使用箭头定义,省去 function 关键词( => ) | function 关键词 |
可否具名 | 只能是匿名函数 | 可以具名也可匿名 |
构造函数 | 没有 prototype 属性,没办法连接它的实例的原型链,所以无法作为构造函数,也不能使用new | 可以用于构造函数,以此创建对象实例 |
argument | 不绑定arguments,使用rest参数…(剩余参数语法)来访问参数列表 | 具有一个arguments对象,用来存储实际传递的参数 |
this指向 | 没有 prototype ,所以本身没有this,一般指向其所在的上下文,任何方法都改变不了其指向,如 call() , bind() , apply() | this一般指向它的调用者 |
三、函数声明和函数表达式的区别
一般定义函数有两种的方式,就是函数声明和函数表达式,他们两者的最大区别在于函数声明会出现函数提升,提升到该函数作用域的最开头,所以无论在该作用域任意方位都能使用。
但是函数表达式并不会出现这种情况,所以函数表达式需要先赋值再使用(函数表达式也叫匿名函数),否则会由于变量声明提升,add === undefined。
//函数声明:
function add(a,b) {
return a +b
}
//函数表达式
let add = function(a,b) {return a+b}
add ()
//使用1
add (1,2) //报错,因为没有先赋值再使用
let add = function(a,b) {return a+b}
//使用2
add (1,2) //3
function add(a,b) {return a+b}
//使用3
if(ifTrue){
add(a,b) {return a+b}
}else {
add(a,b) {return a-b}
}
//这里输出的结果在不同的浏览器就会可能不一样了,大多数浏览器会忽略 ifTrue 的值直接的返回第二个声明,Firefox 会在ifTrue 为 true的时候返回第一个。这里我们使用函数表达式就能正确的输出结果了。
四、构造函数
在 JavaScript 中,通过 new 来实例化对象的函数叫构造函数,也就是初始化一个实例对象,对象的prototype属性是继承一个实例对象。构造函数的命名一般会首字母大写~
1. 那么为什么需要使用构造函数呢?
是为了创建对象
而 JavaScript 中创建对象有两种,一种是 构建函数+prototype,另一种是用 class。这里我们不去讲解 class ,先放到构造函数上。
//当我们需要创建比较多信息时
var person1 = { name: 'aa', age: 6, gender: '男', classRoom:'高一'};
var person2 = { name: 'bb', age: 6, gender: '女', classRoom:'高一'};
var person3 = { name: 'cc', age: 6, gender: '女', classRoom:'高一'};
var person4 = { name: 'dd', age: 6, gender: '男', classRoom:'高一'};
那么如果我们需要创建很多呢?需要这样一个一个的写下去吗?但是实际上我们可以通过下面的形式来实现。
function Person(name, age, gender, classRoom) {
this.name = name;
this.age = age;
this.gender = gender;
this.classRoom= '高一'
}
Person.prototype.sayHi = function() {
console.log("你好,我叫" + this.name + "是一个" + this.sex + "来自"+ classRoom);
};
let person1 = new Person("a", 18, '男');//我们还可以不传 classRoom,让他使用默认的
let person2 = new Person("b", 19, '女');
2. 构造函数的执行过程
构造函数的执行过程其实也就是 new 操作付的基本过程
new 操作符通过构造函数创建的实例,可以访问构造函数的属性和方法,同时实例与构造函数通过原型链连接起来了。
3. 构造函数的返回值
首先说的,构造函数中,不要显式返回任何值,下面我们看看为什么:
//返回原始值
function Person(name) {
this.name = name;
return '啦啦啦啦'
}
let person1 = new Person("a");
console.log(person1.name) //a
//返回对象
function Person(name) {
this.name = name;
return {aaa : "asdas"}
}
let person1 = new Person("a");
console.log(person1) //{aaa : "asdas"}
console.log(person1.name) //'undefined'
可以看到当返回原始值的时候,并不会正常返回这个原始值 “啦啦啦啦”,而当返回直是对象的时候,这个返回值能被正常返回,但是这时候 new 就不生效了。所以,构造函数尽量不要返回值。因为返回原始值不会生效,返回对象会导致 new 操作符没有作用。
4. 构造函数和普通函数又什么区别?
主要是和普通函数在功能上的区别,我们知道函数就是一段能执行重复工作的代码。而这里根据他们的功能不同分为了构造函数和普通函数。
往往我们使用构造函数来进行初始化对象,而使用构造函数还往往会和 new 一起使用 ,并且一般会在定义的时候首字母大写。根据这几个特点我们也能耐比较好的区分。
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 的函数。
一、跨源资源共享(CORS)
在说简单请求前我们先来了解一下我们往往在学习前端之初和后端对接接口的时候遇到的一个常见的问题,那就是跨域!
那么了解啥是跨域之前,我们看看一什么是同源?因为只有当不是 同源(SOP) Same Origin Policy 的时候,才会涉及到 跨域 (CORS)Cross-Origin Resource Sharing 了!
二、同源的MDN定义:
如果两个 URL 的 protocol、port(en-US) (如果有指定的话)和 host 都相同的话,则这两个 URL 是同源。这个方案也被称为“协议/主机/端口元组”,或者直接是 “元组”。
也就是当两个 URL 的协议,端口, 主机相同的时候,他们就是同源!
作用就是:不允许不同的ip、端口、协议的应用在浏览器内进行互相资源共享、请求调用。避免出现一些安全问题!也就是你的是网站A,你无法向网站B发送请求。
出于安全性,浏览器限制脚本内发起的跨源HTTP请求。也就是来自其他域的请求,这意味着使用这些API的Web应用程序只能从加载应用程序的同一个域请求的HTTP资源,除非预检请求的响应报文包含了正确CORS响应头,也就是我们的CORS。
三、如果没有同源策略会如何?
如果没有同源策略的限制,那么如果你打开网站A,且不小心打开病毒网站B,那么病毒网站B,就可以向网站A发送请求,获取到你的在网站A的信息和资料等。现在我们往往试试前后端的分离开发,我们前端获取数据的 API 接口,往往就是跨域请求。如果不使用 CORS 是不被允许的。
四、那么CORS是如何工作的呢?
而使用CORS,就会允许并且能保证安全的发送跨域请求,跨源资源共享标准新增了一组 HTTP 首部字段,允许服务器声明哪些源站通过浏览器有权限访问哪些资源。
预检请求
对那些可能对服务器数据产生副作用的 HTTP 请求方法
浏览器必须首先使用 OPTIONS 方法发起一个预检请求(preflight request),从而获知服务端是否允许该跨源请求。
服务器确认允许之后,才发起实际的 HTTP 请求。在预检请求的返回中,服务器端也可以通知客户端,是否需要携带身份凭证(包括 Cookies和 HTTP 认证相关数据)
具体流程:
网页发起请求
根据请求是复杂请求还是简单请求,复杂请求浏览器会先进行预检请求
根据预检请求获知服务端是否允许跨域请求,允许接收跨域请求的网站地址。
获知服务器返回的结果后,才会真正的发送请求。
那么我们要讲的简单请求和复杂请求所需要了解的基本知识就简单介绍完了!
五、简单请求
某些请求不会触发 CORS 预检请求(发送 OPTIONS 方法),本文称这样的请求为“简单请求
六、复杂请求
不是简单请求的都是复杂请求,也可以看做在实际进行请求之前,需要发起预检请求的请求
七、关于预检请求
在发生跨域的时候,只要不满足简单请求的要求,浏览器在发送真实请求之前就会先发送预检请求(OPTIONS请求),向服务端验证是否允许跨域请求。
关于这个预检请求 一般有三个参数比较重要:Access-Control-Request-Method 和 Access-Control-Request-Headers,以及一个 Origin 首部。
举个例子,一个客户端可能会在实际发送一个 DELETE 请求之前,先向服务器发起一个预检请求,用于询问是否可以向服务器发起一个 DELETE 请求:
OPTIONS /resource/foo
Access-Control-Request-Method: DELETE
Access-Control-Request-Headers: origin, x-requested-with
Origin: https://foo.bar.org
如果服务器允许,那么服务器就会响应这个预检请求。并且其响应首部 Access-Control-Allow-Methods 会将 DELETE 包含在其中:
HTTP/1.1 200 OK
Content-Length: 0
Connection: keep-alive
Access-Control-Allow-Origin: https://foo.bar.org
Access-Control-Allow-Methods: POST, GET, OPTIONS, DELETE
Access-Control-Max-Age: 86400
关于跨域上面有介绍到,这里总结下面两点:
解决方案一: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.组件的命名要大写
前几天做个业务的时候用到插槽,才发现对这个还不太熟,这里来回顾一下基本的使用。
我们在开发中,往往会把一个复杂的页面分为多个组件去组合,或者当复用性比较高的也会提取为一个组件,但是当我们设计的组件还不能在某些特殊的情况满足我们的需求的时候,我们还需要添加额外的时候,那么该怎么办呢?总不可能再写多一个组件吧?这样的话,复用性也太低了。所以插槽就出现了。
当实际使用的组件不能完全的满足我们的需求的时候,我们就可以用插槽来分发内容,往我们的这个组件中添加一点东西。
基本的使用就是在子组件里面我们需要添加内容的地方加上
<slot></slot>
那么我们在父组件中使用该子组件的时候,就能直接在这个子组件中添加进入内容。这就是最基本的使用。
那么要是我们想在父组件中传递多个内容呢?这时候就有我们的对应使用的具名插槽了,我们可以对插槽命名狮子对应在子组件中的位置。
//我们可以在子组件的插槽中写入内容,也就是默认值,当我们在父组件中没有传递值的时候,就会使用默认值
<slot>我是默认的插槽值</slot>
//具名插槽
如下,我们在子组件中的插槽可以命名,这样我们在父组件中可以通过名字来确定位置
//子组件B
const BBorder = {
template: `我是儿子B
这里是头部
这里是默认
这里是尾部 `,
}
const Parents = {
methods: {
},
//实例化BBorder这个子组件
template: `我是父组件:{{msg}}
头部
A 9999999999999
尾部
`,
//注册两个子组件
components: {
BBorder
}
}
同样的,也能出传递值给到父组件中,但是我么要注意,父子组件的编译作用域是不同的,父级模板里的所有内容都是在父级作用域中编译的;子模板里的所有内容都是在子作用域中编译的。
从子组件中传递值到父组件
//子组件中
<slot :user="user">
{{ user.lastName }}
</slot>
//父组件中
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
基本的使用级就这样啦~~
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 }}
<!-- 在 `v-bind` 中 -->
<div v-bind:id="rawId | formatId"></div>
那么过滤器有什么用呢?其实一般用来格式文本,比如我们往往拿到的时间是时间戳,那么就可以很简单的通过过滤器来格式成我们常见的格式,这个非常便捷。
使用上的我们先要定义过滤的方法,这个可以全局定义或者组件内定义
//组件内定义
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('事件',(数据)=>{ })来接收
//下面是一个完整的例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app">
<Parents></Parents>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
<script>
//创建一个媒介来连通两个是组件
const medium = new Vue()
const ABorder = {
template: `我是儿子A
我的名字叫:{{name}}
`,
created () {
medium.$on('letDiscussionName',(val)=>{
alert(val)
})
},
//用props来接收父组件传来的值,同时父组件要绑定属性
props: {
name:{
//设置类型,符合才接收
type: String,
//不传默认为这个
default: '我也不知道呀'
}
},
methods: {
newName() {
this.$emit('newName','大黄狗')
}
}
};
const BBorder = {
template: `我是儿子B
`,
methods: {
discussionName() {
medium.$emit('letDiscussionName','儿子A来讨论')
}
}
}
const Parents = {
data() {
return {
msg: '孩儿们好呀!',
aName: '大花猫',
bName: '大黄狗'
}
},
methods: {
chang(value) {
this.aName = value
}
},
//可以有下面两种形式来调用组件
template: `我是父组件:{{msg}}
`,
components: {
ABorder,
BBorder
}
}
//实例化一个vue,并且绑定到app这个元素
const vm = new Vue({
el: '#app',
components: {
Parents,
}
})
</script>
</body>
</html>
是不是发现太好用了,特别是当我们的项目中还没有复杂到使用vuex的时候,那么为什么没有被大范围使用呢,这里我们就要注意了。写的时候是非常爽,但是当你修改维护的时候,特别别人看的时候,那得叫苦连天了,使用事件总线的话,就好像一量飞机,载着数据满天飞,多了的话,你就很难维护。那时候真是叫天天不应,叫地地不灵了~~·
前些天,公司里的大佬们就有同事强烈认为应该完全禁止使用,哈哈哈,其实工具没错,但不应该滥用。
前段时间在上班的时候,发现有个路由跳转还携带了参数,突然才意识到当年自己vue学又多烂还忘了很多东西,有空真的应该完整的去回顾一次的~~~
也回想当年做过一个项目,当时的做法多傻,当时是一个视频网站,需要点击这个视频就有相关的跳转到该视频的播放页面,当时我居然把那个id存储到localstoram里面,在那个页面再去读取,妈呀,现在回想有好几种方式实现,扎做都不会沦落到用localstorage来~~~
回到正题~
传参方式可划分为 params 传参和 query 传参,而 params 传参又可分为在 url 中显示参数和不显示参数两种方式
方式A:这种需要在路由配置好可以传递参数XXX的,不是最方便的
路由配置
{
path: '/child/:XXX',
component: Child
}
父组件
<router-link to = "/child/XXX"></router-link>
子组件读取
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 () 的简单实现。
//极简版的双向绑定
<input type="text" id = 'txt_id'/>
<p id='p_id'></p>
<script type="text/javascript">
let _xxObj = {};
Object.defineProperty( _xxObj , "xx_val", {
get: function() {
console.log('gggg');
return 'xx123'
},
set: function( _n ) {
console.log(_n);
document.getElementById('txt_id').value = _n
document.getElementById('p_id').innerHTML = _n
}
});
document.addEventListener('keyup', function(e){
// console.log(e.target.value);
_xxObj.xx_val = e.target.value
});
// //写入触发set
// _xxObj.xx_val = 11;
// //读取触发get
// console.log(_xxObj.xx_val);
</script>
效果如图:
可以说我们上面完成简单的双向绑定,但是这里是直接操作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 高的。
我们有时候会写出下面的例子:
<div id="app">
<p v-if="isShow" v-for="item in items">
{{ item.title }}
</p>
</div>
而实际上这段代码会先循环然后再进行判断,每次v-for都会执行v-if,造成不必要的计算,这会带来性能上的浪费。避免这种使用在同一个元素的情况,我们可以在外层嵌套template(页面渲染不生成dom节点),在这一层进行v-if判断,然后在内部进行v-for循环
<template v-if="isShow">
<p v-for="item in items">
</template>
如果条件出现在循环内部,可通过计算属性computed提前过滤掉那些不需要显示的项。
<div>
<div v-for="(user,index) in activeUsers" :key="user.index" >
{{ user.name }}
</div>
</div>
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数组
})
}
}
1. 为什么?
那么为什么需要使用离线缓存呢?那么我们肯定看他能给我们带来什么便利:
2. 原理
首先关键在于使用 manifest 属性,在需要缓存的的 HTML 根节上添加该属性,属性值是一个 . manifest文件,浏览器在发现有该属性就会去请求相应的 manifest 文件。
<!DOCTYPE HTML>
<html manifest = "cache.manifest">
...
</html>
如果是第一次访问,浏览器就会根据 manifest 文件的内容,下载相应的资源,并且缓存起来,便于离线使用。如果是第二次,浏览器就会先比较新的 manifest 文件和旧的是否发送改变,如果没有,直接读取使用之前缓存的资源。
manifest文件就像是一个缓存清单文件,后缀是一个 . appcache 文件。
CACHE MANIFEST
#v0.11
CACHE:
js/app.js
css/style.css
NETWORK:
*
FALLBACK:
/demo/ /404.html
上面的清单文件表示的意思就是:首次加载的时候 js/app.js css/style.css这两个文件会被缓存,而除这两个文件以外的文件都需要联网获取,而如果没有网络获取,就会用 “404.html” 替代/demo/目录中的所有文件。
使用 window.applicationCache.status 可以查看缓存的当前状态
在 window.applicationCache 中还有很多方法,比如update() 来主动更新缓存等。
离线缓存和浏览器缓存的区别:
- 是针对整个应用,浏览器缓存是针对单个文件
- 可以断网使用
1. 利用 border
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Documenttitle>
head>
<body>
<div class="border">div>
body>
<style type="text/css">
.border{
width: 100px;
height: 100px;
border-color: red orange yellow green;
border-width: 50px;
border-style: solid;
}
style>
html>
我们可以看出,其实我们的每一条边,其实是一个梯形。
而当你把内容的宽高都设置为 0 的时候,就会发现出现了我们想要的三角形了
那么我们这时候只需要把不需要的三角形的颜色设置为透明,就能剩下我们想要的了。但还有个问题,虽然设置为透明,但是实际上还是占据了高度呢(进去F12我们可以查看到),这里我们再设置border-bottom-width: 0 就能实现我们想要的结果了。
最终效果:
<style type="text/css">
.border{
width: 0px;
height: 0px;
border-color: red transparent transparent transparent;
border-width: 50px;
border-style: solid;
border-bottom-width: 0
}
</style>
2. 使用svg 来实现
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg width="100%" height="100%" version="1.1"
xmlns="http://www.w3.org/2000/svg">
<polygon points="220,100 300,210 170,250"
style="fill:#cccccc;
stroke:#000000;stroke-width:1"/>
</svg>
3. 贴图
简单粗暴,叫 UI 小姐姐用 AI 画一个给你,多好呀,增加和小姐姐互动的机会。嗯,要是你已经有小姐姐的话,自己上 阿里的 IconFont 上面下载一个吧~
4. 旋转矩形
使用 transform: rotate 和 overflow: hidden 也是能实现的
.triangle {
width: 141px;
height: 100px;
position: relative;
overflow: hidden;
&::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: deeppink;
transform-origin: left bottom;
transform: rotate(45deg);
}
}
5. Canvas 实现
其实实现的方法还有很多,比如使用字符绘制,使用clip-path,还有使用渐变,这里就简单介绍几种
其实这两个对于很多人都了解,但是可能对于一些新手就没有了解到两者的区别
他们最主要的区别在于 == 对比时,若类型不相等,会先转换为相同类型,然后再来比较值。而 === 则不会,只能在相同类型下比较值,不会做类型转换。还有一个是 = ,这个是赋值,不是运算符。
1. === 这个比较简单。下面的规则用来判断两个值是否===相等:
2. ==
还有我们有些时候会在结构读取属性值的时候 会在前面加上 “?” 这个是可选链,表示判断空值的情况。
在VUE中实现通信有很多种的方式,每一种都有其对应的使用情况,下面我们不对每一种做深入的介绍,只要是介绍有多少种方式和他们的区别。
首先我们看看有哪些方式:
常见的是上面的这几种,少见的其实还可以算上插槽 slot, 混入,路由携带参数,localStorage, $parent / $children;等
1. props
常常使用在父组件传递給子组件通信的过程中,首先在子组件中使用 props,来接收对应的属性,而在父组件中使用子组件的地方,添加上面定义的属性。
2. emit
这个就和上面的相反,是使用在子组件给父组件传递值的中。子组件中声明对应的事件,当子组件触发事件,就会通过 this.$emit(‘事件’ , ‘数据’)传递到,而在父组件中使用子组件的地方,添加上面定义的事件,这个可以获取子组件传来的值。
3. provide/inject
这对组合往往使用在层级比较深的时候,比如A组件下可能还有 B组件 ,B组件下有C组件…E组件.而使用这对 API,就能无论层级有多深都能获取到
4. eventBus
也就是事件总线,简单粗暴,可以到处飞。可以不管你是不是父子关系,通过新建一个Vue事件bus对象,然后通过bus. e m i t 触 发 事 件 , b u s . emit触发事件,bus. emit触发事件,bus.on监听触发的事件。但不建议乱用,不好维护。
5. vuex
对于大型的项目来说往往是很必要的,尤其单页面应用,很多页面嵌套页面,关系很多。而使用 VUEX 就能便捷的统一管理。
参考文章: 如何在vue 中使用组件,以及组件通信的方式(父传子/子传父/兄弟传)
首先我们知道我们输入一段 JavaScript 代码,他会先经过词法和语法分析得到 AST 也就是我们的抽象语法树。那么为什么需要转化为 AST 呢?那是我们编写的的的无论是编译型语言还是解释型语言,都是不能被编译器或解释器所理解的。而且转化为 AST 还能有很多的便利,我们的 babel , ESlint 很多工具都是在这一步进行的。
那么我们看看 AST 抽象语法树是如何生成的吧~
1. 首先第一步是分词,也就是词法分析
分词就是把一行行代码拆分为每一个 token ,也就是 语法上不可再分,最小的单位。同时去除空格,对 token 分类等。
// 源码
let aaa = '12: 30'
// Tokens数组
[
{ type: { ... }, value: "let", start: 0, end: 3, loc: { ... } },
{ type: { ... }, value: "aaa", start: 4, end: 7, loc: { ... } },
{ type: { ... }, value: "=", start: 8, end: 9, loc: { ... } },
{ type: { ... }, value: "12:30", start: 10, end: 15, loc: { ... } },
]
上面的代码就会拆分为,其中关键字“let”、标识符“aaa” 、赋值运算符“=”、字符串“12: 30”四个都是 token,而且它们代表的属性还不一样。添加到 Token 数组中。
2. 词法分析之后就进行解析,也就是语法分析
其作用是将上一步生成的 token 数据,根据语法规则(扫描 token 列表,形成语法二叉树)转为 AST。如果源码符合语法规则,这一步就会顺利完成。但如果源码存在语法错误,这一步就会终止,并抛出一个“语法错误”。
通过这样就构建起 AST 抽象语法树。(好吧,也就只能照搬被人想法说说,这个还不能理解,以后来一探究竟吧~)
关于 key ,我们常常在 v-for 中会接触到,当我们需要进行列表循环的时候,如果没有使用 key ,就会有警报提示。
//场景一:循环列表
<div v-for="num in numbers" :key="index">
{{num}}
</div>
//场景二:通过时间戳强制重新渲染
<div :key="+new Date()" >+new Date()</div>
那么为什么需要 key呢?
因为 key 相当于给 vnode 添加上一个唯一的 id。下面我们看个例子:
如果上面的场景一中 items 的值为 [1,2,3,4,5,6,7,8,9] 那么就是渲染出是个div 是吧。如果没有 key ,那么当我们现在 items 的值变为 [0,1,2,3,4,5,6,7,8,9] 呢,那么机会第一个原来是 “1” 的 div内容变为“0”,而原来是“2”的div内容变为“1”…以此类推。(因为没有key属性,Vue无法跟踪每个节点,只能通过这样的方法来完成变更)
但是如果key的情况下,Vue能跟踪每个节点,就会直接新增一个 div 添加到内容是“1”的div前面。根据key,更准确, 更快的找到对应的vnode节点。
设置key能够大大减少对页面的DOM操作,提高了diff效率,更加的高效更新虚拟DOM
还有的用途就是我们的场景二,强制替换元素,当key改变时,Vue认为一个新的元素产生了,从而会新插入一个元素来替换掉原有的元素。所以上面的元素会被删除而而重新添加。但是如果没有添加 key的话,就会直接替换div 里面的内容。而不是删除添加元素。
推荐文章:Vue2.0 v-for 中 :key 到底有什么用?
1. 什么是虚拟 DOM呢?
再过去或者我们原生JavaScript的时候,我们可以发现,当我们需要改变视图的数据的时候,我们往往需要先获取到这个 DOM 元素,然后对其进行更新。也就是:
数据改变–>操作DOM–> 视图更新
但是我们在 VUE或者 React 中是直接改变 Data 就能实现视图的更新了。
数据改变–>视图更新
那么要是我们每一次数据改变都需要操作 DOM,那就非常麻烦,而且慢。因为 JavaScript执行时很快的,但是操作 DOM就不是了。所以就有了虚拟 DOM了
数据改变–>操作虚拟 DOM(计算变更)–>操作真实的 DOM–> 视图更新
那么什么虚拟 DOM 呢?可以说虚拟 DOM 本质上是 JS 和 DOM 之间的映射,表现为是一个能描述 DOM结构 及其属性信息的 JS对象。那么下面我们看一个例子为什么这么说吧:
//我们定义的 DOM
<div id="app">
<p class="text">hello</p>
<h1 class="text">hello world!!!</h1>
</div>
//转换为虚拟 DOM
{
tag: 'div',
props: {
id: 'app'
},
chidren: [
{
tag: 'p',
props: {
className: 'text'
},
chidren: [
'hello'
]
},
{
tag: 'h1',
props: {
className: 'text'
},
chidren: [
'hello world!!!'
]
}
]
}
我们可以看出,实际上就是通过 JavaScript 对象来作为基础的树,用对象的属性来描述节点,然后通过映射成真实的 DOM 结构。
那么这么做有什么好处呢?有两点
1. 优化性能
我们常说要减少重排的发生,那是为什么呢,因为那会涉及到 DOM 树的重新渲染。而操作 DOM 树是比较慢的,且 DOM 元素的数量谁很庞大的,当操作 DOM 很容易带来页面的性能问题。很容易给用户带来不好的体验,而这点是很重要的,可以说我们前端页面是与用户的第一个窗口。
比如,正常情况下,当我们要更新10个节点的时候,浏览器就会计算 10次 ,一次一次的进行更新。而在使用虚拟 DOM ,他相对就好像多了一个缓存区,当 DOM 操作(渲染更新)比较频繁时,它会先将前后两次的虚拟 DOM 树进行对比,定位出具体需要更新的部分,生成一个“补丁集”(也就是一个 js对象),最后只把“补丁”一次性打在需要更新的那部分真实的 DOM 树上,避免了很多没必要的计算。
2. 抽象化渲染过程
很多人认为虚拟 DOM 最大的优势是 diff 算法,减少 JavaScript 操作真实 DOM 的带来的性能消耗。虽然这一个虚拟 DOM 带来的一个优势,但并不是全部。虚拟 DOM 最大的优势在于抽象了原本的渲染过程,实现了跨平台的能力,而不仅仅局限于浏览器的 DOM,可以是安卓和 IOS 的原生组件,可以是近期很火热的小程序,也可以是各种GUI。
解耦了视图层和渲染平台,带来了更多的可能性~
有点像我们语言中,都会编译成 AST ,而在这个阶段,可以实现多种转化,就像 Babel ,和 ESList 等。
缺点也是有的:在初次渲染的时候,会多出一层 虚拟DOM的计算,而且使用虚拟DOM也不一定是高效的,往往当我们每一次改动不大的时候,才是相对高效的,但是如果我们改动大的话,相比还多出了更多的计算,是不利的。
推荐文章:Vitual DOM 的内部工作原理
昨天简单了解了一下虚拟 DOM,那么今天肯定就是和她密不可分的 DIFF算法啦。其实 DIFF算法的作用简单来说就是一句话:同层树节点比较的算法
那么 DIFF算法是如何工作的?
到这里就算结束啦,从源码层面的分析我就不班门弄斧了~~
推荐文章:
针对这个问题只要我们有两点:
首先我们来回答第一点:
我们有些时候没有初始化的时候,是不是会发现当我们添加进去一个 DIV的时候,会发现他并不是紧贴着窗口的,而是有一定的距离的。这就是一个例子,在不同的浏览器中,对一些标签是具有的默认值的,而且不同的浏览器默认值也肯是不一样的。如果没有对其 CSS样式做初始化,就可能导致在不同的浏览器之间展示的效果是不一样的。
关于第二点:
这点也是初始化后,便于我们对代码的统一管理,减少重复样式等。同时修改的时候便于统一管理。
我个人喜欢用这套来初始化CSS样式,供参考:
body,ol,ul,h1,h2,h3,h4,h5,h6,p,th,td,dl,dd,form,fieldset,legend,input,textarea,select{margin:0;padding:0}
body{font:12px"宋体","Arial Narrow",HELVETICA;background:#fff;-webkit-text-size-adjust:100%;}
a{color:#2d374b;text-decoration:none}
a:hover{color:#cd0200;text-decoration:underline}
em{font-style:normal}
li{list-style:none}
img{border:0;vertical-align:middle}
table{border-collapse:collapse;border-spacing:0}
p{word-wrap:break-word}
简单粗暴: {padding: 0; margin: 0;}
有些时候回我们会这样来写,但是这样写有一个弊端,就是在网站比较大时,CSS样式文件也很大,这样写时,它会把所有的标签都初始化一遍,这样会加大网站运行的负荷,会让网站加载的时候需要很长一段时间。
我们看下面的输出:
console.log(0.1 + 0.2) // 结果是0.30000000000000004,而不是3
那么为什么不是呢~主要是因为在JavaScript中,数字是采用的IEEE 754的双精度标准进行存储。比如其中小数使用64位固定长度来表示的,其中的1位表示符号位,11位用来表示指数位,剩下的52位尾数位。
而其中,比如0.1转换为二进制是一个无限循环的数的,就会超过 52 位,就会导致精度缺少,所以在计算机中0.1只能存储成一个近似值
所以同理的, 0.1+0.2 会先分别转换为二进制然后进行相加后存储,最后取出来的时候转化为十进制,而这个值不够近似于0.3,所以就会出现不相等的结果。
那么如何避免呢?
常用简单的办法就是将浮点数转变为整数来计算。
关于浮动我们往往在开始接触前端,写 CSS 的时候就会了解过,这里就简单和大伙们回顾一下吧。其实吧 flost 用的很少了,一般用框架,使用 flex了 ~~
简单来说:当一个元素浮动之后,它会被移出正常的文档流,然后向左或者向右平移,一直平移直到碰到了所处的容器的边框,或者碰到另外一个浮动的元素。
1. 浮动的特点:
那么浮动有什么特点:
2. 浮动的使用场景
而起初提出浮动的概念主要是为了解决左边图片,右边文字的需求,现在往往在我们需要实现一些布局的时候,比如多个盒子一行显示等场景
3. 为什么要清除浮动:
那就是浮动会导致的一个问题,也就是高度塌陷:
什么是高度塌陷呢?其实就是指当父元素没有给确定的高度(父元素高度靠子元素撑起来),而当子元素添加了浮动时,内容无法撑起父元素的高度,即父元素发生高度塌陷。
为什么会发生高度塌陷呢?我们知道当元素浮动的时候,其实这个元素已经脱离了文档流了,也就无法支撑起父元素了。
那么如何解决呢?
关于解决高度塌陷推荐这篇文章: 最详细的高度塌陷解决方案
有些时候傻傻的感觉两者一样但又不一样,有些时候要判断一个值是否为空,不知道选哪个好,感觉两者都一样,这里去看看两者异同吧~
一句简单来说是:undefined代表了不存在的值,null代表了没有值。
也就是,比如我们对一个值声明后,没有赋值,输出他就是 undefined ,是不存在的,而当我们赋值为 null, 那么输出他就是 null。
这两者还有一些区别点要注意的:
1.
undefined == null //true,这里相等是因为 “==” 对他们做了转换
undefined === null //false
//undefined的值 是 null 派生来的,所以他们表面上是相等的
2.
let a;
typeof a;//undefined
let b= null;
typeof b;//object
这里为什么typeof b 输出为 Object 呢?
null不是一个对象,尽管 typeof age输出的是 Object,逻辑上讲, null 值表示一个空对象指针 ,这是一个历史遗留问题,JS 的最初版本中使用的是 32 位系统,为了性能考虑使用低位存储变量的类型信息,000 开头代表是对象,null表示为全零,所以将它错误的判断为 Object 。
之前在和舍友聊天的时候突然了解到这个知识。简单来说就是我们的 HTML文件的完整拼接是在服务端完成返回给客户端,还是在客户端完成,根据不同分为服务端渲染和客户端渲染。
这里为什么出现 HTML文件的拼接呢?其实在早期,我们网页是比较简单的,服务端直接返回完整的 HTML文件给浏览器,浏览器解析展示就完成了。但是随着页面越来越复杂,以及前后端分离的思想。渐渐的服务端不再是返回完整的 HTML文件,而是通过提供 API 来让前端获取数据,前端拿到数据之后再进行拼接完整的 HTML文件。使得前后端可以各自专注负责各自的部分,前端专注 UI 的开发,后端专注与逻辑开发。
所以他们的核心也就在于谁完成 HTML的拼接,通过这点也能区分是服务端渲染还是客户端渲染。
比如:网页发生了整体的刷新(不是第一次打开),且网络地址发生改变,就是服务端渲染,而如果是局部刷新,且网络地址没有发生改变(也就是ajax),就是客户端渲染。比如我们看当前文章的评论,我们点击下一页,我们可以发现评论内容发生改变了,这个就是一个服务端渲染了。而如果你点击其他的专栏或者首页,上方的网址也发生了改变了,就能看出是服务端渲染了,还有我们收到的邮件网页也一般是服务端渲染。同时也还可以通过判断控制台中的网络请求,看是否有请求接口获取数据。
那么这两种渲染方式有什么优缺点呢?
1. 服务器端渲染的优缺点
优点
缺点
2. 客户端渲染的优缺点
优点
缺点
选择服务端渲染还是客户端渲染呢?
根据业务场景哈,没有最好的,只有最适合的。类似官网这些需要SEO的,又或者本身没有过多的交互的。使用服务端渲染是比较合适的。而像类似后台管理的,有很强的交互,就是客户端渲染比较好。
但是没有绝对的,有些时候往往两者一起使用。
我们可以通过 event.bubbles 属性可以判断该事件是否可以冒泡
事件 | 是否冒泡 |
---|---|
click | 可以 |
dbclick | 可以 |
keydown | 可以 |
keyup | 可以 |
mousedown | 可以 |
mousemove | 可以 |
mouseout | 可以 |
mouseover | 可以 |
mouseup | 可以 |
scroll | 可以 |
概括来说,鼠标事件,和键盘事件,以及点击事件是支持冒泡的
事件 | 是否冒泡 |
---|---|
blur | 不可以 |
focus | 不可以 |
resize | 不可以 |
about | 不可以 |
mouseenter | 不可以 |
mouseleave | 不可以 |
load | 不可以 |
unload | 不可以 |
而聚焦和失焦事件,加载事件,ui事件、鼠标移入移出事件是不支持的。
继承属性:
非继承属性:
那么这两类属性都有哪些呢?
一、无继承性的属性
1、display:
2、文本属性:
3、盒子模型的属性:
4、背景属性:
5、定位属性:
6、生成内容属性:
7、轮廓样式属性:
8、页面样式属性:
9、声音样式属性:
二、有继承性的属性
1、字体系列属性
2、文本系列属性
3、元素可见性:
4、表格布局属性:
5、列表布局属性:
6、生成内容属性:
7、光标属性:
8、页面样式属性:
9、声音样式属性:
三、所有元素可以继承的属性
1、元素可见性:
2、光标属性:
四、内联元素可以继承的属性
1、字体系列属性
2、除text-indent、text-align之外的文本系列属性
五、块级元素可以继承的属性
1、text-indent、text-align
这个有些时候我们会没有注意到,往往我们会使用 px 等单位来,但是当我们使用百分比的时候回呢,今天我们来探讨一下。
总结来说:当 padding 属性值为百分比的时候,如果父元素有宽度,相对于父元素宽度,如果没有,找其父辈元素的宽度,均没设宽度时,相对于屏幕的宽度。
不管 margin-top/margin-bottom还是margin-left/margin-right(对于padding 同样)也是,参考的都是 width。
那么为什么是 width, 而不是 height 呢?
CSS权威指南中的解释:
我们认为,正常流中的大多数元素都会足够高以包含其后代元素(包括外边距),如果一个元素的上下外边距时父元素的height的百分数,就可能导致一个无限循环,父元素的height会增加,以适应后代元素上下外边距的增加,而相应的,上下外边距因为父元素height的增加也会增加,如此循环。
选择不同的数据结构往往是为了对应我们常见的 增 / 删 / 改 / 查 这四大操作,而不同的数据结构对于不同的操作效率也是不同的,为了较好的适应我们的需求,就需要了解各种数据结构的特点,来做选择。
一、 数组
1. 场景
2.优缺点
二、 链表
1. 场景
2.优缺点
还有很多其他的,欠着先吧~~
1. 同步
2.异步
同步的话会造成阻塞,当请求一直没有返回,就会导致后面的步骤一直没发进行,但是异步不会,就算某个请求阻塞了,也不会影响其他的。而我们在根据选择是同步还是异步的时候,需要判断的是请求之间的资源是否有依赖关系。当有依赖关系的haul我们就要使用同步了。
最近不是没有更新了哦,而是我在完成第二版的编写,鉴于现在该篇越写越长了,已经很不便于观看,且排版上没有过多的整理,格式不统一,内容耶没有分类。第二版的内容我自己也超级期待哦,但是工程量还是很大的,且现在还得准备春招和毕设,第二版我会知识点分类,重要性分类,格式统一,美观度优化,添加面试回答,知识点内容补充检阅等, 请大家不要错过哦!!记得关注我哦