1.CSS3 中伪元素 after 和 before
伪元素选择器可以帮助我们利用CSS创建新标签元素,而不需要HTML标签,从而简化HTML结构。两个重要的伪元素是::before、::after。伪元素前面是用两个冒号表示。
:: before:在元素内部的前面插入内容
:: after:在元素内部的后面插入内容
注:
before 和 after 创建的元素属于行内元素
before 和 after 创建的元素在文档树中找不到,所以称之为伪元素
语法:element::before{}
before 和 after 必须要有 content 属性
伪元素选择器和标签选择器权重一样:0,0,0,1
2.盒模型概念
标准盒子模型和怪异盒模型
1)一个盒子由外到内可以分成四个部分:margin(外边距)、border(边框)、padding(内边距)、content(内容)。会发现margin、border、padding是CSS属性,因此可以通过这三个属性来控制盒子的这三个部分。而content则是HTML元素的内容。
2)width和height属性设置的就是盒子的宽度和高度。盒子的宽度和高度的计算方式由box-sizing属性控制。
box-sizing属性值
content-box:默认值,width和height属性分别应用到元素的内容框。在宽度和高度之外绘制元素的内边距、边框、外边距。
border-box:为元素设定的width和height属性决定了元素的边框盒。就是说,为元素指定的任何内边距和边框都将在已设定的宽度和高度内进行绘制。通过从已设定的宽度和高度分别减去 边框 和 内边距 才能得到内容的宽度和高度。
inherit:规定应从父元素继承box-sizing属性的值
当box-sizing:content-box时,这种盒子模型成为标准盒子模型,当box-sizing: border-box时,这种盒子模型称为IE盒子模型(怪异盒模型)。
3.元素居中方法
1)行内元素实现水平垂直居中:
text-align: center;(text-align: center只能实现文本的水平居中)
line-height: 50px;(line-height不能实现多行文本的垂直居中)
padding:50px;(不固定高度的垂直居中 通过设置padding实现)
使用display设置为table,配合样式vertical-align设置为middle来实现,如下:
父元素{
display:table;
}
子元素{
display:table-cell;
vertical-align:middle;
}
2)块级元素实现水平垂直居中:
第一种方式:使用弹性盒模型实现水平垂直居中
display: flex;
justify-content: center;
align-items: center;
第二种方式:采取绝对定位配合margin的方式实现(这种方式有缺陷 需要知道固定的宽度和高度才行)
position: absolute;
left:50%;
top:50%;
margin-left: -50px;(高度设置了100px,margin-left就是宽度的一半)
margin-top: -50px;(宽度也设置了100px,margin-top就是高度的一半)
第三种方式:可以采取借助css3的变形属性transform来实现的方式实现
position: absolute;
left:50%;
top:50%;
transform: translate(-50%,-50%);(在当前位置偏移自身宽高的一半)
第四种方式:需要盒子有宽高,但是不需要去计算偏移盒子的宽高
position: absolute;
left:0;
top:0;
right:0;
bottom:0;
margin:auto;
height:100px;
width:100px;
4.防抖和节流
防抖(Debounce)和节流(Throttle)都是用来控制某个函数在一定时间内触发次数,两者都是为了减少触发频率,以便提高性能或者说避免资源浪费。毕竟JS操作DOM对象的代价还是十分昂贵的。
应用场景:处理一些频繁触发的事件,例如mousedown、mousemove、keyup、keydown等,不然的话,页面很可能会十分卡顿哦~
1)防抖
防抖就是指触发事件后在 n 秒内函数只能执行一次,最后一次触发的函数,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。
function debounce(fn, delay){
let timerId = null
return function(){
const context = this
if(timerId){window.clearTimeout(timerId)}
timerId = setTimeout(()=>{
fn.apply(context, arguments)
timerId = null
},delay)
}
}
2)节流
节流就是指连续触发事件但是在 n 秒中只执行一次函数,第一次触发的函数。节流会稀释函数的执行频率
function throttle(fn, delay){
let canUse = true
return function(){
if(canUse){
fn.apply(this, arguments)
canUse = false
setTimeout(()=>canUse = true, delay)
}
}
}
5.深拷贝和浅拷贝?
1)浅拷贝
创建一个新对象,这个对象有着原始对象属性值的一份精确拷贝。如果属性是值类型和string类型,拷贝的就是基本类型的值;如果属性是引用类型,拷贝的就是内存地址(string类型除外),所以修改其中一个对象,就会影响到另一个对象。
1 Object.assign(target, source)
2 = 赋值
2)深拷贝
将一个对象从内存中完整的拷贝一份出来,从堆内存中开辟一个新的区域存放新对象,且修改新对象和原对象的修改不会相互影响.
1 JSON.parse(JSON.stringify(obj1))
2 递归
3 **jquery** $.extent(true,{},sourceObj)
4 **es5写法** require('lodash')
5 **es6写法** [object/array].cloneDeep(sourceObj)
3)二者区别
最根本的区别在于是否真正获取一个对象的复制实体,而不是引用,假设B复制了A,修改A的时候,看B是否发生变化:
(1).如果B跟着也变了,说明是浅拷贝,拿人手短!(修改堆内存中的同一个值)
(2).如果B没有改变,说明是深拷贝,自食其力!(修改堆内存中的不同的值)
简单的来说:
如果拷贝的时候共享被引用的对象就是浅拷贝,如果被引用的对象也拷贝一份出来就是深拷贝。(深拷贝就是说重新new一个对象,然后把之前的那个对象的属性值在重新赋值给这个用户)
6.forEach用什么方法结束?
正常终止for循环,我们可以使用break关键字来实现;
forEach循环,不能使用break和continue这两个关键字;
因为这两个关键字要在循环中使用,而forEach中所执行的是callback,callback是个函数所以不能使用;
使用 return 的话,只能跳出本次循环执行下一次循环,并不会终止forEach循环;
运用抛出异常(try catch)****:throw new Error('error message');
7.怎么结束for循环的一层?
1)continue
当程序运行到 continue; 语句时,会终止当前的这一次循环,进入下一次的循环中。
2)break
当程序运行到 break; 语句时,会结束当前的循环,执行循环后面(外边)的语句。
如果多层循环,只会退出当前的循环,执行该循环后的语句,外循环不受影响。
一般配合判断语句使用
3)return
当程序运行到 return; 语句时,会终止循环,结束当前方法。
8.for in 和 for of ?
for in 和 for of 简单来说就是它们两者都可以用于遍历。
for in 和 for of 的本质区别
1)使用的目标不一样
1-1、for in 适用于 可枚举属性,例如 对象、数组、字符串。
可枚举属性:属性的 enumerable 值为 true 就为可枚举的,通过Object.getOwnPropertyDescriptors() 或 Object.prototype.propertyIsEnumerable() 进行查看 / 验证
1-2、for of 适用于 可迭代对象,像Array、String、Map、Set、函数的arguments对象、nodeList对象
可迭代:ES6中,具有Symbol.iterator 属性,它对应的值是一个函数,调用这个函数后可得到一个对象,每次调用这个对象的next() 方法能得到目标的每一项,只要符合这个特点就是可迭代的
2)遍历的范围不一样
for in 能遍历自身的可枚举属性 && 原型上的可枚举属性
for of 一般只能遍历自身的可枚举属性(具体和迭代器内部的实现有关)
3)得到的结果不一样
for in 得到的是key(并且不能保证顺序)
for of 一般得到的是value(具体和迭代器内部的实现有关)
9.如果要取数组第100条数据,怎么取?
- Array[100]
- for / for in / for of / forEach,但是循环到第一百条之后的数据就白跑了,需要结束循环,节省性能
10.数组哪些方法改变原数组哪些方法不改变原数组?
1)会改变原数组的方法
1.1 push
作用:push 方法往数组里面添加元素,返回数组的长度,原数组会发生改变。
1.2 unshift
作用:unshift 方法会在数组的开头添加一个元素,它会返回数组新的长度,原数组会发生改变。
1.3 pop
作用:pop() 方法从数组中删除最后一个元素,并返回该元素的值,原数组会发生改变。
1.4 shift
作用:shift() 方法从数组中删除第一个元素,并返回该元素的值原数组会发生改变。
1.5 sort
作用:sort() 方法用原地算法对数组的元素进行排序,并返回数组。默认排序顺序是在将元素转换为字符串,然后比较它们的 UTF-16 代码单元值序列时构建的。它返回的就是排序后的数组,原数组会发生改变。
注意:因为 sort 的默认排序是将元素转化为字符串后排序的,所以上述代码中的排序结果可能不是我们想要的,想要获得正确的排序结果,我们可以传入一个函数,来规定排序的规则。例:arr5.sort((a, b) => a - b);
1.6 splice
作用:splice() 方法用于添加或删除数组中的元素,如果删除一个元素,则返回一个元素的数组。 如果未删除任何元素,则返回空数组,原数组会发生改变。
1.7 reverse
作用:reverse() 方法用于颠倒数组中元素的顺序,原数组会发生改变。
2)不会改变原数组的方法
2.1 concat
作用:concat() 方法用于连接两个或多个数组。该方法不会改变现有的数组,而仅仅会返回被连接数组的一个副本,不会改变原数组。
2.2 join
作用:join() 方法用于把数组中的所有元素转换一个字符串,元素是通过指定的分隔符进行分隔的,不会改变原数组。
2.3 reduce
作用:reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终计算为一个值。reduce 方法的使用情况稍微复杂,不熟悉的小伙伴建议去官网好好学学,它的应用范围还是挺宽泛的,不会改变原数组。
2.4 map
作用:map() 方法返回一个新数组,数组中的元素为原始数组元素调用函数处理后的值,不会改变原数组。
2.5 forEach
作用:forEach() 方法用于调用数组的每个元素,并将元素传递给回调函数,不会改变原数组。
注意:forEach 方法没有返回值,而且它也不会改变原数组,有些同学误以为 forEach 会改变原数组,通常是因为在 forEach 方法的回调函数中,我们自己做了更改原数组的操作。
2.6 filter
作用:filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,不会改变原数组。
2.7 slice
作用:slice() 方法可从已有的数组中返回选定的元素。slice() 方法可提取字符串的某个部分,并以新的字符串返回被提取的部分,不会改变原数组。
2.8 findIndex
作用:findIndex 接收一个测试函数,也可以叫做条件函数,最终返回满足该测试函数的元素的索引位置,如果没有找到符合条件的元素,则返回-1。如果满足条件的有多个,那么只会返回第一个满足条件的元素索引。
总结
数组方法基本上每一版本的 JS 中都会增加一些,本篇文章主要总结了我们开发中比较常用而且容易混淆的。至于为什么要区分是否更改原数组,那就得根据实际情况来决定了。打个比方,如果有一道算法题,可以用数组方法中的 unshift 和 slice 来解决,那么选用哪一个呢?首先 unshift 会改变原数组,它的事件复杂度是 O(n),而 slice 方法不会改变原数组,它的时间复杂度是 O(1),改用哪个一目了然。
11.SSR服务端渲染流程 及SEO,服务端渲染框架?
页面渲染的流程:
浏览器通过请求得到一个HTML文本
渲染进程解析HTML文本,构建DOM树
解析HTML的同时,如果遇到内联样式或者样式脚本,则下载并构建样式规则(stytle rules),若遇到JavaScript脚本,则会下载执行脚本。
DOM树和样式规则构建完成之后,渲染进程将两者合并成渲染树(render tree)
渲染进程开始对渲染树进行布局,生成布局树(layout tree)
渲染进程对布局树进行绘制,生成绘制记录
渲染进程的对布局树进行分层,分别栅格化每一层,并得到合成帧
渲染进程将合成帧信息发送给GPU进程显示到页面中
服务端渲染(SSR):服务端渲染就是在浏览器请求页面URL的时候,服务端将我们需要的HTML文本组装好,并返回给浏览器,这个HTML文本被浏览器解析之后,不需要经过 JavaScript 脚本的执行,即可直接构建出希望的 DOM 树并展示到页面中。这个服务端组装HTML的过程,叫做服务端渲染。
SSR:
利:首屏渲染快、利于SEO、可以生成缓存片段,生成静态化文件、节能(对比客户端渲染的耗电)
弊:用户体验较差、不容易维护,通常前端改了部分html或者css,后端也需要修改。
服务端渲染的模式下,当用户第一次请求页面时,由服务器把需要的组件或页面渲染成 HTML 字符串,然后把它返回给客户端。客户端拿到手的,是可以直接渲染然后呈现给用户的 HTML 内容,不需要为了生成 DOM 内容自己再去跑一遍 JS 代码。使用服务端渲染的网站,可以说是“所见即所得”,页面上呈现的内容,我们在 html 源文件里也能找到。
由于需要在页面加载之前就加载所有页面需要的 JavaScript 库,这使得首次打开页面所需要的时间比较久;
需要研发专门针对于 SPA 的 Web 框架(各种具备 SSR 能力的框架,包括 Next.js 等)
搜索引擎爬虫
浏览器历史记录的问题(基于 pushState 的各种 router)
首屏加载快: 相比于加载单页应用,我只需要加载当前页面的内容,而不需要像 React 或者 Vue 一样加载全部的 js 文件
SEO 优化: 对于单页应用,搜索引擎并不能收录到 ajax 爬取数据之后然后再动态 js 渲染出来的页面。
SSR怎么做:开箱即用的SSR脚手架
目前前端流行的三种技术栈 React, Vue 和 Angula ,已经孵化出对应的服务端渲染框架,开箱即用
React: Next.js
Vue: Nuxt.js
Angula: Nest.js
12.前端项目中环境变量怎么处理,怎么配置?
可以看这篇文章:前端环境变量配置
13.axios有封装过吗?
可以看这篇文章:Axios封装
14.后台管理侧边栏权限管理?
核心想法:
1.登陆后获得用户角色,通过角色获得用户的权限,注入权限对应的路由。
2.刷新页面,从localStorage用角色(更好的方式是通过token)再次获得所属权限,再次注入路由。在管理界面左端循环权限对应的路由菜单。
3.localStorage存用户的信息(token),权限路由不会存。
所有的路由分为2种:
公共路由:所有用户可以查看。
权限路由:当前用户权限所属的路由。
实现控制的方式分两种:
通过 vue-router addRoutes 方法注入路由实现控制
通过 vue-router beforeEach 钩子限制路由跳转
一般来讲,需要采用第一种方式,addRoutes注入的方式,第二种每次判断,开销比较大,而且一次性拿不到角色对应的所有路由列表。这个列表需要再登陆后立即渲染在左边。
权限的控制分为好几种:
一级菜单的权限控制,
二级菜单的权限控制,
按钮级别的权限控制。
前后端约定控制方式有2种:
后端返回路由控制列表
前后端约定路由控制列表
15.Vue项目运行后查看网页源代码可以看到页面代码吗?
1)查看网页源代码是看不到相关页面代码的,能看到的只有:
html、meta、title、link、script、style
<body data-spy=scroll data-target=#myScrollspy><div id=app></div></body>
2)F12/右键检查 是可以看到相关代码的
<body data-spy=scroll data-target=#myScrollspy> 这里有很多子标签页面元素 </body>
16.简述TCP/IP?
1)TCP/IP四层模型,各层的结构、功能、协议、作用
** 应用层** : 提供应用层服务,文件传输(FTP), 电子邮件(SMTP),HTTP(超文本传输协议),DNS(域名系统),telnet(远程登陆服务)
** 传输层: TCP 、 UDP
** 网络层: IP, ICMP(用于在IP主机、路由器之间传递控制消息) ,OSPF(用于网际协议(IP)网络的链路状态路由协议,用于IP选路)
** 物理链路层**:ARP(地址解析协议,根据IP地址获得MAC地址)
2)TCP和UDP
TCP是传输控制协议,提供的是面向连接的、可靠的字节流服务。实际的数据传输之前,服务器和客户端要进行三次握手,会话结束后结束连接。
UDP是用户数据报协议,是无连接的,传输速度很快,可以用于实时视频。
TCP保证数据按时到达,提供流量控制和拥塞控制,在网络拥堵的时候会减慢发送字节数,而UDP不管网络是否拥堵。
TCP是一对一服务(是连接的),而UDP可以一对一、一对多、多对多。
3)TCP拥塞控制
拥塞控制就是为了防止过多的数据注入到网络中,这样使网络中的路由器不至于过载。
拥塞控制:慢开始,拥塞避免,快重传,快恢复。
4)TCP的三次握手和四次挥手
SYN:请求建立连接,FIN:请求断开连接,ACK:确认是否有效, seq:序列号, ack:确认号
1--三次握手
所谓三次握手,是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。
三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。
(1) 第一次握手:建立连接时,客户端A发送SYN包(SYN=j)到服务器B,并进入SYN_SEND状态,等待服务器B确认。
(2) 第二次握手:服务器B收到SYN包,必须确认客户A的SYN(ACK=j+1),同时自己也发送一个SYN包(SYN=k),即SYN+ACK包,此时服务器B进入SYN_RECV状态。
(3) 第三次握手:客户端A收到服务器B的SYN+ACK包,向服务器B发送确认包ACK(ACK=k+1),此包发送完毕,客户端A和服务器B进入ESTABLISHED状态,完成三次握手。
2--四次挥手
TCP的连接的拆除需要发送四个包,因此称为四次挥手。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。
TCP连接是全双工的,因此每个方向都必须单独进行关闭。这原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1) TCP客户端发送一个FIN,用来关闭客户到服务器的数据传送。
(2) 服务器收到这个FIN,它发回一个ACK,确认序号为收到的序号加1。和SYN一样,一个FIN将占用一个序号。
(3) 服务器关闭客户端的连接,发送一个FIN给客户端。
(4) 客户端发回ACK报文确认,并将确认序号设置为收到序号加1
5)为什么是三次握手而不是两次握手?
因为如果客户端第一次发送的SYN报文因为网络问题而迟迟没有到达服务端,此时客户端会因为超时而重新发送一个新的SYN报文,此时上一个SYN报文在客户端就会失效,如果这里只采用两次握手,会因为客户端第二次发送SYN后,第一次发送的SYN又成功到达服务端,这时就会建立两个连接,产生问题。
6)为什么连接的时候是三次握手,关闭的时候却是四次握手?
因为当Server端收到Client端的SYN连接请求报文后,可以直接发送SYN+ACK报文。其中ACK报文是用来应答的,SYN报文是用来同步的。但是关闭连接时,当Server端收到FIN报文时,很可能并不会立即关闭SOCKET,所以只能先回复一个ACK报文,告诉Client端,"你发的FIN报文我收到了"。只有等到我Server端所有的报文都发送完了,我才能发送FIN报文,因此不能一起发送。故需要四步握手。
7)为什么需要经过2MSL(报文段在网络中最大生存时间)才能返回到CLOSE状态?
是为了给最后一次发送的ACK报文成功到达服务端预留时间,因为如果因为网络阻塞最后一次ACK未能及时到达服务端,服务端会以为客户端为收到上一次发送的FIN报文,则服务端会重新发送FIN报文,而这是客户端已经断开连接了,这时就是产生错乱的问题。
17.对于SEO怎么优化?
可以看这篇文章:seo具体怎么优化-优化SEO的方法
18.简述后台管理登录流程?
1)首先打开网页链接,进入登陆页面,然后输入账号密码和验证码,然后点击登录按钮
2)与此同时,我们请求后端登陆接口,把账号/密码/验证码传给后端,接收后端返回结果
3)Token存储/登陆状态响应式:接收到后端返回结果后,把Token存起来
1-存储到状态管理里面(VueX),mutations中编写setToken方法;在getters当中同步监听Token,代表是否登陆状态
2-存到Cookie当中,在setToken方法中cookie.setItem("token",payload,{expires:1}),保存Token数据,设置过期时间(只要不主动点击退出登录,状态会一直在,退出浏览器也会存在)
3-封装公共的请求类,将Token封装起来,方便后续接口调用(当前登陆用户每发送一次请求都需要带上这个Token)
4)请求拦截:获取个人信息,getUserInfo方法向后端请求当前登录的用户信息,上面存的token信息这个时候就用到了,放到获取用户信息接口中的header里面
5)路由守卫拦截:防止用户未登陆状态访问网站,跳转登陆界面
1-beforeRouteEnter(to,from,next) 方法中 判断当前用户登陆状态是否正常,有没有存储Cookie
2-如果登陆状态及Cookie都正常,那就正常展示页面;如果只有登陆状态,那就需要更新Cookie中的Token存储
到这里还疑惑的话可以查看此篇文章:vue后台管理系统流程(面试必选)
19.简述Git代码冲突怎么处理?
方法一、最最最笨的方法(放弃本地,使用远程),将自己提交代码取消删除,重新pull最新代码,然后在最新代码中修改(自己更改少的话可以用此方法,但是更改代码量多的话劝你换一种方法)
方法二、暂存到暂存区,更新后,从暂存区取出合并解决冲突
1、git pull 出现冲突后可以暂存本地修改git stash
2、然后git pull 更新代码
3、git stash list 可查看暂存记录列表
4、释放本地暂存 git stash apply stash@{0}
5、出现冲突文件,找到并解决
6、然后可以提交git add . 加入索引库
7、然后本地提交git commit -m '注释'
8、最后git push到远程
方法三、更新发现冲突,提交本地,再更新,找到冲突地方解决后,再次提交推送远程
1、git pull更新代码,发现error: Your local changes to the following files would be overwritten by merge:pom.xmlPlease commit your changes or stash them before you merge.这说明你的pom.xml与远程有冲突,你需要先提交本地的修改然后更新。
2、git add pom.xml
git commit -m '冲突解决'
提交本地的pom.xml文件,不进行推送远程
3、git pull
更新代码Auto-merging pom.xmlCONFLICT (content): Merge conflict in pom.xmlAutomatic merge failed; fix conflicts and then commit the result.更新后你的本地分支上会出现 (develop|MERGING)类似这种标志。
4、找到你本地的test.txt文件,并打开你会在文件中发现<<<<<<< HEAD ,======= ,>>>>>>> ae24sgwmfp2m2ojr2jaagwhhfawe2类似这样的标记。
<<<<<<< HEAD和=======中间的是你自己的代码, ======= 和>>>>>>>中间的是其他人修改的代码自己确定保留那一部分代码,最后删除<<<<<<< HEAD ,======= ,>>>>>>>这种标志。
5、git add test.txt && git commit -m '冲突解决结束' 再次将本地的test.txt文件提交。
6、git push将解决冲突后的文件推送到远程。
方法四、Vscode git插件解决
vscode当中的git插件不仅会用不同的颜色高亮不同分支的代码,而且为我们提供了几个非常好用的代码合并功能,分别是:
Accept Current Change 保留当前分支的代码
Accept Incoming Change 保留合并分支的代码
Accept Both Change 保留两者
Compare Change 对比改动
根据自己需要,点击这四个按钮中的一个就行。
20.简述懒加载?
1)什么是懒加载?
懒加载也就是延迟加载,当访问一个页面的时候,先把img元素或是其他元素的背景图片路径替换成一张大小为1*1px图片的路径(这样就只需请求一次,俗称占位图),只有当图片出现在浏览器的可视区域内时,才设置图片正真的路径,让图片显示出来。这就是图片懒加载。
2) 为什么要使用懒加载?
很多页面很长,图片很多,图片很大,页面如果一次性加载完毕会消耗很多的时间。
3) 懒加载的原理是什么?
页面中的img元素,如果没有src属性,浏览器就不会发出请求去下载图片,只有通过javascript设置了图片路径,浏览器才会发送请求。 懒加载的原理就是先在页面中把所有的图片统一使用一张占位图进行占位,把真正的路径存在元素的“data-url”属性里,要用的时候就取出来,再设置。
4) 懒加载的实现步骤?
不要将图片地址放到src属性中,而是放到其它属性(data-original)中。 页面加载完成后,根据scrollTop判断图片是否在用户的视野内,如果在,则将data-original属性中的值取出存放到src属性中。 在滚动事件中重复判断图片是否进入视野,如果进入,则将data-original属性中的值取出存放到src属性中。
21.线程和进程的区别?
1)进程
进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
2)线程
线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源,但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
3)区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,单线程应用的线程死掉就等于整个进程死掉,所以多进程的程序要比单线程的程序健壮。
一个程序至少有一个进程,一个进程至少有一个线程。
线程的划分尺度小于进程,使得多线程程序的并发性高。
进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
22.如何判断变量类型?
1)length属性
对象没有length属性值,其值为undefiend。
数组的length值为number类型,一般不用,当对象的属性存在length。
2)instanceof
let arr = [1, 2, 3];
let obj = {
name: 'fyjc',
age: 20,
1: 'name'
}
console.log(arr instanceof Array); //true
console.log(obj instanceof Array); //false
3)constructor
let arr = [1, 2, 3];
let obj = {
name: 'fyjc',
age: 20,
1: 'name'
}
console.log(arr.constructor === Array); //true
console.log(obj.constructor === Array); //false
4)toString
toString()方法,数组原型和对象原型定义的toString()方法不同
let arr = [1, 2, 3];
let obj = {
name: 'fyjc',
age: 20,
1: 'name'
}
console.log(Object.prototype.toString.call(arr) === '[object Array]'); //true
console.log(Object.prototype.toString.call(boj) === '[object Array]'); //false
**欢迎大家指正,评论区见哦~ **
版权归原作者 风雨兼程^_^ 所有, 如有侵权,请联系我们删除。