文章会持续更新css
XSS: Cross Site Scripting攻击,全称跨站脚本攻击.html
XSS指恶意攻击者利用网站没有对用户提交数据进行转义处理或者过滤不足的缺点,进而添加一些 代码,嵌入到web页面中去。使别的用户访问都会执行相应的嵌入代码。 从而盗取用户资料、利用用户身份进行某种动做或者对访问者进行病毒侵害的一种攻击方式。前端
XSS攻击的危害包括:vue
一、盗取各种用户账号,如机器登陆账号、用户网银账号、各种管理员账号html5
二、控制企业数据,包括读取、篡改、添加、删除企业敏感数据的能力java
三、盗窃企业重要的具备商业价值的资料node
四、非法转帐webpack
五、强制发送电子邮件nginx
六、网站挂马git
七、控制受害者机器向其它网站发起攻击
缘由解析
主要缘由:过于信任客户端提交的数据!
解决办法: 不信任客户端提交的数据,只要是客户端提交的数据就应该先进行相应的过滤处理后方可进行下一步的操做.
进一步分析: 客户端提交的数据自己就是应用所需的,可是恶心攻击者利用网站对客户端提提交数据的信任,在数据中插入一些符号以及JavaScript代码,那么这些数据就会成为应用代码中给的一部分了,那么攻击者就能够肆无忌惮的展开攻击
所以咱们绝对不可信任任何客户端提交的数据
类型
持久性(存储型)和非持久性(反射型)
持久型: 持久型也就是攻击的代码被写入数据库中,这个攻击危害性很大,若是网站访问量很大 的话,就会致使大量正常访问的页面,就会致使大量正常访问页面的用户都受到攻击。最典型的 就是留言板的XSS攻击
非持久型: 非持久型XSS是指发送请求时,XSS代码出如今请求的URL中,做为参数提交到服务 器,服务器解析并响应。响应结果中包含XSS代码,最后浏览器解析并执行,从概念上能够看出, 反射型XSS代码首先是出如今URL中,而后须要服务端解析,最后须要浏览器以后XSS代码才能攻 击
防护方法
两种方式用来防护
内容安全策略 (CSP) 是一个额外的安全层,用于检测并削弱某些特定类型的攻击,包括跨站脚本 (XSS) 和数据注入攻击等。不管是数据盗取、网站内容污染仍是散发恶意软件,这些攻击都是主要的手段
CSP 的主要目标是减小和报告 XSS 攻击 ,XSS 攻击利用了浏览器对于从服务器所获取的内容的信任。恶意脚本在受害者的浏览器中得以运行,由于浏览器信任其内容来源,即便有的时候这些脚本并不是来自于它本该来的地方。
CSP经过指定有效域——即浏览器承认的可执行脚本的有效来源——使服务器管理者有能力减小或消除XSS攻击所依赖的载体。一个CSP兼容的浏览器将会仅执行从白名单域获取到的脚本文件,忽略全部的其余脚本 (包括内联脚本和HTML的事件处理属性)。
做为一种终极防御形式,始终不容许执行脚本的站点能够选择全面禁止脚本执行
CSP本质上就是创建白名单,开发者明确告诉浏览器哪些外部资源能够进行加载和执行,咱们只须要配置规则,如何拦截是由浏览器本身实现的,咱们能够经过这种方式来尽可能减小XSS攻击
设置HTTP Header中的Content-Security-Policy: policy
复制代码
meta http-equiv=“Content-Security-Policy” content=“default-src ‘self’; img-src https://*; child-src ‘none’;”>
复制代码
常见用例(设置HTTP Header来举例)
Content-Security-Policy: default-src ‘self’
复制代码
Content-Security-Policy: default-src ‘self’ *.trusted.com
复制代码
Content-Security-Policy: default-src ‘self’; img-src *; media-src media1.com media2.com; script-src userscripts.example.com
复制代码
Content-Security-Policy: img-src https://*
复制代码
Content-Security-Policy: child-src ‘none’
复制代码
对于这种方式来讲,这要开发者配置了正确的规则,那么即便网站存在漏洞,攻击者也不能执行它的攻击代码,并且CSP的兼容性不错.
CSRF(Cross-site request forgery)跨站请求伪造,也被称为“One Click Attack”或者Session Riding,一般缩写为CSRF或者XSRF,是一种对网站的恶意利用。尽管听起来像跨站脚本(XSS),但它与XSS很是不一样,XSS利用站点内的信任用户,而CSRF则经过假装成受信任用户的请求来利用受信任的网站。与XSS攻击相比,CSRF攻击每每不大流行(所以对其进行防范的资源也至关稀少)和难以防范,因此被认为比XSS更具危险性。
原理就是攻击者构造出一个后端请求地址,诱导用户点击或者经过某些途径自动发起请求。若是用户是在登陆状态下的话,后端就觉得是用户在操做,从而进行相应的逻辑
也就是完成一次CSRF攻击,受害者必须依次完成两个步骤: 1 登陆受信任的网站,并在本地生成Cookie 2 在不登出信任网站的状况下,访问危险网站
你也许有疑问:若是我不知足以上两个条件中的一个,我就不会收到CSRF攻击。 的确如此,可是你不能保证如下状况的发生: 1 你不能保证你登陆了一个网站后,再也不打开一个tab页面并访问其余页面 2 你不能保证你关闭浏览器后,你的本地Cookie马上过时,你的上次会话已经结束.(事实上,关闭浏览器不能结束一个会话,)
攻击者盗用了你身份,以你的名义发送恶意请求,CSRF可以作的事情:以你的名义发送邮件,盗取你的帐号甚至是购买商品,虚拟货币的转帐,我的隐私泄露和财产安全
CSRF攻击是源于WEB的隐式身份验证机制!WEB的身份验证机制虽然能够保证一个请求是来自于某个用户的浏览器,但却没法保证该请求是用户批准发送的!
防范CSRF攻击能够遵循如下几种规则:
SameSite
验证 Referer HTTP头部
Token服务端核对令牌
验证码
这个其实就是强缓存和协商缓存的问题,强缓存会直接去取缓存的文件,而协商缓存会去像服务器发送一次确认文档是否有效的请求。
详细: mp.weixin.qq.com/s/G5FIrWOts…
通常js、css等静态资源 走强缓存, html走协商缓存(没有hash)
jsonp的原理就是利用<script>标签没有跨域限制,经过<script>标签src属性,发送带有callback参数的GET请求,服务端将接口返回数据拼凑到callback函数中,返回给浏览器,浏览器解析执行,从而前端拿到callback函数返回的数据。
jsonp的缺点:只能发送get一种请求。
CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。 它容许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。 CORS须要浏览器和服务器同时支持。目前,全部浏览器都支持该功能,IE浏览器不能低于IE10。
浏览器将CORS跨域请求分为简单请求和非简单请求。
只要同时知足一下两个条件,就属于简单请求
(1)使用下列方法之一:
(2)请求的Heder是
不一样时知足上面的两个条件,就属于非简单请求。浏览器对这两种的处理,是不同的。
对于简单请求,浏览器直接发出CORS请求。具体来讲,就是在头信息之中,增长一个Origin字段。
GET /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...
复制代码
上面的头信息中,Origin字段用来讲明,本次请求来自哪一个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否赞成此次请求。
CORS请求设置的响应头字段,都以 Access-Control-开头:
1)Access-Control-Allow-Origin:必选
它的值要么是请求时Origin字段的值,要么是一个*,表示接受任意域名的请求。
2)Access-Control-Allow-Credentials:可选
它的值是一个布尔值,表示是否容许发送Cookie。默认状况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie能够包含在请求中,一块儿发给服务器。这个值也只能设为true,若是服务器不要浏览器发送Cookie,删除该字段便可。
3)Access-Control-Expose-Headers:可选
CORS请求时,XMLHttpRequest对象的getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。若是想拿到其余字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader(‘FooBar’)能够返回FooBar字段的值。
非简单请求是那种对服务器有特殊要求的请求,好比请求方法是PUT或DELETE,或者Content-Type字段的类型是application/json。非简单请求的CORS请求,会在正式通讯以前,增长一次HTTP查询请求,称为"预检"请求(preflight)。
预检"请求用的请求方法是OPTIONS,表示这个请求是用来询问的。请求头信息里面,关键字段是Origin,表示请求来自哪一个源。除了Origin字段,"预检"请求的头信息包括两个特殊字段。
OPTIONS /cors HTTP/1.1
Origin: http://api.bob.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: X-Custom-Header
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0..
复制代码
1)Access-Control-Request-Method:必选
用来列出浏览器的CORS请求会用到哪些HTTP方法,上例是PUT。
2)Access-Control-Request-Headers:可选
该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段,上例是X-Custom-Header。
预检请求的回应 服务器收到"预检"请求之后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段之后,确认容许跨源请求,就能够作出回应。
HTTP回应中,除了关键的是Access-Control-Allow-Origin字段,其余CORS相关字段以下:
1)Access-Control-Allow-Methods:必选
它的值是逗号分隔的一个字符串,代表服务器支持的全部跨域请求的方法。注意,返回的是全部支持的方法,而不单是浏览器请求的那个方法。这是为了不屡次"预检"请求。
2)Access-Control-Allow-Headers
若是浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,代表服务器支持的全部头信息字段,不限于浏览器在"预检"中请求的字段。
3)Access-Control-Allow-Credentials:可选
该字段与简单请求时的含义相同。
4)Access-Control-Max-Age:可选
用来指定本次预检请求的有效期,单位为秒。
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反向代理接口跨域
跨域问题:同源策略仅是针对浏览器的安全策略。服务器端调用HTTP接口只是使用HTTP协议,不须要同源策略,也就不存在跨域问题。
实现思路:经过Nginx配置一个代理服务器域名与domain1相同,端口不一样)作跳板机,反向代理访问domain2接口,而且能够顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域访问。
nginx具体配置:
#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相同,都是经过启一个代理服务器,实现数据的转发,也能够经过设置cookieDomainRewrite参数修改响应头中cookie中域名,实现当前域的cookie写入,方便接口登陆认证。
1)非vue框架的跨域
使用node + express + http-proxy-middleware搭建一个proxy服务器。
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写cookie
xhr.withCredentials = true;
// 访问http-proxy-middleware代理服务器
xhr.open('get', 'http://www.domain1.com:3000/login?user=admin', true);
xhr.send();
复制代码
var express = require('express');
var proxy = require('http-proxy-middleware');
var 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框架的跨域
node + vue + webpack + webpack-dev-server搭建的项目,跨域请求接口,直接修改webpack.config.js配置。开发环境下,vue渲染服务和接口代理服务都是webpack-dev-server同一个,因此页面与代理接口之间再也不跨域。
webpack.config.js部分配置:
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为基础主域,就实现了同域。
实现原理: a欲与b跨域相互通讯,经过中间页c来实现。 三个页面,不一样域之间利用iframe的location.hash传值,相同域之间直接js访问来通讯。
具体实现:A域:a.html -> B域:b.html -> A域:c.html,a与b不一样域只能经过hash值单向通讯,b与c也不一样域也只能单向通讯,但c与a同域,因此c可经过parent.parent访问a页面全部对象。
window.name属性的独特之处:name值在不一样的页面(甚至不一样域名)加载后依旧存在,而且能够支持很是长的 name 值(2MB)。
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数很少能够跨域操做的window属性之一,它可用于解决如下方面的问题:
用法:postMessage(data,origin)方法接受两个参数:
WebSocket protocol是HTML5一种新的协议。它实现了浏览器与服务器全双工通讯,同时容许跨域通信,是server push技术的一种很好的实现。 原生WebSocket API使用起来不太方便,咱们使用Socket.io,它很好地封装了webSocket接口,提供了更简单、灵活的接口,也对不支持webSocket的浏览器提供了向下兼容。
以上就是9种常见的跨域解决方案,jsonp(只支持get请求,支持老的IE浏览器)适合加载不一样域名的js、css,img等静态资源;CORS(支持全部类型的HTTP请求,但浏览器IE10如下不支持)适合作ajax各类跨域请求;Nginx代理跨域和nodejs中间件跨域原理都类似,都是搭建一个服务器,直接在服务器端请求HTTP接口,这适合先后端分离的前端项目调后端接口。document.domain+iframe适合主域名相同,子域名不一样的跨域请求。postMessage、websocket都是HTML5新特性,兼容性不是很好,只适用于主流浏览器和IE10+。
答:三次是最少的安全次数,两次不安全,四次浪费资源;
Client向Server发送FIN包,表示Client主动要关闭链接,而后进入FIN_WAIT_1状态,等待Server返回ACK包。此后Client不能再向Server发送数据,但能读取数据。
Server收到FIN包后向Client发送ACK包,而后进入CLOSE_WAIT状态,此后Server不能再读取数据,但能够继续向Client发送数据。
Client收到Server返回的ACK包后进入FIN_WAIT_2状态,等待Server发送FIN包。
Server完成数据的发送后,将FIN包发送给Client,而后进入LAST_ACK状态,等待Client返回ACK包,此后Server既不能读取数据,也不能发送数据。
Client收到FIN包后向Server发送ACK包,而后进入TIME_WAIT状态,接着等待足够长的时间(2MSL)以确保Server接收到ACK包,最后回到CLOSED状态,释放网络资源。
Server收到Client返回的ACK包后便回到CLOSED状态,释放网络资源
TCP是全双工信道,何为全双工就是客户端与服务端创建两条通道,通道1:客户端的输出链接服务端的输入;通道2:客户端的输入链接服务端的输出。两个通道能够同时工做:客户端向服务端发送信号的同时服务端也能够向客户端发送信号。因此关闭双通道的时候就是这样:
客户端:我要关闭输入通道了。
服务端:好的,你关闭吧,我这边也关闭这个通道。
服务端:我也要关闭输入通道了。
客户端:好的你关闭吧,我也把这个通道关闭。
1.递归
function f(n) {
if (n === 1 || n === 2) {
return 1;
} else {
return f(n-1) + f(n-2);
}
}
复制代码
时间复杂度:O(2^N)
空间复杂度:O(N)
时间复杂度是指数阶,属于爆炸增量函数,在程序设计中咱们应该避免这样的复杂度。
用递归法实现斐波那契数列代码实现比较简洁,但在n比较大时会引发栈溢出,没法实现所需功能。
function f(n, ac1=1, ac2=1) {
if (n<=2) {
return ac2;
}
return f(n-1, ac2, ac1+ac2);
}
复制代码
function* f(){
let [prev, curr] = [0, 1];
// for(;;)至关于死循环 等于while(1)
for(;;) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
for(let n of f()) {
if (n > 1000) break;
console.log(n);
}
复制代码
function memozi(fn) {
var r = {};
return function(n) {
if (r[n] == null) {
r[n] = fn(n);
return r[n];
} else {
return r[n];
}
}
}
var fibfn = memozi(function(n) {
if (n==0) {
return 0;
} else if (n==1) {
return 1;
} else {
return fibfn(n-1) + fibfn(n-2)
}
})
复制代码
这里用到闭包相关知识,因此问了“闭包”相关知识(详情见第15题)。
给定一个只包括 '(',')','{','}','[',']' 的字符串,判断字符串是否有效。 有效字符串需知足: 左括号必须用相同类型的右括号闭合。 左括号必须以正确的顺序闭合。 注意空字符串可被认为是有效字符串
var isValid = function(s) {
const n = s.length;
if (n % 2 === 1) {
return false;
}
const pairs = new Map([
[')','('],
[']','['],
['}','{']
]);
const stk = [];
for(let i=0; i<s.length; i++) {
if (pairs.has(s[i])) {
if (!stk.length || stk[stk.length-1] !== pairs.get(s[i])) {
return false;
}
stk.pop();
} else {
stk.push(s[i]);
}
}
return !stk.length;
}
复制代码
URL传值和form表单提交的区别和原理
区别:
一、url传值就是get ,from表单就是post ,get是从服务器获取数据,post是向服务器传送数据
二、 对于get方式,服务器端用Request.QueryString获取变量的值,对于post方式,服务器端用Request.Form获取提交的数据。
三、get传送的数据量较小,不能大于2KB。post传送的数据量较大,通常被默认为不受限制。
四、get安全性很是低,post安全性较高
1.经过 DNS 预解析来告诉浏览器将来咱们可能从某个特定的 URL 获取资源,当浏览器真正使用到该域中的某个资源时就能够尽快地完成 DNS 解析。
第一步:打开或关闭DNS预解析
你能够经过在服务器端发送 X-DNS-Prefetch-Control 报头。或是在文档中使用值为 http-equiv 的meta标签:
<meta http-equiv="x-dns-prefetch-control" content="on">
复制代码
须要说明的是,在一些高级浏览器中,页面中全部的超连接(<a>标签),默认打开了DNS预解析。可是,若是页面中采用的https协议,不少浏览器是默认关闭了超连接的DNS预解析。若是加了上面这行代码,则代表强制打开浏览器的预解析。(若是你能在面试中把这句话说出来,则必定是你出彩的地方)
第二步:对指定的域名进行DNS预解析
若是咱们未来可能从 smyhvae.com 获取图片或音频资源,那么能够在文档顶部的 标签中加入如下内容:
<link rel="dns-prefetch" href="http://www.smyhvae.com/">
复制代码
当咱们从该 URL 请求一个资源时,就再也不须要等待 DNS 解析的过程。该技术对使用第三方资源特别有用。
function debounce (f, wait) {
let timer
return (...args) => {
clearTimeout(timer)
timer = setTimeout(() => {
f(...args)
}, wait)
}
}
复制代码
使用场景
function throttle (f, wait) {
let timer
return (...args) => {
if (timer) { return }
timer = setTimeout(() => {
f(...args)
timer = null
}, wait)
}
}
复制代码
使用场景
手写Promise(简易版)
function myPromise(fn) {
this.cbs = [];
const resolve = (value) => {
setTimeout(() => {
this.data = value;
this.cbs.forEach((cb) => cb(value));
})
}
fn(resolve);
}
myPromise.prototype.then = function (onResolved) {
return new myPromise((resolve) => {
this.cbs.push(() => {
const res = onResolved(this.data);
if (res instanceof myPromise) {
res.then(resolve);
} else {
resolve(res);
}
});
});
}
export default myPromise;
复制代码
promise详细知识点 juejin.cn/post/690555…
Set-Cookie: name=value; HttpOnly
HTML布局:
<div class="outer">
<div class="sidebar">固定宽度区(sideBar)</div>
<div class="content">自适应区(content)</div>
</div>
<div class="footer">footer</div>
复制代码
方法:
一、将左侧div浮动,右侧div设置margin-left
/*方法1*/
.outer{overflow: hidden; border: 1px solid red;}
.sidebar{float: left;width:200px;height: 150px; background: #BCE8F1;}
.content{margin-left:200px;height:100px;background: #F0AD4E;}
复制代码
/*方法2*/
.outer7{display: flex; border: 1px solid red;}
.sidebar7{flex:0 0 200px;height:150px;background: #BCE8F1;}
.content7{flex: 1;height:100px;background: #F0AD4E;}
复制代码
flex能够说是最好的方案了,代码少,使用简单。但存在兼容性,有朝一日,你们都改用现代浏览器,就能够使用了。
须要注意的是,flex容器的一个默认属性值:align-items: stretch;。这个属性致使了列等高的效果。 为了让两个盒子高度自动,须要设置: align-items: flex-start;
/*方法3*/
.outer6{overflow: auto; border: 1px solid red;}
.sidebar6{float: left;height:150px;background: #BCE8F1;}
.content6{overflow:auto;height:100px;background: #F0AD4E;}
复制代码
这个方案一样是利用了左侧浮动,可是右侧盒子经过overflow: auto;造成了BFC,所以右侧盒子不会与浮动的元素重叠。
延伸问题:由于代码中用了flex布局, 问了flex布局相关知识
Flex是Flexible Box的缩写,意为”弹性布局”,用来为盒状模型提供最大的灵活性。 任何一个容器均可以指定为Flex布局。容器分为两种,块flex和行内flex.
.box{
display: flex; /*webkit须要加前缀*/
/*display:inline-flex;*/
}
复制代码
Flex布局有两层,采用flex布局的元素称为flex容器,其子元素则自动成flex item,即项目. 注:flex不一样于block,flex容器的子元素的float,clear,vertical-align属性将失效.
flex容器有两根轴:水平主轴就是x轴(main axis)和竖直轴也是y轴(cross axis),两轴相关位置标识以下:
flex容器属性:
flex-direction:决定项目的排列方向。
flex-wrap:即一条轴线排不下时如何换行。
flex-flow:是flex-direction属性和flex-wrap属性的简写形式,默认值为row nowrap。
justify-content:定义了项目在主轴上的对齐方式。(justify)
align-items:定义项目在交叉轴上如何对齐。
align-content:定义了多根轴线的对齐方式。若是项目只有一根轴线,该属性不起做用。(换行会产生多轴)
order:定义项目的排列顺序。数值越小,排列越靠前,默认为0。
flex-grow:定义项目的放大比例,若是全部项目的flex-grow属性都为1,则它们将等分剩余空间(若是有的话)。若是一个项目的flex-grow属性为2,其余项目都为1,则前者占据的剩余空间将比其余项多一倍。
flex-shrink:定义了项目的缩小比例,默认为1,若是全部项目的flex-shrink属性都为1,当空间不足时,都将等比例缩小。若是一个项目的flex-shrink属性为0,其余项目都为1,则空间不足时,前者不缩小。
flex-basis:定义了在分配多余空间以前,项目占据的主轴空间(main size)。
flex:是flex-grow, flex-shrink 和 flex-basis的简写,默认值为0 1 auto。后两个属性可选。
align-self:容许单个项目有与其余项目不同的对齐方式,可覆盖align-items属性。默认值为auto,表示继承父元素的align-items属性,若是没有父元素,则等同于stretch。
「函数」和「函数内部能访问到的变量」(也叫环境)的总和,就是一个闭包。
复制代码
闭包经常用来「间接访问一个变量」。换句话说,「隐藏一个变量」。
复制代码
闭包会形成内存泄露?
错。
说这话的人根本不知道什么是内存泄露。内存泄露是指你用不到(访问不到)的变量,依然占居着内存空间,不能被再次利用起来。
闭包里面的变量明明就是咱们须要的变量(lives),凭什么说是内存泄露?
这个谣言是如何来的?
由于 IE。IE 有 bug,IE 在咱们使用完闭包以后,依然回收不了闭包里面引用的变量。
这是 IE 的问题,不是闭包的问题。
1.vue 双向数据绑定是经过 数据劫持 结合 发布订阅模式的方式来实现的, 也就是说数据和视图同步,数据发生变化,视图跟着变化,视图变化,数据也随之发生改变;
2.核心:关于VUE双向数据绑定,其核心是 Object.defineProperty()方法;
3.介绍一下Object.defineProperty()方法
(1)Object.defineProperty(obj, prop, descriptor) ,这个语法内有三个参数,分别为 obj (要定义其上属性的对象) prop (要定义或修改的属性) descriptor (具体的改变方法)
(2)简单地说,就是用这个方法来定义一个值。当调用时咱们使用了它里面的get方法,当咱们给这个属性赋值时,又用到了它里面的set方法;
webpack打包流程归纳
webpack的运行流程是一个串行的过程,从启动到结束会依次执行如下流程:
在以上过程当中, Webpack 会在特定的时间点广播特定的事件,插件在监听到感兴趣的事件后会执行特定的逻辑,井且插件能够调用 Webpack 提供的 API 改变 Webpack 的运行结果。其实以上7个步骤,能够简单概括为初始化、编译、输出,三个过程,而这个过程其实就是前面说的基本模型的扩展。
在 Object 中, key 必须是简单数据类型(整数,字符串或者是 symbol),而在 Map 中则能够是 JavaScript 支持的全部数据类型,也就是说能够用一个 Object 来当作一个Map元素的 key。
Map 元素的顺序遵循插入的顺序,而 Object 的则没有这一特性。
Map 继承自 Object 对象。
新建实例
var obj = {...};
var obj = new Object();
var obj = Object.create(null);
复制代码
var map = new Map([[1, 2], [2, 3]]); // map = {1 => 2, 2 => 3}
复制代码
数据访问
map.get(1) // 2
复制代码
obj.id;
obj['id'];
复制代码
map.has(1);
复制代码
obj.id === undefined;
// 或者
'id' in obj;
复制代码
另外须要注意的一点是,Object 能够使用 Object.prototype.hasOwnProperty() 来判断某个key是不是这个对象自己的属性,从原型链继承的属性不包括在内。
新增一个数据
map.set(key, value) // 当传入的 key 已经存在的时候,Map 会覆盖以前的值
复制代码
obj['key'] = value;
obj.key = value;
// object也会覆盖
复制代码
删除数据
delete obj.id;
// 下面这种作法效率更高
obj.id = undefined
复制代码
须要注意的是,使用 delete 会真正的将属性从对象中删除,而使用赋值 undefined 的方式,仅仅是值变成了 undefined。属性仍然在对象上,也就意味着 在使用 for … in… 去遍历的时候,仍然会访问到该属性。
var isDeleteSucceeded = map.delete(1);
console.log(isDeleteSucceeded ); // true
// 所有删除
map.clear();
复制代码
获取size
Map 自身有 size 属性,能够本身维持 size 的变化。
Object 则须要借助 Object.keys() 来计算
console.log(Object.keys(obj).length);
复制代码
Iterating
Map 自身支持迭代,Object 不支持。
如何肯定一个类型是否是支持迭代呢? 能够使用如下方法:
console.log(typeof obj[Symbol.iterator]); // undefined
console.log(typeof map[Symbol.iterator]); // function
复制代码
当所要存储的是简单数据类型,而且 key 都为字符串或者整数或者 Symbol 的时候,优先使用 Object ,由于Object能够使用 字符变量 的方式建立,更加高效。
当须要在单独的逻辑中访问属性或者元素的时候,应该使用 Object
JSON 直接支持 Object,但不支持 Map
Map 是纯粹的 hash, 而 Object 还存在一些其余内在逻辑,因此在执行 delete 的时候会有性能问题。因此写入删除密集的状况应该使用 Map。
Map 会按照插入顺序保持元素的顺序,而Object作不到。
Map 在存储大量元素的时候性能表现更好,特别是在代码执行时不能肯定 key 的类型的状况
构造调用:
若是函数返回一个对象,那么new 这个函数调用返回这个函数的返回对象,不然返回 new 建立的新对象
Array.from(new Set([1, 1, 2, 2]))
复制代码
function flatten(arr) {
let result = [];
for (let i = 0; i < arr.length; i++) {
if (Array.isArray(arr[i])) {
result = result.concat(flatten(arr[i]));
} else {
result = result.concat(arr[i]);
}
}
return result;
}
const a = [1, [2, [3, 4]]];
console.log(flatten(a));
复制代码
事件循环机制从总体上告诉了咱们 JavaScript 代码的执行顺序 Event Loop即事件循环,是指浏览器或Node的一种解决javaScript单线程运行时不会阻塞的一种机制,也就是咱们常用异步的原理。 先执行宏任务队列,而后执行微任务队列,而后开始下一轮事件循环,继续先执行宏任务队列,再执行微任务队列。
上诉的 setTimeout 和 setInterval 等都是任务源,真正进入任务队列的是他们分发的任务。
执行微任务过程当中产生的新的微任务并不会推迟到下一个循环中执行,而是在当前的循环中继续执行
是类数组,是属于鸭子类型的范畴,长得像数组,
一个典型的Webpack插件代码以下:
class MyWebpackPlugin {
constructor(options) {
}
apply(compiler) {
// 插入钩子函数
compiler.hooks.emit.tap('MyWebpackPlugin', (compilation) => {});
}
}
module.exports = MyWebpackPlugin;
复制代码
接下来须要在webpack.config.js中引入这个插件。
module.exports = {
plugins:[
// 传入插件实例
new MyWebpackPlugin({
param:'paramValue'
}),
]
};
复制代码
Webpack在启动时会实例化插件对象,在初始化compiler对象以后会调用插件实例的apply方法,传入compiler对象,插件实例在apply方法中会注册感兴趣的钩子,Webpack在执行过程当中会根据构建阶段回调相应的钩子。
详细知识点请google
function EventEmitter() {
this.__events = {};
}
EventEmitter.VERSION = '1.0.0';
EventEmitter.prototype.on = function(eventName, listener) {
if (!eventName || !listener) return;
// 判断回调的 listener(或listener.listener) 是否为函数
if (!isValidListener(listener)) {
throw new TypeError('listener must be a function');
}
var events = this.__events;
var listeners = events[eventName] = events[eventName] || [];
var listenerIsWrapped = typeof listener === 'object';
// 不重复添加事件, 判断是否有同样的
if (indexOf(listeners, listener) === -1) {
listeners.push(listenerIsWrapped ? listener : {
listener: listener,
once: false
})
}
return this;
}
EventEmitter.prototype.emit = function(eventName, args) {
// 经过内部对象获取对应自定义事件的回调函数
var listeners = this.__events[eventName];
if (!listeners) return;
// 考虑多个 listener 的状况
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
if (listener) {
listener.listener.apply(this, args || []);
// listener 中 once 为true 的进行特殊处理
if (listener.once) {
this.off(eventName, listener.listener)
}
}
}
return this;
}
EventEmitter.prototype.off = function(eventName, listener) {
var listeners = this.__events[eventName];
if (!listeners) return;
var index;
for (var i = 0, len = listeners.length; i < len; i++) {
if (listeners[i] && listeners[i].listener === listener) {
index = i;
break;
}
}
if (typeof index !== 'undefined') {
listeners.splice(index, 1, null);
}
return this;
}
EventEmitter.prototype.once = function(eventName, listener) {
// 调用 on 方法, once 参数传入 true,待执行以后进行 once 处理
return this.on(eventName, {
listener: listener,
once: true
})
}
EventEmitter.prototype.alloOff = function(eventName) {
// 若是该 eventName 存在,则将其对应的 listeners 的数组直接清空
if (eventName && this.__events[eventName]) {
this.__events[eventName] = [];
} else {
this.__events = {};
}
}
// 判断是不是合法的 listener
function isValidListener(listener) {
if (typeof listener === 'function') {
return true;
} else if (listener && typeof listener === 'object') {
return isValidListener(listener.listener);
} else {
return false;
}
}
// 判断新增自定义事件是否存在
function indexOf(array, item) {
var result = -1;
item = typeof item === 'object' ? item.listener : item;
for(var i = 0, len = array.length; i<len; i++) {
if (array[i].listener === item) {
result = i;
break;
}
}
return result;
}
复制代码
在 Vue 框架中不一样组件之间的通信里,有一种解决方案叫 EventBus。和 EventEmitter的思路相似,它的基本用途是将 EventBus 做为组件传递数据的桥梁,全部组件共用相同的事件中心,能够向该中心注册发送事件或接收事件,全部组件均可以收到通知,使用起来很是便利,其核心其实就是发布-订阅模式的落地实现
栈内存中的基本类型,能够经过操做系统直接处理;而堆内存中的引用类型,正是因为能够常常变化,大小不固定,所以须要 JavaScript 的引擎经过垃圾回收机制来处理
新生代内存回收: Scavenge 算法
老生代内存回收: Mark-Sweep(标记清除) 和 Mark-Compact(标记整理)
老生代内存的管理方式和新生代的内存管理方式区别仍是比较大的。Scavenge 算法比较适合内存较小的状况处理;而对于老生代内存较大、变量较多的时候,仍是须要采用“标记-清除”结合“标记-整理”这样的方式处理内存问题,并尽可能避免内存碎片的产生
场景
1.过多的缓存未释放;
2.闭包太多未释放;
3.定时器或者回调太多未释放;
4.太多无效的 DOM 未释放;
5.全局变量太多未被发现。
1.CommonJs
CommonJs能够动态加载语句,代码发生在运行时
CommonJs混合导出,仍是一种语法,只不过不用声明前面对象而已,当我导出引用对象时以前的导出就被覆盖了
CommonJs导出值是拷贝,能够修改导出的值,这在代码出错时,很差排查引发变量污染
2.Es Module
html,
body {
width: 100%;
height: 100%;
}
.outer {
width: 400px;
height: 100%;
background: blue;
margin: 0 auto;
display: flex;
align-items: center;
}
.inner {
position: relative;
width: 100%;
height: 0;
padding-bottom: 50%;
background: red;
}
.box {
position: absolute;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
<div class="outer">
<div class="inner">
<div class="box">hello</div>
</div>
</div>
复制代码
padding-bottom 值为%时,是基于父元素宽度的百分比下内边距.
const isComplexDataType = obj => (typeof obj === 'object' || typeof obj === 'function') && (obj !== null)
const deepClone = function (obj, hash = new WeakMap()) {
if (obj.constructor === Date)
return new Date(obj) // 日期对象直接返回一个新的日期对象
if (obj.constructor === RegExp)
return new RegExp(obj) //正则对象直接返回一个新的正则对象
//若是循环引用了就用 weakMap 来解决
if (hash.has(obj)) return hash.get(obj)
let allDesc = Object.getOwnPropertyDescriptors(obj)
//遍历传入参数全部键的特性
let cloneObj = Object.create(Object.getPrototypeOf(obj), allDesc)
//继承原型链
hash.set(obj, cloneObj)
for (let key of Reflect.ownKeys(obj)) {
cloneObj[key] = (isComplexDataType(obj[key]) && typeof obj[key] !== 'function') ? deepClone(obj[key], hash) : obj[key]
}
return cloneObj
}
复制代码
父beforeCreate->父created->父beforeMount->子beforeCreate->子created->子beforeMount->子mounted->父mounted
父beforeUpdate->子beforeUpdate->子updated->父updated
父beforeUpdate->父updated
父beforeDestroy->子beforeDestroy->子destroyed->父destroyed
总结:从外到内,再从内到外
var a = ?;
if(a == 1 && a == 2 && a == 3){
conso.log(1);
}
复制代码
答案解析 由于==会进行隐式类型转换 因此咱们重写toString方法就能够了
var a = {
i: 1,
toString() {
return a.i++;
}
}
if( a == 1 && a == 2 && a == 3 ) {
console.log(1);
}
复制代码
下面代码输出什么
var a = 10;
(function () {
console.log(a)
a = 5
console.log(window.a)
var a = 20;
console.log(a)
})()
复制代码
依次输出:undefined -> 10 -> 20
解析:
在当即执行函数中,var a = 20; 语句定义了一个局部变量 a,因为js的变量声明提高机制,局部变量a的声明会被提高至当即执行函数的函数体最上方,且因为这样的提高并不包括赋值,所以第一条打印语句会打印undefined,最后一条语句会打印20。
因为变量声明提高,a = 5; 这条语句执行时,局部的变量a已经声明,所以它产生的效果是对局部的变量a赋值,此时window.a 依旧是最开始赋值的10,
1.子组件为什么不能够修改父组件传递的 Prop
单向数据流,易于监测数据的流动,出现了错误能够更加迅速的定位到错误发生的位置。
2.若是修改了,Vue 是如何监控到属性的修改并给出警告的
if (process.env.NODE_ENV !== 'production') {
var hyphenatedKey = hyphenate(key);
if (isReservedAttribute(hyphenatedKey) ||
config.isReservedAttr(hyphenatedKey)) {
warn(
("\"" + hyphenatedKey + "\" is a reserved attribute and cannot be used as component prop."),
vm
);
}
defineReactive$$1(props, key, value, function () {
if (!isRoot && !isUpdatingChildComponent) {
warn(
"Avoid mutating a prop directly since the value will be " +
"overwritten whenever the parent component re-renders. " +
"Instead, use a data or computed property based on the prop's " +
"value. Prop being mutated: \"" + key + "\"",
vm
);
}
});
}
复制代码
在initProps的时候,在defineReactive时经过判断是否在开发环境,若是是开发环境,会在触发set的时候判断是否此key是否处于updatingChildren中被修改,若是不是,说明此修改来自子组件,触发warning提示。
须要特别注意的是,当你从子组件修改的prop属于基础类型时会触发提示。 这种状况下,你是没法修改父组件的数据源的, 由于基础类型赋值时是值拷贝。你直接将另外一个非基础类型(Object, array)赋值到此key时也会触发提示(但实际上不会影响父组件的数据源), 当你修改object的属性时不会触发提示,而且会修改父组件数据源的数据。
一、首先token不是防止XSS的,而是为了防止CSRF的;
二、CSRF攻击的缘由是浏览器会自动带上cookie,而浏览器不会自动带上token
var b = 10;
(function b(){
b = 20;
console.log(b);
})();
复制代码
1打印结果内容以下:
ƒ b() {
b = 20;
console.log(b)
}
复制代码
缘由:
做用域:执行上下文中包含做用于链: 在理解做用域链以前,先介绍一下做用域,做用域能够理解为执行上下文中申明的变量和做用的范围;包括块级做用域/函数做用域;
特性:声明提早:一个声明在函数体内都是可见的,函数声明优先于变量声明; 在非匿名自执行函数中,函数变量为只读状态没法修改;
Promise._race = promises => new Promise((resolve, reject) => {
promises.forEach(promise => {
promise.then(resolve, reject)
})
})
复制代码
var obj = {
'2': 3,
'3': 4,
'length': 2,
'splice': Array.prototype.splice,
'push': Array.prototype.push
}
obj.push(1)
obj.push(2)
console.log(obj)
复制代码
结果:
在对象中加入splice属性方法,和length属性后。这个对象变成一个类数组。
题目的解释应该是:
第一第二步还能够具体解释为:由于每次push只传入了一个参数,因此 obj.length 的长度只增长了 1。push方法自己还能够增长更多参数
for-of循环不支持普通对象,但若是你想迭代一个对象的属性,如下方法能够实现
方法1:
for (var key of Object.keys(someObject)) {
console.log(key + ": " + someObject[key]);
}
方法2:
var obj = {
a:1,
b:2,
c:3
};
obj[Symbol.iterator] = function*(){
var keys = Object.keys(obj);
for(var k of keys){
yield [k,obj[k]]
}
};
for(var [k,v] of obj){
console.log(k,v);
}
复制代码
全部拥有Symbol.iterator的对象被称为可迭代的
连接: www.bilibili.com/video/BV1G5…
1.函数式组件(Functional components)
优化前的组件代码以下:
<template>
<div class="cell">
<div v-if="value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
<script>
export default {
props: ['value'],
}
</script>
复制代码
优化后的组件代码以下:
<template functional>
<div class="cell">
<div v-if="props.value" class="on"></div>
<section v-else class="off"></section>
</div>
</template>
复制代码
函数式组件和普通的对象类型的组件不一样,它不会被看做成一个真正的组件,咱们知道在 patch 过程当中,若是遇到一个节点是组件 vnode,会递归执行子组件的初始化过程;而函数式组件的 render 生成的是普通的 vnode,不会有递归子组件的过程,所以渲染开销会低不少。所以,函数式组件也不会有状态,不会有响应式数据,生命周期钩子函数这些东西。你能够把它当成把普通组件模板中的一部分 DOM 剥离出来,经过函数的方式渲染出来,是一种在 DOM 层面的复用。
若是你在用 Vue 开发应用,那么就要小心内存泄漏的问题。这个问题在单页应用 (SPA) 中尤其重要,由于在 SPA 的设计中,用户使用它时是不须要刷新浏览器的,因此 JavaScript 应用须要自行清理组件来确保垃圾回收以预期的方式生效。
内存泄漏在 Vue 应用中一般不是来自 Vue 自身的,更多地发生于把其它库集成到应用中的时候。
一个更常见的实际的场景是使用 Vue Router 在一个单页应用中路由到不一样的组件。当一个用户在你的应用中导航时,Vue Router 从虚拟 DOM 中移除了元素,并替换为了新的元素。Vue 的 beforeDestroy() 生命周期钩子是一个解决基于 Vue Router 的应用中的这类问题的好地方。 咱们能够将清理工做放入 beforeDestroy() 钩子,像这样:
beforeDestroy: function () {
this.choicesSelect.destroy()
}
复制代码
Vue 让开发很是棒的响应式的 JavaScript 应用程序变得很是简单,可是你仍然须要警戒内存泄漏。这些内存泄漏每每会发生在使用 Vue 以外的其它进行 DOM 操做的三方库时。请确保测试应用的内存泄漏问题并在适当的时机作必要的组件清理。
如下讲解基于Vue 2.6
slot 和 solt-scope在组件内部被统一整合成了函数
他们的渲染做用域都是 子组件
而且都能经过 this.$scopedSlots去访问
父组件通过初始化时的一系列处理,每一个插槽会转换成一个key(插槽名,未命名时是default)对应的函数(有做用域参数的话,会传做用越参数). 子组件的实例 this.$scopedSlots 就能够访问到 父组件里的 ‘插槽函数’。 若是是 普通插槽, 就直接调用函数生成 vnode, 若是是 做用域插槽, 就带着 props 去调用函数生成 vnode.
总结: Vue 2.6 版本后对 slot 和 slot-scope 作了一次统一的整合,让它们所有都变为函数的形式,全部的插槽均可以在 this.$scopedSlots 上直接访问,这让咱们在开发高级组件的时候变得更加方便。在优化上,Vue 2.6 也尽量的让 slot 的更新不触发父组件的渲染,经过一系列巧妙的判断和算法去尽量避免没必要要的渲染。在 2.5 的版本中,因为生成 slot 的做用域是在父组件中,因此明明是子组件的插槽 slot 的更新是会带着父组件一块儿更新的)
具体文章连接: juejin.cn/post/684490…
1.完整的路由导航解析流程(不包括其余生命周期)
2.触发钩子的完整顺序
路由导航、keep-alive、和组件生命周期钩子结合起来的, 触发顺序, 假设是从a组件离开, 第一次进入b组件:
beforeCreate:实例刚在内存中被建立出来,此时,尚未初始化好 data 和 methods 属性
created:实例已经在内存中建立OK,此时 data 和 methods 已经建立OK,此时尚未开始 编译模板
beforeMount:此时已经完成了模板的编译,可是尚未挂载到页面中
mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
beforeUpdate:状态更新以前执行此函数, 此时 data 中的状态值是最新的,可是界面上显示的 数据仍是旧的,由于此时尚未开始从新渲染DOM节点
updated:实例更新完毕以后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被从新渲染好了!
mounted生命周期能够获取到真实DOM
修改data里面的数据,会触发 beforeUpdate、updated生命周期
data是一个函数的时候,每个实例的data属性都是独立的,不会相互影响
props/$emit
on
vuex
$attrs/$listeners
简单来讲:$attrs与$listeners 是两个对象, listeners里存放的是父组件中绑定的非原生事件。
provide/inject
Vue2.2.0新增API,这对选项须要一块儿使用,以容许一个祖先组件向其全部子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。一言而蔽之:祖先组件中经过provider来提供变量,而后在子孙组件中经过inject来注入变量。 provide / inject API 主要解决了跨级组件间的通讯问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间创建了一种主动提供与依赖注入的关系。
children与 ref
$nextTick 做用:在下次 DOM 更新循环结束以后执行延迟回调。在修改数据以后当即使用这个方法,获取更新后的 DOM。
1.能力检测
这一块其实很简单,众所周知,Event Loop分为宏任务(macro task)以及微任务( micro task),无论执行宏任务仍是微任务,完成后都会进入下一个tick,并在两个tick之间执行UI渲染。 可是,宏任务耗费的时间是大于微任务的,因此在浏览器支持的状况下,优先使用微任务。若是浏览器不支持微任务,使用宏任务;可是,各类宏任务之间也有效率的不一样,须要根据浏览器的支持状况,使用不一样的宏任务。
2.根据能力检测以不一样方式执行回调队列
降级处理: Promise -> MutationObserver -> setImmediate -> setTimeout
I/O,事件队列中的每个事件都是一个macrotask
setTimeout / setInterval
MessageChannel是通讯渠道API,ie11以上和其它浏览器支持。
setImmediate 目前只有IE10以上实现了该方法其它浏览器不支持.做用回调功能,node支持。
requestAnimationFrame 也算宏任务 node不支持。
实现一个简易的nextTick
let callbacks = []
let pending = false
function nextTick (cb) {
callbacks.push(cb)
if (!pending) {
pending = true
setTimeout(flushCallback, 0)
}
}
function flushCallback () {
pending = false
let copies = callbacks.slice()
callbacks.length = 0
copies.forEach(copy => {
copy()
})
}
复制代码
当style标签具备该scoped属性时,其CSS将仅应用于当前组件的元素
<style scoped>
.example {
color: red;
}
</style>
<template>
<div class="example">hi</div>
</template>
<style>
.example[data-v-5558831a] {
color: red;
}
</style>
<template>
<div class="example" data-v-5558831a>hi</div>
</template>
复制代码
实现原理: PostCSS给一个组件中的全部dom添加了一个独一无二的动态属性,而后,给CSS选择器额外添加一个对应的属性选择器来选择该组件中dom,这种作法使得样式只做用于含有该属性的dom——组件内部dom
hash模式的工做原理是hashchange事件,能够在window监听hash的变化。咱们在url后面随便添加一个#xx触发这个事件。
HashHistory的push和replace()
window.onhashchange = function(event){
console.log(event);
}
复制代码
HTML5History.pushState()和HTML5History.replaceState()
在HTML5History中添加对修改浏览器地址栏URL的监听是直接在构造函数中执行的,对HTML5History的popstate 事件进行监听:
constructor (router: Router, base: ?string) {
window.addEventListener('popstate', e => {
const current = this.current
this.transitionTo(getLocation(this.base), route => {
if (expectScroll) {
handleScroll(router, route, current, true)
}
})
})
}
复制代码
key的做用主要是为了高效的更新虚拟DOM,其原理是vue在patch过程当中经过key能够精准判断两个节点是不是同一个,从而避免频繁更新不一样元素, 使得整个patch过程更加高效, 减小DOM操做, 提升性能。
另外, 若不设置key还可能在列表更新时引起一些隐藏的bug
vue中在使用相同标签名元素的过分切换时,也会使用到key属性, 其目的也是为了让vue能够区分它们, 不然vue只会替换其内部属性而不会触发过分效果。
Object.defineProperty没法监控到数组下标的变化,致使经过数组下标添加元素,不能实时响应;
Object.defineProperty只能劫持对象的属性,从而须要对每一个对象,每一个属性进行遍历,若是,属性值是对象,还须要深度遍历。Proxy能够劫持整个对象,并返回一个新的对象。
不能监听动态length的变化.
例如,arr = [1],直接更改arr[10] = 20,这样就监听不到了,由于length在规范不容许重写,而arr[0]直接更改是能够监听到的。
数组方法使用不能监听到数组的变动,例如push
这也是为何vue重写这些方法的缘由
数据的变化是经过getter/setter来追踪的。由于这种追踪方式,有些语法中,即使是数据发生了变化,vue也检查不到。好比 向Object添加属性/ 删除Object的属性。
检测数组的变化,由于只是拦截了unshift shift push pop splice sort reverse 这几个方法,因此像
list[0] = 4
list.length = 0
复制代码
检测不到
业务代码处理:this.$set(this.data,”key”,value’)
Object.defineProperty自己有必定的监控到数组下标变化的能力:Object.defineProperty自己是能够监控到数组下标的变化的,可是在 Vue 中,从性能/体验的性价比考虑,尤大大就弃用了这个特性。
在 set 方法中,对 target 是数组和对象作了分别的处理, target 是数组时,会调用重写过的 splice 方法进行手动 Observe 。
对于对象,若是 key 原本就是对象的属性,则直接修改值触发更新,不然调用 defineReactive 方法从新定义响应式对象。
const arrayProto = Array.prototype
export const arrayMethods = Object.create(arrayProto)
const methodsToPatch = [
'push',
'pop',
'shift',
'unshift',
'splice',
'sort',
'reverse'
]
/**
* Intercept mutating methods and emit events
*/
methodsToPatch.forEach(function (method) {
// cache original method
const original = arrayProto[method]
def(arrayMethods, method, function mutator (...args) {
const result = original.apply(this, args)
const ob = this.__ob__
let inserted
switch (method) {
case 'push':
case 'unshift':
inserted = args
break
case 'splice':
inserted = args.slice(2)
break
}
if (inserted) ob.observeArray(inserted)
// notify change
ob.dep.notify()
return result
})
})
复制代码
重写了数组中的那些方法,首先获取到这个数组的__ob__,也就是它的Observer对象,若是有新的值,就调用observeArray继续对新的值观察变化,而后手动调用notify,通知渲染watcher,执行update
computed:计算属性
计算属性是由data中的已知值,获得的一个新值。 这个新值只会根据已知值的变化而变化,其余不相关的数据的变化不会影响该新值。 计算属性不在data中,计算属性新值的相关已知值在data中。 别人变化影响我本身。 watch:监听数据的变化
监听data中数据的变化 监听的数据就是data中的已知值 个人变化影响别人
1.watch擅长处理的场景:一个数据影响多个数据
2.computed擅长处理的场景:一个数据受多个数据影响
思路: 如何判断图片出如今了当前视口 (即如何判断咱们可以看到图片)
如何控制图片的加载
方案一: 位置计算 + 滚动事件 (Scroll) + DataSet API
1.位置计算: clientTop,offsetTop,clientHeight 以及 scrollTop 各类关于图片的高度做比对
2.监听 window.scroll 事件
3.DataSet API: <img data-src="solo.jpg">
首先设置一个临时 Data 属性 data-src,控制加载时使用 src 代替 data-src,可利用 DataSet API 实现
img.src = img.datset.src
方案二: getBoundingClientRect API + Scroll + DataSet API
// clientHeight 表明当前视口的高度
img.getBoundingClientRect().top < document.documentElement.clientHeight
复制代码
方案三: IntersectionObserver API + DataSet API
const observer = new IntersectionObserver((changes) => {
// changes: 目标元素集合
changes.forEach((change) => {
// intersectionRatio
if (change.isIntersecting) {
const img = change.target
img.src = img.dataset.src
observer.unobserve(img)
}
})
})
observer.observe(img)
复制代码
ie不支持
方案四: LazyLoading属性
<img src="shanyue.jpg" loading="lazy">
复制代码
除chrome几乎都不支持
考察 element-ui 源码表单设计, 代码仓库地址: github.com/glihui/guo-…