区别 | GET | POST |
---|---|---|
幂等性 | 是 | 否 |
应用场景 | 用于对服务器资源不会产生影响的场景(比如请求一个网页的资源等) | 用于对服务器资源会产生影响的情景(比如注册用户等) |
是否缓存 | 是 | 否 |
传参方式 | 查询字符串传参 | 请求体传参 |
安全性 | 将参数放入url中向服务器发送,不安全 | 在请求体中,安全 |
请求长度 | 浏览器对于url长度有限制,会受到影响 | 在请求体中,不会收到浏览器影响 |
参数类型 | ASCII字符 | 文件、图片等 |
幂等性:指一个请求方法执行一次和多次的效果完全相同
区别 | POST | PUT |
---|---|---|
作用 | 创建数据 | 更新数据 |
为什么POST请求会发送两次?
作用1:询问服务器是否支持修改的请求头,如果服务器支持,则在第二次中发送真正的请求
作用2: 检测服务器是否为同源请求,是否支持跨域
HTTP Request Header | 定义 |
---|---|
Accept | 浏览器能够处理的内容类型 |
Accept-Charset | 浏览器能够显示的字符集 |
Accept-Encoding | 浏览器能够处理的压缩编码 |
Accept-Language | 浏览器当前设置的语言 |
Connection | 浏览器与服务器之间连接的类型 |
Cookie | 浏览器当前页面设置的任何Cookie |
Host | 当前发出请求的页面所在的域 |
Referer | 当前发出请求的页面的URL |
User-Agent | 浏览器的用户代理字符串 |
HTTP Responses Header | 定义 |
---|---|
Date | 表示消息发送的时间,时间的描述格式由rfc822定义 |
server | 服务器名称 |
Connection | 浏览器与服务器之间连接的类型 |
Cache-Control | 控制HTTP缓存 |
content-type | 表示后面的文档属于什么MIME类型 |
Content-Type | 定义 |
---|---|
application/x-www-form-urlencoded | 浏览器原生form表单 |
multipart/form-data | 表单上传文件 |
application/json | 服务器消息主体是序列化后的 JSON 字符串 |
text/xml | 提交 XML 格式的数据 |
为什么会有304?
服务器为了提高网站访问速度,对之前访问的部分页面指定缓存机制。当客户端再次请求页面时,服务器会判断请求的页面是否已被缓存,若已经被缓存则返回304,此时客户端将调用缓存内容。
状态码304不应该被认为是一种错误,而是对客户端有缓存情况下服务端的一种响应。
产生较多304状态码的原因是什么?
304状态码过多会造成什么问题?
方法 | 作用 |
---|---|
GET | 向服务器获取数据 |
POST | 向服务器发送数据 |
PUT | 修改数据 |
PATCH | 用于对资源进行部分修改 |
DELETE | 删除指定数据 |
http请求过程:
1.对www.abc.com这个网址进行DNS域名解析,得到对应的IP地址
2.根据这个IP,找到对应的服务器,发起TCP的三次握手
3.建立TCP连接后发起HTTP请求
4.服务器响应HTTP请求,浏览器得到html代码
5.浏览器解析html代码,并请求html代码中的资源(如js、css、图片等)(先得到html 代码,才能去找这些资源)
6.浏览器对页面进行渲染呈现给用户
7.服务器关闭关闭TCP连接
浏览器输入一个地址。到页面展示中间经历了哪些东西?
#这个问题前端面试基本上百分百问的。测试的话,基础的功能面试可能不会问。自动化的话基本上也会问的。
1、浏览器输入url。先解析url地址是否合法
2、浏览器检查是否有ip缓存(游览器缓存-系统缓存-路由器缓存)。如果有,直接显示。如果没有,跳到第三步。
3、在发送http请求前,需要域名解析(DNS解析),解析获取对应过的ip地址。
4、浏览器向服务器发起tcp链接,与游览器简历tcp三次握手
5、握手成功后,游览器向服务器发送http请求,请求数据包
6、服务器收到处理的请求,将数据返回至浏览器
7、浏览器收到http响应。
8、浏览器解析响应。如果响应可以缓存,则存入缓存
9、游览器发送请求获取嵌入在HTML中的资源(html,css,JavaScript,图片,音乐等),对于未知类型,会弹出对话框
10、游览器发送异步请求
11、页面全部渲染结束。
http和https的区别:https=http+ssl;http是超文本传输协议,信息是明文传输。https则是具有安全性的ssl加密传输协议。
Cookie是最早被提出来的本地存储方式,在此之前,服务端是无法判断网络中的两个请求是否是同一用户发起的,为解决这个问题,Cookie就出现了。Cookie的大小只有4kb,它是一种纯文本文件,每次发起HTTP请求都会携带Cookie
LocalStorage是HTML5新引入的特性,由于有的时候我们存储的信息较大,Cookie就不能满足我们的需求,这时候LocalStorage就派上用场了
API | 注释 |
---|---|
localStorage.setItem(key, value) | 保存数据到 localStorage |
localStorage.getItem(key) | 从 localStorage 获取数据 |
localStorage.removeItem(key) | 从 localStorage 删除key对应的数据 |
localStorage.clear() | 从 localStorage 删除所有保存的数据 |
localStorage.key(index) | 获取某个索引的Key |
SessionStorage和LocalStorage都是在HTML5才提出来的存储方案,SessionStorage 主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据
API | 注释 |
---|---|
sessionStorage.setItem(key, value) | 保存数据到 sessionStorage |
sessionStorage.getItem(key) | 从 sessionStorage获取数据 |
sessionStorage.removeItem(key) | 从 sessionStorage删除key对应的数据 |
sessionStorage.clear() | 从 sessionStorage删除所有保存的数据 |
sessionStorage.key(index) | 获取某个索引的Key |
Cookie | LocalStorage | SessionStorage |
---|---|---|
实最开始是服务器端用于记录用户状态的一种方式,由服务器设置,在客户端存储,然后每次发起同源请求时,发送给服务器端。cookie 最多能存储 4 k 数据,它的生存时间由 expires 属性指定,并且 cookie 只能被同源的页面访问共享 | html5 提供的一种浏览器本地存储的方法,它一般也能够存储 5M 或者更大的数据。它和 sessionStorage 不同的是,除非手动删除它,否则它不会失效,并且 localStorage 也只能被同源页面所访问共享 | html5 提供的一种浏览器本地存储的方法,它借鉴了服务器端 session 的概念,代表的是一次会话中所保存的数据。它一般能够存储 5M 或者更大的数据,它在当前窗口关闭后就失效了,并且 sessionStorage 只能被同一个窗口的同源页面所访问共享 |
注意事项
!important
声明的样式的优先级最高属性值 | 作用 |
---|---|
none | 元素不显示,并且会从文档流中移除 |
block | 块元素类型。默认宽度为父元素宽度,可设置宽高,换行显示 |
inline | 行内元素类型。默认宽度为内容宽度,不可设置宽高,同行显示 |
inline-block | 行内块元素类型。默认宽度为内容宽度,可以设置宽高,同行显示 |
list-item | 像块类型元素一样显示,并添加样式列表标记 |
table | 此元素会作为块级表格来显示 |
inherit | 规定应该从父元素继承display属性的值 |
区别 | block | inline | inline-block |
---|---|---|---|
独占一行 | 是 | 否 | 否 |
width | 是 | 否 | 是 |
height | 是 | 否 | 是 |
margin | 是 | 水平方向有效 | 是 |
padding | 是 | 是 | 是 |
行内元素:span,img,input
块级元素:h1-h6,div,p,header
区别 | 行内元素 | 块级元素 |
---|---|---|
宽高 | 无效 | 有效 |
padding | 有效 | 有效 |
margin | 水平方向有效 | 有效 |
自动换行 | 不可以 | 可以 |
多个元素排列 | 默认从左到右 | 默认从上到下 |
方法 | 说明 |
---|---|
display: none; | 渲染树不会包含该渲染对象,因此该元素不会在页面中占据位置,也不会响应绑定的监听事件 |
visibility: hidden; | 元素在页面中仍占据空间,但是不会响应绑定的监听事件 |
opacity: 0; | 透明度设置为0,来隐藏元素。元素在页面中仍然占据空间,并且能够响应元素绑定的监听事件 |
position: absolute; | 通过使用绝对定位将元素移除可视区域内,以此来实现元素的隐藏 |
z-index: -10; | 使用其余元素遮盖当前元素实现隐藏 |
clip/clip-path | 使用元素裁剪的方法来实现元素的隐藏,这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件 |
transform: scale(0,0) | 将元素缩放为 0,来实现元素的隐藏。这种方法下,元素仍在页面中占据位置,但是不会响应绑定的监听事件 |
transition | animation |
---|---|
过渡属性,强调过渡,需要触发事件来实现过渡效果。 | 动画属性,不需要触发事件,可自己执行,并且可以循环 |
伪元素 | 伪类 |
---|---|
在元素前后插入额外的元素或样式,插入的元素没有子文档中生成,它们只在外部显示可见 | 将特殊的效果添加到特定的选择器上。它是在现有元素上添加类别,并不会产生新的元素 |
css3中伪元素在书写是使用双冒号::,比如::before | 冒号:用于伪类,比如:hover |
伪类是通过在元素选择器上加入伪类改变元素的状态,而伪元素通过对元素的操作来改变元素
盒模型由四个部分组成,分别是margin、border、padding、content
标准盒模型和IE盒模型的区别在于:在设置width和height时,所对应的范围不同
可以通过修改元素的box-sizing属性来改变元素的盒模型:
box-sizing: content-box
表示标准盒模型(默认值)box-sizing: border-box
表示IE盒模型(怪异盒模型)单行文本溢出
overflow: hidden; // 溢出隐藏
text-overflow: ellipsis; // 溢出部分使用省略号显示
white-space: nowrap; // 规定段落中的文本不可换行
多行文本溢出
overflow: hidden; // 溢出隐藏
text-overflow: ellipsis; // 溢出用省略号显示
display:-webkit-box; // 作为弹性伸缩盒子模型显示。
-webkit-box-orient:vertical; // 设置伸缩盒子的子元素排列方式:从上到下垂直排列
-webkit-line-clamp:3; // 显示的行数
利用绝对定位(一)
.parent {
position: relative;
}
.child {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%)
}
利用绝对定位(二):适用于已知盒子宽高
.parent {
position: relative;
}
.child {
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
margin: auto;
}
利用绝对定位(三):适用于已知盒子宽高
.parent {
position: relative;
}
.child {
position: absolute;
top: 50%;
left: 50%;
margin-top: -50px; /* 自身 height 的一半 */
margin-left: -50px; /* 自身 width 的一半 */
}
flex布局
.parent {
display: flex;
justify-content: center;
align-items: center;
}
非IE浏览器下,容器不设置高度且子元素浮动时,容器高度不能被撑开。此时,内容会溢出到容器外面影响布局
浮动元素可以左右移动,知道遇到另一个浮动元素或者遇到它外边缘的包含框。浮动框不属于文档流中的普通流,但元素浮动之后,不会影响块级元素的布局,只会影响内联元素的布局。此时文档流中的普通流就会表现得该浮动框不存在一样的布局模式。当包含框的高度小于浮动框的时候,此时就会出现“高度塌陷”
clear: both
样式overflow: hidden
或overflow: auto
样式::after
伪元素BFC: 块格式化上下文(Block Formatting Context,BFC)是Web页面的可视化CSS渲染的一部分,是布局过程中生成块级盒子的区域,也是浮动元素与其他元素的交互限定区域。
通俗的讲,BFC是一个独立的环境布局,可以理解为一个容器,在这个容器中按照一定的规则进行物品摆放,并且不会影响其他环境中的物品。如果一个元素符合触发BFC的条件,则BFC中的元素布局不受外部影响。
overflow:hidden
float:left
,右侧设置overflow: hidden
。这样右边就触发了BFC,BFC的区域不会与浮动元素发生重叠,所以两侧就不会发生重叠,实现了自适应两栏布局两个块级元素的上外边距和下外边距可能会合并(折叠)为一个外边距,其大小会取其中外边距值大的那个,这种行为就是外边距折叠。需要注意的是, 浮动的元素和绝对定位这种脱离文档流的元素的外边距不会折叠。重叠只会出现在 垂直方向。
对于折叠的情况,主要有两种: 兄弟之间重叠和 父子之间重叠
兄弟间折叠:
display: inline-block
float
absolute/fixed
父子间的折叠:
overflow: hidden
border: 1px solid transparent
display: inline-block
CSS选择符:
通配(*)
id选择器(#)
类选择器(.)
标签选择器(div、p、h1...)
相邻选择器(+)
后代选择器(ul li)
子元素选择器( > )
属性选择器(a[href])
CSS属性哪些可以继承:
文字系列:font-size、color、line-height、text-align...
***不可继承属性:border、padding、margin...
用边框画(border),例如:
{
width: 0;
height: 0;border-left:100px solid transparent;
border-right:100px solid transparent;
border-top:100px solid transparent;
border-bottom:100px solid #ccc;
}
1. 占用位置的区别
display: none; 是不占用位置的
visibility: hidden; 虽然隐藏了,但是占用位置2. 重绘和回流的问题
visibility: hidden; 、 display: none; 产生重绘
display: none; 还会产生一次回流产生回流一定会造成重绘,但是重绘不一定会造成回流。
产生回流的情况:改变元素的位置(left、top...)、显示隐藏元素....
产生重绘的情况:样式改变、换皮肤
基本数据类型 | 引用数据类型 |
---|---|
Undefined、Null、Boolean、String、Number、Symbol(ES6)、BigInt(ES6) | Object(包括数组、函数、对象等) |
在操作系统中,内存被分为栈区和堆区,栈区内存由编译器自动分配释放,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。堆区内存一般由开发着分配释放,若开发者不释放,程序结束时可能由垃圾回收机制回收。
在数据结构中,栈中数据的存取方式为先进后出。堆是一个优先队列,是按优先级来进行排序的,优先级可以按照大小来规定。
数据存储方式
img
优缺点 | typeof | instanceof | constructor | Object.prototype.toString.call |
---|---|---|---|---|
优点 | 使用简单 | 能检测出引用类型数据 | 基本能检测所有的类型(除了null和undefined) | 检测出所有的类型 |
缺点 | 只能检测出除null外的基本数据类型和引用数据类型中的function | 不能检测出基本类型,且不能跨iframe | constructor易被修改,也不能跨iframe | IE6下,undefined和null均为Object |
1. 作者在设计js的都是先设计的null(为什么设计了null:最初设计js的时候借鉴了java的语言)
2. null会被隐式转换成0,很不容易发现错误。
3. 先有null后有undefined,出来undefined是为了填补之前的坑。具体区别:JavaScript的最初版本是这样区分的:null是一个表示"无"的对象(空对象指针),转为数值时为0;undefined是一个表示"无"的原始值,转为数值时为NaN。
this 是一个在运行时才进行绑定的引用
,在不同的情况下它可能会被绑定不同的对象
this 永远指向最后调用它的那个对象
如何改变this的指向?
this绑定的优先级:new绑定优先级 > 显示绑定优先级 > 隐式绑定优先级 > 默认绑定优先级
apply、bind和call都可以改变this的指向
apply()
方法调用一个具有给定this
值的函数,以及以一个数组(或一个 类数组对象)的形式提供的参数
thisArg: 在函数运行时使用的 this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null
或 undefined
时会自动替换为指向全局对象,原始值会被包装
argsArray: 可选。一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 函数。如果该参数的值为 null
或 undefined
,则表示不需要传入任何参数
返回值:调用有指定 this
值和参数的函数的结果
const numbers = [1, 3, 2, 5, 7, 4]
const max = Math.max.apply(null, numbers) // 7
const min = Math.max.apply(null, numbers) // 1
call()
方法使用一个指定的this
值和单独给出的一个或多个参数来调用一个函数
该方法的语法和作用与apply()
方法类似,只有一个区别,就是call()
方法接受的是 一个参数列表,而apply()
方法接受的是 一个包含多个参数的数组
thisArg: 在函数运行时使用的 this
值。请注意,this
可能不是该方法看到的实际值:如果这个函数处于非严格模式下,则指定为 null
或 undefined
时会自动替换为指向全局对象,原始值会被包装
arg1, arg2, ...: 指定的参数列表
返回值:调用有指定 this
值和参数的函数的结果
bind()
方法创建一个新的函数,在bind()
被调用时,这个新函数的this
被指定为bind()
的第一个参数,而其余参数将作为新函数的参数,供调用时使用(bind 是创建一个新的函数,我们必须要手动去调用)
thisArg: 调用绑定函数时作为 this
参数传递给目标函数的值。如果使用new
运算符构造绑定函数,则忽略该值。当使用 bind
在 setTimeout
中创建一个函数(作为回调提供)时,作为 thisArg
传递的任何原始值都将转换为 object
。如果 bind
函数的参数列表为空,或者thisArg
是null
或undefined
,执行作用域的 this
将被视为新函数的 thisArg
arg1, arg2, ...: 指定的参数列表
返回值:返回一个原函数的拷贝,并拥有指定的 this
值和初始参数
一个拥有length属性和若干索引属性的对象可以被成为类数组对象,类数组对象和数组类似,但不能调用数组的方法
常见的类数组对象:arguments和DOM方法的返回结果,还有 一个函数也可以被看作是类数组对象,因为它含有 length 属性值,代表可接收的参数个数
Array.prototype.slice.call(arrayLike);
Array.prototype.splice.call(arrayLike, 0);
Array.prototype.concat.apply([], arrayLike);
Array.from(arrayLike);
const array = [...arrayLike]
arguments
是一个对象,它的属性是从 0 开始依次递增的数字,还有callee
和length
等属性,与数组相似;但是它却没有数组常见的方法属性,如forEach
,reduce
等,所以叫它们类数组
function sum() {
Array.prototype.forEach.call(arguements, a => { console.log(a) })
}
function sum() {
Array.prototype.forEach.apply(arguements, [a => { console.log(a)] })
}
function sum() {
const args = Array.from(arguements)
args.forEach(a => { console.log(a) })
}
function sum() {
const args = [...arguements]
args.forEach(a => { console.log(a) })
}
区别 | for...in | for...of |
---|---|---|
遍历对象 | 对象的键名,会遍历整个原型链,性能差 | 对象的键值,只遍历当前对象 |
遍历数组 | 返回数组中所有可枚举属性,包括原型链上的属性 | 只返回对应数组的下标对应的属性值 |
for...in循环主要是为了遍历对象,不适用于遍历数组,for...of 循环可以用来遍历数组、类数组对象,字符串、Set、Map 以及 Generator 对象
AJAX Ajax 即“AsynchronousJavascriptAndXML”(异步 JavaScript 和 XML),是指一种创建交互式网页应用的网页开发技术。它是一种在无需重新加载整个网页的情况下,能够更新部分网页的技术。通过在后台与服务器进行少量数据交换,Ajax 可以使网页实现异步更新。这意味着可以在不重新加载整个网页的情况下,对网页的某部分进行更新。传统的网页(不使用 Ajax)如果需要更新内容,必须重载整个网页页面。其缺点如下:
ajax是一种能够实现局部网页刷新的技术,可以使网页异步刷新。
ajax的实现主要包括四个步骤:
(1)创建核心对象XMLhttpRequest;
(2)利用open方法打开与服务器的连接;
(3)利用send方法发送请求;("POST"请求时,还需额外设置请求头)
(4)监听服务器响应,接收返回值。
Ajax和后台的交互:封装好的Ajax的几个参数:url:发送请求的地址;type:发送请求的方式(post,get等,默认get);async: 同步异步请求,默认true所有请求均为异步请求。timeout : 超时时间设置,单位毫秒;data:要求为Object或String类型的参数,发送到服务器的数据等;
Ajax的实现流程:
- 创建XMLHttpRequest对象,也就是创建一个异步调用对象.
- 创建一个新的HTTP请求,并指定该HTTP请求的方法、URL及验证信息.
- 设置响应HTTP请求状态变化的函数.
- 发送HTTP请求.
- 获取异步调用返回的数据.
使用JavaScript和DOM实现局部刷新.
Fetch fetch号称是AJAX的替代品,是在ES6出现的,使用了ES6中的promise对象。Fetch是基于promise设计的。Fetch的代码结构比起ajax简单多。 fetch不是ajax的进一步封装,而是原生js,没有使用XMLHttpRequest对象
优点 | 缺点 |
---|---|
语法简洁,更加语义化 | fetch只对网络请求报错,对400,500都当做成功的请求,服务器返回 400,500 错误码时并不会 reject,只有网络错误这些导致请求不能完成时,fetch 才会被 reject。 |
基于标准 Promise 实现,支持 async/await | fetch默认不会带cookie,需要添加配置项: fetch(url, {credentials: 'include'}) |
更加底层,提供的API丰富(request, response) | fetch不支持abort,不支持超时控制,使用setTimeout及Promise.reject的实现的超时控制并不能阻止请求过程继续在后台运行,造成了流量的浪费 |
脱离了XHR,是ES规范里新的实现方式 | fetch没有办法原生监测请求的进度,而XHR可以 |
Axios 是一种基于Promise封装的HTTP客户端
方法 | 改变原数组 | 特点 |
---|---|---|
forEach | 否 | 无返回值 |
map | 否 | 返回新数组,可链式调用 |
filter | 否 | 过滤数组,返回包含符合条件的元素的数组,可链式调用 |
for...of | 否 | for...of遍历具有Iterator迭代器的对象的属性,返回的是数组的元素、对象的属性值,不能遍历普通的obj对象,将异步循环变成同步循环 |
every | 否 | 遍历的数组里的元素全部符合条件时,返回true |
some | 否 | 遍历的数组里的元素至少有一个符合条件时,返回true |
find | 否 | 返回第一个符合条件的值 |
findIndex | 否 | 返回第一个返回条件的值的索引值 |
reduce | 否 | 对数组正序操作 |
reduceRight | 否 | 对数组逆序操作 |
深拷贝是将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象(新旧对象不共享同一块内存),且 修改新对象不会影响原来的对象(深拷贝采用了在堆内存中申请新的空间来存储数据,这样每个可以避免指针悬挂)
实现方式如下:
这也是利用JSON.stringify将对象转成JSON字符串,再用JSON.parse把字符串解析成对象,一去一来,新的对象产生了,而且对象会开辟新的栈,实现深拷贝。 这种方法虽然可以实现数组或对象深拷贝,但不能处理函数和正则,因为这两者基于JSON.stringify和JSON.parse处理后,得到的正则就不再是正则(变为空对象),得到的函数就不再是函数(变为null)了
需要安装lodash
递归方法实现深度克隆原理: 遍历对象、数组直到里边都是基本数据类型,然后再去复制,就是深度拷贝
function deepClone(obj, hash = new WeakMap()) {
if (obj === null) return obj // 如果是null或者undefined我就不进行拷贝操作
if (obj instanceof Date) return new Date(obj)
if (obj instanceof RegExp) return new RegExp(obj)
// 可能是对象或者普通的值 如果是函数的话是不需要深拷贝
if (typeof obj !== "object") return obj
// 是对象的话就要进行深拷贝
if (hash.get(obj)) return hash.get(obj)
let cloneObj = new obj.constructor()
// 找到的是所属类原型上的constructor,而原型上的 constructor指向的是当前类本身
hash.set(obj, cloneObj)
for (let key in obj) {
if (obj.hasOwnProperty(key)) {
// 实现一个递归拷贝
cloneObj[key] = deepClone(obj[key], hash)
}
}
return cloneObj
}
浅拷贝是创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是基本类型,拷贝的就是基本类型的值,如果属性是引用类型,拷贝的就是内存地址 ,所以 如果其中一个对象改变了这个地址,就会影响到另一个对象
实现方式如下:
Object.assign() 方法可以把任意多个的源对象自身的可枚举属性拷贝给目标对象,然后返回目标对象
let obj1 = { person: {name: "kobe", age: 41},sports:'basketball' }
let obj2 = Object.assign({}, obj1);
obj2.person.name = "wade";
obj2.sports = 'football'
console.log(obj1); // { person: { name: 'wade', age: 41 }, sports: 'basketball' }
需要安装lodash
展开运算符是一个 es6 / es2015特性,它提供了一种非常方便的方式来执行浅拷贝,这与 Object.assign ()的功能相同
let obj1 = { name: 'Kobe', address:{x:100,y:100}}
let obj2= {... obj1}
obj1.address.x = 200;
obj1.name = 'wade'
console.log('obj2',obj2) // obj2 { name: 'Kobe', address: { x: 200, y: 100 } }
let arr = [1, 3, {
username: 'kobe'
}];
let arr2 = arr.concat();
arr2[2].username = 'wade';
console.log(arr); //[ 1, 3, { username: 'wade' } ]
let arr = [1, 3, {
username: ' kobe'
}];
let arr3 = arr.slice();
arr3[2].username = 'wade'
console.log(arr); // [ 1, 3, { username: 'wade' } ]
对于引用数据类型
赋值 | 深拷贝 | 浅拷贝 |
---|---|---|
当我们把一个对象赋值给一个新的变量时,赋的其实是该对象的在栈中的地址,而不是堆中的数据。也就是两个对象指向的是同一个存储空间,无论哪个对象发生改变,其实都是改变的存储空间的内容,因此,两个对象是联动的。 | 从堆内存中开辟一个新的区域存放新对象,对对象中的子对象进行递归拷贝,拷贝前后的两个对象互不影响。 | 重新在堆中创建内存,拷贝前后对象的基本数据类型互不影响,但拷贝前后对象的引用类型因共享同一块内存,会相互影响。 |
和原数据是否指向同一对象 | 第一层数据为基本数据类型且修改基本类型数据时 | 原数据中包含子对象且修改子对象时 | |
---|---|---|---|
赋值 | 是 | 改变会使原数据一起改变 | 改变会使原数据一起改变 |
深拷贝 | 否 | 改变不会使原数据一起改变 | 改变不会使原数据一起改变 |
浅拷贝 | 否 | 改变不会使原数据一起改变 | 改变会使原数据一起改变 |
forEach是不能通过break
或者return
来实现跳出循环的,forEach的回调函数形成了一个作用域,在里面使用return
并不会跳出,只会被当做continue
实现方法:try...catch
function getItemById(arr, id) {
var item = null;
try {
arr.forEach(function (curItem, i) {
if (curItem.id == id) {
item = curItem;
throw Error();
}
})
} catch (e) {}
return item;
}
闭包是指有权访问另一个函数作用域中的变量的 函数
闭包是一种特殊的对象。它由两部分构成:函数,以及创建该函数的环境。环境由闭包创建时在作用域中的任何局部变量组成
return
回一个函数容易导致内存泄漏。闭包会携带包含其它的函数作用域,因此会比其他函数占用更多的内存。过度使用闭包会导致内存占用过多,所以要谨慎使用闭包。
执行上下文是评估和执行 JavaScript 代码的环境的抽象概念。每当 Javascript 代码在运行的时候,它都是在执行上下文中运行
执行上下文有三种类型
eval
函数内部的代码也会有它属于自己的执行上下文JavaScript引擎使用执行上下文栈来管理执行上下文
当JavaScript执行代码时,首先遇到全局代码,会创建一个全局执行上下文并且压入执行栈中,每当遇到一个函数调用,就会为该函数创建一个新的执行上下文并压入栈顶,引擎会执行位于执行上下文栈顶的函数,当函数执行完成之后,执行上下文从栈中弹出,继续执行下一个上下文。当所有的代码都执行完毕之后,从栈中弹出全局执行上下文
let a = 'Hello World!';
function first() {
console.log('Inside first function');
second();
console.log('Again inside first function');
}
function second() {
console.log('Inside second function');
}
first();
console.log('Inside Global Execution Context');
img
当上述代码在浏览器加载时,JavaScript 引擎创建了一个全局执行上下文并把它压入当前执行栈。当遇到first()
函数调用时,JavaScript 引擎为该函数创建一个新的执行上下文并把它压入当前执行栈的顶部。
当从first()
函数内部调用second()
函数时,JavaScript 引擎为second()
函数创建了一个新的执行上下文并把它压入当前执行栈的顶部。当second()
函数执行完毕,它的执行上下文会从当前栈弹出,并且控制流程到达下一个执行上下文,即first()
函数的执行上下文。
当first()
执行完毕,它的执行上下文从栈弹出,控制流程到达全局执行上下文。一旦所有代码执行完毕,JavaScript 引擎从当前栈中移除全局执行上下文
创建阶段 → 执行阶段 → 回收阶段
在这阶段,执行变量赋值、代码执行。如果 Javascript
引擎在源代码中声明的实际位置找不到变量的值,那么将为其分配 undefined
值
执行上下文出栈等待虚拟机回收执行上下文
作用域是在运行时代码中的某些特定部分中变量、函数和对象的可访问性。换句话说,作用域决定了代码区块中变量和其他资源的可见性。 作用域就是一个独立的地盘,让变量不会外泄、暴露出去。也就是说 作用域最大的用处就是隔离变量,不同作用域下同名变量不会有冲突。
var和function命令声明的全局变量和函数是window对象的属性和方法
let命令、const命令、class命令声明的全局变量,不属于window对象的属性
值得注意的是,块语句(大括号之间的语句,如if语句、switch语句、for循环语句、while语句)不会创建一个新的作用域,在块语句中定义的变量将保留在它们存在的作用域中
块级作用域可通过新增命令let和const声明,所声明的变量在指定的块级作用域外无法被访问,块级作用域在如下情况被创建:
let 声明的语法与 var 的语法一致。基本上可以用 let 来代替 var 进行变量声明,但会将变量的作用域限制在当前代码块中。块级作用域有以下几个特点:
在某个作用域内访问一个变量时,会先在当前作用域内寻找,如果没有找到,则去上一级作用域内寻找,以此类推。这样的变量作用域访问的链式结构,被称为作用域链
作用域链的作用是保证对执行环境有权访问的所有变量和函数的有序访问,通过作用域链,可以访问到外层环境的变量和函数。
JS 引擎在执行一段代码的时候,会按照下面的步骤进行工作
区别
内存泄露是指由于疏忽或错误造成程序未能释放已经不再使用的内存
内存泄露的原因有以下几种:
优点 | 缺点 |
---|---|
降低维护成本 | 过渡包装会导致性能开销 |
代码的复用性更强 | 资源占用更强 |
组合起来更加优雅 | 为了实现迭代,可能会掉入递归陷阱 |
纯函数是对给定的输入返还相同的输出的函数,并且要求所有的数据都是不可变的
特性
优势
高阶函数是指使用其它函数作为参数、或者返回一个函数作为返回值的函数
常见的高阶函数
柯里化(Currying)又叫函数的部分求值,是把 接受多个参数的函数变换成 接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数且返回结果的新函数的技术
优点
function curry(fn, args) {
args = args || []
var arity = fn.length
return function() {
var _args = Array.prototype.slice.call(arguments)
Array.prototype.unshift.call(_args, ...args)
_args = _args.concat(args)
if (_args.length < arity) {
return currying.call(null, fn, _args)
}
return fn.apply(null, _args)
}
}
ES6中允许使用“箭头”( =>) 来定义函数。箭头函数相当于匿名函数,并且简化了函数定义
特点
ES6新增的一种异步编程的解决方案,比传统的回调函数和事件更加的合理和强大。通过Promise
可以将异步操作以同步操作的流程表达出来,避免了层层嵌套的回调函数。Promise
可以解决异步的问题,但不能说Promise是异步的
Promise
对象代表一个异步操作,有三种状态:pending
——进行中resolved
——已成功rejected
——已失败Promise
对象状态的改变只有两种可能:pending
——resolved
pending
——rejected
Promise
内部发生错误,不会影响到外部程序的执行。Promise
一旦执行则无法取消:Promise
内部抛出的错误将不会反应到外部(缺点2)pending
状态时,无法得知目前进展到哪一阶段,即无法预测是刚刚开始还是即将完成(缺点3)创建Promise
实例时,必须传入一个函数作为参数:
new Promise(() => {})
该函数可以接收另外两个由JavaScript引擎提供的函数,resolve
和reject
:
resolve
——将Promise
对象的状态从pending
变为resolved
,将异步操作的结果作为参数传递出去reject
——将Promise
对象的状态从pending
变为rejected
,将异步操作报出的错误作为参数传递出去const promise = new Promise((resolve, reject) => {
if (true) resolve('value')
else reject('error')
})
Promise
实例生成以后,可以用then
方法分别指定resolved
状态和rejected
状态的回调函数:
promise.then(value => {
console.log(value) // 'value'
}, error => {
console.log(error) // 'error'
})
当then
方法只有一个函数参数时,此时为resolved
状态的回调方法:
promise.then(value => {
// 只有状态为resolved时才能调用,如果返回的是rejected状态,则报错 Uncaught (in promise) error
console.log(value)
})
只有当promise
的状态变为resolved
或者rejected
时,then
方法才会被调用
Promise
新建后就会立即执行,并且调用resolve
或reject
后不会终结 Promise
的参数函数的执行。
let promise = new Promise(resolve => {
console.log('1')
resolve()
console.log('2')
})
promise.then(resolved => {
console.log('3')
})
console.log('3')
resolve
返回的是另外一个Promise
实例:
const p1 = new Promise((_, reject) => {
setTimeout(() => reject('error'), 3000);
})
const p2 = new Promise(resolve => {
setTimeout(() => resolve(p1), 1000);
})
p2.then(
result => console.log(result),
error => console.log(error) // error
)
上面代码中,p1
是一个Promise
,3 秒之后变为rejected
。p2
的状态在 1 秒之后改变,resolve
方法返回的是p1
。由于p2
返回的是另一个 Promise,导致p2
自己的状态无效了,由p1
的状态决定p2
的状态。所以,后面的then
语句都变成针对后者(p1
)。又过了 2 秒,p1
变为rejected
,导致触发catch
方法指定的回调函数。可以理解成p2.then
实际上是p1.then
当resolve
返回的是另一个Promise
实例的时候,当前Promise
实例的状态会根据返回的Promise
实例的状态来决定
Promise.resolve()
有时需要将现有对象转为 Promise 对象,Promise.resolve()
方法就起到这个作用,且实例状态为resolve:
Promise.resolve('foo')
// 等价于
return new Promise(resolve => resolve('foo'))
Promise.resolve()
的参数有以下几种情况:
Promise
实例:const promise = new Promise(resolve => {
resolve('resolve')
})
let p = Promise.resolve(promise)
// p 相当于
let p = new Promise(resolve => {
resolve(promise)
})
console.log(p === promise) // true
thenable
对象:thenable
对象指的是具有then方法的对象,Promise.resolve()
会将这个对象转为Promise
对象,然后立即执行thenable
对象的then
方法
const thenable = {
then(resolve, reject) {
resolve('resolved')
}
}
const p1 = Promise.resolve(thenable)
p1.then(res => {
console.log(res) // 'resolved'
})
上面代码中,thenable
对象的then()
方法执行后,对象p1
的状态就变为resolved
,从而立即执行最后那个then()
方法指定的回调函数,输出'resolved'
then()
方法的对象,或者根本不是对象const promise = Promise.resolve({name: 'James'})
promise.then(res => {
console.log(res) // {name: 'James'}
})
当参数是不含有 then()
方法的对象,或者根本不是对象时,会直接返回该参数
const promise = Promise.resolve()
promise.then(res => {
console.log(res) // undefined
})
Promise.resolve()
方法允许调用时不带参数,直接返回一个resolved
状态的Promise
对象,传参为undefined
Promise.reject()
Promise.reject(reason)
方法也会返回一个新的 Promise
实例,该实例的状态为rejected
const promise = Promise.reject('Error')
// 等价于
const promise = new Promise((resolve, reject) => {
reject('Error')
})
Promise.all()
Promise.all()
方法用于将多个 Promise
实例,包装成一个新的 Promise
实例
const p1 = new Promise((resolve, reject) => {})
const p1 = new Promise((resolve, reject) => {})
const p1 = new Promise((resolve, reject) => {})
const promise = Promise.all([p1, p2, p3])
promise.then(result => {}, error => {})
面代码中,Promise.all()
方法接受一个数组作为参数,p1
、p2
、p3
都是 Promise 实例,如果不是,就会调用Promise.resolve
方法,将参数转为 Promise
实例,再进一步处理。另外,Promise.all()
方法的参数可以不是数组,但必须具有 Iterator
接口,且返回的每个成员都是 Promise
实例。p
的状态由p1
、p2
、p3
决定,分成两种情况:
p1
、p2
、p3
的状态都变成fulfilled
,p
的状态才会变成fulfilled
,此时p1
、p2
、p3
的返回值组成一个数组,传递给p
的回调函数p1
、p2
、p3
之中有一个被rejected
,p
的状态就变成rejected
,此时第一个被reject
的实例的返回值,会传递给p
的回调函数const number = 35
const p1 = new Promise((resolve, reject) => {
if (number >= 10) resolve('p1 success!')
else reject('p1 failed!')
})
const p2 = new Promise((resolve, reject) => {
if (number >= 20) resolve('p2 success!')
else reject('p2 failed!')
})
const p3 = new Promise((resolve, reject) => {
if (number >= 30) resolve('p3 success!')
else reject('p3 failed!')
})
const promise = Promise.all([p1, p2, p3]).then(res => {
console.log(res) // 当number为35时,res值为[ 'p1 success!', 'p2 success!', 'p3 success!' ]
}, error => {
console.log(error) // 当number为25时,p3会返回rejected,promise状态会变成rejected,error值为p3 failed!
})
如果作为参数的Promise
实例,自己定义了catch
方法,那么它一旦被rejected
,并不会触发Promise.all()
的catch
方法
const p1 = new Promise(resolve => {
resolve("hello");
}).then(result => result).catch(e => e);
const p2 = new Promise(() => {
throw new Error("报错了");
}).then(result => result).catch(e => e); // p2实际上是catch返回的promise实例
Promise.all([p1, p2]).then(result => console.log(result)).catch(e => console.log(e));
为了解决单任务执行过长的问题
,和处理高优先级的任务
,所以需要将任务划
我们知道了浏览器页面是由任务队列和事件循环系统来驱动的,但是队列要一个一个执行,如果某个任务(http请求)是个耗时任务,那浏览器总不能一直卡着,所以为了防止主线程阻塞,就将任务分为同步任务和异步任务
JS执行时,V8会创建一个全局执行上下文,在创建上下文的同时, V8也会在内部创建一个微任务队列
有微任务队列,自然就有宏任务队列,宏任务队列中的每一个任务则都称为宏任务,在当前宏任务执行过程中,如果有新的微任务产生,就添加到微任务队列中
367e4062e66b2c2512768749e533393.jpg
任务进栈到出栈的循环。即一个宏任务,所有微任务,渲染;一个宏任务,所有微任务,渲染.....
循环过程
输出结果:promise1
->promise2
->script
->promise3
->setTimeout
async
声明function是一个异步函数,返回一个promise
对象,可以使用 then 方法添加回调函数。
async
函数内部return
语句返回的值,会成为then
方法回调函数的参数。
如果async函数没有返回值 async函数返回一个undefined的promise对象
async function test() {
return 'test'
}
console.log(test) // [AsyncFunction: test] async函数是[`AsyncFunction`]构造函数的实例
console.log(test()) // Promise { 'test' }
// async返回的是一个promise对象
test().then(res => {
console.log(res) // 'test'
})
await 操作符只能在异步函数 async function 内部使用
如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果,也就是说它会阻塞后面的代码,等待 Promise 对象结果;如果等待的不是 Promise 对象,则返回该值本身
async function test() {
return new Promise((resolve)=>{
setTimeout(() => {
resolve('test 1000');
}, 1000);
})
}
function fn() {
return 'fn';
}
async function next() {
let res0 = await fn(),
res1 = await test(),
res2 = await fn();
console.log(res0);
console.log(res1);
console.log(res2);
}
next(); // 1s 后才打印出结果 为什么呢 就是因为 res1在等待promise的结果 阻塞了后面代码。
如果await
后面的异步操作出错,那么等同于async
函数返回的 Promise 对象被reject
async function test() {
await Promise.reject('错误了')
}
test().then(res=>{
console.log('success',res)
},err=>{
console.log('err ',err) // err 错误了
})
防止出错的方法,也是将其放在 try...catch
代码块之中
async function test() {
try {
await new Promise(function (resolve, reject) {
throw new Error('错误了');
});
} catch(e) {
console.log('err', e)
}
return await('成功了');
}
多个 await
命令后面的异步操作,如果不存在继发关系(即互不依赖),最好让它们同时触发
let foo = await getFoo();
let bar = await getBar();
// 上面这样写法 getFoo完成以后,才会执行getBar
// 同时触发写法 ↓
// 写法一
let [foo, bar] = await Promise.all([getFoo(), getBar()]);
// 写法二
let fooPromise = getFoo();
let barPromise = getBar();
let foo = await fooPromise;
let bar = await barPromise;
async/await的优势在于处理由多个Promise组成的 then 链,在之前的Promise文章中提过用then处理回调地狱的问题,async/await相当于对promise的进一步优化。 假设一个业务,分多个步骤,且每个步骤都是异步的,而且依赖上个步骤的执行结果
// 假设表单提交前要通过俩个校验接口
async function check(ms) { // 模仿异步
return new Promise((resolve)=>{
setTimeout(() => {
resolve(`check ${ms}`);
}, ms);
})
}
function check1() {
console.log('check1');
return check(1000);
}
function check2() {
console.log('check2');
return check(2000);
}
// -------------promise------------
function submit() {
console.log('submit');
// 经过俩个校验 多级关联 promise传值嵌套较深
check1().then(res1=>{
check2(res1).then(res2=>{
/*
* 提交请求
*/
})
})
}
submit();
// -------------async/await-----------
async function asyncAwaitSubmit() {
let res1 = await check1(),
res2 = await check2(res1);
console.log(res1, res2);
/*
* 提交请求
*/
}
当事件触发时,相应的函数不会立即触发,而是等待一段时间;
当事件连续触发时,函数的触发等待时间会被不断重置(推迟)。
通俗的讲,防抖就是,每次触发事件时,在一段时间后才真正响应这个事件,具体应用如下:
function debounce(callback, time) {
let timer
return function() {
clearTimeout(timer)
let args = arguments
timer = setTimeout(() => {
callback.apply(this, args)
}, time)
}
}
如果事件被频繁出发,那么节流函数会按照一定的频率来执行函数;
不管中间触发了多少次,执行函数的 频率总是固定的。
说白了节流就是在间隔一段时间执行一次,具体应用如下:
function throttle(func, delay) {
let timer;
return function() {
let args = arguments;
if (!timer) {
timer = setTimeout(() => {
timer = null;
func.apply(this, args)
}, delay)
}
}
}
js var a = [1,2,3,4,5] var b = a.forEach((item) => { item = item * 2 }) console.log(b) // undefined
return
跳过当前循环;null
和undefined
;js var a = [null, , undefined] a.forEach(item => { console.log('item', item) // null undefined })
js var a = [1,2,3,4,5] a.forEach((item) => { item = item * 2 }) console.log(a) // [1,2,3,4,5]
重新赋值
,即新对象的值为2,原数组中的对象还是{num:1}
。js var a = [1,'1',{num:1},true] a.forEach((item, index, arr) => { item = 2 }) console.log(a) // [1,'1',{num:1},true]
js var a = [1,'1',{num:1},true] a.forEach((item, index, arr) => { item.num = 2 item = 2 }) console.log(a) // [1,'1',{num:2},true]
js Array.prototype.new_forEach = function(callback) { for (let i = 0; i < this.length; i++) { callback(this[i], i, this) } }
js var a = [1,2,3,4,5] var b = a.map((item) => { return item = item * 2 }) console.log(a) // [1,2,3,4,5] console.log(b) // [2,4,6,8,10]
return
跳过当前循环,同forEach;null
和undefined
,同forEach;js Array.prototype.new_map = function(callback) { const res = [] for (let i = 0; i < this.length; i++) { res.push(callback(this[i], i, this)) } return res }
break
结束循环;continue
语句跳过当前循环;array.forEach(function(currentValue, index, arr), thisValue)
它不是普通的 for 循环的语法糖,还有诸多参数和上下文需要在执行的时候考虑进来,这里可能拖慢性能;1. 原型可以解决什么问题
对象共享属性和共享方法
2. 谁有原型
函数拥有:prototype
对象拥有:__proto__
3. 对象查找属性或者方法的顺序
先在对象本身查找 --> 构造函数中查找 --> 对象的原型 --> 构造函数的原型中 --> 当前原型的原型中查找
4. 原型链
4.1 是什么?:就是把原型串联起来
4.2 原型链的最顶端是null5.原型链的原理和作用:类的公有方法不会开辟新内存,以此达到节约内存的目的,可以优化项目性能;对象里面放自己独有的数据和方法代码 原型上面放对象共有的数据和代码;js用原型链来实现继承。
方式一:isArray
var arr = [1,2,3]; console.log( Array.isArray( arr ) );
方式二:instanceof 【可写,可不写】
var arr = [1,2,3]; console.log( arr instanceof Array );
方式三:原型prototype
var arr = [1,2,3]; console.log( Object.prototype.toString.call(arr).indexOf('Array') > -1 );
方式四:isPrototypeOf()
var arr = [1,2,3]; console.log( Array.prototype.isPrototypeOf(arr) )
方式五:constructor
var arr = [1,2,3]; console.log( arr.constructor.toString().indexOf('Array') > -1 )
1. slice是来截取的 参数可以写slice(3)、slice(1,3)、slice(-3) 返回的是一个新的数组 2. splice 功能有:插入、删除、替换 返回:删除的元素 该方法会改变原数组
方式一:new set
var arr1 = [1,2,3,2,4,1]; function unique(arr){ return [...new Set(arr)] } console.log( unique(arr1) );
方式二:indexOf
var arr2 = [1,2,3,2,4,1]; function unique( arr ){ var brr = []; for( var i=0;i
方式三:sort
var arr3 = [1,2,3,2,4,1]; function unique( arr ){ arr = arr.sort(); var brr = []; for(var i=0;i
1. 创建了一个空的对象
2. 将空对象的原型,指向于构造函数的原型
3. 将空对象作为构造函数的上下文(改变this指向)
4. 对构造函数有返回值的处理判断
js继承有哪些方式?
方式一:ES6
class Parent{ constructor(){ this.age = 18; } } class Child extends Parent{ constructor(){ super(); this.name = '张三'; } } let o1 = new Child(); console.log( o1,o1.name,o1.age );
方式二:原型链继承
function Parent(){ this.age = 20; } function Child(){ this.name = '张三' } Child.prototype = new Parent(); let o2 = new Child(); console.log( o2,o2.name,o2.age );
方式三:借用构造函数继承
function Parent(){ this.age = 22; } function Child(){ this.name = '张三' Parent.call(this); } let o3 = new Child(); console.log( o3,o3.name,o3.age );
方式四:组合式继承
function Parent(){ this.age = 100; } function Child(){ Parent.call(this); this.name = '张三' } Child.prototype = new Parent(); let o4 = new Child(); console.log( o4,o4.name,o4.age );
共同点:功能一致
可以改变this指向 语法: 函数.call()、函数.apply()、函数.bind()
区别:
1. call、apply可以立即执行。bind不会立即执行,因为bind返回的是一个函数需要加入()执行。 2. 参数不同:apply第二个参数是数组。call和bind有多个参数需要挨个写。
场景:
1. 用apply的情况 var arr1 = [1,2,4,5,7,3,321]; console.log( Math.max.apply(null,arr1) ) 2. 用bind的情况 var btn = document.getElementById('btn'); var h1s = document.getElementById('h1s'); btn.onclick = function(){ console.log( this.id ); }.bind(h1s)
1. computed vs methods区别 computed是有缓存的 methods没有缓存 watch也没有缓存 2. computed vs watch区别 watch是监听,数据或者路由发生了改变才可以响应(执行) computed计算某一个属性的改变,如果某一个值改变了,计算属性会监听到进行返回 watch是当前监听到数据改变了,才会执行内部代码计算属性缓存结果时每次都会重新创建变量,而侦听器是直接计算,不会创建变量保存结果。也就意味着,数据如果会反复的发生变化,计算很多次的情况下,计算属性的开销将会更大,也就意味着这种情况不适合使用计算属性,适合使用侦听器。
那么,如果一个数据反复会被使用,但是它计算依赖的内容很少发生变化的情况下,计算属性会缓存结果,就更加适合这种情况。
计算属性:
- 支持缓存,只有当数据放生改变时,才会重新进行计算
- 不支持异步,当computed内有异步操作时无效,无法将听数据的变化。
- computed 属性值会默认走缓存,计算属性是基于它们的响应式依赖进行缓存的,也就是基于data中声明过或者父组件传递的props中的数据通过计算得到的值
- 如果一个属性是由其他属性计算而来的,这个属性依赖其他属性,是一个多对一或者一对一,一般用computed
- 如果computed属性属性值是函数,那么默认会走get方法;函数的返回值就是属性的属性值;在computed中的,属性都有一个get和一个set方法,当数据变化时,调用set方法。
监视属性watch:
- 不支持缓存,数据变,直接会触发相应的操作;
- watch支持异步;
- 监听的函数接收两个参数,第一个参数是最新的值;第二个参数是输入之前的值;
- 当一个属性发生变化时,需要执行对应的操作;一对多;
- 监听数据必须是data中声明过或者父组件传递过来的props中的数据,当数据变化时,触发其他操作,函数有两个参数,
两个重要的小原则:
- 所被Vue管理的函数,最好写成普通函数,这样this的指向才是vm或组件实例对象。
- 所有不必Vue所管理的函数(定时器的回调函数,ajax的回调函数等),最好写成箭头函数,这样this的指向才是vm或者组件实例对象。
面 试题:props和data优先级谁高?
当一个Vue实例创建时,Vue会遍历data中的属性,用Object.defineProperty将它们转为getter/setter,并且在内部追踪相关依赖,在属性被访问和修改时通知变化。每个组件实例都有相应的watcher程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的setter被调用时,会通知watcher重新计算,从而使它关联的组件得以更新
Snipaste_2022-10-07_10-07-50.png
利用Object.defineProperty劫持对象的访问器,在属性发生变化时我们可以获取变化,从而进行下一步操作
在软件架构中,发布订阅是一种消息范式,发布者不会将消息直接发送给订阅者,而是将发布的消息分为不同的类别,无需了解哪些订阅者是否存在。同样的,订阅者可以表达对一个或多个类别的兴趣,只接收感兴趣的消息,无需了解哪些发布者是否存在
发布者和订阅者都不知道对方的存在,发布者只需发送消息到订阅器里,订阅者只管接收自己订阅的内容
vue的响应式原理就是 采用数据劫持结合发布者-订阅者模式的方式,通过Object.defineProperty()来劫持各个属性的getter和setter,在数据变动时发布消息给订阅者,触发相应的监听回调。只要分为以下几个步骤:
Snipaste_2022-10-07_10-09-27.png
undefined
。当访问该属性时,会调用此函数。执行时不传入任何参数,但是会传入 this
对象(由于继承关系,这里的this
并不一定是定义该属性的对象)。该函数的返回值会被用作属性的值undefined
]set 属性的 setter 函数,如果没有 setter,则为 undefined
。当属性值被修改时,会调用此函数。该方法接受一个参数(也就是被赋予的新值),会传入赋值时的 this
对象。 默认为 [undefined
]缺点:在对一些属性进行操作时,使用这种方法无法拦截,比如通过下标方式修改数组数据或者给对象新增属性,这都不能触发组件的重新渲染,因为 Object.defineProperty() 不能拦截到这些操作。更精确的来说,对于数组而言,大部分操作都是拦截不到的,只是 Vue 内部通过重写函数的方式解决了这个问题
MVVM(Model-View-ViewModel)模式是一种基于前端开发的架构模式,其核心是提供对 View 和 Model 的双向数据绑定,即当 View 或 Model 其中任意一个发生变化时,都会通过 ViewModel 引起另外一个的改变。
Snipaste_2022-10-07_10-12-52.png
指令 | 作用 |
---|---|
v-on | 缩写为@,绑定事件 |
v-bind | 简写为:,动态绑定 |
v-slot | 简写为#,组件插槽 |
v-for | 循环对象或数组元素,同时生成DOM |
v-show | 显示内容与否 |
v-if | 显示与隐藏,决定当前DOM元素是否渲染 |
v-else | 必须和v-if连用 不能单独使用 否则报错 |
v-text | 解析文本 |
v-html | 解析html标签 |
class | style | |
---|---|---|
绑定对象 | :class="{ className: isActive }" 或 :class="classNameObject" | :style="{color: '#ffffff'}" 或 :style="styleObject" |
绑定数组 | :class="['active', 'is-success', { 'is-disabled': isDisabled }]" | :style="[styleObject1, styleObject2, styleObject3, ...]" |
修饰符 | 作用 |
---|---|
.stop | event.stopPropagation(),阻止单击事件继续传播(阻止默认事件) |
.prevent | event.preventDefault(),提交事件不再重载页面(阻止默认行为) |
.native | 监听组件根元素的原生事件 |
.once | 点击事件将只会触发一次 |
修饰符 | 作用 |
---|---|
.lazy | 取代 input 监听 change 事件 |
.number | 输入值转为数值类型 |
.trim | 输入首尾空格过滤 |
需要频繁切换DOM时,使用v-show;反之则使用v-if
v-show和v-if都是用来控制元素的呈现与隐藏。v-show只会编译一次,通过控制css中的display实现(频繁切换状态用v-show)。v-if编译多次,通过动态控制DOM元素实现(不频繁切换)。
Vue 处理指令时,v-for 比 v-if 具有更高的 优先级,存在性能问题。 如果你有5个元素被v-for循环,,v-if也会分别执行5次
提升vue渲染性能
使用index作为key和没写基本上没区别,因为不管数组的顺序怎么颠倒,index 都是 0, 1, 2...这样排列,导致 Vue 会复用错误的旧子节点,做很多无意义的额外工作
v-model用于实现视图层与数据层的双向绑定,数据层变化时可以驱动视图层更新,当视图层变化时会改变数据。v-model本质上是一个语法糖,默认情况下相当于:value
和@input
的结合。
v-model通常使用在表单项上,但也能使用在自定义组件上,表示对某个值的输入和输出控制。使用v-model可以减少大量繁琐的事件处理代码,提高开发效率。
共同点:都是语法糖,都可以实现父子组件中数据的双向通信
不同点:
v-model | .sync |
---|---|
父组件中使用v-model传递数据时,子组件通过@input触发事件 | 父组件中传递数据时,子组件通过@update:xxx触发事件 |
一个组件只能绑定一个v-model vue2 | 一个组件可以多个属性用.sync修饰符,可以同时"双向绑定多个“prop” |
v-model针对更多的是最终操作结果,是双向绑定的结果,是value,是一种change操作 | .sync针对更多的是各种各样的状态,是状态的互相传递,是status,是一种update操作 |
computed
watch
当想要执行异步或者昂贵的操作以响应不断的变化时,就需要使用watch
总结
computed
计算属性 : 依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值。
watch
侦听器 : 更多的是 观察的作用, 无缓存性,类似于某些数据的监听回调,每当监听的数据变化时都会执行回调进行后续操作。
组件通信指的是组件通过某一种方式来传递信息以达到某个目的的过程。每个组件都是独立的,在开发中我们就是通过组件通信使各个组件的功能联动起来
父组件通过props向子组件传递数据,子组件通过$emit事件和父组件通信
v-on
注册监听事件同时接收参数这种方式是通过依赖注入的方式实现组件的(可跨级)通信。 依赖注入所提供的属性是非响应式的。
在父组件中通过ref可以获取子组件实例,通过实例来访问子组件的属性和方法
inheritAttrs:默认值为true,继承所有的父组件属性(除props之外的所有属性),为false表示只继承class属性
v-on="$listeners"
将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)eventBus事件总线适用于父子组件、非父子组件等之间的通信。这种组件通信方式会造成后期维护困难。vue3中移除了事件总线,取而代之的是插件,使用方式并无变化。
子组件不能直接修改父组件传递的数据,这样做是维护父子组件之间形成的单向数据流。如果子组件随意更改父组件传递的数据,会导致数据流混乱,提高开发和维护成本
生命周期指的是Vue组件从创建到销毁经历的一系列的过程
this
仍能获取到实例父子组件的生命周期执行顺序
created和mounted的区别
一般在哪个生命周期请求异步数据
我们可以在钩子函数created
、beforeMount
、mounted
中进行调用,因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值
推荐在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有以下优点:
keep-alive用于缓存组件。在进行动态组件切换的时候对组件内部数据进行缓存,而不是走销毁流程。keep-alive是一个抽象组件,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。 当组件在内被切换,它的
activated
和deactivated
这两个生命周期钩子函数将会被对应执行
包含的参数:
activated | deactivated |
---|---|
在 keep-alive 组件激活时调用 | 在keep-alive 组件停用时调用 |
该钩子函数在服务器端渲染期间不被调用 | 该钩子在服务器端渲染期间不被调用 |
设置缓存后的钩子调用情况:
slot是Vue的内容分发机制,组件内部的模板引擎使用slot元素作为承载分发内容的出口。插槽slot是子组件的一个模板标签元素,而这一个标签元素是否显示,以及怎么显示是由父组件决定的
子组件用标签来确定渲染的位置,标签里面可以放
DOM
结构,当父组件使用的时候没有往插槽传入内容,标签内DOM
结构就会显示在页面。父组件在使用的时候,直接在子组件的标签内写入内容即可
子组件用name
属性来表示插槽的名字,不传为默认插槽。父组件中在使用时在默认插槽的基础上加上slot
属性,值为子组件插槽name
属性值
子组件在作用域上绑定属性来将子组件的信息传给父组件使用,这些属性会被挂在父组件v-slot
接受的对象上父组件中在使用时通过v-slot:
(简写:#)获取子组件的信息,在内容中使用
小结:
v-slot
属性只能在
上使用,但在只有默认插槽时可以在组件标签上使用default
,可以省略default直接写v-slot
。缩写为#
时不能不写参数,写成#default
v-slot={user}
,还可以重命名v-slot="{user: newName}"
和定义默认值v-slot="{user = '默认值'}"
保证每个组件内数据的独立性,防止出现变量污染。对象为引用类型,当复用组件时,由于数据对象都指向同一个data对象,当在一个组件中修改data时,其他重用的组件中的data会同时被修改;而使用返回对象的函数,由于每次返回的都是一个新对象(Object的实例),引用地址不同,则不会出现这个问题。
前端路由的核心,就在于改变视图的同时不会向后端发出请求;而是加载路由对应的组件。Vue-Router就是将组件映射到路由, 然后渲染出来的
Vue-Router是Vue官方的路由管理器。它和Vue.js的核心深度集成,路径和组件的映射关系使得构建SPA(Single Page Application,单页面应用)变得易如反掌
优点:
缺点:
基于浏览器的hashchange事件,当url发生变化时,通过 window.location.hash
获取地址上的hash值,并通过Router类,配置routes对象设置与hash值对应的组件内容
优点:
缺点:
基于H5新增的pushState()和replaceState()两个api,以及浏览器的popstate事件,地址变化时,通过 window.location.pathname
找到对应的组件,并通过构造Router类,配置routes对象设置pathname值与对应的组件内容
优点:
缺点:
hash
模式是一种把前端路由的路径用#
拼接在真实url
后面的模式。当#
后面的路径发生变化时,浏览器并不会重新发起请求,而是会触发onhashchange
事件。
hash模式的特点:
hash
变化会触发网页跳转,即浏览器的前进和后退hash
可以改变 url
,但是不会触发页面重新加载(hash的改变是记录在 window.history
中),即不会刷新页面。也就是说,所有页面的跳转都是在客户端进行操作。因此,这并不算是一次 http
请求,所以这种模式不利于 SEO
优化。hash
只能修改 #
后面的部分,所以只能跳转到与当前 url
同文档的 url
hash
通过触发 hashchange
事件,来监听 hash
的改变,借此实现无刷新跳转的功能hash
永远不会提交到 server
端(可以理解为只在前端自生自灭)history API
是 H5
提供的新特性,允许开发者直接更改前端路由,即更新浏览器 URL
地址而不重新发起请求
url
可以是与当前 url
同源的任意 url
,也可以是与当前 url
一样的地址,但是这样会导致的一个问题是,会把重复的这一次操作记录到栈当中history.state
,添加任意类型的数据到记录中title
属性,以便后续使用pushState
、 replaceState
来实现无刷新跳转的功能,需要后端配合方式 | 作用 |
---|---|
$router.push() | 跳转到指定的url,并在history中添加记录,点击回退返回到上一个页面 |
$router.replace() | 跳转到指定的url,但是history中不会添加记录,点击回退到上上个页面 |
$router.go(n) | 向前或者后跳转n个页面,n可以是正数也可以是负数 |
$router.back() | 后退、回到上一页 |
$router.forward() | 前进、回到下一页 |
query传参需要使用path来引入,页面跳转后参数将会出现在url中;也可以直降将参数以?xxx=xx&xxx=xx的形式拼接在路由地址中
this.$router.push({ path: '/example', query: { id: '1'}}) // 传递query参数
this.$route.query.id // 获取query参数
以query的方式传参时,刷新页面不会导致参数丢失
params传参需要使用name来引入,页面跳转后参数不会出现在url中;在4.1.4版本开始,在未定义动态路由的情况下,将不能直接使用编程式导航传递params参数,目的是解决刷新页面参数丢失的问题
传参的方式总结
原文链接:2023前端面试必备个人总结(持续更新中) - 知乎