文章會持續更新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-…