哈喽,大家好,我是Fine。
今天这篇文章主要分享一些浏览器原理相关的高频面试题,包括浏览器安全,浏览器缓存,浏览器渲染原理,本地存储,同源策略,以及其他面试中特别容易被考到的经典面试题。
近期看工作机会的同学可以好好阅读一下这篇文章,会有收获!
以下是正文:
XSS 攻击指的是跨站脚本攻击,攻击者通过在网站注入恶意脚本,使之在用户的浏览器上运行,从而盗取用户的信息。其本质是因为网站没有对恶意代码进行过滤,与正常的代码混合在一起了,浏览器没有办法分辨哪些脚本是可信的,从而导致了恶意代码的执行。
攻击者可以通过这种攻击方式进行以下操作:
XSS 可以分为存储型、反射型和 DOM 型:
CSRF 攻击指的是跨站请求伪造攻击,攻击者诱导用户进入一个第三方网站,然后该网站向被攻击网站发送跨站请求。如果用户在被攻击网站中保存了登录状态,那么攻击者就可以利用这个登录状态,绕过后台的用户验证,冒充用户向服务器执行一些操作。
CSRF 攻击的本质是利用 cookie 会在同源请求中携带发送给服务器的特点,以此来实现用户的冒充。
常见的 CSRF 攻击有三种:
是指攻击者与通讯的两端分别创建独立的联系, 并交换其所收到的数据, 使通讯的两端认为他们正在通过⼀个私密的连接与对方直接对话, 但事实上整个会话都被攻击者完全控制,攻击者可以拦截通讯双⽅的通话并插⼊新的内容。
攻击过程如下:
DNS劫持: (输⼊京东被强制跳转到淘宝这就属于dns劫持)
HTTP劫持: (访问⾕歌但是⼀直有贪玩蓝⽉的⼴告),由于http明⽂传输,运营商会修改你的http响应内容(即加⼴告)
DNS劫持由于涉嫌违法,已经被监管起来,现在很少会有DNS劫持,⽽http劫持依然⾮常盛⾏,最有效的办法就是全站HTTPS,将HTTP加密,这使得运营商⽆法获取明⽂,就⽆法劫持你的响应内容。
如果浏览器判断所请求的目标资源有效命中,则直接从强制缓存中返回,无须与服务器进行通信。
其中与强制缓存相关的两个字段是expires和cache-control,expires是在HTTP1.0协议中声明的用来控制缓存失效日期时间戳的字段。若之后浏览器再次发起相同的资源请求,便会对比expires与本地当前的时间戳,如果当前请求的本地时间戳小于expires的值,则说明浏览器缓存的响应还未过期,可以直接使用而无须向服务器端再次发起请求。
由于expires对本地时间戳过分依赖,如果客户端的时间与服务器端的时间不同步,或者客户端的时间被篡改,那么对于缓存过期的判断可能就无法和预期相符。
为了解决expires判断的局限性,从HTTP1.1协议开始新增了cache-control字段进行完善。cache-control通过设置max-age来控制响应资源的有效期,它是一个以秒为单位的时间长度,如此便可避免服务器端和客户端时间戳不同步而造成的问题。如果Cache-Control的max-age和expires同时存在,则以max-age为准,Cache-Control的优先级更高。
Cache-Control的一些参数:
1.no-cache:强制进行协商缓存
2.no-store:禁止使用任何缓存
3.public:表示响应资源既可以被浏览器缓存,又可以被代理服务器缓存
4.private:响应资源只能被浏览器缓存,默认值
5.max-age:过期时长
6.s-maxage:表示缓存在代理服务器中的过期时长
由此可见,cache-control能够作为expires的完全替代方案,在项目实践中使用它就足够了,目前expires还存在的唯一理由就是向下兼容。
在使用本地缓存之前,需要向服务器发起一次GET请求,与之协商当前浏览器保存的本地缓存是否已经过期。通常是采用所请求资源的最近一次的修改时间戳来判断的。
(1)Last-Modified协商缓存流程
客户端第一次请求目标资源时,服务器返回的响应头包含last-modified和该资源的最后一次修改的时间戳,以及cache-control:no-cache,当客户端再次请求该资源的时候,会携带一个if-modified-since字段,将这个字段对应的时间与目标资源的时间戳进行对比,如果没有变化则返回一个304状态码。
需要注意的是:协商缓存判断缓存有效的响应状态码是304,强制缓存判断有效的话,响应状态码是200
(2)last-modified 的不足
last-modified是根据请求资源最后的修改时间戳进行判断的,但是有时候我们对请求的文件资源进行了编辑,但是内容并没有发生任何变化,此时时间戳也会更新,需要重新进行完整的资源请求,这会造成网络带宽资源的浪费。
文件资源修改的时间戳单位是秒,如果文件修改的速度非常快,假设在几百毫秒内完成,此时是无法识别出该文件资源是否更新。
(3)Etag协商缓存的流程
由于这些不足,在HTTP1.1协议中增加了 Etag 字段进行完善,它会根据文件资源的内容进行哈希计算生成一个字符串,如果文件资源的内容发生改变,那么会被感知到。
首先,服务端将要返回给客户端的资源计算生成一个字符串;接着检测客户端的请求标头中的if-None-Match字段的值和第一步计算的值是否一致,一致则返回304;如果不一致则返回etag标头和Cache-Control:no-cache
(4)Etag的不足
服务器对于生成文件资源的Etag需要付出额外的计算开销,如果资源的尺寸比较大,数量较多且修改频繁,那么生成的Etag的过程就会影响服务器的性能。
(1)针对JavaScript
1.将JavaScript文件放在body的最后
2.使用async、defer来异步引入
(2)针对CSS
1.导入外部样式使用link,而不用@import
2.如果css少,尽可能采用内嵌样式,直接写在style标签中
(3)针对DOM树、CSSOM树
1.HTML文件的代码层级尽量不要太深
2.使用语义化的标签,来避免不标准语义化的特殊处理
3.减少CSSD代码的层级,因为选择器是从右向左进行解析的
(4)减少回流与重绘
1.不要使用table布局, 一个小的改动可能会使整个table进行重新布局
2.不要频繁操作元素的样式
3.将DOM的多个读写操作放在一起
4.操作DOM时,尽量在低层级的DOM节点进行操作
5.避免频繁操作DOM,可以创建一个文档片段documentFragment,在它上面应用所有DOM操作,最后再把它添加到文档中
JavaScript 的加载、解析与执行会阻塞文档的解析,在构建 DOM 时,HTML 解析器若遇到了 JavaScript,那么它会暂停文档的解析,将控制权移交给 JavaScript 引擎,等 JavaScript 引擎运行完毕,浏览器再从中断的地方恢复继续解析文档。
当执行 JavaScript 脚本时,另一个线程解析剩下的文档,并加载后面需要通过网络加载的资源。这种方式可以使资源并行加载从而使整体速度更快。
理论上,既然样式表不改变 DOM 树,也就没有必要停下文档的解析等待它们。然而,JavaScript 脚本执行时可能在文档的解析过程中请求样式信息,如果样式还没有加载和解析,脚本将得到错误的值,显然这将会导致很多问题。所以如果浏览器尚未完成 CSSOM 的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟 JavaScript 脚本执行和文档的解析,直至其完成 CSSOM 的下载和构建。
(1)Cookie
1.大小只有4kb,每次发起HTTP请求都会携带
1.Cookie一旦创建成功,名称就无法修改,无法跨域名
3.每个域名下Cookie的数量不能超过20个
4.有安全问题,如果Cookie被拦截,那就可获得session的信息
5.可以设置过期时间
6.可以设置很多字段
(2)LocalStorage
优点:
1.大小一般为5MB
2.持久储存,除非主动清理,不然会永久存在
3.仅储存在本地,不像Cookie那样每次HTTP请求都会被携带
缺点:
1.存在浏览器兼容问题,IE8以下版本的浏览器不支持
2.如果浏览器设置为隐私模式,那我们将无法读取到LocalStorage
3.LocalStorage受到同源策略的限制
(3)SessionStorage: 是HTML5提出来的存储方案,主要用于临时保存同一窗口(或标签页)的数据,刷新页面时不会删除,关闭窗口或标签页之后将会删除这些数据。
Value:值
Size: 大小
Name:名称
Path:可以访问此cookie的页面路径
Secure: 指定是否使用HTTPS安全协议发送Cookie。
Domain:可以访问该cookie的域名
HTTP: 该字段包含httpOnly属性 ,该属性用来设置cookie能否通过脚本来访问。
Expires/Max-size:超时时间
cookies
localStorage
sessionStorage
IndexedDB
1.采用明文存储,所以在存储的时候最好在服务器端进行加密
2.可能会被植入广告追踪标志
3.用户定期清除浏览器缓存有助于 cookie 更有效地发挥作用,但是会丢失localstorage中存储的数据
安全性问题,如果不设置过期时间,那么token 容易被盗,就会产生不可估量的影响。比如支付宝因为涉及强资金安全性,如果时长设置过长可能导致用户本人离开后由他人操作故意导致恶心资金流转的问题等。
那么token 时限多长合适呢?这就需要根据业务性来区分,涉及支付类,对数据账号安全特别敏感的,建议每次都重新登录;对于日常使用的,比如学习类APP,工具类APP,不涉及到金额,token 时限可以长点。
当一个请求url的协议
、域名
、端口
三者之间任意一个与当前页面url不同即为跨域。
跨域问题其实就是浏览器的同源策略造成的。同源策略是一种安全机制,如果两个url
协议、域名、端口任意一个不相同,则这两个url
就是不同源的,他们的请求就算是跨域。
同源政策主要限制了三个方面:
同源政策的目的主要是为了保证用户的信息安全,它只是对 js 脚本的一种限制,并不是对浏览器的限制,对于一般的 img、或者script 脚本请求都不会有跨域的限制,这是因为这些操作都不会通过响应结果来进行可能出现安全问题的操作。
这里假设有A
网站,如果没有相关的安全策略,Web
世界就是开放的。那么任何资源都可以接入A
网站,,但这样会造成无序或者混沌的局面,出现很多不可控的问题。
比如A
网站请求到了一个恶意网站(B
)的恶意脚本,就可以修改A
网站的DOM、CSSOM结构,还可以插入一段JavaScript脚本,甚至可以获取A
网站的用户名和密码及Cookie信息。
因此,我们就需要一种安全策略来保障我们的隐私和数据的安全。
跨域资源共享(CORS) 是一种机制,它使用额外的 HTTP 头来告诉浏览器,让运行在一个 origin 上的Web应用被允许访问来自不同源服务器上的指定的资源。CORS 是在后端服务器做相应配置的。
// 配置关键代码Access-Control-Allow-Origin与Access-Control-Allow-Methods
app.use((req,res,next)=>{
res.header('Access-Control-Allow-Origin', 'http://localhost:8080')
res.header('Access-Control-Allow-Methods', 'GET, PUT, POST, OPTIONS')
next()
})
Access-Control-Allow-Origin
:设定请求源,就告诉了浏览器。如果请求我的资源的页面是我这个响应头里记录了的"源",则不要拦截此响应,允许数据通行。
其原理就是利用<script>
标签没有跨域限制,通过标签的 src 属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
1)原生JS实现:
<script>
let script = document.createElement('script');
script.type = 'text/javascript';
// 传参一个回调函数名给后端,方便后端返回时执行这个在前端定义的回调函数
script.src = 'http://www.domain2.com:8080/login?user=admin&callback=handleCallback';
document.head.appendChild(script);
// 回调执行函数
function handleCallback(res) {
alert(JSON.stringify(res));
}
</script>
服务端返回如下(返回时即执行全局函数):
handleCallback({"success": true, "user": "admin"})
2)Vue axios实现:
this.$http = axios;
this.$http.jsonp('http://www.domain2.com:8080/login', {
params: {},
jsonp: 'handleCallback'
}).then((res) => {
console.log(res);
})
后端node.js代码:
let querystring = require('querystring');
let http = require('http');
let server = http.createServer();
server.on('request', function(req, res) {
var params = querystring.parse(req.url.split('?')[1]);
var fn = params.callback;
// jsonp返回设置
res.writeHead(200, { 'Content-Type': 'text/javascript' });
res.write(fn + '(' + JSON.stringify(params) + ')');
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
JSONP的缺点:
它可用于解决以下方面的问题:
postMessage(data,origin)方法接受两个参数:
1)a.html:(domain1.com/a.html)
<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>
let iframe = document.getElementById('iframe');
iframe.onload = function() {
let data = {
name: 'aym'
};
// 向domain2传送跨域数据
iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
};
// 接受domain2返回数据
window.addEventListener('message', function(e) {
alert('data from domain2 ---> ' + e.data);
}, false);
</script>
2)b.html:(domain2.com/b.html)
<script>
// 接收domain1的数据
window.addEventListener('message', function(e) {
alert('data from domain1 ---> ' + e.data);
let data = JSON.parse(e.data);
if (data) {
data.number = 16;
// 处理后再发回domain1
window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
}
}, false);
</script>
nginx代理跨域,实质和CORS跨域原理一样,通过配置文件设置请求响应头Access-Control-Allow-Origin字段
1)nginx配置解决iconfont跨域,浏览器跨域访问 js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在 nginx 的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
2)nginx反向代理接口跨域:通过 Nginx 配置一个代理服务器域名与 domain1 相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。
#proxy服务器
server {
listen 81;
server_name www.domain1.com;
location / {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
node中间件实现跨域代理,原理大致与nginx相同,都是通过启一个代理服务器,实现数据的转发。
1)非Vue框架的跨域
let xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();
let express = require('express');
let proxy = require('http-proxy-middleware');
let app = express();
app.use('/', proxy({
// 代理跨域目标接口
target: 'http://www.domain2.com:8080',
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带cookie
onProxyRes: function(proxyRes, req, res) {
res.header('Access-Control-Allow-Origin', 'http://www.domain1.com');
res.header('Access-Control-Allow-Credentials', 'true');
},
// 修改响应信息中的cookie域名
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}));
app.listen(3000);
console.log('Proxy server is listen at port 3000...');
2)Vue 框架的跨域
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.domain2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些https服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改
}],
noInfo: true
}
}
此方案仅限主域相同,子域不同的跨域应用场景。实现原理:两个页面都通过js强制设置document.domain为基础主域,就实现了同域。
1)父窗口:(domain.com/a.html)
<iframe id="iframe" src="http://child.domain.com/b.html"></iframe>
<script>
document.domain = 'domain.com';
let user = 'admin';
</script>
2)子窗口:(child.domain.com/a.html)
<script>
document.domain = 'domain.com';
// 获取父窗口中变量
console.log('get js data from parent ---> ' + window.parent.user);
</script>
WebSocket protocol是HTML5一种新的协议,它实现了浏览器与服务器全双工通信,同时允许跨域通讯。 原生 WebSocket API使用起来不太方便,我们使用 Socket.io,它很好地封装了 webSocket 接口,提供了更简单、灵活的接口。
1)前端代码:
<div>user input:<input type="text"></div>
<script src="https://cdn.bootcss.com/socket.io/2.2.0/socket.io.js"></script>
<script>
let socket = io('http://www.domain2.com:8080');
// 连接成功处理
socket.on('connect', function() {
// 监听服务端消息
socket.on('message', function(msg) {
console.log('data from server: ---> ' + msg);
});
// 监听服务端关闭
socket.on('disconnect', function() {
console.log('Server socket has closed.');
});
});
document.getElementsByTagName('input')[0].onblur = function() {
socket.send(this.value);
};
</script>
2)Nodejs socket后台:
let http = require('http');
let socket = require('socket.io');
// 启http服务
let server = http.createServer(function(req, res) {
res.writeHead(200, {
'Content-type': 'text/html'
});
res.end();
});
server.listen('8080');
console.log('Server is running at port 8080...');
// 监听socket连接
socket.listen(server).on('connection', function(client) {
// 接收信息
client.on('message', function(msg) {
client.send('hello:' + msg);
console.log('data from client: ---> ' + msg);
});
// 断开处理
client.on('disconnect', function() {
console.log('Client socket has closed.');
});
});
客户端想获得一个服务器的数据,但是因为种种原因无法直接获取。于是客户端设置了一个代理服务器,并且指定目标服务器,之后代理服务器向目标服务器转交请求并将获得的内容发送给客户端。这样本质上起到了对真实服务器隐藏真实客户端的目的。
服务器为了能够将工作负载分布到多个服务器来提高网站性能 (负载均衡),当其收到请求后,会首先根据转发规则来确定请求应该被转发到哪个服务器上,然后将请求转发到对应的真实服务器上。这样本质上起到了对客户端隐藏真实服务器的作用。 一般使用反向代理后,需要通过修改 DNS 让域名解析到代理服务器 IP,这时浏览器无法察觉到真正服务器的存在,当然也就不需要修改配置了。
两者区别如图示:
正向代理和反向代理的结构是一样的,都是 client-proxy-server 的结构,它们主要的区别就在于中间这个 proxy 是哪一方设置的。在正向代理中,proxy 是 client 设置的,用来隐藏 client;而在反向代理中,proxy 是 server 设置的,用来隐藏 server。
Nginx 是一款轻量级的 Web 服务器,也可以用于反向代理、负载平衡和 HTTP 缓存等。Nginx 使用异步事件驱动的方法来处理请求,是一款面向性能设计的 HTTP 服务器。
传统的 Web 服务器如 Apache 是 process-based 模型的,而 Nginx 是基于event-driven模型的。正是这个主要的区别带给了 Nginx 在性能上的优势。
Nginx 架构的最顶层是一个 master process,这个 master process 用于产生其他的 worker process,这一点和Apache 非常像,但是 Nginx 的 worker process 可以同时处理大量的HTTP请求,而每个 Apache process 只能处理一个。
首先浏览器是禁止跨域的,但是服务端不禁止,在本地运行 npm run dev 等命令时实际上是用 node 运行了一个服务器,因此 Vue 的转发机制 proxyTable 实际上是将请求发给自己的服务器,再由服务器转发给后台服务器,做了一层代理。Vue的proxyTable 用的是 http-proxy-middleware 中间件, 因此不会出现跨域问题。
<script>
、<img>
、<iframe>
、<link>
、<a>
等标签都可以加载跨域资源
CDN(Content Delivery Network,内容分发网络)是指一种通过互联网互相连接的电脑网络系统,利用最靠近每位用户的服务器,更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户。CDN一般会用来托管Web资源(包括文本、图片、脚本、软件、文档、应用程序),使用CDN可以加速这些资源的访问。使用场景如下:
(1)直播传送:直播本质上是使用流媒体进行传送,CDN也是支持流媒体传送的,所以直播完全可以使用CDN来提高访问速度。
(2)使用第三方的CDN服务:如果想要开源一些项目,可以使用第三方的CDN服务
(3)使用CDN进行静态资源的缓存:将自己网站的静态资源放在CDN上,比如js、css、图片等;可以将整个项目放在CDN上,完成一键部署。
这两种方式都是提高网页性能的方式,两者主要区别是一个是提前加载,一个是迟缓甚至不加载。懒加载对服务器前端有一定的缓解压力作用,预加载则会增加服务器前端压力。
当渲染树中部分或者全部元素的尺寸、结构、位置、内容等属性发生变化时,浏览器会重新渲染部分或者全部文档的过程就称为回流。
下面这些操作会导致回流:
当页面中某些元素的样式发生变化,但是不会影响其在文档流中的位置时,浏览器就会对元素进行重新绘制,这个过程就是重绘。
下面这些操作会导致重绘:
注意:当触发回流时,一定会触发重绘,但是重绘不一定会引发回流
浏览器针对页面的回流与重绘,进行了自身的优化——渲染队列。浏览器会将所有的回流、重绘的操作放在一个队列中,当队列中的操作到了一定的数量或者到了一定的时间间隔,浏览器就会对队列进行批处理。这样就会让多次的回流、重绘变成一次回流重绘。
当我们把一个 DocumentFragment 节点插入文档树时,插入的不是 DocumentFragment 自身,而是它的所有子孙节点。在频繁的DOM操作时,我们就可以将DOM元素插入DocumentFragment,之后一次性的将所有的子孙节点插入文档中。DocumentFragment不是真实 DOM 树的一部分,它的变化不会触发 DOM 树的重新渲染,这样就大大提高了页面的性能。
运用场景:假如有 10000 个元素需要添加到页面上,你觉得怎么操作性能最好?
const oFrag = document.createDocumentFragment()
for (let i = 1; i <= 1000; i++) {
const oDiv = document.createElement('div')
oDiv.innerHTML = i
oFrag.appendChild(oDiv)
}
document.body.appendChild(oFrag)
一般情况下,动画需要频繁的操作DOM,这就会导致页面的性能问题,我们可以将动画的 position 属性设置为 absolute 或者 fixed,将动画脱离文档流,这样他的回流就不会影响到页面了。
虚拟列表其实是按需显示的一种实现,即只对可见区域
进行渲染,对非可见区域
中的数据不渲染或部分渲染的技术,从而达到极高的渲染性能。
假设有1万条记录需要同时渲染,我们屏幕的可见区域的高度为500px,而列表项的高度为50px,则此时我们在屏幕中最多只能看到10个列表项,那么在首次渲染的时候,我们只需加载10条即可。
虚拟列表的实现,实际上就是在首屏加载的时候,只加载可视区域
内需要的列表项,当滚动发生时,动态通过计算获得可视区域
内的列表项,并将非可视区域
内存在的列表项删除。
<div class="infinite-list-container">
<div class="infinite-list-phantom"></div>
<div class="infinite-list">
<!-- item-1 -->
<!-- item-2 -->
<!-- ...... -->
<!-- item-n -->
</div>
</div>
infinite-list-container
为可视区域的容器infinite-list-phantom
为容器内的占位,高度为总列表高度,用于形成滚动条infinite-list
为列表项的渲染区域接着,监听infinite-list-container
的scroll
事件,获取滚动位置scrollTop
可视区域
高度固定,称之为screenHeight
列表每项
高度固定,称之为itemSize
列表数据
称之为listData
当前滚动位置
称之为scrollTop
则可推算出:
listHeight
= listData.length * itemSizevisibleCount
= Math.ceil(screenHeight / itemSize)startIndex
= Math.floor(scrollTop / itemSize)endIndex
= startIndex + visibleCountvisibleData
= listData.slice(startIndex,endIndex)当滚动后,由于渲染区域相对于可视区域已经发生了偏移,此时我需要获取一个偏移量startOffset
,通过样式控制将渲染区域偏移至可视区域中。
当滚动发生后,scroll事件会频繁触发,很多时候会造成重复计算的问题,从性能上来说无疑存在浪费的情况。可以使用 IntersectionObserver 替换监听scroll事件。
单点登录(Single Sign On),简称为 SSO,是目前比较流行的企业业务整合的解决方案之一。
SSO 的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。SSO 一般都需要一个独立的认证中心(passport),子系统的登录均得通过 passport,子系统本身将不参与登录操作。
当一个系统成功登录以后,passport 将会颁发一个令牌给各个子系统,子系统可以拿着令牌会获取各自的受保护资源,为了减少频繁认证,各个子系统在被 passport 授权以后,会建立一个局部会话,在一定时间内可以无需再次向 passport 发起认证。
Babel 的主要工作是对代码进行转译 (解决兼容, 解析执行一部分代码)
let a = 1 + 1 => var a = 2
转译分为三阶段:
前端渲染:指的是后端返回JSON数据,前端利用预先写的html模板,循环读取JSON数据,拼接字符串并插入页面
后端渲染:前端请求,后端用后台模板引擎直接生成html,前端接收到数据之后,直接插入页面
区别
前端渲染 | 后端渲染 | |
---|---|---|
页面呈现速度 | 主要受限于带宽和客户端机器的好坏,优化的好,可以逐步动态展开内容,感觉上会更快一点 | 快,受限于用户的带宽 |
流量消耗 | 多一点点(一个前端框架大概50KB) | 少一点点(可以省去前端框架部分的代码) |
可维护性 | 好,前后端分离,各施其职,代码一目明了 | 差(前后端东西放一起,不利于维护) |
SEO友好度 | 差,大量使用Ajax,多数浏览器不能抓取Ajax数据 | 好 |
编码效率 | 高,前后端各自只做自己擅长的东西,后端最后只输出接口,不用管页面呈现,只要前后端人员能力不错,效率不会低 | 低(这个跟不同的团队不同,可能不对) |
升序冒泡:两次循环,相邻元素两两比较,如果前面的大于后面的就交换位置
降序冒泡:两次循环,相邻元素两两比较,如果前面的小于后面的就交换位置
// 升序冒泡
function maopao(arr){
const array = [...arr]
for(let i = array.length; i > 0; i--){
for(let j = 0; j < i - 1; j++) {
if (array[j] > array[j + 1]) {
let temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
}
}
}
return array
}
看起来没问题,不过一般生产环境都不用这个,原因是效率低下,就算你给一个已经排好序的数组,它也会走一遍流程,白白浪费资源。所以有没有什么好的解决方法呢?答案:加个标识,如果已经排好序了就直接跳出循环。
// 优化版:
function maopao1(arr){
const array = [...arr]
for(let i = array.length; i > 0; i--){
let isOk = true
for(let j = 0; j < i - 1; j++) {
if (array[j] > array[j + 1]) {
let temp = array[j]
array[j] = array[j + 1]
array[j + 1] = temp
isOk = false
}
}
if (isOk) {
break
}
}
return array
}
Node.js是一个基于 Chrome V8 引擎的 JavaScript 运行环境,这个环境就好比是服务器上的浏览器,但正是因为有了它才使得 js 变成了一门后台语言。
1.什么叫模块化?(模块 == js文件)
2.模块化开发好处?
(1)将功能分离出来
(2)按需导入
(3)避免变量污染
原文地址:https://juejin.cn/post/7208466455879221285
侵删
还没有使用过我们刷题网站(https://fe.ecool.fun/)或者刷题小程序的同学,如果近期准备或者正在找工作,千万不要错过,题库主打题全和更新快哦~。
有会员购买、辅导咨询的小伙伴,可以通过下面的二维码,联系我们的小助手。