88个前端面试题
话术总结
1、水平垂直居中的方式
第一种,用纯flex布局吧。
直接给对应的父盒子加一个display:flex开启布局。然后使用主轴居中;侧轴居中。(两者都是添加给父盒子的。)
第二种,用flex+margin的方式。
还是同样的先给父盒子添加一个display:flex开启布局,然后给子盒子添加一个margin:auto就可以水平垂直居中了。
第三种,用定位+margin的方式
首先根据子绝父相的方式进行定位,然后子盒子的绝对定位是top:50%,left:50%,但由于这里的50%都是以盒子的左上角为中心点的,所以需要用margin来返回自身宽高的一半,也就是margin-top:负的子盒子高度的一半。margin-left:负的子盒子宽度的一半。
第四种,用定位+transform的位移操作
这种方法可以说是上一种方法的改进,他省略了我们自己计算盒子宽高一半的过程。因为transform的位移,可以使用百分比的方式,并且百分比的对象就是盒子本身。也就是在子绝父相的情况下,直接使用transform:translate(-50%,-50%)即可。
第五种,用定位+margin的方式
在子绝父相的情况下,给子盒子添加上下左右都设置为0,最后再给子盒子添加一个margin:auto,就可以达到水平垂直居中的效果。
像很多人都会说直接采用margin:auto的方式,但是当盒子没有高度的情况下,这样的方式是不可取的。所以基本不会采用。
2、盒模型
盒模型由四个部分组成,分别是内容、内边距、外边距和边框
盒模型的分类有两种,content-box和border-box,可以通过box-sizing来设置。默认值是content-box。
两者的区别主要是对盒子的大小计算方式不同。
content-box又被称为w3c标准盒模型。
width设置的是内容的宽度
实际宽度=padding+border+width
border-box又被称为c3模型/ie盒模型等
width设置的是实际的宽度
width=内容+padding+border
3、关于flex:1
flex其实是flex-grow、flex-shrink、flex-basis 这三个属性的简化版
flex-grow:用来增大盒子。当父盒子有剩余空间的时候,可以利用flex-grow来设置盒子增大的比例。比如,有两个子盒子,剩余200,盒子a的flex-grow=1,盒子b的flex-grow=3,那么盒子a在原有的基础上加50,盒子b在原有的基础上加150.
flex-shrink:用来缩小盒子。当总数超过父盒子的时候,可以利用flex-shrink来设置盒子缩小的比例。比如,有两个子盒子,超出200,盒子a的flex-shrink为1,盒子b的flex-shrink为3,那么盒子a在原有的基础上减去50,盒子b在原有的基础上减去150.
flex-basis:用来设置盒子的基础宽度。(如果存在,会覆盖原本的width)直接用px设置大小。
flex:1表示1,1,0% 可扩大可缩小,一般为平均分
flex:auto表示1,1,auto 可扩大可缩小,根据内容的大小来分
flex:0表示0,1,0% 不可扩大,可缩小,最小内容宽度(一般为一个字的宽度)
flex:none表示0,0,auto 不可扩大,不可缩小,一般为内容本身的宽度
auto表示的是容器原本的大小,0%表示为零尺寸的。
4、css3的新特性
css3的新特性,那太多了。我讲几个常用的吧
(1)选择器
新增了属性选择器,伪类选择器等等,最常用的应该是
:hover鼠标移动到元素上面
:nth-child(n)某个元素的第n个子元素
:last-child 某个元素的最后一个子元素
:first-child 某个元素的第一个子元素
(2)新样式
边框方面
border-radius:圆角边框,如果设置50%则表示为圆形
box-shadow:添加阴影
背景方面
background-size图片的缩放,cover表示要铺满整个盒子,contain表示宽高有一个铺满就结束
颜色方面
rgba(),hsla()以及opacity的透明度
(3)转换transform
主要是位移translate、旋转rotate、缩放scale几个技能
(4)过渡动画transition
CSS属性,花费时间,运动曲线(默认ease),延迟时间(默认0)
(5)自定义动画animation,和transition差不多,最大的区别就是自定义动画不需要触发,定义了就会有动画
(6)flex布局
5、BFC
BFC就是块级格式上下文,它是一种属性,可以让渲染区域独立,并且渲染区域中的布局不会影响外界。
BFC的触发情况有很多种,像overflow,float,position,display等等,最常用的应该是overflow:hidden,position:absolute吧。
BFC解决的问题有很多。
情况一:兄弟上下重叠
当上下两个盒子同时拥有上下间距,不是取两个间距之和,而是取最大值。
解决方式就是给盒子添加一个父盒子,并且给父盒子开启BFC。
情况二:外边距塌陷
就是父盒子不存在boder或者是padding的时候,子盒子有一个margin-top,那么父盒子也会对这个指令进行生效。解决方式是给父盒子开启BFC模式,当然,可以给父盒子加一个border或者padding,但是这样容易改变原本的样式,不可取。
情况三:左右布局问题
当A盒子浮动,B盒子不浮动的时候,两者就会产生覆盖。解决的方式就是给非浮动的盒子添加一个BFC模式。一定是给没有浮动的盒子进行添加。
情况四:清除浮动
在父盒子没有设置高度的时候,高度是由内容撑开的,所以子盒子浮动以后,父盒子就会没有高度。只要给父盒子添加BFC布局即可。
6、rem适配原理,响应式布局
rem是相对长度单位,相对于根元素的fomt-size计算值的大小。
1rem=根节点的字体大小
原理:在不同的屏幕下,修改根节点的字体大小。
在项目中:下载一个flexible包,即可动态更改html的字体大小
7、重绘和回流(重排)
重排和重绘是浏览器关键渲染路径上的两个节点, 浏览器的关键渲染路径就是 DOM 和 CSSOM 生成渲染树,然后根据渲染树通过一个布局步骤来确定页面上所有内容的大小和位置,确定布局后,将像素绘制到屏幕上。
其中重排就是当元素的位置发生变动的时候,浏览器重新执行布局这个步骤,来重新确定页面上内容的大小和位置,确定完之后就会进行重新绘制到屏幕上,所以重排一定会导致重绘。
如果元素位置没有发生变动,仅仅只是样式发生变动,这个时候浏览器重新渲染的时候会跳过布局步骤,直接进入绘制步骤,这就是重绘,所以重绘不一定会导致重排。
8、状态码 3
200;请求成功
201:请求成功,但是逻辑有问题
204:请求正常处理,但是没有数据可以返回
304:协商缓存
400:传的参数出现问题
401:一般都是登录过期
404:资源不存在
500:服务器出错
3XX表示重定向,表明浏览器需要执行某些特殊的处理以正确处理请求。
301永久移动,302临时移动
303和302状态码有着相同的功能,但303明确表示客户端应当采用get方法获取资源,这点与302状态码有区别。
9、浏览器的缓存机制
浏览器会将请求后的资源进行存贮为离线资源,当下次需要该资源时,浏览器会根据缓存机制决定直接使用缓存资源还是再次向服务器发送请求。
--作用:
减少了不必要数据的传输、降低服务器的压力
加快了客户端访问速度
增强用户体验
缓存机制分为强缓存和协商缓存
--强缓存:
概念:不向服务端发送请求,强制使用缓存数据
实现方式:后端在响应头中返回 Expires 和 Cache-Control
Expires :http 协议 1.0 的字段,缓存过期时间,用来指定资源到期的时间,是服务器端的具体的时间点
缺点:浏览器使用 expires 到期时间和本地时间进行对比,如果本地时间被修改或者和服务器时间差距较大,造成不准确的问题
Cache-Control: HTTP 1.1 的字段,约定过期时间的相对时间。
!当 cache-control 和 expires 同时存在 cache-control 的优先级会比 expires 高。
--协商缓存:
当强缓存失效后,会使用协商缓存
协商缓存由服务器决定是否使用缓存
向服务器发送请求资源并携带标识
Etag 字段:表示请求资源在服务器的唯一标识,浏览器可以根据 ETag 值缓存数据,下次请求的时候以 If-None-Match 字段请求
Last-Modified 字段:用于标记请求资源的最后一次修改时间
服务器会进行判断浏览器缓存的资源是否真的失效(也就是是否更新)
服务端已经更新,返回 200,重新返回最新资源和缓存标识
浏览器再次存入缓存
后续再次从强缓存开始
缓存时间到了,但是资源没更新,就还使用本地的,直接返回 304
image.png
10、slice,substr,substring
三者都是截取的意思。都可以截取字符串,slice可以截取数组。
第一个参数为开始位置,substr的第二个参数是个数,其他两个第二参数表示结束位置。
第一个参数大于第二个参数时,substring会自动调换顺序,slice会在第一参数大于第二参数时返回空,substr不在乎两者的比较
参数<0,substring无效,slice会给负数加上数据长度,substr第一参数为负数也会加上数据长度,第二参数不可为负数。
11、slice,splice,split
splice
可以进行增删改
修改原数组,返回修改的内容
第一个参数为起始索引,第二个参数是个数,第三个参数开始是添加或修改的值
slice
进行截取
原数组不变,返回截取的内容
第一个参数为起始索引(包括),第二个参数为截止索引(不包括)
start (可选)
如果start为负数,则加上数组长度
如果start被省略,则从索引 0 开始。
如果start超出原数组的索引范围,则会返回空数组。
end (可选)
如果 end 被省略,则 slice 会一直提取到原数组末尾。
如果 end 大于数组的长度,slice 也会一直提取到原数组末尾。
如果end小于start,则会返回空数组。
split:字符串 => 数组
字符串的方法,不是数组的方法。
返回一个字符串数组。
str.split(分隔符)
12、数据类型,检测数据类型 3
简单数据类型和复杂数据类型。
简单数据类型有number数字,boolen布尔值,string字符串,null,undefined,还有es6新增的symbol唯一值。
复杂数据类型有object对象,array数组,function函数以及特殊对象正则和日期。
第一种,typeof,不常用,因为只能检测简单数据类型,函数,其他的对象,数组以及null都会被检测为object。
第二种instance of,用于检测构造函数的prototype属性是否出现在某个实例对象的原型链上。
写法就是:某个实例对象A instanceof 某个构造函数B。
第三种toString.call(),toString是Object原型对象上的一个方法,必须通过Object.prototype.toString.call来获取。因为从原型链的角度讲,所有的对象最终都会指向object。但大部分的对象,本身也可能有toString的方法,这样就会导致没有查到object的toString方法就会被自身的发放给终止掉。所以需要用call来强制执行,确保执行的是object上的toString方法。
第四种是根据对象的contructor(con s jua k t)判断,检测的是由字面量方式创建出来的数据类型。
13、递增递减
x++ 先返回值后递增
++x 先递增后返回值
undefined++ = NaN
14、作用域
作用域就是所作用的一个范围。作用域和函数的定义有关。而和函数的调用有关的是this的指向
作用域的作用是提高了程序逻辑的局部性,增强了程序的可靠性,减少了命名的冲突。
有全局作用域和局部作用域两种。
全局作用域是作用于所有代码执行的环境,局部作用域是作用于函数内部的代码环境。
相对应的还有全局变量和局部变量。
全局变量可以在任何一个地方使用,只有在浏览器关闭的时候才会被销毁,比较占内存。
这里有一个特殊就是,如果在函数内部,没有声明直接赋值的变量,也属于全局变量。
局部变量是在函数内部使用的,只有所在函数被执行,才会初始化,函数执行结束,就会被销毁。因此比较节省内存空间。
这里还有一个作用域链
内部函数访问外部函数的变量,采取的就是链式查找的方式
站在目标角度出发,一层一层往外找,并追求就近原则
15、预解析
JavaScript 解析器在运行 JavaScript 代码的时候分为两步:预解析和代码执行.
预解析会把变量和函数的声明在代码执行之前执行完成。
预解析有两种,变量预解析和函数预解析
变量预解析,也就是变量的提升,变量的声明会被提升到当前作用域的最上面,变量的赋值不会提升。(只提升声明,不提升赋值,提升的是var的,和let,const无关)
函数预解析,函数的声明会被提升到当前作用域最上面,但是不会调用函数
16、new的过程 3
内存中创建一个空对象
将这个空对象的__proto__ 指向了 构造函数的Prototype属性
obj.proto=Person.prototype
构造函数内部的this被赋值为这个新对象(即this指向新对象)。
调用构造函数,执行构造函数内部的代码(给新对象添加属性和方法)
构造函数默认返回return,也就是this的实例化对象
如果写了return,那么看return后面的数据类型
如果是简单数据类型,忽略return简单数据类型,return this
如果是复杂数据类型,忽略return this,return复杂数据类型
17、静态成员和实例成员
实例成员就是构造函数通过this添加的成员,只能够通过实例化的对象来访问
静态成员就是在构造函数本身上添加的成员,只能够通过构造函数来访问
18、关于原型链、构造函数
构造函数的prototype和其实例的__proto__是等价的,都指向原型对象。
构造函数就是可以用来new的函数。(箭头函数不能当做构造函数)
也是在new的过程中,将实例.__proto__指向了对应的构造函数的prototype
1、每个构造函数都有一个prototype属性,指向另一个对象。这个对象的所有属性和方法,都会被构造函数所拥有的。
(通过构造函数创建的实例,实例再去访问属性的时候,自身没有就会去构造函数的prototype上面寻找。 我们可以把那些不变的方法,直接定义在 prototype 对象上,这样所有对象的实例就可以共享这些方法。也可以解决构造函数方法浪费内存的问题。)
2、每一个对象(除了null)都会有一个__proto__属性,指向构造函数的prototype。
(之所以我们对象可以使用构造函数 prototype 原型对象的属性和方法,就是因为对象有 proto 的存在。 __proto__对象原型的意义就在于为对象的查找机制提供一个方向,或者说一条路线,但是它是一个非标准属性,因此实际开发中,不可以使用这个属性,它只是内部指向原型对象 prototype。)
3、每个原型对象都有一个constructor 属性,指向构造函数。
(constructor 主要用于记录该对象引用于哪个构造函数,它可以让原型对象重新指向原来的构造函数。)
原型链的底层基础,首先是创建一个构造函数,然后new得到一个实例。构造函数的prototype和其实例的__proto__都指向原型对象。这就形成了一个三角形。并且原型对象的constructor指回构造函数本身。又因为原型对象也是一个对象,也有__proto__属性,这样一层一层往上就形成了原型链。原型链的最上面一层是Object的原型对象。再往上就指向了null。
实例对象在查找属性的时候,如果找不到,就会沿着__proto__去与对象关联的原型上查找,如果还查不到,就去找原型的原型,直至查到顶层的Object.prototype,这也就是原型链的概念。或者也可以说顶层是Object.prototype.proto,也就是null。
19、继承有哪几种方式 3
继承和多态、封装共为面向对象的三个基本特征。继承可以使子类具有父类的属性和方法。
第一种原型链继承
关键核心:让父类的实例作为子类的原型。
将子类共有的方法,创建在父类的原型对象上。
将子类共有的属性,创建在父类的构造函数内。
缺点:一、创建子类的时候无法传递参数,就算传递了参数,也无法传递给父类。二、如果属性值是复杂数据类型,那么该属性会被所有的实例共享。
第二种借用构造函数继承
关键核心:在子类构造函数中使用call()调用父类构造函数
让父类的this指向子类的实例,在子类的构造函数中,用call改变this的指向
缺点:只能实现属性的继承,不能继承方法
第三种组合式继承
关键核心:使用 call() 调用父类的属性,使用 new 获取父类原型上的方法
通俗的讲,就是将原型链继承中的方法继承和借用构造函数继承中的属性继承进行组合
缺点:多次调用了父类构造函数。原型链继承方法的时候,会创建一个没有用的父类构造函数,比较浪费。
第四种寄生式组合继承
关键核心:使用 Object.create() 来解决多次调用父类构造函数问题
用Object.create(参数),作用是创建出来一个新对象,他的原型对象是括号内的参数。
子类的prototype=Object.create(父类的prototype),
也就是新对象的原型对象为父类的prototype,而实例的原型对象指向了新对象,也就是实例的原型对象的原型对象为父类的prototype
第五种es6类继承
使用class 父类,并且用extends使子类继承父类。
20、常用的es5数组方法 3
第一种forEach
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,没有返回值。
第二种indexOf
第一个参数是需要查找的值,第二个参数是从第几个索引开始查找。
原数组不变,返回值为查到的索引,如果没有该元素,返回-1
第三种some
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回布尔值。
有满足条件的,则返回true,并终止循环。
第四种every
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回布尔值。
全部满足条件返回true,有不满足的,返回false并终止循环。
第五种map
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回新数组。
新数组长度和原数组一致,新数组由原数组中的每个元素进行return之后得到,没有返回值,会返回undefind
第六种filter
参数为回调函数,该回调函数有三个参数,分别表示数组的当前项,索引以及该数组本身。
原数组不变,返回新数组
新数组长度小于原数组,新数组的数组来自于条件判断为true的原数组数组。
第七种reduce(最复杂的一种)
reduce(callback,[initialValue])
有两个参数,第一参数是回调函数,第二个参数是第一次回调函数的参数1。
第二个参数是可选的,如果没有,那么reduce会从索引1的地方开始执行回调。
回调函数有四个参数,
参数1:上一次返回的值。
参数2:当前项
参数3:当前索引
参数4,:调用reduce的数组本身
原数组不变,返回最后一次return的值
口诀:
单纯循坏数组,使用forEach
循环数组得到一个新数组,新数组和循环的数组长度一致,此时用map
循环数组得到一个新数组,新数组少于循环的数组,此时用filter
查找后需要返回true和false的,复杂数据类型(里面是对象的)用some
简单数据类型的,用includes
查找后需要返回索引的,简单数据类型,用indexOf
复杂数据类型,用findIndex
21、本地存储
localStorage, sessionStorage, cookie
cookie:
生命周期:默认自己添加,可设置失效时间,关闭浏览器后失效。
存储数据大小:4kB左右
服务器端通信:参与通信,每次自动携带在请求头中,如果使用cookie保存过多数据会带来性能问题
易用性:很不友好,获取某个cookie,会获取到整个cookie字符串‘key:value;key:value’的形式,一般使用第三方库js-cookie进行处理
localStorage和sessionStorage除了生命周期,其他都相同
生命周期:localStorage除非手动清除,否则永久存储。
sessionStorage仅在当前会话下有效,关闭页面和浏览器后会被清除。
存储数据大小:一般为5-20MB
服务器端通信:仅在浏览器中保存,不参与和服务器的通信
易用性:还可以,但存储的时候只能存字符串(用到JSON.stringify和JSON.parse进行处理)
22、this指向
除了箭头函数,和函数的定义无关,和函数的调用有关。
直接调用,this指向window,严格模式下指向undefined
谁调用指向谁
new,this指向实例
箭头函数的this指向上下文
改变this指向的方法
fn.call(this指向的,函数需要的实参)
fn.apply(this指向的,[函数需要的实参])
fn.bind(this指向的),bind不会调用函数,而是根据该函数生成一个新函数,并且新函数的this指向了()的对象
23、异步
js 是单线程的,也就代表 js 只能一件事情一件事情执行,那如果一件事情执行时间太久,后面要执行的就需要等待,需要等前面的事情执行完成,后面的才会执行。
所以为了解决这个问题,js 委托宿主环境(浏览器)帮忙执行耗时的任务,执行完成后,在通知 js 去执行回调函数,而宿主环境帮我们执行的这些耗时任务也就是异步任务
js 本身是无法发起异步的,是委托给宿主环境发起异步的,但是 es5 之后提出了 Promise 可以进行异步操作
--执行流程如下:
主线程先判断任务类型
如果是同步任务,主线程自己执行
如果是异步任务,交给宿主环境(浏览器)执行
宿主环境进行异步任务的执行,每个异步执行完后,会将回调放进任务队列,先执行完成的先放进任务队列。
等主线程任务全部执行完后,会取任务队列中的任务,根据先进先出原则
在任务队列中取出来的任务,会回到主线程执行,执行完成后,在取下一个,依次重复,这个过程也称为 eventLoop 事件轮训
--而我们所提道的异步任务,也分为宏任务和微任务。
由宿主环境发起的异步被称为宏任务。(setTimeOut、setInterval)
由js自身发起的异步被称为微任务。(promise)
(判断由谁发起的,看是否是ecma的,如果是,则表示是js自身发起的。也就被称为微任务。但是我们用到的微任务基本只有promise————还有一个特殊情况MutationObserver,虽然是由web发起的,但是也是微任务)
promise不是异步的,(.then)才是异步的微任务
await下面的也可以看作是微任务
--执行顺序
先执行宏任务(将整个script标签的代码段看作是一次宏任务)----同步任务
宏任务执行完后看微任务队列是否有微任务
没有微任务执行下一个宏任务
有微任务将所有微任务执行
执行完微任务,执行下一个宏任务
解决异步,最常用的就是promise
24、Generator
async和await是Generator一个语法糖
Generator函数的作用是可以将函数执行时进行暂停,而普通函数是一直执行到return,同样也是异步的一种解决方式。
定义函数时通过 function* 进行区分为 Generator 函数,
当调用Generator函数后,只是得到一个Generator对象,但并不会执行,需要使用yield和next进行配合来执行。
函数内部使用 yield 进行暂停并向外传递数据。函数外部使用next进行逐步执行并接收函数内传递出来的数据。
而向内传递数据的话就返回来。由next向内进行传参,而yield进行接收。但是它的第一次传参是无效的。因为第一次的传参,在使用fn的调用的时候就传过去了。
Generator配合promise可以处理异步问题。可以得到async和await的效果。这里推荐使用一个co库,它可以让Generator函数进行自动执行。
25、闭包 3
闭包的概念就是让你可以在一个内层函数中访问到其外层函数的作用域。
闭包的原理就是作用域链。就是利用作用域链的特性,首先在当前作用域访问数据,当前作用域访问不到,则向父级访问,父级也没有,一直找到全局。
闭包的作用就是数据私有化,防止污染全局。延长数据的作用域。
闭包的缺点是如果使用不当,会造成内存泄漏,因为闭包的数据没有被回收
解决方案就是将全局指向的函数重新置为 null,利用标记清除的特性。也就是让内层函数置为null,这样没有使用到外层函数的数据,数据就会被回收。
(而数据的回收就需要说道垃圾回收。)
闭包的使用场景:
vue源码中的dep使用
柯里化函数,高阶函数的使用
可以使用闭包来模拟私有方法
26、垃圾回收 3
垃圾回收,简称GC
垃圾回收的概念是,js 的内存是自动进行分配和回收的,内存在不使用的时候会被垃圾回收器自动进行回收,那就需要了解垃圾回收的机制,从而防止内存泄漏(内存无法被回收)
--关于垃圾回收的生命周期
在声明一个变量、对象或者函数等的时候,都会创建一段内存
而在适应变量、函数或者对象的时候,会对内存进行读写,也就是会使用内存
最后再变量、函数、对象等不再需要使用的时候,就会被垃圾回收自动回收掉,进行内存销毁
所以说,全局的是不会被垃圾回收的。
--垃圾回收的核心算法就是判断内存是否需要再使用,如果不需要使用,则进行回收。
那么整个垃圾回收的重点就是如何判断是不是垃圾。这里有多种算法策略。我记得的比较常见的是引用计数和标记清除。
先说引用计数:ie会使用,就是计算当前内存被引用的次数,被引用一次计数+1,不再引用则-1,当计数为0,则表示该内存不再被需要,就进行垃圾回收,释放内存。
优点:简单有效。缺点:循坏引用导致内存泄漏。
标记清除:现在多数浏览器采用的就是这个。它是通过根节点(全局),标记所有从根节点开始的能够访问到的对象。未被标记的对象就是未被全局引用的垃圾对象。这里说的根节点,并不是指window。因为在全局使用的let和const,它是根节点可以访问到的,但是window并不可以。
27、es6及以上 3
(1)let和const的变量声明
es6新增了let声明变量,const用来声明常量,const所申明的值,后期不能修改。
和var相比,let和const都具有块级作用域,但是不存在变量提升。
这里还会有一个暂时性死区的概念。只要写了let或者const,就会形成一个块级作用域。这个块级作用域就像一个封闭区域,里面的变量不会再受到外界的影响。所以,块级作用域内,在let和const声明之前使用的这些变量,会产生报错。这样不能使用外界的值,作用域内的值又还没声明的情况,就被称为暂时性死区。
(2)解构赋值
(3)模板字符串
为了方便字符串的拼接,使用反引号``,反引号内部可以通过${}插入表达式,变量甚至是函数调用。
(4)字符串的几种方法
includes:用来查找一个字符串中是否包含另一个字符串,返回值为布尔
stratsWith:用来判断某字符串是否以某字符串开头,返回值为布尔
endsWith:用来判断某字符串是否以某字符串结尾,返回值为布尔
(5)箭头函数
箭头函数就是函数的一种简写形式。使用小括号包裹参数,跟随一个 =>,紧接着是花括号包裹的函数体;
箭头函数如果只有一个参数,可以省略小括号
如果只有一行函数体,可以省略花括号,省略后会默认return
尖头函数中没有绑定this,this指向上下文
(6)函数的形参部分
形参默认值:可以给形参直接设置默认值。
形参解构:函数的形参也可以解构
形参剩余参数:...语法变量名,返回值是数组,可以将剩余的实参用数组进行获取。
单独的...是扩展运算符,是展开内容的。
(7)对象的使用
对象的简写:当对象内的key和值相同时,可以只写一个可以。
对象方法的简写:方法名:function(){},可以简写为方法名(){}
对象内key写法:对象中的key值写成一个表达式,用['字符串']或者[变量名]
(8)对象的方法
Object.assign(目标对象,源对象):类似于合并数据。如果值是复杂数据类型,那么指向的还是同一个值。实现的是浅拷贝。
Object.create(参数):创建一个对象,并将对象的__proto__指向参数。Object.create(null)可以用来创建一个没有原型的对象。
(9)promise
(10)模块化
commonjs模块化规范,导出用module.exprots,导入用require(),最早出来的,一般nodejs里面会使用。
但现在web更流行的是es6模块化规范。浏览器是不识别es6的模块化规范的。
web过渡时期的产品:
AMD: AMD加载完模块后,就立马执行该模块 代表(require.js)
CMD:CMD加载完某个模块后没有立即执行而是等到遇到require语句的时再执行(Sea.js)
运行某个文件:import ‘路径’
默认导入:import 名字(任意) from ‘路径’
默认导出:export default 名字
按需导入:import {名字(必须和导出一致)} from ‘路径’
import {名字 as 修改的名字} from ‘路径’
按需导出:export const 名字=值
同时引入默认和按需:import 名字,{名字} from ‘路径’(必须先默认再按需)
image.png
全部引入:import * as 名字 from ‘路径’
必须要as起别名
返回是一个对象,对象的属性名为导出的名字
属性值为等号后面的值
28、深拷贝和浅拷贝
浅拷贝:
概念:对数据拷贝的时候只拷贝一层,深层次的只拷贝了地址。
...和object.assign都属于浅拷贝
我们会发现通过浅拷贝更深层次的引用类型,如果修改 b.googs,最终 obj.goods 也会跟着修改,是因为在拷贝的时候,我们只是将引用地址拷贝给了 b.goods,也就是说 b.goods 和 ob.goodsj 引用的是同一个对象
缺点:拷贝复杂数据类型的时候,新数据和旧数据都会指向同一个地址。
深拷贝:
方法一:JSON 方法实现深拷贝
我们先将需要拷贝的代码利用 JSON.stringify 转成字符串,
然后再利用JSON.parse 将字符转转回对象,即完成拷贝
问题:造成数据丢失和数据异常
function、undefined 直接丢失
NaN、Infinity 和-Infinity 变成 null
RegExp、Error对象只得到空对象;
方法二:递归深拷贝
定义一个方法,返回一个深拷贝的数据
既然要返回一个数据,我们首先就要定义一个数据,但是数据是对象还是数组?所以需要判断,如果要拷贝的数据是数组,即定义一个数组,如果是一个对象,即定义一个对象
方法里面怎么拷贝啊?还是一样的利用 for in 循环,在循环内部,需要判断,如果是类型是简单类型,直接拷贝,如果是引用类型,就需要在一次的将引用类型里面的值取出来
但是递归也会遇到上面同样的问题
数据丢失和异常处理:处理函数 Symbol 正则 Error 等数据类型正常拷贝
循环引用问题:数据自己引用自己,此时拷贝就会进入死循环
解决思路(循环引用问题)
将每次拷贝的数据进行存储,每次在拷贝之前,先看该数据是否拷贝过,如果拷贝过,直接返回,如果没有拷贝,对该数据进行拷贝并记录该数据以拷贝
存储数据的方式:
使用数组
使用 map 数据:强引用,无法被垃圾回收,key可以是任何形式
使用 hash 表:弱引用,可被垃圾回收
真正在开发中,我们一般都是使用一个lodash的包。
29、数组去重的方法 3
双重for循环
for循环就数组,indexOf 遍历新数组,判断新数组中有没有for循环的每一项,没有就放入新数组。最后新数组就是去重后的数组。
用filter加indexOf,用filter代替for来循环旧数组的每一项
用sort进行排序,然后for循环判断相邻两个是否相等,不相对就放入新数组中
for循环每一项,然后用对象的方式放入,计算个数。
用set与解构赋值。set数组类型的最大特点就是数据不重复
30、你能不能自己配置一个webpack搭建一个项目 3
简单来说webpack就是一个打包工具。但是作用很多:
js高级语法转换兼容
css兼容/预处理语言处理(less、scss)
代码压缩混淆
jsx转换
图片压缩
项目搭建过程:
初始化npm init,搭建项目环境,搭建一个基本的项目目录架构。
安装包webpack和webpack-cil
package.json中添加运行命令‘build’:‘webpack’
添加默认打包入口src/index.js
配置打包模式,根目录创建webpack.config.js。添加commonjs模块化规范的导出module.exports={mode:'development'}
执行打包命令npm run build
正确操作应该会生成dist/main.js的默认出口文件
出入口是可以进行修改的。
31、webpack的打包流程 3
查找配置文件
根据配置文件的入口和出口进行打包
找到对应的入口,根据import文件依赖查找进行打包
但是webpack默认只能打包低级的js文件,遇到其他类型的文件,需要借助相应的loader进行打包。遇到高级的语法文件,需要借助babel来进行降级处理。
打包完成后需要生成html文件,用html-webpack-plugin进行处理打包。
最后输出到出口,将文件进行类型,单独打包,使用plugin插件。
通过output多出口
32、bable和plugin、loader
webpack默认只能处理js文件,如果想处理其他类型文件则借助各种loader实现,用来处理非js文件。
css-loader:负责webpack打包到css文件时(import './css/index.css'),加载该css文件,并将css文件转换为commonjs对象
css开启模块化,开启模块化后,引入的css样式类名会被重新编译,并通过对象中key返回原始类名,value返回编译后的类名,防止样式冲突。开启方式就是给css-loader添加一个modules为true。
style-loader:负责将样式生成style标签插入到DOM中
postcss-loader:可以进一步打包css,处理兼容性,或者将单位统一转为px等
file-loader
babel是一个JavaScript编译器,会对js高级语法进行降级处理。配置babel可以在根目录创建 .babelrc 文件
plugin 插件可以扩展丰富webpack功能
html-webpack-plugin:简化了 HTML 文件的创建,可以根据模版html生成新的html,并自动引入打包的js文件
babel: 将高级语法转换成浏览器可以识别的语法
loader: 加载器, 结合 webpack 来处理非 js 资源文件 .css .less .sass .png
plugin: webpack 的各种各样的插件,能够增强 webpack 的功能
babel工作原理:
parse:通过 parser 把源码转成抽象语法树(AST)
transform:遍历 AST,调用各种 transform 插件对对抽象语法树(AST)进行变换操作
generate:把转换后的 AST 打印成目标代码,并生成 sourcemap
33、computed和watch的区别 3
计算属性computed:
支持缓存,只有依赖数据发生改变,才会重新进行计算
不支持异步,因为有return的存在
定义的时候是方法,使用的是属性
侦听属性watch:
不支持缓存,数据变,直接会触发相应的操作
watch支持异步
监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
watch可以监视:props\data\computed$route
监视的是一个对象: 开启深度监听 deep:true
34、生命周期 1
生命周期:是指一个对象从创建到运行到销毁的整个过程,被称为生命周期
生命周函数:在不同的生命周期阶段会自动执行对应的函数,而这些函数则被成为生命周期函数
生命周期有四个阶段,常用的八个钩子函数
阶段1:创建阶段
beforeCreate:开始创建实例,此时实例的数据和方法还没有。
created:实例已经创建完成,数据和方法都已存在
应用场景:发送请求获取数据,页面一进入需要立即执行
!如果非要在created中操作dom也可以利用$nextTick
阶段2:挂载阶段
beforeMount:开始挂载dom,真正的dom元素还没有挂载完成,不可以操作dom
mouted:dom已经挂载完成,可以操作真实dom
应用场景:页面一进去就需要操作dom元素
阶段3:更新阶段
beforeUpdate:数据变了,但是视图没有变
updated:数据和视图都变了
阶段4:销毁阶段
beforeDestory:即将销毁
destoryed:组件销毁
应用场景:清除挂载在window相关的行为,例如定义器/事件
父子生命周期
==创建挂载阶段
父beforeCreated > 父created > 父beforeMounted >子TbeforeCreate > 子Tcreated > 子TbeforeMount > 子>mounted > 父mounted
==更新阶段
如果更新的数据不涉及到子组件,只会父组件更新父beforeUpdate>父updated
如果更新的数据涉及到子组件, 父beforeUpdate >子TbeforeUpdate > 子Tupdated > 父updated
==销毁阶段
父beforeDestory >子 TbeforeDestory > 子Tdestoryed> 父destoryed
35、vue组件通信(传值)
(1)父传子
父组件属性方式传值,子组件用props进行接收
(2)子传父
子组件通过$emit传值,父组件通过自定义事件接收
(3)eventbus:
本质:就是一个vue实例对象,实现原理是利用发布订阅模式,至于发布订阅模式的话,
发布订阅模式是一对多的依赖关系,发布方是一,订阅方是多。发布方通过eventBus的$emit发布自定义事件,并传递数据。订阅方通过eventBus的$on订阅自定义事件,并通过回调函数接收数据。
(4)vuex
(5)v-model
(6).sync
(7)ref获取子组件
ref加在普通的元素上,可以用this.$refs来获取相应的dom元素
ref加在子组件上,用this.$refs获取到的是组件实例,可以使用组件上的所有方法,用this.$refs.方法名()进行使用。
ref必须在dom渲染完成之后才会有
(8)$children:
可以获取当前组件的所有子组件,并以数组的格式返回数据
$children 并不保证顺序,也不是响应式的。
(9)$parent:
可以获取到当前组件的父组件, 返回当前组件的父组件
(10)provide/inject
provide可以给当前组件所有的后代组件提供数据
使用方式和data类似,只不过data是给当前组件用的数据,而provide是给后代用的数据
inject在后代组件上使用,表示接受值,用数组加字符串的形式接收,和props的最初方式相同。
(11)$attrs
获取到当前组件节点上的所有属性集合
父组件在引用的时候传值,子组件不使用props接收,而是在需要的时候用$attrs进行使用
36、动态路由和静态路由
静态路由是管理员手动配置的,不便于拓展网络拓扑结构,一但网络拓扑发生改变,静态路由配置量会很大。动态路由是路由器通过网络协议,动态的学习路由,当网络拓扑发生变化的时候,路由器会根据路由协议自动学习新的路由。
动态路由,因为OSPF,RIP等路由协议都会有周期更新,所以更新量大,占用宽带大。
使用静态路由的好处是网络安全保密性高。动态路由因为路由器之间频繁交替,需要经常使用路由表,而路由表可以分析出拓扑结构和网络地址等,所以安全性低。
动态路由:灵活性高
静态路由:安全,占用宽带小,简单,高效。
37、路由传参
动态路由传参
路由配置,需要在path上进行参数设置
params方法 path: '/xxx/:uid',
query方法 path: '/xxx?uid=值',
路由跳转,this.$router.push('地址');
在对应页面中拿到路由参数,this.$route.params.uid / this.$route.query.uid;
query方式传参
路由配置不变
路由跳转
用path跳转,this.$router.push(),参数为对象,path:对应路由配置的值,query参数
用name跳转,this.$router.push(),参数为对象,name:对应路由配置的值,query参数
在对应页面中拿到路由参数,this.$route.query.uid;
params方式传参,只能通过name跳转
路由配置不变
路由跳转,this.$router.push(),参数为对象,name:对应路由配置的值,params参数
在对应页面中拿到路由参数,this.$route.params.uid;
页面跳转的方法:
1、
2、this.$router.push()跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面
3、this.$router.replace()跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面
4、this.$router.go(n)向前或者后跳转n个页面,n可以是正数也可以是负数
38、路由守卫
路由守卫分为三种,全局守卫,路由独享守卫以及组件内守卫。
全局守卫
全局前置守卫
写法router.beforeEach()
每一个路由进入前触发
可以用来进行路由权限的控制。
在回调中有三个参数,to,from,next,其中next()必写。
全局后置守卫
写法router.afterEach()
每一个路由进入后触发
用于提示语之类的
路由独享守卫
写法beforeEnter(),写在路由规则配置里面,和path,name同级
单独进入某个路由前触发
在回调中有三个参数,to,from,next,其中next()必写
组件内守卫
写在组件内,和created等生命周期同级
在回调中有三个参数,to,from,next
写法有三个
beforeRouteEnter
渲染组件前触发
不能获取组件实例 this
beforeRouteUpdate
在当前路由改变,但是该组件被复用时调用
一般就是是同一个页面,不同的参数这样,此时组件复用,没有被创建,也就无法重新创建。
可以访问组件实例 this
beforeRouteLeave:路由离开
路由离开时调用
可以访问组件实例 this
比如在编辑过程中,要离开给页面,就会跳出没有保存,是否需要离开的提示。
39、聊聊vuex
对于vuex的话。
vuex是一个状态管理的库,是用来实现组件之间的数据共享的。
vuex的优点是数据的响应式,可以便于数据的传递,方便维护和管理。
vuex包含了5个属性:
state,用于定义和存储共享的数据。用$store.state或者是辅助函数mapState来进行触发。
mutations,用来修改数据,也是修改数据的唯一来源。(当然,其实state数据是可以直接进行赋值修改的,但是并不建议使用,就像是所有人都可以更改公司数据一样,存在着各种不安全隐患。)用commit或者辅助函数mapmutations来进行触发修改数据。
actions:说道actions,就要提到刚刚说的mutations,mutations的同步的,而actions是异步的。用dispatch或者是辅助函数mapactions来触发。但是actions只能够进行异步处理,不能修改state的数据。所以如果需要修改数据,还是需要调用mutations来处理。
所以我们的同步流程就是直接触发mutations,而异步流程则是先触发actions,再由actions进行异步处理以后再去触发mutations。
getters:基于state进行派生数据
moudles:模块化,将数据模块化后,会有上述的四种属性。模块化后的数据方便维护和管理。每一个模块可以设置命名空间,如果不设置,那么mutations和actions的使用和全局并无差别。如果开启命名空间,则需要通过模块名进行访问。
vuex的缺点就是不能持久化。我们在使用的时候,如果数据是从后台请求回来的,那么可以直接忽略持久化的问题。如果不是请求回来的,那么就需要解决持久化的问题。
解决的方式就是本地存储。除了localStorage, sessionStorage, cookie以外,我们还常用一个自动存储的插件
vuex-persistedstate。
mutations是处理同步的数据的,如果处理异步的话,会导致devtool的记录出现问题,也就无法及时的知道状态是何时更新的,无法追踪状态,给调试带来困难。那我们公司就是不管同步还是异步,都会先进行一个actions的异步处理,再去调用mutations来处理修改数据。
40、vue.use()的原理
通过全局方法vue.use()使用插件
Vue.use会自动阻止多次注册相同插件
需要在调用new Vue()启动应用之前完成
Vue. use()至少传入一个参数
如果参数是一个Object对象,那么这个对象必须提供一个install函数
如果参数是一个function函数,那么就相当于install函数
Vue. use()的其他参数,都会依次传给install函数
install函数默认的第一个参数是vue,后面的参数来自于Vue.use后续参数
vue.use本质是无法注册全局组件或者给vue原型添加方法的,但是我们在使用路由或者vuex或者element ui,实际还是在install函数内部通过vue.component注册了全局组件或者给Vvue.prototype手动添加方法。
41、v-if和v-show
v-show和v-if都是可以用来控制元素的隐藏和显示的。true的时候表示显示,false的时候表示隐藏。
v-show的隐藏使用的是css样式display:none的方式,不管是true还是false,它都会渲染对应的dom元素。
优点:不会频繁创建dom
缺点:首次渲染为false也会创建
场景:适用于频繁切换的场景。
v-if的隐藏是直接从dom上移出,不会产生对应的元素。在true的时候,再创建相对应的整个标签。
优点:他是懒渲染,默认首次如果是false,元素不会创建。如果是组件,利用v-if可以重新触发生命周期
缺点:频繁的删除和重建
场景:适用于一进入页面,就确定是显示和隐藏,后期不会改变的场景。
或者组件需要重新触发生命周期的场景
v-if还有一个v-show没有的高级语法,就是和v-else搭配使用的判断条件的用法。
42、$route和$router
$route:用来获取当前路由信息的,每个路由都会有一个$route对象,是一个局部的对象
可以写作$route.path(当前路径)params,name等
$router:全局路由实例,用来操作路由的
等价于 new VueRouter
包含所有的路由,路由的跳转方式,钩子函数等等。
最常用的就是this.$router.push()进行跳转
43、v-model
作用:一是数据双向绑定,二是实现组件通信
原理:v-model就是一个语法糖,动态绑定了value和注册了input事件
使用场景:
一是在表单中使用,比如input输入框之类的,需要双向绑定这个数据,就可以绑定一个v-model,绑定的就是input内输入的值
二是在组件上使用,在组件上使用的情况就是:父组件数据要传给子组件,并且子组件需要修改数据(两件事情都需要的情况下才会使用)
缺点:v-model在一个组件上只能使用一次
44、MVVM 的设计思想的优势
mvc的改进版
双向绑定技术,当 数据变化时,视图也会自动变化,视图发生更新,数据也跟着同步
我们减少了 dom 的操作,因为我们只需要关注数据就可以
mvvm 的设计思想大大提高了代码的耦合性
数据响应式的原理
45、keep-alive
keep-alive是一个内置组件,它会缓存不活动的组件实例,而不是将其销毁。不会渲染dom元素
它提供了include和exclude属性。
它包含了activated和deactivated钩子函数
include:指定缓存的组件
exclude:指定不缓存的组件
activate:激活状态
deactivated:失去激活状态
46、图片懒加载
图片懒加载的原理:优先加载可视区域的内容,其他部分等进入了可视区域再加载,从而提高性能。
一张图片就是一个标签,浏览器是否发起请求图片是根据
的src属性,所以实现懒加载的关键就是,在图片没有进入可视区域时,先不给
的src赋值,这样浏览器就不会发送请求了,等到图片进入可视区域再给src赋值。
47、虚拟dom和diff算法
虚拟dom的本质就是一个js对象,用来描述真实dom是什么样子的,这个对象就是我们常说的虚拟dom。
虚拟dom的出现可以进行高效更新,同时可以使用虚拟dom进行跨平台。
我们在初始化渲染的时候,会根据数据和模板生成一个虚拟dom树,当数据发生变化的时候,又会根据新的数据和模板,生成一个新的虚拟dom树。然后将新旧两颗虚拟dom树进行对比。对比的过程使用的就是diff算法。说道diff算法。它的特点是同级比较,深度优先,且采用双指针算法。这里的双指针算法会产生四个指针,新旧虚拟dom树各有两根指针,都是一个指向开始位置,一个指向结束位置。在进行循环的时候,开始位置的指针会在对比完以后向后推,结束位置的指针在对比玩以后会向前推,从而达到高效更新。
diff对比情况分为三种。
一是元素不同,会直接删除重建。
二是元素相同,属性不同,那么元素会进行复用,只会更新属性。
三是v-for循环的情况。这也会分成两种,
一是没有key的时候,如果数据的变化没有影响到顺序,那么性能没有影响。如果数据的变化影响到了顺序,那么性能也会受到影响。因为没有key的时候,我们是根据顺序进行对比的。
二是有key的时候,key是不建议使用索引的,因为索引是会变化的。我们推荐使用唯一值,对比的使用会根据key值进行对比。
48、路由模式
路由模式分为三种
abstract,支持所有 JavaScript 运行环境,如果发现没有浏览器的 API,路由会自动强制进入这个模式。
hash模式:
有#/,
通过window.location.href进行跳转
通过window.onhashchange进行监听
history模式:(推荐使用)
没有#/
通过history.pushState和history.repleaceState进行跳转,
通过onpopState进行监听,只能监听前进和后退,无法监听history.pushState和history.repleaceState, 源码中将history.pushState和history.repleaceState,进行了统一的包装,通过pushState函数进行包装,不管是history.pushState还是history.repleaceState实际底层最终都会通过pushState这个函数进行跳转,通过pushState进行监听
刷新会404,需要后端的支持。
49、为什么会出现跨域(同源策略)
跨域产生的原因就是同源策略的存在。
同源策略是浏览器提供的一种安全机制,可以防止跨站脚本攻击,
也就是A网站请求B网站的资源,若是不同源,则不能够请求。
满足同源的条件是:协议、域名/IP地址、端口号,三者完全一致则表示同源,可以进行资源共享
有一项不同既不同源,代表是两个网站,此时资源不共享
跨域的本质是浏览器,在浏览器中才会出现。
那么为什么会出现跨域呢?
是因为当下,最流行的就是前后分离项目,也就是前端项目和后端接口并不在一个域名之下,那么前端项目访问后端接口必然存在跨域的现象。
跨域存在着两种,一种是开发环境的跨域,一种是生产环境的跨域
生产环境的跨域的话,等我们项目打包上线,会有运维人员处理的。
我讲一下开发环境的跨域的解决方式吧。
解决的方法有:JSONP,CORS(后端开启),代理服务器
如果需要我们自己解决的话,就要用到代理服务器。
我们在使用浏览器进行请求的时候,不再直接请求服务器的接口,而是向本地服务器进行请求,这就不会出现跨域。再让本地服务器向服务器的接口进行请求,两个服务器之间也不存在跨域问题。再反向进行响应。
这整个过程就是本地服务器启到一个代理服务器的作用。
当然也可以使用jsonp,但他只支持get,不支持post。
50、环境变量
一个项目在开发的过程中,会经历各种过程才会发布,每个过程的环境不同,就会需要不同的配置参数,所以就可以用环境变量来方便我们管理。
每个不同的环境有一个不同的文件,这些文件都和src同级
一个项目,基准地址(环境)会有3套,分别是开发期间的、测试的、线上的
在package.json中会有相对应的配置,运行不同的命令代表不同的环境,使用的就是不同文件内,环境变量的值。
环境变量是存放基准地址的,不同的环境(比如测试,开发期间,上线等)的基准地址可能会不同。用环境变量来存储。环境变量的值来自于不同的环境文件.env.的文件中
51、nextTick
作用:数据发生变化后,可以利用nextTick获取最新的视图
原理:数据发生变化会驱动视图,这是一个异步操作。为了性能,等所有数据变化后,进行合并更新视图。因为这个原因,导致数据发生变化后,无法立即获取最新视图。
解决方案就是使用nextTick
nextTick不传回调的时候,则内部是promise对象
nextTick传回调的时候,则内部是setTimeout定时器
52、token过期问题
token一般的过期时间是2个小时。这里要引入一个其他内容,refresh_token过期时间较长(一周、两周)
在token过期的时候,我们会在用户不知情的情况下,偷偷的发送一个请求,获取新的token。通过refresh__token偷偷进行换取,登录状态就可以继续维持一周到两周。如果refresh__token也过期了,那么就会跳转到登录页,需要重新进行登录。
如何判断token过期
1、前端主动处理
当判断token过期时候,不再发送请求,就可以优化性能。减少网络请求的次数
借助时间戳:
登录成功的时候存下token的时间戳
发送请求前,获取当前的时间戳-存下token的时间戳,等到的值如果超过了token的有效期(两个小时),那么就不发送请求,而是跳转到登录页面,要求重新登录。
2、前端被动处理,由后端主动处理。也就是需要判断请求的时候,返回的状态码,一般401是登录过期,也就是token过期的情况(当然具体是要看后端的返回,这里的401只是一般情况下)
53、.sync修饰符
修饰符有哪些:
v-on的
.stop - 阻止事件冒泡
.prevent - 阻止默认行为
.once - 程序运行期间, 只触发一次事件处理函数(不可以和其他修饰符连用)
.native - 在某个组件的根元素上监听一个原生事件
v-model的
.number 以parseFloat转成数字类型
.trim 去除首尾空白字符
.lazy 在change时触发而非inupt时
作用: 语法糖,也可以实现组件通信, 类似双向绑定(父向子传,子向父改)
原理: .sync解析出一个动态绑定的数据,解析一个自定义事件,@update:属性名,组件内部可以通过this.$emit('update:属性名的')进行触发
.sync: 可以使用多次, 而且.sync可以和v-bind结合直接传递一个对象,将对象的每个属性单独传递进去,单独的绑定v-on事件
:属性名.sync=‘变量’
等价于
:属性名=‘变量’(父向子传值)
@update:属性名=‘变量=$event’(@update表示自定义事件)
54、你们的项目是如何打包部署的
运行npm run build 进行打包,可以进行打包优化,打包之后将dist文件交给后端
首先自测,没问题我们将代码合并到development分支,我们的development分支是受保护的,所以需要进行合并申请,审核通过合并成功,代码合到release分支,测试人员进行自动化测试,从release合到master,从master发布一个tag发布到线上.实际上我们的是cicd.会进行自动打包部署gitlab+docker+Jenkins
55、webpack打包优化 3
移除console
soucemap,可以映射,精确的定位到开发代码的哪一行
splitChunks:将公共代码进行提取,设置那些重复使用的代码,用合并方式进行打包
vue-cli3默认开启prefetch,在加载首页的时候,就会提前获取用户可能访问的内容,提前加载其他路由模,所以我们要关闭该功能
这里要注意:不会影响首屏的加载速度,实际是为了优化子页面,可以快速打开子页面,但是像移动端,用户可能只会访问首页,也预加载其他模块的资源,浪费用户流量。
打包成gzip,可以进行资源请求的时候速度更快
runtimeChunk:运行时代码,也就是异步加载的代码,比如路由懒加载的都属于运行时代码。
没有开启runtimeChunk,运行时代码或者代码没有发生变化,重新打包时,主模块也会重新打包hash会发生变化。会导致项目部署后,强缓存失效。
开启runtimeChunk,会将运行时代码的信息单独存放在runTime中,主模块就不会被影响,也就不会重新打包,可以继续使用本地缓存。(但要配合7使用)
script-ext-html-webpack-plugin:用这个插件可以将runTime代码生成到行内
通过image-webpack-loader进行图片的打包压缩
开启路由懒加载 将每个路由进行单独打包
排除打包,用externals将比较大的包排除掉,然后引入响应的CDN资源
56、首屏优化 3
soucemap: 关闭
路由懒加载
cdn资源
splitChunks: 提取公共资源
图片压缩
gzip
runtimeChunks
ssr: 服务端渲染
解决首屏加载速度慢的问题,因为首屏服务端直接返回,不需要加载js文件
还可以解决seo,html不再是只有一个id为app的标签,更加有利于seo搜索
实现ssr的方法是使用vue结合nuxt
spa单页面应用
我们vue是使用spa的,spa页面响应速度快,可以减轻服务器压力,但是不利于seo,首屏加载也会比较慢。
这时候就会用到ssr。也就是在vue中使用nuxt框架。虽然可以让爬虫更容易爬到数据,响应速度也更快,但是会增加服务器的压力,并且开发的难度也比较大。
57、技术栈(了解一下)
vue(全家桶 vue、vuex、vueRouter、axios、elementUi、)echarts-cos-js-sdk-v5、dayjs、js-cookie、vuex-persistedstate
xlsx、file-saver excel导入导出
58、封装创建组件
(1)组件封装思想
组件的结构:结构考虑复用灵活,一般使用插槽、允许自定义
组件的样式:考虑支持自定义,一般使用属性传值(通过样式或者类名)
组件的数据:通过数据传递
暴露事件:例如弹框组件,点击遮罩弹框关闭,用户使用组件的时候,也需要监听到点击遮罩的行为,用户可以进行自定义的逻辑
(2)如何创建一个全局组件
通过 Vue.component 来创建一个全局组件,第一个参数是组件名字,第二个参数是组件的配置对象,可以通过 template 配置组件的结构,data 定义数据等等
(3)如何创建一个局部组件
在组件内部通过 components 来创建一个局部组件
全局组件和局部组件的区别
局部组件:只能在当前的父组件中使用
全局组件: 在任意地方使用
(4)如何定义局部自定义指令
在组件内部通过 directives 来创建一个局部指令
全局指令和局部指令的区别
局部指令:只能在当前的组件中使用
全局指令: 在任意地方使用
(5)如何定义局部过滤器
在组件内部通过 filters 来创建一个局部过滤器
全局过滤器和局部过滤器的区别
局部过滤器:只能在当前的组件中使用
全局过滤器: 在任意地方使用
59、出现bug怎么解决
线上出现bug,首先要去评估bug的影响范围。
首先看是在新版本上的还是旧版本上
新版本上,回退到上一个版本。然后去fixBug进行修复新版本上产生的bug。
旧版本上,说明这个bug很长时间才出现,说明不是很严重,可以定位bug的位置进行修复,完成后将bug修复的代码发布到线上。也可以等下个版本上线的时候,进行覆盖。
总的来说,就是要将影响范围缩到最小为主要思想,来使用不同的方式进行bug的解决。
60、工作流程
首先我们组长去拿到项目需求,然后他会开会召集我们去进行开发时间评估,然后ui会根据需求出设计稿,前后端会沟通API接口,沟通完以后我们开始静态页面的开发,后端也开始API接口的开发,后面我们静态页面开发完了,如果后端接口写好了我们直接调用渲染页面,后端还没完成的话我们可以先用mock模拟数据进行测试。后边如果发现啥问题的话再跟后端协商处理(联调),我们自己写的时候是会边写边测试的,基本完成感觉没啥问题了就会交给测试人员测试,然后就开始测bug,测试人员会把bug发布到协作平台上(禅道、ones),然后我们去解决bug。 等到项目可以上线的时候就由运维人员发布上线。
61、如何和后端进行联调
概念:在我们开发的过程中,发送请求的ajax数据都不是后端返回的真数据,而是我们自己通过接口mock模拟的假数据,当前端的代码编写完成后,后端的接口也写好后,我们就需要把mock数据换点,尝试使用后端提供的数据,进行一个前后端的调试,我们会把这个过程叫做前后端接口联调。
我们需要测试后端的数据和我们所使用的mock数据是否适配,格式是否正确。看所返回的数据是否够用。比如你想实现分页或者列表功能,可是后端就只写了两条数据, 这些问题就会在联调的过程中进行协商解决。
我们公司开发是前后端分离,部署时是一个域名和一台服务器。
62、link与@import的区别是什么
1、从属关系区别
@import是 CSS 提供的语法规则,只有导入样式表的作用;link是HTML提供的标签,不仅可以加载 CSS 文件,还可以定义 RSS、rel 连接属性等。
2、加载顺序区别
加载页面时,link标签引入的 CSS 被同时加载;@import引入的 CSS 将在页面加载完毕后被加载。
3、兼容性区别
@import是 CSS2.1 才有的语法,故只可在 IE5+ 才能识别;link标签作为 HTML 元素,不存在兼容性问题。
4、DOM可控性区别
可以通过 JS 操作 DOM ,插入link标签来改变样式;由于 DOM 方法是基于文档的,无法使用@import的方式插入样式。
63、http和https
http超文本传输协议,用于在浏览器和服务器之间传递信息。不适合传输敏感信息。
https安全套接字超文本传输协议,在http的基础上加上了SSL协议。
http的默认端口是80,https是443
http是传输过程是明文的,https是加密传输的
http是无状态连接,https由ssl+http构成的
http是基于7层协议中的应用层,https是基于传输层的
https使用非对称加密进行密钥传输,使用对称加密进行数据传输
对称加密(数据传输)
发送方和接收方使用同一个密钥(一串字符串)进行加密和解密
服务端使用密钥进行加密
客户端使用密钥进行解密
但是第一次要传输一次密钥,如果密钥被拦截,就被破解了
性能好,速度快,缺点,密钥被拦截被破解
非对称加密(密钥传输)
一个公钥,一个私钥
服务端使用私钥解密
客户端使用公钥加密
优点:安全
缺点:性能差,耗时间
64、双向数据绑定? 2
数据发生变化,同步视图,视图发生变化,同步数据。
v-model可以完成数据双向绑定。
但原理是v-on绑定事件和v-bind绑定数据,是一个语法糖。
v-bind可以实现数据变同步视图,这是因为数据响应式的原理。
v-on绑定事件可以实现视图变同步数据,这是数据响应式的原理。
65、是单向数据流? 1
在父向子传值的时候,如果改变父组件的值,子组件会跟着同步更新,反之不允许
66、事件传参 2
事件函数没有括号,表示没有传递参数,则默认的第一个参数是事件对象。
事件函数有括号,就算为空,也表示传递参数。则形参和实参一一对应。如果需要事件对象,实参用$event进行传递。
image.png
image.png
67、自定义指令:directive
当vue 提供的系统指令满足不了我们的需求时,我们就需要自定义指令
全局通过 Vue.directive 进行自定义指令的定义。
局部通过directives进行定义。key为自定义指令的名字,value为对象,对象内有下面三个钩子函数。
bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性的初始化设置。
inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是你可以通过比较更新前后的值来忽略不必要的模板更新 (详细的钩子函数参数见下)。
68、vue 的两个核心
组件系统、数据驱动
69、组件插槽
默认插槽:
在组件标签中间可以传递一些子节点
组件内部利用 slot 标签进行接收
具名插槽(可以传多个插槽)
在组件标签中间通过定义 slot 的名字传递子节点
组件内部利用 slot 的 name 进行对应接收
作用域插槽
在组件内部定义数据,将数据传递给插槽的结构
通过给 slot 动态绑定属性
插槽内部:通过 slot-scope=“scope”来接收
70、vue 单页面应用的优缺点
缺点:
不利于 seo
兼容到 ie9
初次加载耗时相对增多
优点
用户体验好,不用重新刷新整个页面
前后端分离
mvvm 设计模式
减轻服务期压力,只需要服务器提供数据
71、v-if和v-for为什么避免同时使用
v2中:
v-for的优先级高于v-if,所以还是会先循环创建虚拟dom,再利用v-if进行移除
解决方式:
v-if写到外层
先通过计算属性将数据计算好
v3中: v-if优先级高
72、mock假数据
现在的项目都是前后端分离的,在前后端同时开发的过程中,后端接口数据没有出来,前端可以使用mock假数据。
优点:
团队可以并行工作。更好的进行前后端分离。
增加测试的真实性,通过随机数据,模拟各种场景。
开发无侵入,不需要修改既有代码,就可以拦截ajax请求,返回模拟的响应数据。
数据类型丰富,支持生成随机的文本、数字、布尔值、日期、邮箱、链接、图片、颜色等。
方便扩展,支持扩展更多数据类型,支持自定义函数和正则。
不涉及跨域问题
73、mixins
mixins: 将组件中的逻辑功能进行复用,复用部分可以提取到一个js文件中,然后通过mixins这个选项将该文件中暴漏的对象进行混入即可
可以混入哪些: 正常的实例对象一样包含实例选项,这些选项将会被合并到最终的选项中
优先级:
生命周期:组件和混入的都会调用(混入的先调用)
data数据::进行合并,发生冲突以组件为主,mixins中的会被覆盖
methods、components 和 directives,将被合并为同一个对象。两个对象键名冲突时,取组件对象的键值对
74、axios取消重复请求
场景:如果在输入框中输入12,然后先发1的请求,再发12的请求,但是如果关键词1的请求响应慢,12请求的数据先回来,那么1请求回来的数据会覆盖12的数据。和我们所需要的结果产生了出入。
解决方案:使用取消请求的方式。
原生的ajax可以使用abort()
取消axios请求,则使用axios内部的cancelToken方法
75、浏览器的进程和线程
当我们启动某个程序时,操作系统会给该程序创建一块内存(当程序关闭时,该内存空间就会被回收),用来存放代码、运行中的数据和一个执行任务的主线程,这样的一个运行环境就叫进程
而线程是依附于进程的,在进程中使用多线程并行处理能提升运算效率,进程将任务分成很多细小的任务,再创建多个线程,在里面并行分别执行。
进程与进程之间完全分离,互不影响。
进程与进程之间传递数据使用进程通信管道IPC
一个进程中可以并发多个线程,每个线程中可以并发执行不同的任务
一个线程出错会导致所在的整个进程奔溃
同一个进程内的线程之间可以相互通信和共享数据
进程关闭,会被操作系统回收至内存空间。
一个浏览器会包含多个进程,一般为:
浏览器主进程: 负责控制浏览器除标签页外的界面,同时提供存储功能
GPU进程:负责整个浏览器界面的渲染。
网络进程:负责发起和接受网络请求。
插件进程:主要是负责插件的运行。
渲染进程:负责控制显示tab标签页内的所有内容。
我们在浏览器中的工作,多数都是通过渲染进程完成的。
渲染进程中的线程:
GUI渲染线程:负责渲染页面
JS引擎线程:一个tab页中只有一个JS引擎线程(单线程),负责解析和执行JS。和GUI不能同时运行。
计时器线程:负责计时器工作
异步请求线程: 处理异步的
事件触发线程:主要用来控制事件循环eventLoop
76、三次握手和四次挥手 1
三次握手
客户端向服务端发送信息,“我发信息,你收得到吗”
服务端向客户端发送信息,“收到了,我发信息你收得到吗”
客户端向服务端发送信息,“收到了,我们连接成功了”
四次挥手
客户端向服务端发送信息,“我说完了”
服务端向客户端发送信息,“好的,我知道了,我看看我还有没有要说的”
服务端向客户端发送信息,“我也说完了”
客户端向服务端发送信息,“好的,我知道了” 2MSL之后挂断
77、浏览器输入url,敲下回车后发生的事情 1
URL解析
首先判断你输入的是一个合法的 URL 还是一个待搜索的关键词,并且根据你输入的内容进行解析。如果是一个关键字,会使用浏览器默认的搜索引擎去添加关键字。
DNS查询
通过域名解析得到IP地址(先进行本地解析,没有再通过DNS解析)
三次握手
得到服务器的IP地址后,进行tcp连接
发送http请求
tcp连接成功后,浏览器发送http请求到目标服务器(数据请求)
响应请求
返回http响应消息(返回数据资源)
四次挥手(http1.0的时候)
数据请求完成以后,为避免资源占用和损耗,关闭连接。
页面渲染
对返回的资源进行解析渲染
四次挥手(http1.1的时候,会判断有没有keep-alive,有,就会在关闭网页的时候进行断开。没有keep-alive,请求结束就直接断开连接。)
78、防抖和节流
防抖和节流都是为了避免函数被多次调用导致页面调用,但它们的本质不一样,防抖是将多次执行变为最后一次执行,节流是将多次执行变为每个一段时间执行一次。
防抖是指触发事件函数后,函数在n秒后只能执行依稀,如果n秒内再次触发,会重新计算时间。也就说说连续触发,只执行最后一次。
场景:
搜索框搜索输入,只需要用户最后一次输入完成,再发送请求。
节流会限制一个函数在n秒内只能执行一次,过了n秒又可以执行一次。
场景:
发送验证码的时候,60秒内只能发送一次。
79、假值有哪些
假值就是值boolean转出来是false的
0
Null
NaN
False
undefined
空字符串
80、get和post的区别
从标准上来说:
GET 用于获取信息,是无副作用的,是幂等的,且可缓存 ,安全性差
POST 用于修改服务器上的数据,有副作用,非幂等,不可缓存
从请求报文上来说:
GET 和 POST 只是 HTTP 协议中两种请求方式(异曲同工),而 HTTP 协议是基于 TCP/IP 的应用层协议,无论 GET 还是 POST,用的都是同一个传输层协议,所以在传输上,没有区别。
在报文格式上,不带参数时,基本一致,带参数时,在约定中,GET 方法的参数应该放在 url 中,POST 方法参数应该放在 body 中。
81、响应拦截器里面都做什么事情
请求拦截器
在请求发送前进行必要操作处理,例如添加统一cookie、请求体加验证、设置请求头等,相当于是对每个接口里相同操作的一个封装;
响应拦截器
同理,响应拦截器也是如此功能,只是在请求得到响应之后,对响应体的一些处理,通常是数据统一处理等,也常来判断登录失效等。
82、实现实时更新
websocket
比如直播间弹幕啊,股票的实时数据更新,进入页面客服的自动发送信息等等
websocket是一种数据通信协议,常见的是http协议。
http协议的缺陷:通信只能由客户端发起,http基于请求响应实现。
83、vue 组件中的 data 为什么是一个函数,返回一个对象?
如果不是一个函数返回一个新的对象,组件如果多次使用,实际公用的是同一个数据
但是如果是通过函数 返回一个新的对象,这样的话,每个组件的使用数据是独立的
83、babel的使用过程
babel是一个降级语法的工具
首先需要下载安装这个包
在根目录比如babel。config。js中babelrc进行配置
在plugins中配置babel的插件
84、浅谈事件冒泡和事件捕获
事件冒泡和事件捕获分别由微软和网景公司提出,这两个概念都是为了解决页面中事件流(事件发生顺序)的问题。
阻止事件冒泡e.stopPropagation()
简单来说,当你鼠标在浏览器上点击了一下。
浏览器捕获到了click事件。
然后浏览器根据你点击的事件,从window开始向下,就会触发每个父祖element捕获模式的事件回调。
直到找到点击所在的最终(最小的element)
然后浏览器开始继续又向上冒泡其父祖element的click事件,直至window。
默认的事件都是冒泡模式下触发的。
85、二维转一维数组,扁平化
1、flat
flat是ES10新增的一个数组处理的方法,非常的好用,它专门用来扁平化数组。
合并返回新数组
参数是层级数,默认为1,层级未知可以用Infinity
2、concat + 扩展符
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
扩展运算符(...)可以将数组转为用逗号分隔的参数序列。
只能二维转一维,多维就不行
3、reduce + concat
reduce() 方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(上一次回调的返回值),当前元素值,当前索引,原数组 。
callback:函数中包含四个参数
- previousValue (上一次调用回调返回的值,或者是提供的初始值(initialValue))
- currentValue (数组中当前被处理的元素)
- index (当前元素在数组中的索引)
- array (调用的数组)
initialValue (作为第一次调用 callback 的第一个参数。)
将初始值设置为了[]空数组,然后将需要扁平化的数组的每一项都用concat重新连接,最终得到一个一维数组。
4、toString + split
先使用 toString 把数组转成字符串,再使用 split 把字符串转回数组:
该方法存在局限性,不适用于一些包含相对特殊子元素的数组,比如包含 null、undefined、对象类型等。
使用map是为了让数组元素变为Number类型。
86、前端性能优化方式
缩小html、css和js
使用到插件Gulp和JSMin
87、单点登录
概念: 一个大型公司有很多系统,用的是同一个账号,登录一个系统时,其它系统也可以正常访问
cookie:
某个系统登陆成功,再次去登录其它系统系带token,token如何在多个网站中共享
domain/path
domain: 设置网站域名 设置为主域名(父级域名)/二级域名是可以获取到cookie数据
path: 路径 /
脱离父级域名不可以共享了
认证中心
iframe
88、webpack+browserify+gulp+grunt四个工具的区别
gulp和grunt是前端自动化构建的工具,帮助用户完成js\css压缩、less编译等(只不过现在webpack也可以完成压缩等任务,可以替代gulp的这部分功能)。
webpack和browserify是前端模块化方案,与seajs和requirejs是一个东西,只不过seajs和requirejs是在线编译方案,引入一个CMD\AMD编译器,让浏览器能认识export、module、define等,而webpack和browserify是预编译方案,提前将es6模块、AMD、CMD模块编译成浏览器认识的js。
他们之间的区别见以上两点,只不过相互之间也会有一些相似的功能。
grunt配置复杂繁重,是基于文件流的操作,比较慢;gulp是基于内存流的操作,配置轻量级,代码组织简单易懂,异步任务。
webpack的话,就是配置复杂,文档杂乱,插件繁多,难于上手。
JavaScript
\1. Promise 的理解
Promise 是一种为了避免回调地狱的异步解决方案 2. Promise 是一种状态机: pending(进行中)、fulfilled(已成功)和rejected(已失败) 只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态。
回调地狱
回调函数中嵌套回调函数的情况就叫做回调地狱。
回调地狱就是为是实现代码顺序执行而出现的一种操作,它会造成我们的代码可读性非常差,后期不好维护。
一、Promise是什么?
Promise是最早由社区提出和实现的一种解决异步编程的方案,比其他传统的解决方案(回调函数和事件)更合理和更强大。
ES6 将其写进了语言标准,统一了用法,原生提供了Promise对象。
ES6 规定,Promise对象是一个构造函数,用来生成Promise实例。
二、Promise是为解决什么问题而产生的?
promise是为解决异步处理回调金字塔问题而产生的
三、Promise的两个特点
1、Promise对象的状态不受外界影响
1)pending 初始状态
2)fulfilled 成功状态
3)rejected 失败状态
Promise 有以上三种状态,只有异步操作的结果可以决定当前是哪一种状态,其他任何操作都无法改变这个状态
2、Promise的状态一旦改变,就不会再变,任何时候都可以得到这个结果,状态不可以逆,只能由 pending变成fulfilled或者由pending变成rejected
四、Promise的三个缺点
1)无法取消Promise,一旦新建它就会立即执行,无法中途取消
2)如果不设置回调函数,Promise内部抛出的错误,不会反映到外部
3)当处于pending状态时,无法得知目前进展到哪一个阶段,是刚刚开始还是即将完成
五、Promise在哪存放成功回调序列和失败回调序列?
1)onResolvedCallbacks 成功后要执行的回调序列 是一个数组
2)onRejectedCallbacks 失败后要执行的回调序列 是一个数组
以上两个数组存放在Promise 创建实例时给Promise这个类传的函数中,默认都是空数组。
每次实例then的时候 传入 onFulfilled 成功回调 onRejected 失败回调,如果此时的状态是pending 则将onFulfilled和onRejected push到对应的成功回调序列数组和失败回调序列数组中,如果此时的状态是fulfilled 则onFulfilled立即执行,如果此时的状态是rejected则onRejected立即执行
上述序列中的回调函数执行的时候 是有顺序的,即按照顺序依次执行
\2. 箭头函数和普通函数的区别
箭头函数与普通函数的区别在于: 1、箭头函数没有this,所以需要通过查找作用域链来确定this的值,这就意味着如果箭头函数被非箭头函数包含,this绑定的就是最近一层非箭头函数的this, 2、箭头函数没有自己的arguments对象,但是可以访问外围函数的arguments对象 3、不能通过new关键字调用,同样也没有new.target值和原型
1、语法更加简洁、清晰
2、箭头函数不会创建自己的this,它只会从自己的作用域链的上一层继承this。
3、箭头函数继承而来的this指向永远不变
4、.call()/.apply()/.bind()无法改变箭头函数中this的指向
5、箭头函数不能作为构造函数使用
6、箭头函数没有自己的arguments,可以在箭头函数中使用rest参数代替arguments对象,来访问箭头函数的参数列表
7、箭头函数没有原型prototype
8、箭头函数不能用作Generator函数,不能使用yeild关键字
9、箭头函数不具有super,不具有new.target
\3. ES6新特性
1、let( let 允许创建块级作用域(最靠近的一个花括号内有效),不具备变量提升,不允许重复声明: )、const( const 允许创建块级作用域(最靠近的一个花括号内有效)、变量声明不提升、const 在声明时必须被赋值、声明时大写变量(默认规则): )、block作用域
2、箭头函数 ES6 中,箭头函数就是函数的一种简写形式,使用括号包裹参数,跟随一个 =>,紧接着是函数体:
3、函数默认参数值
ES6 中允许你对函数参数设置默认值:
4、对象超类
ES6 允许在对象中使用 super 方法:
5、Map VS WeakMap
ES6 中两种新的数据结构集:Map 和 WeakMap。事实上每个对象都可以看作是一个 Map。
一个对象由多个 key-val 对构成,在 Map 中,任何类型都可以作为对象的 key,如:
6、类
ES6 中有 class 语法。值得注意是,这里的 class 不是新的对象继承模型,它只是原型链的语法糖表现形式。
函数中使用 static 关键词定义构造函数的的方法和属性:
\4. Var let const 的区别
共同点:都能声明变量
不同点:var 在ECMAScript 的所有版本中都可以使用,而const和let只能在ECMAScript6【ES2015】及更晚中使用
var let const
作用域 函数作用域 块作用域 块作用域
声明提升 能 不能 不能
重复声明 能 不能 不能
全局声明时为window对象的属性 是 不是 不是
var
ECMAScript6 增加了let 和 const 之后要尽可能少使用var。因为let 和 const 申明的变量有了更加明确的作用域、声明位置以及不变的值。
优先使用const来声明变量,只在提前知道未来会修改时,再使用let。
let
因为let作用域为块作用域!!!!【得要时刻记住这一点】
不能进行条件式声明
for循环使用let来声明迭代变量不会导致迭代变量外渗透。
const
声明时得直接初始化变量,且不能修改const声明的变量的值
该限制只适用于它指向的变量的引用,如果它一个对象的,则可以修改这个对象的内部的属性。
\5. 实现继承的几种方式
原型链继承
父类的实例作为子类的原型
function Woman(){
}
Woman.prototype= new People();
Woman.prototype.name = 'haixia';
let womanObj = new Woman();
1
2
3
4
5
优点:
简单易于实现,父类的新增的实例与属性子类都能访问
缺点:
可以在子类中增加实例属性,如果要新增加原型属性和方法需要在new 父类构造函数的后面
无法实现多继承
创建子类实例时,不能向父类构造函数中传参数
借用构造函数继承(伪造对象、经典继承)
复制父类的实例属性给子类
function Woman(name){
//继承了People
People.call(this); //People.call(this,'wangxiaoxia');
this.name = name || 'renbo'
}
let womanObj = new Woman();
1
2
3
4
5
6
优点:
解决了子类构造函数向父类构造函数中传递参数
可以实现多继承(call或者apply多个父类)
缺点:
方法都在构造函数中定义,无法复用
不能继承原型属性/方法,只能继承父类的实例属性和方法
实例继承(原型式继承)
function Wonman(name){
let instance = new People();
instance.name = name || 'wangxiaoxia';
return instance;
}
let wonmanObj = new Wonman();
1
2
3
4
5
6
优点:
不限制调用方式
简单,易实现
缺点:不能多次继承
\6. Null 和 undefined 的区别
undefined和null的区别:. ● undefined 表示一个变量没有被声明,或者被声明了但没有被赋值(未初始化),一个没有传入实参的形参变量的值为undefined,如果一个函数什么都不返回,则该函数默认返回undefined。. null 则表示"什么都没有",即"空值"。. ● Javascript将未赋值的变量默认值设为 undefined ;Javascript从来不会将变量设为 null 。. 它是用来让程序员表明某个用var声明的变量时没有值的;
\7. Call bind apply的区别
apply方法
apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入,且当第一个参数为null、undefined的时候,默认指向window(在浏览器中),使用apply方法改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次。
call方法
call方法的第一个参数也是this的指向,后面传入的是一个参数列表(注意和apply传参的区别)。当一个参数为null或undefined的时候,表示指向window(在浏览器中),和apply一样,call也只是临时改变一次this指向,并立即执行。
bind方法
bind方法和call很相似,第一参数也是this的指向,后面传入的也是一个参数列表(但是这个参数列表可以分多次传入,call则必须一次性传入所有参数),但是它改变this指向后不会立即执行,而是返回一个永久改变this指向的函数。
\8. 前端缓存的理解 或者 前端数据持久化的理解
前端缓存分为HTTP缓存和浏览器缓存
其中HTTP缓存是在HTTP请求传输时用到的缓存,主要在服务器代码上设置;而浏览器缓存则主要由前端开发在前端js上进行设置。
缓存可以说是性能优化中简单高效的一种优化方式了。一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。
对于一个数据请求来说,可以分为发起网络请求、后端处理、浏览器响应三个步骤。浏览器缓存可以帮助我们在第一和第三步骤中优化性能。比如说直接使用缓存而不发起请求,或者发起了请求但后端存储的数据和前端一致,那么就没有必要再将数据回传回来,这样就减少了响应数据。
强制缓存就是向浏览器缓存查找该请求结果,并根据该结果的缓存规则来决定是否使用该缓存结果的过程,强制缓存的情况主要有三种,如下:
①不存在该缓存结果和缓存标识,强制缓存失效,则直接向服务器发起请求
②存在该缓存结果和缓存标识,但该结果已失效,强制缓存失效,则使用协商缓存
③存在该缓存结果和缓存标识,且该结果尚未失效,强制缓存生效,直接返回该结果
协商缓存就是强制缓存失效后,浏览器携带缓存标识向服务器发起请求,由服务器根据缓存标识决定是否使用缓存的过程
①协商缓存生效,返回304
②协商缓存失效,返回200和请求结果
\9. 防抖和节流
防抖(debounce)
所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
非立即执行版的意思是触发事件后函数不会立即执行,而是在 n 秒后执行,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
立即执行版的意思是触发事件后函数会立即执行,然后 n 秒内不触发事件才能继续执行函数的效果。
节流(throttle)
所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。
对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版
\10. 闭包
1、变量作用域
要理解闭包,首先要理解 JavasSript 的特殊的变量作用域。
变量的作用域无非就两种:全局变量和局部变量。
JavasSript 语言的特别之处就在于:函数内部可以直接读取全局变量,但是在函数外部无法读取函数内部的局部变量。
注意点:在函数内部声明变量的时候,一定要使用 var 命令。如果不用的话,你实际上声明的是一个全局变量!
2、如何从外部读取函数内部的局部变量?
出于种种原因,我们有时候需要获取到函数内部的局部变量。但是,上面已经说过了,正常情况下,这是办不到的!只有通过变通的方法才能实现。
那就是在函数内部,再定义一个函数。
function f1(){
var n=999;
function f2(){
alert(n); // 999
}
}
1
2
3
4
5
6
在上面的代码中,函数 f2 就被包括在函数 f1 内部,这时 f1 内部的所有局部变量,对 f2 都是可见的。但是反过来就不行,f2 内部的局部变量,对 f1 就是不可见的。
这就是 JavasSript 语言特有的"链式作用域"结构(chain scope),
子对象会一级一级地向上寻找所有父对象的变量。所以,父对象的所有变量,对子对象都是可见的,反之则不成立。
既然 f2 可以读取 f1 中的局部变量,那么只要把 f2 作为返回值,我们不就可以在 f1 外部读取它的内部变量了吗!
3、闭包的概念
上面代码中的 f2 函数,就是闭包。
各种专业文献的闭包定义都非常抽象,我的理解是: 闭包就是能够读取其他函数内部变量的函数。
由于在 JavaScript 中,只有函数内部的子函数才能读取局部变量,所以说,闭包可以简单理解成"定义在一个函数内部的函数"。
所以,在本质上,闭包是将函数内部和函数外部连接起来的桥梁。
4、闭包的用途
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中,不会在 f1 调用后被自动清除。
为什么会这样呢?原因就在于 f1 是 f2 的父函数,而 f2 被赋给了一个全局变量,这导致 f2 始终在内存中,而 f2 的存在依赖于 f1,因此 f1 也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
这段代码中另一个值得注意的地方,就是 “nAdd=function(){n+=1}” 这一行,首先在 nAdd 前面没有使用 var 关键字,因此 nAdd 是一个全局变量,而不是局部变量。其次,nAdd 的值是一个匿名函数(anonymous function),而这个匿名函数本身也是一个闭包,所以 nAdd 相当于是一个 setter,可以在函数外部对函数内部的局部变量进行操作。
5、使用闭包的注意点
(1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
(2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值
\11. 数组去重
一、利用ES6 Set去重(ES6中最常用)
function unique (arr) {
return Array.from(new Set(arr))
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {}, {}]
1
2
3
4
5
6
不考虑兼容性,这种去重的方法代码最少。这种方法还无法去掉“{}”空对象,后面的高阶方法会添加去掉重复“{}”的方法。
二、利用for嵌套for,然后splice去重(ES5中最常用)
function unique(arr){
for(var i=0; i<arr.length; i++){
for(var j=i+1; j<arr.length; j++){
if(arr[i]==arr[j]){ //第一个等同于第二个,splice方法删除第二个
arr.splice(j,1);
j--;
}
}
}
return arr;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", 15, false, undefined, NaN, NaN, "NaN", "a", {…}, {…}] //NaN和{}没有去重,两个null直接消失了
1
2
3
4
5
6
7
8
9
10
11
12
13
14
双层循环,外层循环元素,内层循环时比较值。值相同时,则删去这个值。
想快速学习更多常用的ES6语法,可以看我之前的文章《学习ES6笔记──工作中常用到的ES6语法》。
三、利用indexOf去重
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array = [];
for (var i = 0; i < arr.length; i++) {
if (array .indexOf(arr[i]) === -1) {
array .push(arr[i])
}
}
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [1, "true", true, 15, false, undefined, null, NaN, NaN, "NaN", 0, "a", {…}, {…}] //NaN、{}没有去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
新建一个空的结果数组,for 循环原数组,判断结果数组是否存在当前元素,如果有相同的值则跳过,不相同则push进数组。
四、利用sort()
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return;
}
arr = arr.sort()
var arrry= [arr[0]];
for (var i = 1; i < arr.length; i++) {
if (arr[i] !== arr[i-1]) {
arrry.push(arr[i]);
}
}
return arrry;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
// [0, 1, 15, "NaN", NaN, NaN, {…}, {…}, "a", false, null, true, "true", undefined] //NaN、{}没有去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
利用sort()排序方法,然后根据排序后的结果进行遍历及相邻元素比对。
六、利用includes
function unique(arr) {
if (!Array.isArray(arr)) {
console.log('type error!')
return
}
var array =[];
for(var i = 0; i < arr.length; i++) {
if( !array.includes( arr[i]) ) {//includes 检测数组是否有某个值
array.push(arr[i]);
}
}
return array
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}, {…}] //{}没有去重
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
七、利用hasOwnProperty
function unique(arr) {
var obj = {};
return arr.filter(function(item, index, arr){
return obj.hasOwnProperty(typeof item + item) ? false : (obj[typeof item + item] = true)
})
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, NaN, "NaN", 0, "a", {…}] //所有的都去重了
1
2
3
4
5
6
7
8
9
利用hasOwnProperty 判断是否存在对象属性
八、利用filter
function unique(arr) {
return arr.filter(function(item, index, arr) {
//当前元素,在原始数组中的第一个索引==当前索引值,否则返回当前元素
return arr.indexOf(item, 0) === index;
});
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "true", true, 15, false, undefined, null, "NaN", 0, "a", {…}, {…}]
1
2
3
4
5
6
7
8
9
九、利用递归去重
function unique(arr) {
var array= arr;
var len = array.length;
array.sort(function(a,b){ //排序后更加方便去重
return a - b;
})
function loop(index){
if(index >= 1){
if(array[index] === array[index-1]){
array.splice(index,1);
}
loop(index - 1); //递归loop,然后数组去重
}
}
loop(len-1);
return array;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
十、利用Map数据结构去重
function arrayNonRepeatfy(arr) {
let map = new Map();
let array = new Array(); // 数组用于返回结果
for (let i = 0; i < arr.length; i++) {
if(map .has(arr[i])) { // 如果有该key值
map .set(arr[i], true);
} else {
map .set(arr[i], false); // 如果没有该key值
array .push(arr[i]);
}
}
return array ;
}
var arr = [1,1,'true','true',true,true,15,15,false,false, undefined,undefined, null,null, NaN, NaN,'NaN', 0, 0, 'a', 'a',{},{}];
console.log(unique(arr))
//[1, "a", "true", true, 15, false, 1, {…}, null, NaN, NaN, "NaN", 0, "a", {…}, undefined]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
创建一个空Map数据结构,遍历需要去重的数组,把数组的每一个元素作为key存到Map中。由于Map中不会出现相同的key值,所以最终得到的就是去重后的结果
\12. 深浅拷贝
深拷贝和浅拷贝是只针对Object和Array这样的引用数据类型的。
深拷贝和浅拷贝的示意图大致如下:
示意图
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是共享同一块内存。但深拷贝会另外创造一个一模一样的对象,新对象跟原对象不共享内存,修改新对象不会改到原对象。
\13. 原型链
那什么是原型链呢?
简单理解就是原型组成的链,对象的__proto__它的是原型,而原型也是一个对象,也有__proto__属性,原型的__proto__又是原型的原型,就这样可以一直通过__proto__想上找,这就是原型链,当向上找找到Object的原型的时候,这条原型链就算到头了。
原型对象和实例之间有什么作用呢?
通过一个构造函数创建出来的多个实例,如果都要添加一个方法,给每个实例去添加并不是一个明智的选择。这时就该用上原型了。
在实例的原型上添加一个方法,这个原型的所有实例便都有了这个方法。
prototype:
prototype属性,它是函数所独有的,它是从一个函数指向一个对象。它的含义是函数的原型对象,也就是这个函数(其实所有函数都可以作为构造函数)所创建的实例的原型对象; 这个属性是一个指针,指向一个对象,这个对象的用途就是包含所有实例共享的属性和方法(我们把这个对象叫做原型对象);
proto:
proto 是原型链查询中实际用到的,它总是指向 prototype,换句话说就是指向构造函数的原型对象,它是对象独有的。注意,为什么Foo构造也有这个属性呢,因为再js的宇宙里万物皆对象,包括函数
constructor:
我们看到途中最中间灰色模块有一个constructor属性,这个又是做什么用的呢?**
**
每个函数都有一个原型对象,该原型对象有一个constructor属性,指向创建对象的函数本身。
此外,我们还可以使用constructor属性,所有的实例对象都可以访问constructor属性,constructor属性是创建实例对象的函数的引用。我们可以使用constructor属性验证实例的原型类型(与操作符instanceof非常类似)。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tbxes1Jd-1646559635648)(D:\Typora\原型链.png)]
\14. Require 和 import
require和import的区别
1.import在代码编译时被加载,所以必须放在文件开头,require在代码运行时被加载,所以require理论上可以运用在代码的任何地方,所以import性能更好。
2.import引入的对象被修改时,源对象也会被修改,相当于浅拷贝,require引入的对象被修改时,源对象不会被修改,官网称值拷贝,我们可以理解为深拷贝。
3.import有利于tree-shaking(移除JavaScript上下文中未引用的代码),require对tree-shaking不友好。 4.import会触发代码分割(把代码分离到不同的bundle中,然后可以按需加载或者并行加载这些文件),require不会触发。
5.import是es6的一个语法标准,如果要兼容浏览器的话必须转化成es5的语法,require 是 AMD规范引入方式。
目前所有的引擎都还没有实现import,import最终都会被转码为require,在webpack打包中,import和require都会变为_webpack_require_。
CSS
\1. 常见的块级元素和行内块元素,以及它们有何不同
块级元素和内联元素的区别:
1.块级元素,宽度默认是它容器的100%,各占据一行,垂直方向排列;内联元素,都是同一行,水平方向排列;
2.块级元素,能容纳其他块元素或者内联元素;内联元素,只能容纳文本或其他内联元素;
3.块级元素中height,line-height以及margin和padding都可以控制;行内元素设置width无效,height无效(可以设置line-height),margin上下无效,padding上下无效
2.行内元素和块级元素有哪些
常见的内联元素:
a - 锚点
select - 项目选择
span - 常用内联容器,定义文本内区块
strong - 粗体强调
img - 图片
input - 输入框
label - 表格标签
textarea - 多行文本输入框
br - 换行
常见的块级元素
div
ul ,li 非排序列表
form - 交互表单
table - 表格
h1 -h6 标题
hr - 水平分隔线
p - 段落
dl -dt-dd - 定义列表
address - 地址
blockquote - 块引用
fieldset - form控制组
\2. 常见选择器
标签选择器
HTML标签名称作为选择器
//语法
标签名 {
属性1:属性值1;
属性2:属性值2;
属性3:属性值3;
...
}
1
2
3
4
5
6
7
类选择器
//语法
.类名 {
属性1.属性值1;
属性2.属性值2;
属性3.属性值3;
...
}
1
2
3
4
5
6
7
id选择器
id名 {
属性1:属性值1;
...
}
1
2
3
4
口诀:样式#定义,结构id调用,只能调用一次,别人切勿使用
通配符选择器
选取页面中所有元素(标签)
- {
属性名1: 属性名1;
...
}
1
2
3
4
CSS的复合选择器
建立在基础选择器之上,对基础选择器进行组合形成的
后代选择器/包含选择器(重要)
元素2只要包含在元素1里面即可,无论是儿子还是孙子
1
2
3
4
子选择器(重要)
只能选择某元素的最近一级的子元素
元素1>元素2 {样式声明}
1
并集选择器(重要)
可以选择多组标签为他们定义相同的样式,通常用于集体声明
标签1,标签2 {样式声明}
1
约定语法规范:并集选择器喜欢竖着写
伪类选择器
可以为某些选择器添加一些特殊的效果
链接伪类选择器
写的时候按照顺序来
a:link #选择未被访问的链接
a:visited #选择所有已经被访问过的链接
a:hover #选择鼠标指针位于其上的链接
a:active #选择活动链接(鼠标按下未弹起的链接)
1
2
3
4
5
:foucus伪类选择器
用于选取获得焦点的表单元素
input:foucus {
background-color: red;
}
1
2
3
\3. px em 和 rem的区别
一、px是固定的像素,一旦设置了就无法因为适应页面大小而改变。
二、em和rem相对于px更具有灵活性,他们是相对长度单位,意思是长度不是定死了的,更适用于响应式布局。
三、em是相对于其父元素来设置字体大小的,一般都是以的“font-size”为基准。这样就会存在一个问题,进行任何元素设置,都有可能需要知道他父元素的大小。而Rem是相对于根元素,这样就意味着,我们只需要在根元素确定一个参考值
总之:对于em和rem的区别一句话概括:
em相对于父元素,rem相对于根元素。
\4. 水平垂直居中的几种方法
1 使用flex布局
利用flex的alignItems:center垂直居中,justifycontent:center水平居中
2 利用相对定位和绝对定位的margin:auto
相对定位下,使用绝对定位将上下左右都设置为0,再设置margin:auto即可实现居中
3 利用相对定位和绝对定位,再加上外边距和平移的配合
相对定位下,使用绝对定位,利用margin偏移外容器的50%,再利用translate平移回补自身宽高的50%即可
4 利用textAlign和verticalAlign
利用textAlign:center实现行内元素的水平居中,再利用verticalAlign:middle实现行内元素的垂直居中,前提是要先加上伪元素并给设置高度为100%,用过elementUI的可以去看看其消息弹窗居中实现方式就是如此
5 其他
上面都是在未知外容器和自身宽高下实现水平垂直居中的,如果已知其宽高,可以有更多种简单的方式实现居中,其原理无非是利用绝对定位的top/left偏移、margin偏移、padding填充,在此就不分析了。还有就是单纯文字的居中利用lineHeight和textAlign即可实现。
\5. 盒模型的理解
盒模型包括margin、border、padding、content四个部分,主要的设置属性是margin、border、padding。
盒子模型又分为两种W3C和IE盒子。
W3C的元素宽度=content的宽度
IE的元素宽度=content+padding+border
盒模型就是用来做容器,为了把内容打包和整理,为了不让页面显得杂乱无章。一个好的包装能够给用户不一样地体验。并且如果没有好的包装,再好的内容也不会也看下去的欲望。这就像一个干净整洁的桌面和堆满杂物的桌面给人的观感效果。
所以,合理灵活的应用好盒模型是前端的必要基础!!!
\6. Flex布局
一、Flex 布局是什么?
Flex 是 Flexible Box 的缩写,意为"弹性布局",用来为盒状模型提供最大的灵活性。
任何一个容器都可以指定为 Flex 布局。
注意,设为 Flex 布局以后,子元素的float、clear和vertical-align属性将失效。
二、基本概念
采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。
三、容器的属性
以下6个属性设置在容器上。
flex-direction
flex-wrap
flex-flow
justify-content
align-items
align-content
\7. 怎么解决浮动中塌陷的问题
第一种:开启BFC
根据W3C的标准,在页面中元素都有一个隐含的属性 Block Formatting Context,简称BFC,默认是关闭的;
开启元素BFC后,元素将会具有以下特性:
父元素的垂直外边距不会和子元素重叠
开启BFC的元素不会被浮动元素所覆盖
开启BFC的元素可以包含浮动元素
开启BFC的方法:。
** overflow【常见,副作用最小】**
overflow设置为非visible的值。推荐使用hidden(副作用最小)
a) auto (溢出显示滚动条)
b) scroll (默认就显示滚动条)
c) hidden (溢出隐藏)[常用]
注:但在IE6及以下的浏览器中不支持BFC,所以使用这种方式不兼容IE6。在IE6中有类似BFC的隐含属性 hasLayout,开启方式很多,推荐使用zoom:1
第二种:在浮动元素后添加元素,并设置其clear属性
第三种:br元素的clear属性
br元素本身没有高度,所以也就不会存在IE低版本下最小高度问题。只需要在浮动元素下添加一句 :
※第四种:after伪类【各大公司推荐】
可以通过after伪类向元素的最后添加一个空白的块元素,然后对其清除浮动,和第二种方法原理相同,可达到相同的效果,而且不会在页面中添加多余的div,这是最推荐的方式,几乎没有副作用
\8. CSS3新特性
CSS3 是 CSS 规范的最新版本,在 CSS2.1 的基础上增加了很多强大的新功能,以帮助开发人员解决一些实际面临的问题,并且不再需要非语义标签、复杂的 JavaScript 脚本以及图片。 例如,CSS3 支持圆角、多背景、透明度、阴影、动画、图表等功能。 CSS1 和 CSS2.1 都是单一的规范,其中 CSS1 主要定义了网页对象的基本样式,如字体、颜色、背景、边框等,CSS2 添加了高级概念,如浮动、定位、高级选择器(如子选择器、相邻选择器和通用选择器等)。 整个 CSS3 的规范发布不会因为部分存在争论而影响其他模块的推进。 对于浏览器来说,可以根据需要,决定哪些 CSS 功能被支持。
\9. 前端常见的布局方式
一、静态布局
静态布局是最为原始的布局方式,没有什么技术性可言,往往是计算机行业刚刚入门的小白使用的布局方式。制作的网页上的元素尺寸一律以px为单位
布局特点: 页面上的布局是按最初写代码时候的布局方式进行布局的,常规的pc网站是进行设置了宽度值进行布局的,不会随着pc端的屏幕的大小而变化。
优点: 这种布局方式不管是对资深的前端开发工程师还是刚入门的小白来说都是最简单的,最让人容易以接受、学习的,没有我们所说的兼容性的问题。这种布局方式大多用在门户网站和企业的官网上,这些官网的设备的尺寸是固定的,这种布局方式往往是最简单的方法。
缺点: 不会随着pc端的屏幕大小而变化。
二、弹性布局(flexbox)
弹性布局可以简便、完整、响应的实现各种页面上的布局。与静态不同的是,使用em或rem单位(lem=16px,1rem=10px)进行相对布局,相对使用百分比更加方便、灵活,相应同时支持浏览器的字体大小调整和缩放的等正常显示。
优点:
1.适应性强,在做多种不同的屏幕分辨率不同的界面是非常使用。
2.随意按照宽度、比例划分元素的宽高。
3.可以轻松的改变元素的显示顺序。
4.网页布局实现快捷,维护起来更加容易。
如果做移动端时,如果客户对细微的之处的要求不高,使用弹性布局进行制作是最好的选择,一份css+一份js调节font-size搞定。
缺点: 浏览器兼容性较差,只能兼容到IE9及以上。
三、自适应布局(bootstrap)
自适应布局分别为不同屏幕不同分辨率定义布局,即是创建多个静态页面,每个静态页面对应一个屏幕分辨率的一个范围内。在改变不同的屏幕分辨率可以切换到不同的静态布局上,但是布局中的元素位置会发生改变,但是在每个静态布局中,页面中的元素不会随着窗口大小的调整发生变化。使用 @media 媒体查询给不同尺寸和介质的设备切换不同的样式。在优秀的响应范围设计下可以给适配范围内的设备最好的体验,在同一个设备下实际还是固定的布局。
优点:
1.对网站的复杂程度兼容性更大;
2.对开发工程师来说制作的成本代价更低;
3.代码执行效果更高效;
4.测试时更加容易,运营相对更加精准。
缺点: 在现如今的移动端设计百花齐放的时期之下,同一个网站往往需要为不同的设备制作不同的页面,不但会增加开发成本,还会因为客户的需求改变时,可能会改动多套代码、流程相比较来说较繁琐。
四、流式布局(fluid)
流式布局的布局方式是页面的元素的宽度按照屏幕的分辨率进行适配的调整,但是整体布局不变,也称之为栅栏系统。使用%百分比定义宽度,高度大都是用px来固定住,可以根据可视区域 (viewport) 和父元素的实时尺寸进行调整,尽可能的适应各种分辨率。往往配合 max-width/min-width 等属性控制尺寸流动范围以免过大或者过小影响阅读。
缺点: 屏幕大小变化时,页面元素也随之变化但是布局不变。这就会因为如果屏幕太大或太小都会布局时元素无法正常显示。
五、响应式布局
响应式布局是css3增加的新布局方式,该布局方式2010年提出来的一个概念,说白了就是一个网站能够兼容多个终端——而不是为每个终端做一个特定的版本。这个概念是为解决移动互联网浏览而诞生的。响应式布局可以为不同终端的用户提供更加舒适的界面和更好的用户体验,而且随着目前大屏幕移动设备的普及,用“大势所趋”来形容也不为过。响应式几乎成为优秀页面布局的标准。
设计方法: 媒体查询+流式布局。通常使用@media媒体查询,和网格系统配合相对布局单位进行布局,实际上说白了就是综合响应式等技术通过css给单一网页不同设备分辨率返回不式时的技术。
优点: 适应pc端和移动端,如果有足够的耐心,页面效果会很完美。
缺点:
1.只能适应主流的宽高;
2.如果匹配足够多的设备屏幕的大小,对于工程师来说工作量不小,设计更需要多个版本,工作量增大。
六、浮动布局
浮动布局进行调用浮动属性改变页面中元素的位置,浮动布局应该是目前各大网站用的最多的一种布局方式了,但是也特别复杂。浮动元素是脱离文档流的,但不脱离文本流。浮动元素有左浮动(float : left)和右浮动(float : right)两种
优点: 兼容性比较好
缺点: 浮动带来的影响比较多,页面宽度不够的时候会影响布局。
七、定位布局
定位布局时利用position属性控制页面元素设置一些不规则布局。
HTML
\1. HTML的语义化标签
\2. 前端优化的解决方案
我们的优化原则有以下几个:
能缓存的,尽量强缓存。
引入外部资源时不要出现超时、404的状况。
减少HTTP请求数。
合理设置cookie的大小以及过期时间。
合理利用懒加载
网页内容的优化
1、懒加载数据。
首先根据标签的left和top属性判断是否显示在了屏幕中(如果显示在屏幕中,其left和top属性值应该是在0到窗口长宽之间)。
如果显示在屏幕中,则将src标签的内容替换为图片的url。
2、使用外部引入的css和js文件,并且引入的css和js越少越好(HTTP2.0不适用)。
这里可以使用webpack打包这些文件,也可以使用强缓存与协商缓存来缓存这些文件。
3、不要在中缩放图片。
img计算缩放也需要时间
4、避免重定向。
重定向会重新渲染网页。
5、尽量不要用iframe。
因为iframe会阻塞渲染。
6、使用base64编码将图片嵌入到样式表中,减少请求数(由于base64会比一般的图片大一点,只适用于那些体积比较小但是很常用的图片)。
7、使用雪碧图(精灵图):
通过使用background-position:-xpx -ypx;来调整图片的位置,不过HTTP2不适用,原因为HTTP2实际上是多路复用的,只用一个TCP连接,所以多个图片的请求也是在同一个TCP连接里,这样能省下非常多的请求时间,但坏处就是单连接开销很大,如果要传多个大文件,就很麻烦。
8、要有网站小图标favicon.ico。
如果没有小图标,会引起404,拖慢网页加载进度。
9、能使用jpeg就不要用png,能使用png8就不要用png24。
(1)色彩丰富的、大的图片切成jpg的;
(2)尺寸小的,色彩不丰富的和背景透明的切成gif或者png8的;
(3)半透明的切成png24。
10、使用canvas压缩图片。
css的优化
1、避免使用@import。
使用@import相当于将引入的css放在了页面底部,因为使用@import引用的文件只有在引用它的那个css文件被下载、解析之后,浏览器才会知道还有另外一个css需要下载,这时才去下载,然后下载后开始解析、构建render tree等一系列操作。因此使用@import会拖慢渲染的过程。
2、将样式表放在head中。
如果放在body中,可能出现在浏览器下载好css样式表之前,组件就已经加载好了的情况,这可能会导致重新渲染。
3、避免使用css表达式。
如:expression((new Date()).getHours()%2 ? “#B8D4FF” : “#F08A00” );
解析表达式和计算都需要时间。
JavaScript的优化
1、尽量减少DOM访问。
2、使用事件代理(减少DOM操作)。
3、把脚本放在底部(加载脚本时会阻塞页面渲染)。
4、合理使用节流函数和防抖函数。
使用CDN优化加载速度
CDN即内容分发网络。它依靠部署在各地的边缘服务器,通过中心平台的负载均衡、内容分发、调度等功能模块,使用户就近获取所需内容,降低网络拥塞,提高用户访问响应速度和命中率。
针对webpack打包优化
针对webpack打包优化主要是减少打包后的代码体积,主要的措施有:
1、进行tree-shaking
2、使用UglifyJS等插件压缩代码
3、分割代码、按需加载
我们可以使用webpack-bundle-analyzer这个插件来查看每部分代码的加载耗时,进而分析可以优化的地方
\3. HTML5新特性
html5总的来说比html4多了十个新特性,但其不支持ie8及ie8以下版本的浏览器
一、语义标签
二、增强型表单
三、视频和音频
四、Canvas绘图
五、SVG绘图
六、地理定位
七、拖放API
八、WebWorker
九、WebStorage
十、WebSocket
详细地址 https://www.cnblogs.com/binguo666/p/10928907.html
\4. 常见的浏览器兼容问题
1.不同浏览器的标签默认的外补丁(margin)和内补丁(padding)不同
解决方案:css里增加通配符*{margin:0;padding:0}
2.IE6双边距问题;在IE6中设置了float,同时又设置margin,就会出现边距问题
解决方案:设置display:inline;
3.当标签的高度设置小于10px,在IE6、IE7中会超出自己设置的高度
解决方案:设置display:inline;
4.图片默认有间距
解决方案:使用float为img布局
5.IE9以下浏览器不能使用opacity
解决方案:opacity:0.5;filter:alfha(opacity=50);filter:progid:DXlmageTransform.Microsoft.Alfha(style=0,opacity=50);
6.边距重叠问题;
解决方案: 当相邻两个元素都设置了margin边距时,margin将取最大值,舍弃最小值;
7.cursor:hand显示手型在safari上不支持
解决方案:统一使用cursor:pointer;
8.两个块级元素,父元素设置了overflow:auto;子元素设置了position:relative;且高度大于父元素,在IE6、IE7会被隐藏而不是溢出;
解决方案:父级元素设置position:relative
9.const问题
说明:Firefox下,可以使用const关键字来定义常量;IE下,只能使用var关键字来定义常量。
解决方法:统一使用var关键字来定义常量。
10.event.srcElement问题
问题说明:IE下,event对象有srcElement属性,但是没有target属性;Firefox下,event对象有target属性,但是没有srcElement属性。
解决方法:使用srcObj = event.srcElement?event.srcElement:event.target;
11.事件绑定
IE:dom.attachEvent();
其他浏览器:dom.addEventListener();
标准浏览器采用事件捕获的方式对应IE的事件冒泡机制(即标准由最外元素至最内元素或者IE由最内元素到最外元素)最后标准方亦觉得IE这方面的比较合理,所以便将事件冒泡纳入了标准,这也是addEventListener第三个参数的由来,而且事件冒泡作为了默认值。
12.操作tr的html
在ie9以下,不能操作tr的innerHTML
13.ajax略有不同
IE:ActiveXObject
其他:xmlHttpReuest
14.对象宽高赋值问题
问题说明:FireFox中类似obj.style.height = imgObj.height的语句无效。
CSS
1.cursor:hand VS cursor:pointerfirefox不支持hand,但ie支持pointer
解决方法: 统一使用pointer
- innerText在IE中能正常工作,但在FireFox中却不行.
需用textContent。
- CSS透明
IE:filter:progid:DXImageTransform.Microsoft.Alpha(style=0,opacity=60)。
FF:opacity:0.6。
- css中的width和padding
在IE7和FF中width宽度不包括padding,在Ie6中包括padding.
- FF和IEBOX模型解释不一致导致相差2px
详细地址 常见的浏览器兼容 - 知乎 (zhihu.com)
Vue
\1. Vuex
1.1 关于VueX
Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。
状态管理模式。
把组件的共享状态抽取出来,以一个全局单例模式管理。在这种模式下,我们的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为!这就是“状态管理模式”。
应用场景有:单页应用中,组件之间的数据状态。 应用实例: 1、购物车功能; 2、下单页面有选择优惠券按钮,点击进入优惠券页面,选择后返回到下单页,数据会绑定回来,显示已选择的优惠券; 3、登录状态等等
Vuex有哪几种属性?
有五种,分别是 State、 Getter、Mutation 、Action、 Module
Vuex的State特性
1、Vuex就是一个仓库,仓库里面放了很多对象。其中state就是数据源存放地,对应于一般Vue对象里面的data
2、state里面存放的数据是响应式的,Vue组件从store中读取数据,若是store中的数据发生改变,依赖这个数据的组件也会发生更新
3、它通过mapState把全局的 state 和 getters 映射到当前组件的 computed 计算属性中
Vuex的Getter特性
1、getters 可以对State进行计算操作,它就是Store的计算属性
2、虽然在组件内也可以做计算属性,但是getters 可以在多组件之间复用
3、如果一个状态只在一个组件内使用,是可以不用getters
Vuex的Mutation特性
Action 类似于 mutation,不同在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
Vuex的Module特性
Module 可以让每一个模块拥有自己的state、mutation、action、getters,使得结构非常清晰,方便管理。
使用Vuex的好处?
1、多层嵌套的组件、兄弟组件间的状态会更好管理维护。 2、缓存一些当前要使用请求远程或本地的数据集(刷新后会自己销毁)。 3、有了第二条,就可以减少向服务器的请求,节省资源。如果你的用户足够多,那么每多出一个请求,对公司来说,都是一大笔钱。 4、对开发者来说,如果你的项目足够复杂,团队的规模也不仅是一个人,数据集中处理更利于程序的稳定和维护
\2. 双向绑定的原理
MVC模式
以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新
MVVM模式
MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。
双向绑定原理
vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的
我们已经知道实现数据的双向绑定,首先要对数据进行劫持监听,所以我们需要设置一个监听器Observer,用来监听所有属性。如果属性发上变化了,就需要告诉订阅者Watcher看是否需要更新。因为订阅者是有很多个,所以我们需要有一个消息订阅器Dep来专门收集这些订阅者,然后在监听器Observer和订阅者Watcher之间进行统一管理的。接着,我们还需要有一个指令解析器Compile,对每个节点元素进行扫描和解析,将相关指令(如v-model,v-on)对应初始化成一个订阅者Watcher,并替换模板数据或者绑定相应的函数,此时当订阅者Watcher接收到相应属性的变化,就会执行对应的更新函数,从而更新视图。
\3. 组件间传递数据
父组件向子组件传递数据,使用props属性;子组件向父组件中传递数据,在子组件中使用$emit派发事件,父组件中使用v-on
监听事件;缺点:组件嵌套层次多的话,传递数据比较麻烦。
祖先组件通过依赖注入(inject / provide)的方式,向其所有子孙后代传递数据;缺点:无法监听数据修改的来源,不支持响应式。
通过属性$root / $parent / $children /
ref,访问根组件、父级组件、子组件中的数据;缺点:要求组件之间要有传递性。
通过事件总线(event bus)的方式,可以实现任意两个组件间进行数据传递;缺点:不支持响应式,这个概念是vue1.0版本中的,现在已经废弃。
通过 VueJs 的状态管理模式 Vuex,实现多个组件进行数据共享,推荐使用这种方式进行项目中各组件间的数据传递。
\4. Vue项目优化
Vue 项目性能优化实践 - 知乎 (zhihu.com)
\5. MVVM和MVC
MVC模式
以往的MVC模式是单向绑定,即Model绑定到View,当我们用JavaScript代码更新Model时,View就会自动更新
MVVM模式
MVVM模式就是Model–View–ViewModel模式。它实现了View的变动,自动反映在 ViewModel,反之亦然。对于双向绑定的理解,就是用户更新了View,Model的数据也自动被更新了,这种情况就是双向绑定。再说细点,就是在单向绑定的基础上给可输入元素input、textare等添加了change(input)事件,(change事件触发,View的状态就被更新了)来动态修改model。
在MVVM框架下视图和模型是不能直接通信的,只能通过ViewModel进行交互,它能够监听到数据的变化,然后通知视图进行自动更新,而当用户操作视图时,VM也能监听到视图的变化,然后通知数据做相应改动,这实际上就实现了数据的双向绑定。并且V和VM可以进行通信。
MVVM模式的优点:
低耦合:View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,当View变化的时候Model可以不变,当Model变化的时候View也可以不变。
可重用性: 可以把一些视图逻辑放在一个ViewModel里面,让很多View重用这段视图逻辑。
独立开发: 开发人员可以专注于业务逻辑和数据的开发,设计人员可以专注于页面的设计。
mvc与mvvm的区别:
MVC和MVVM的区别并不是VM完全取代了C,ViewModel存在目的在于抽离Controller中展示的业务逻辑,而不是替代Controller,其它视图操作业务等还是应该放在Controller中实现。也就是说MVVM实现的是业务逻辑组件的重用。
- MVC中Controller演变成MVVM中的ViewModel
-MVVM通过数据来显示视图层而不是节点操作
-MVVM主要解决了MVC中大量的dom操作使页面渲染性能降低,加载速度变慢,影响用户体验等问题。
\6. Computed和Watch
computed 和 watch 区分使用场景
computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
运用场景:
当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。
\7. V-for 和 v-if同时使用的问题
v-for 遍历必须为 item 添加 key,且避免同时使用 v-if
(1)v-for 遍历必须为 item 添加 key
在列表数据进行遍历渲染时,需要为每一项 item 设置唯一 key 值,方便 Vue.js 内部机制精准找到该条列表数据。当 state 更新时,新的状态值和旧的状态值对比,较快地定位到 diff 。
(2)v-for 遍历避免同时使用 v-if
v-for 比 v-if 优先级高,如果每一次都需要遍历整个数组,将会影响速度,尤其是当之需要渲染很小一部分的时候,必要情况下应该替换成 computed 属性
\8. 什么时候使用$.nextTick()
你在Vue生命周期的created()钩子函数进行的DOM操作一定要放在Vue.nextTick()的回调函数中。原因是什么呢,原因是在created()钩子函数执行的时候DOM 其实并未进行任何渲染,而此时进行DOM操作无异于徒劳,所以此处一定要将DOM操作的js代码放进Vue.nextTick()的回调函数中。与之对应的就是mounted钩子函数,因为该钩子函数执行时所有的DOM挂载和渲染都已完成,此时在该钩子函数中进行任何DOM操作都不会有问题 。
在数据变化后要执行的某个操作,而这个操作需要使用随数据改变而改变的DOM结构的时候,这个操作都应该放进Vue.nextTick()的回调函数中。
原因是,Vue是异步执行dom更新的,一旦观察到数据变化,Vue就会开启一个队列,然后把在同一个事件循环 (event loop) 当中观察到数据变化的 watcher 推送进这个队列。如果这个watcher被触发多次,只会被推送到队列一次。这种缓冲行为可以有效的去掉重复数据造成的不必要的计算和DOm操作。而在下一个事件循环时,Vue会清空队列,并进行必要的DOM更新。
当你设置 vm.someData = ‘new value’,DOM 并不会马上更新,而是在异步队列被清除,也就是下一个事件循环开始时执行更新时才会进行必要的DOM更新。如果此时你想要根据更新的 DOM 状态去做某些事情,就会出现问题。。为了在数据变化之后等待 Vue 完成更新 DOM ,可以在数据变化之后立即使用 Vue.nextTick(callback) 。这样回调函数在 DOM 更新完成后就会调用。
\9. 路由的原理
- 何为前端路由?
路由(Router)这个概念最先是后端出现的,是用来跟后端服务器进行交互的一种方式,通过不同的路径,来请求不同的资源,请求不同的页面是路由的其中一种功能。
前端随着 ajax 的流行,数据请求可以在不刷新浏览器的情况下进行。异步交互体验中最盛行的就是 SPA —— 单页应用。单页应用不仅仅是在页面交互时无刷新的,连页面跳转都是无刷新的,为了实现单页应用,所以就有了前端路由。
- 前端Router基本功能
一个基本的前端路由至少应该提供以下功能:
前端Router可以控制浏览器的 history,使的浏览器不会在 URL 发生改变时刷新整个页面。
前端Router需要维护一个 URL 历史栈,通过这个栈可以返回之前页面,进入下一个页面。
前端路由实现原理就是匹配不同的 url 路径,进行解析,然后动态的渲染出区域 html 内容。但是这样存在一个问题,就是 url 每次变化的时候,都会造成页面的刷新。那解决问题的思路便是在改变 url 的情况下,保证页面的不刷新。目前 Router有两种实现方式 History 和 hash。
History 和 Hash 对比
hash 使用 # 后面的内容模拟一个完整路径,不太美观。
hash 在请求时不会发送给服务器,用户手动刷新页面,后端接受到了也是同一个地址。
History 直接修改浏览器 URL,用户手动刷新页面,后端接受到是不同的地址,需要后端做处理跳转到统一的html页面
\10. 常用的事件修饰符
①.stop:阻止冒泡
–对于嵌套的两级,如果子父级元素都存在click事件,点击子级元素会触发父级元素的事件;如果子级元素设置@click.stop的话就不会触发父级的click事件
②.prevent:阻止默认行为
–对于如<a href=“www.baidu.com” @click.prevent=“linkMethod”>百度自带事件的,添加prevent事件后,href跳转路径将不会触发
③.self:仅绑定元素自身触发,防止事件冒泡
–对于嵌套的两级,如果子父级元素都存在click事件,点击子级元素会触发父级元素的事件;如果父级元素设置@click.self的话就不会被子级元素的click事件影响
④.once: 事件只触发一次(常用表单提交)
⑤.passive: 滚动事件的默认行为 (即滚动行为) 将会立即触发,不能和.prevent 一起使用,浏览器内核线程在每个事件执行时查询prevent,造成卡顿,使用passive将会跳过内核线程查询,进而提升流畅度
⑥.capture: 对于冒泡事件,且存在多个冒泡事件时,存在该修饰符的会优先执行,如果有多个,则从外到内执行
⑦.native: 将vue组件转换为一个普通的HTML标签,如果该修饰符用在普通html标签上是不起任何作用的
浏览器原理相关
\1. 常用HTTP请求
1、GET方法
GET方法用于使用给定的URI从给定服务器中检索信息,即从指定资源中请求数据。使用GET方法的请求应该只是检索数据,并且不应对数据产生其他影响。
在GET请求的URL中发送查询字符串(名称/值对),需要这样写:
/test/demo_form.php?name1=value1&name2=value2
1
说明:
GET请求是可以缓存的,我们可以从浏览器历史记录中查找到GET请求,还可以把它收藏到书签中;且GET请求有长度限制,仅用于请求数据(不修改)。
注:因GET请求的不安全性,在处理敏感数据时,绝不可以使用GET请求。
2、POST方法
POST方法用于将数据发送到服务器以创建或更新资源,它要求服务器确认请求中包含的内容作为由URI区分的Web资源的另一个下属。
POST请求永远不会被缓存,且对数据长度没有限制;我们无法从浏览器历史记录中查找到POST请求。
3、HEAD方法
HEAD方法与GET方法相同,但没有响应体,仅传输状态行和标题部分。这对于恢复相应头部编写的元数据非常有用,而无需传输整个内容。
4、PUT方法
PUT方法用于将数据发送到服务器以创建或更新资源,它可以用上传的内容替换目标资源中的所有当前内容。
它会将包含的元素放在所提供的URI下,如果URI指示的是当前资源,则会被改变。如果URI未指示当前资源,则服务器可以使用该URI创建资源。
5、DELETE方法
DELETE方法用来删除指定的资源,它会删除URI给出的目标资源的所有当前内容。
6、CONNECT方法
CONNECT方法用来建立到给定URI标识的服务器的隧道;它通过简单的TCP / IP隧道更改请求连接,通常实使用解码的HTTP代理来进行SSL编码的通信(HTTPS)。
7、OPTIONS方法
OPTIONS方法用来描述了目标资源的通信选项,会返回服务器支持预定义URL的HTTP策略。
8、TRACE方法
TRACE方法用于沿着目标资源的路径执行消息环回测试;它回应收到的请求,以便客户可以看到中间服务器进行了哪些(假设任何)进度或增量。
\2. Get和Post的区别
GET方法
GET是获取的意思,顾名思义就是获取信息。
GET是默认的HTTP请求方法。
GET方法把参数通过key/value形式存放在URL里面,如果参数是英文数字原样显示,如果是中文或者其他字符加密(Base64)URL长度一般有限制所以GET方法的参数长度不能太长。由于参数显示再地址栏所以不安全,一般需要保密的请求不使用GET。
POST方法
POST是邮件的意思,顾名思义就像一封信一样将参数放在信封里面传输。它用于修改服务器上的数据,一般这些数据是应该保密的,就像信件一样,信的内容只能收信的人看见。例入当用户输入账号和密码登录时账号和密码作为参数通过HTTP请求传输到服务器,这时候肯定不能用GET方法将账号密码直接显示再URL上,这时候就应该用POST方法保证数据的保密性。
POST和GET的区别
GET提交的数据放在URL中,POST则不会。这是最显而易见的差别。这点意味着GET更不安全(POST也不安全,因为HTTP是明文传输抓包就能获取数据内容,要想安全还得加密)
GET回退浏览器无害,POST会再次提交请求(GET方法回退后浏览器再缓存中拿结果,POST每次都会创建新资源)
GET提交的数据大小有限制(是因为浏览器对URL的长度有限制,GET本身没有限制),POST没有
GET可以被保存为书签,POST不可以。这一点也能感受到。
GET能被缓存,POST不能
GET只允许ASCII字符,POST没有限制
GET会保存再浏览器历史记录中,POST不会。这点也能感受到。
总之,两者之间没有本质区别,区别就在于数据存储的位置。各自有适用环境,根据需求选择合适的方法即可。
\3. 跨域的解决办法
同源策略
同源策略,它是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略来对脚本和请求进行校验,若不同源,则禁止使用。
同源的定义
那如果判断是否同源?主要根据三个维度,域名,协议,端口三个都相同才算同源。
举个 :
网站A网站B结果http://www.zhenai.comhttp://i.z.com不同源,域名不同http://www.zhenai.comhttp://www.z.cn不同源,域名不同http://www.zhenai.comhttps://www.z.com不同源,协议不同http://www.zhenai.comhttp://www.z.com:3000不同源,端口不同(默认端口80)
同源策略的作用
①无法用js读取非同源的Cookie、LocalStorage 和 IndexDB
这个主要是为了防止恶意网站通过js获取用户其他网站的cookie等用户信息。
②无法用js获取非同源的DOM
防止恶意网站通过iframe获取页面dom,从而窃取页面的信息。
③无法用js发送非同源的AJAX请求
防止恶意的请求攻击服务器窃取数据信息。
那是不是说非同源的请求就无法实现呢?也不是,这就引出了我们本文主要阐述的解决跨域请求问题的方法。
jsonp
jsonp能实现跨域是利用了img、script和link标签自身的跨域能力。
我们知道当img或者script中的src是一个链接的时候,浏览器会请求这个链接获取资源,那么这个链接如果是跨域的,浏览器也会请求,从而达到了跨域请求的一个功能。
用法
var script = document.createElement('script');
script.src = 'http://localhost:3000/api/test.do?a=1&b=2&callback=cb';
$('body').append(script);
function cb(res){
// do something
console.log(res)
}
1
2
3
4
5
6
7
8
可以看到,我们创建一个script标签,将src改成我们要请求的接口,并将script添加在body中,那么当浏览器解析到这个script时,会想src对应的服务器发送一个get请求,并将参数带过去。
然后当浏览器接收到服务端返回的数据,就会触发参数中callbak对应的回调函数cb,从而完成整个get请求。
优点
简单粗暴
缺点
①只支持get请求
②需要后台配合,将返回结果包装成callback(res)的形式
防范
那如果黑客植入script脚本通过jsonp的方式对服务器进行攻击,怎么办?
可以通过页面设置的内容安全协议csp进行防范。
cors跨域
cors 是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing),它允许浏览器向跨源服务器发送XMLHttpRequest请求,从而克服了 AJAX 只能同源使用的限制
cors 需要浏览器和服务器同时支持,整个 CORS通信过程,都是浏览器自动完成不需要用户参与,对于开发者来说,cors的代码和正常的 ajax 没有什么差别,浏览器一旦发现跨域请求,就会添加一些附加的头信息
但是,cors不支持ie10及以下版本。
简单请求和复杂请求
浏览器将cors请求分为简单请求和复杂请求。
简单请求则直接发送请求到服务器,只请求一次。
而复杂请求在正式请求前都会有预检请求,在浏览器中都能看到有OPTIONS请求,用于向服务器请求权限信息的,需要请求两次。
那如何区分是简单请求还是复杂请求呢?
简单请求
简单请求必须要同时满足下面三个条件:
请求方式只能是:GET、POST、HEAD
HTTP请求头限制这几种字段:Accept、Accept-Language、Content-Language、Content-Type、Last-Event-ID
Content-type只能取:application/x-www-form-urlencoded、multipart/form-data、text/plain
content-type的类型
类型描述application/json消息主体是序列化后的 JSON 字符串application/x-www-form-urlencoded数据被编码为键值对。这是标准的编码格式multipart/form-data需要在表单中进行文件上传时,就需要使用该格式。常见的媒体格式是上传文件之时使用的text/plain数据以纯文本形式(text/json/xml/html)进行编码,其中不含任何控件或格式字符
application/json:
作用: 告诉服务器请求的主题内容是json格式的字符串,服务器端会对json字符串进行解析,
好处: 前端人员不需要关心数据结构的复杂度,只要是标准的json格式就能提交成功。
application/x-www-form-urlencoded:是Jquery的Ajax请求默认方式
作用:在请求发送过程中会对数据进行序列化处理,以键值对形式?key1=value1&key2=value2的方式发送到服务器。
好处: 所有浏览器都支持。
复杂请求
不满足简单请求的条件,那么就是复杂请求。
复杂请求会在正式请求发送之前,先发一个预检请求进行校验,校验通过后才能进行正式请求。
举个
浏览器现在要发送一个put的复杂请求,那么在put请求发送之前,浏览器先发送一个options请求。
options请求头信息:
OPTIONS /cors HTTP/1.1
Origin: localhost:3000
Access-Control-Request-Method: PUT // 表示使用的什么HTTP请求方法
Access-Control-Request-Headers: X-Custom-Header // 表示浏览器发送的自定义字段
Host: localhost:3000
Accept-Language: zh-CN,zh;q=0.9
Connection: keep-alive
User-Agent: Mozilla/5.0...
1
2
3
4
5
6
7
8
服务器收到options请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应
options响应头信息
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://localhost:3000 // 表示http://localhost:3000可以访问数据
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
1
2
3
4
5
6
7
8
9
10
11
12
当options请求通过之后发出正式的HTTP请求,倘若options请求不通过,则服务器不允许此次访问,从而抛出错误
options请求通过之后的,浏览器发出发请求
PUT /cors HTTP/1.1
Origin: http://api.zhenai.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
1
2
3
4
5
6
7
options请求缓存
那这样的话,如果页面存在大量的复杂请求,岂不是每个请求前面都要进行一次options的请求,那不会造成大量资源的浪费么?
如果基于cors请求的方法来解决跨域问题,那么复杂请求之前是需要进行一个options的请求的,但我们可以通过对options请求进行缓存来减轻请求的压力。
在options请求中,我们可以通过设置响应头的参数Access-Control-Max-Age来对结果进行缓存
比如: Access-Control-Max-Age: 600 表示对options检验结果进行十分钟的缓存
url变化会导致缓存失效,需要重新验证options请求的返回值
预检不关心post data
header变化,如果是去掉了自定义的header使得请求变成简单请求,不会发送options请求。如果是增加其他的header,是会重新验证Access-Control-Allow-Headers的值。
cookie变化,只要后端允许发送cookie,cookie值变化不会导致缓存失效。
该字段的兼容性如下:
nginx
nginx解决跨域的问题跟之前的方法有所不同,它是通过服务器的方向代理,将前端访问域名跟后端服务域名映射到同源的地址下,从而实现前端服务和后端服务的同源,那自然不存在跨域的问题了。
举个 :
前端服务:http://localhost:3000,
前端页面路由:http://localhost:3000/page.html,
后端服务:http://localhost:3001,
后端接口路由:http://localhost:3001/api/test.do
可以看出,两个服务处于跨域的状态
通过nginx的配置进行反向代理,即可实现前后端服务同源,如下:
server
{
listen 80;
server_name localhost;
location = / {
proxy_pass http://localhost:3000;
}
location /api {
proxy_pass http://localhost:3001;
#指定允许跨域的方法,*代表所有
add_header Access-Control-Allow-Methods *;
#预检命令的缓存,如果不缓存每次会发送两次请求
add_header Access-Control-Max-Age 3600;
#带cookie请求需要加上这个字段,并设置为true
add_header Access-Control-Allow-Credentials true;
#表示允许这个域跨域调用(客户端发送请求的域名和端口)
#$http_origin动态获取请求客户端请求的域 不用*的原因是带cookie的请求不支持*号
add_header Access-Control-Allow-Origin $http_origin;
#表示请求头的字段 动态获取
add_header Access-Control-Allow-Headers
$http_access_control_request_headers;
#OPTIONS预检命令,预检命令通过时才发送请求
#检查请求的类型是不是预检命令
if ($request_method = OPTIONS){
return 200;
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
其实nginx不仅仅只是用于解决跨域问题,而是涉及到很多服务器资源分配的处理,在此就不详细探讨了。
vue proxyTable
其实,在我们主流使用的MVVM框架中,配置项里面也提供解决跨域问题的能力,继续举个 ,以vue2.x为例,我们可以通过在config/index.js中添加配置项实现跨域请求:
proxyTable: {
'/apis': {
// 测试环境
target: 'http://www.zhenai.com/', // 接口域名
changeOrigin: true, //是否跨域
pathRewrite: {
'^/apis': '' //需要rewrite重写的,
}
}
}
1
2
3
4
5
6
7
8
9
10
原理
其实原理很简单,就是在我们使用npm run dev命中,启动了一个node服务,然后将前端发出的请求发送到node服务,再将该服务转发到原本的后台服务,在这过程中实现了一层代理,由一个node服务发送一个请求到另外一个后台服务,自然也没有了浏览器所限制的跨域问题。
\4. URL输出到页面的全过程
1 浏览器根据请求的 URL 交给 DNS 域名解析,找到真实 IP ,向服务器发起请求;
2 服务器交给后台处理完成后返回数据,浏览器接收⽂件( HTML、JS、CSS 、图象等);
3 浏览器对加载到的资源( HTML、JS、CSS 等)进⾏语法解析,建立相应的内部数据结构 (如 HTML 的 DOM);
4 载⼊解析到的资源⽂件,渲染页面,完成。
————————————————
客户端网址中输入url
将输入的url发送到DNS获得该域名对应的WEB服务器的ip地址
客户端游览器与WEB服务器建立TCP连接
客户端游览器向WEB服务器发送HTTP或HTTPS请求
WEB服务器响应请求,返回指定的URL数据或错误信息
客户端拿到WEB服务器的数据后解析源文件,进行页面排版,显示基础页面
分析页面中的超链接,渲染页面
————————————————
详细简版:
1 从浏览器接收 url 到开启⽹络请求线程(这⼀部分可以展开浏览器的机制以及进程与线程 之间的关系)
2 开启⽹络线程到发出⼀个完整的 HTTP 请求(这⼀部分涉及到dns查询, TCP/IP 请求,五层因特⽹协议栈等知识)
3 从服务器接收到请求到对应后台接收到请求(这⼀部分可能涉及到负载均衡,安全拦截以及后台内部的处理等等)
4 后台和前台的 HTTP 交互(这⼀部分包括 HTTP 头部、响应码、报⽂结构、 cookie 等知 识,可以提下静态资源 的 cookie 优化,以及编码解码,如 gzip 压缩等)
6 单独拎出来的缓存问题, HTTP 的缓存(这部分包括http缓存头部, ETag , catchcontrol 等)
浏览器接收到 HTTP 数据包后的解析流程(解析 html、 词法分析然后解析成 dom 树、解析 css ⽣成 css 规则树、合并成 render 树,然后 layout 、 painting 渲染、复合图层的合成、 GPU 绘制、外链资源的处理、 loaded 和 DOMContentLoaded 等)
7 CSS 的可视化格式模型(元素的渲染规则,如包含块,控制框, BFC , IFC 等概念)
JS 引擎解析过程( JS 的解释阶段,预处理阶段,执⾏阶段⽣成执⾏上下⽂, VO ,作 ⽤域链、回收机制等等)
8 其它(可以拓展不同的知识模块,如跨域,web安全, hybrid 模式等等内容)
\5. 常用的请求状态码
500 内部服务器错误
Internal Server Error 500内部服务器错误,服务器遇到未知无法解决的问题。
一般情况下,出现500响应状态的原因有很多种,但是主要的是“程序代码和服务器配置”两个问题。相对于代码而言,就是对站点进行升级,网页改版,新增加了一些常用的插件。就比如WordPress插件的版本可能就需要更高版本的PHP才能兼容。
而相对服务器而言的话,更多的是在系统版本升级导致,就比如最开始使用的是Windows Server 2003,后期想要升级2008、2012等版本的时候配置稍有不慎就会导致Internal Server Error 500。
404 请求错误
Not Found 404 错误请求,因发送的请求语法错误,服务器无法正常读取。
相信绝大多数的人都见过404的状态码,当用户试图请求Web服务器上一个不存在的资源时,就会触发Not Found404。出现404状态码可能是链接失效导致,也有可能是URL拼写错误,还有可能是因为Web服务器将所请求的资源移到了其他的地方。一般的网站都会设置自定义页面以防链接失效所产生不良的影响。
403 禁止访问
Forbidden 403 禁止访问,客户端没有权利访问所请求内容,服务器拒绝本次请求。
状态码403通常代表客户端错误,是指的服务器端有能力处理该请求,但是拒绝授权访问。这个状态码类似于401,但是进入该状态后不能再继续进行验证,该访问是长期禁止的,并且与应用逻辑密切相关,比如密码不正确等。
400 错误请求
Bad Request 400 错误请求,因发送的请求语法错误,服务器无法正常读取。
状态码400表示该语法无效,服务器无法理解该请求。客服端不应该在未经修改的情况下重复此请求。一般会因为前端提交数据的字段名称,或者是字段类型和后台的实体类不一致,导致无法封装。
401 未经授权
Unauthorized 401 未经授权,需要身份验证后才能获取所请求的内容,类似于403错误.不同点是.401错误后,只要正确输入帐号密码,验证即可通过。
状态码401就是Web服务器认为,客户端发送的HTTP数据流浪是正确的,但是进入URL资源的时候需要身份验证,而客户端尚未提供相关的验证信息,或者是已提供但是没有通过验证。这也是通常所知的“HTTP基本验证”。
200 请求成功
200 OK 请求成功,表示已经请求成功,默认情况下的状态码为200的响应就可以被缓存了。
不同请求方式对于请求成功的意义如下:
GET: 已经取得资源,并将资源添加到响应的消息体中。
HEAD: 响应的消息体为头部信息。
POST: 响应的消息体中包含此次请求的结果。
TRACE: 响应的消息体中包含服务器接收到的请求信息。
PUT 和 DELETE 的请求成功通常并不是响应200OK的状态码而是 204No Content 表示无内容(或者 201Created表示一个资源首次被创建成功)。
206 部分内容
Partial Content 206 部分内容,当客户端通过使用range头字段进行文件分段下载时使用该状态码。
状态码206表示服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
301 永久重定向
Moved Permanently 301 永久移动,该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。
尽管标准要求浏览器在收到该响应并进行重定向时不应该修改http method和body,但是有一些浏览器可能会有问题。所以最好是在应对GET 或 HEAD 方法时使用301,其他情况使用308 来替代301。
302 临时重定向
Found 302临时移动,该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变.因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。
即使规范要求浏览器在重定向时保证请求方法和请求主体不变,但并不是所有的用户代理都会遵循这一点,你依然可以看到有缺陷的软件的存在。所以推荐仅在响应 GET 或 HEAD 方法时采用 302 状态码,而在其他时候使用 307 Temporary Redirect 来替代,因为在这些场景下方法变换是明确禁止的。
502 无效网关
Bad Gateway 502 网关错误,服务器作为网关且从上游服务器获取到了一个无效的HTTP响应。
404 请求错误
Not Found 404 错误请求,因发送的请求语法错误,服务器无法正常读取。
相信绝大多数的人都见过404的状态码,当用户试图请求Web服务器上一个不存在的资源时,就会触发Not Found404。出现404状态码可能是链接失效导致,也有可能是URL拼写错误,还有可能是因为Web服务器将所请求的资源移到了其他的地方。一般的网站都会设置自定义页面以防链接失效所产生不良的影响。
403 禁止访问
Forbidden 403 禁止访问,客户端没有权利访问所请求内容,服务器拒绝本次请求。
状态码403通常代表客户端错误,是指的服务器端有能力处理该请求,但是拒绝授权访问。这个状态码类似于401,但是进入该状态后不能再继续进行验证,该访问是长期禁止的,并且与应用逻辑密切相关,比如密码不正确等。
400 错误请求
Bad Request 400 错误请求,因发送的请求语法错误,服务器无法正常读取。
状态码400表示该语法无效,服务器无法理解该请求。客服端不应该在未经修改的情况下重复此请求。一般会因为前端提交数据的字段名称,或者是字段类型和后台的实体类不一致,导致无法封装。
401 未经授权
Unauthorized 401 未经授权,需要身份验证后才能获取所请求的内容,类似于403错误.不同点是.401错误后,只要正确输入帐号密码,验证即可通过。
状态码401就是Web服务器认为,客户端发送的HTTP数据流浪是正确的,但是进入URL资源的时候需要身份验证,而客户端尚未提供相关的验证信息,或者是已提供但是没有通过验证。这也是通常所知的“HTTP基本验证”。
200 请求成功
200 OK 请求成功,表示已经请求成功,默认情况下的状态码为200的响应就可以被缓存了。
不同请求方式对于请求成功的意义如下:
GET: 已经取得资源,并将资源添加到响应的消息体中。
HEAD: 响应的消息体为头部信息。
POST: 响应的消息体中包含此次请求的结果。
TRACE: 响应的消息体中包含服务器接收到的请求信息。
PUT 和 DELETE 的请求成功通常并不是响应200OK的状态码而是 204No Content 表示无内容(或者 201Created表示一个资源首次被创建成功)。
206 部分内容
Partial Content 206 部分内容,当客户端通过使用range头字段进行文件分段下载时使用该状态码。
状态码206表示服务器已经成功处理了部分GET请求。类似于FlashGet或者迅雷这类的HTTP 下载工具都是使用此类响应实现断点续传或者将一个大文档分解为多个下载段同时下载。
301 永久重定向
Moved Permanently 301 永久移动,该状态码表示所请求的URI资源路径已经改变,新的URL会在响应的Location:头字段里找到。
尽管标准要求浏览器在收到该响应并进行重定向时不应该修改http method和body,但是有一些浏览器可能会有问题。所以最好是在应对GET 或 HEAD 方法时使用301,其他情况使用308 来替代301。
302 临时重定向
Found 302临时移动,该状态码表示所请求的URI资源路径临时改变,并且还可能继续改变.因此客户端在以后访问时还得继续使用该URI.新的URL会在响应的Location:头字段里找到。
即使规范要求浏览器在重定向时保证请求方法和请求主体不变,但并不是所有的用户代理都会遵循这一点,你依然可以看到有缺陷的软件的存在。所以推荐仅在响应 GET 或 HEAD 方法时采用 302 状态码,而在其他时候使用 307 Temporary Redirect 来替代,因为在这些场景下方法变换是明确禁止的。
502 无效网关
Bad Gateway 502 网关错误,服务器作为网关且从上游服务器获取到了一个无效的HTTP响应。
bad gateway502代表您所访问的网站出了问题,因为502 Bad Gateway 服务器作为网关或者代理时,是为了完成访问下一个服务器,但该服务器返回了非法的应答。也许是暂时的,也许是永久的。建议大家稍等一下再从新访问试试。
————————————————
版权声明:本文为CSDN博主「小泽今天早睡」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/m0_50861631/article/details/123313909
作者:ywjbalabala
出处:https://www.cnblogs.com/ywjbalabala/
本文版权归作者和博客园所有,欢迎转载。转载请在留言板处留言给我,且在文章标明原文链接,谢谢!
如果您觉得本篇博文对您有所收获,觉得我还算用心,请点击右下角的 [推荐],谢谢!

浙公网安备 33010602011771号