前言
在平時H5或者RN開發時,我們業務場景中大部分都不是單頁面的需求,那這時我們就能使用路由在進行多頁面的切換。下面會對比一下react路由和RN路由的本質區別和使用方法。
路由(routing)是指分組從源到目的地時,決定端到端路徑的網絡范圍的進程
React路由
簡介
使用React構建的單頁面應用,要想實現頁面間的跳轉,首先想到的就是使用路由。在React中,常用的有兩個包可以實現這個需求,那就是react-router和react-router-dom。本文主要針對react-router-dom進行說明
在根組件上配置路由,引用react-router-dom結構{ HashRouter as Router, Route ,Link ,Redirect ,Switch },HashRouter組件是路由的根組件,定義屬性和方法傳遞給子組件。Router組件進行路由,指定每個路由跳轉到相應的組件。Link組件指定跳轉鏈接。Redirect組件路由重定向,不管什么情況下,都會跳轉當前指定的路徑,和switch組件聯合起來一起調用,當路徑匹配到路由,不在往下匹配
兩類路由
HashRouter:利用監聽hash變化(有一個事件hashchange)實現路由切換,它是路由容器,
渲染子組件,并向下層子組件傳遞(Context上下文傳遞)loaction,history等路由信息
BrowserHistory:利用H5Api實現路由切換,是路由容器,渲染子組件,
并向子組件傳遞loaction,history等路由信息
路由配置
image-20200601110809995
路由實現原理
HashRouter只是一個容器,本身并沒有DOM結構
它渲染的就是它的子組件,并向下層傳遞location
組件掛載完成之后根據hash改變pathname的值,如果沒有hash值就默認展示根組件
需要跳轉路由頁面時我們使用link或者push去賦值hash的pathname 如this.props.history.push({ pathname: preview, param: { pic, index } });
當hash值發生變化的時候會通過hashchange捕獲變化,并給pathname重新賦值
拿到上下文中傳過來的location,然后取出pathname。再對它的子組件進行遍歷,如果子組件的path屬性和當前上下文中傳過來的pathname屬性相匹配就進行渲染,若不匹配就返回null。
總結
React路由是實質就是,根據遍歷識別路由的pathname,來切換router路由容器中component組件的加載渲染。每次更改pathname就都是組件的重新渲染流程,頁面也都會呈現出刷新的效果。
RN路由
簡介
RN把導航和路由都集中到了react-navigation庫里面
組件使用堆棧式的頁面導航來實現各個頁面跳轉
構造函數:StackNavigator(RouteConfigs, StackNavigatorConfig)
RouteConfigs:頁面路由配置
StackNavigatorConfig:路由參數配置
路由配置
image-20200601111333107
參數詳解
navigationOptions:配置StackNavigator的一些屬性。
title:標題,如果設置了這個導航欄和標簽欄的title就會變成一樣的,不推薦使用
header:可以設置一些導航的屬性,如果隱藏頂部導航欄只要將這個屬性設置為null
headerTitle:設置導航欄標題,推薦
headerBackTitle:設置跳轉頁面左側返回箭頭后面的文字,默認是上一個頁面的標題。可以自定義,也可以設置為null
headerTruncatedBackTitle:設置當上個頁面標題不符合返回箭頭后的文字時,默認改成"返回"
headerRight:設置導航條右側??梢允前粹o或者其他視圖控件
headerLeft:設置導航條左側??梢允前粹o或者其他視圖控件
headerStyle:設置導航條的樣式。背景色,寬高等
headerTitleStyle:設置導航欄文字樣式
headerBackTitleStyle:設置導航欄‘返回’文字樣式
headerTintColor:設置導航欄顏色
headerPressColorAndroid:安卓獨有的設置顏色紋理,需要安卓版本大于5.0
gesturesEnabled:是否支持滑動返回手勢,iOS默認支持,安卓默認關閉
screen:對應界面名稱,需要填入import之后的頁面
mode:定義跳轉風格
card:使用iOS和安卓默認的風格
modal:iOS獨有的使屏幕從底部畫出。類似iOS的present效果
headerMode:返回上級頁面時動畫效果
float:iOS默認的效果
screen:滑動過程中,整個頁面都會返回
none:無動畫
cardStyle:自定義設置跳轉效果
transitionConfig: 自定義設置滑動返回的配置
onTransitionStart:當轉換動畫即將開始時被調用的功能
onTransitionEnd:當轉換動畫完成,將被調用的功能
path:路由中設置的路徑的覆蓋映射配置
initialRouteName:設置默認的頁面組件,必須是上面已注冊的頁面組件
initialRouteParams:初始路由參數
路由首頁
react:
image-20200601111638902
在react中初始化時沒有指定hash值,route會匹配路由表里面的根組件”/”
RN:
image-20200601111722749
RN 需要在StackNavigatorConfig里面指定首頁
RN路由使用
image-20200601112012191
在入口路由列表注冊完成之后 在導航器中的每一個頁面,都有 navigation 屬性 通過提供的navigate方法來提供跳轉
navigation
在導航器中每一個頁面都有navigation屬性,該屬性有以下幾個屬性/方法
navigate 跳轉到其他頁面 常用參數如下
routeName 導航器中配置的路由名稱
params 傳遞到下一個頁面的參數
state:state 里面包含有傳遞過來的參數 params 、 key 、路由名稱 routeName
setParams 更改當前頁面路由參數(后面詳細介紹)
goBack: 回退可穿參數
navigate
setParams
原始transition組件和CSS
定義transition的最簡單方法是使用transition·或transition-group 組件。這需要為transition定義一個name`和一些CSS。
<template>
<div id="app">
<button v-on:click="show = !show">
Toggle
</button>
<transition name="fade">
<p v-if="show">hello</p>
</transition>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
show: true
};
}
};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
圖片描述
看起來容易,對吧?然而,這種方法有一個問題。我們不能在另一個項目中真正重用這個transition。
封裝transition組件
如果我們將前面的邏輯封裝到一個組件中,并將其用作一個組件,結果會怎樣呢?
// FadeTransition.vue
<template>
<transition name="fade">
<slot></slot>
</transition>
</template>
<script>
export default {
};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
// App.vue
<template>
<div id="app">
<button v-on:click="show = !show">
Toggle transition
</button>
<fade-transition>
<div v-if="show" class="box"></div>
</fade-transition>
</div>
</template>
<script>...</script>
<style>...</style>
圖片描述
通過在transition組件中提供一個slot,我們幾乎可以像使用基本transition組件一樣使用它。這比前面的例子稍微好一點,但是如果我們想要傳遞其他特定于transition的prop,比如mode或者一些hook,該怎么辦呢
封裝的包裝器transition組件
幸運的是,Vue 中有一個功能,使我們可以將用戶指定的所有額外props和監聽器傳遞給我們的內部標簽/組件。 如果你還不知道,則可以通過$attrs訪問額外傳遞的 props,并將它們與v-bind結合使用以將它們綁定為props。 這同樣適用于通過$listeners進行的事件,并通過v-on對其進行應用。
// FadeTransition.vue
<template>
<transition name="fade" v-bind="$attrs" v-on="$listeners">
<slot></slot>
</transition>
</template>
<script>
export default {};
</script>
<style>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.3s;
}
.fade-enter,
.fade-leave-to {
opacity: 0;
}
</style>
// App.vue
...
<fade-transition mode="out-in">
<div key="blue" v-if="show" class="box"></div>
<div key="red" v-else class="red-box"></div>
</fade-transition>
...
圖片描述
完整事例地址:https://codesandbox.io/s/yjl1...
現在,我們可以傳遞普通transition組件可以接受的任何事件和支持,這使得我們的組件更加可重用。但為什么不更進一步,增加通過 prop 輕松定制持續時間的可能性。
顯式持續時間 prop
Vue 為transition組件提供了一個duration prop,然而,它是為更復雜的動畫鏈接而設計的,它幫助 Vue 正確地將它們鏈接在一起。
在我們的案例中,我們真正需要的是通過組件prop控制CSS animation/transition。 我們可以通過不在CSS中指定顯式的CSS動畫持續時間,而是將其作為樣式來實現。 我們可以借助transition hook來做到這一點,該transition hook與組件生命周期 hook 非常相似,但是它們在過渡所需元素之前和之后被調用。 讓我們看看效果如何。
// FadeTransition.vue
<template>
<transition name="fade"
enter-active-class="fadeIn"
leave-active-class="fadeOut"
v-bind="$attrs"
v-on="hooks">
<slot></slot>
</transition>
</template>
<script>
export default {
props: {
duration: {
type: Number,
default: 300
}
},
computed: {
hooks() {
return {
beforeEnter: this.setDuration,
afterEnter: this.cleanUpDuration,
beforeLeave: this.setDuration,
afterLeave: this.cleanUpDuration,
...this.$listeners
};
}
},
methods: {
setDuration(el) {
el.style.animationDuration = `${this.duration}ms`;
},
cleanUpDuration(el) {
el.style.animationDuration = "";
}
}
};
</script>
<style>
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fadeIn {
animation-name: fadeIn;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.fadeOut {
animation-name: fadeOut;
}
</style>
圖片描述
完整事例地址:https://codesandbox.io/s/j4qn...
現在,我們可以控制實際的可見過渡時間,這使我們可重用的過渡變得靈活且易于使用。 但是,如何過渡多個元素(如列表項)呢?
Transition group 支持
你想到的最直接的方法可能是創建一個新組件,比如fade-transition-group,然后將當前transition標簽替換為transition-group標簽,以實現 group transition。如果我們可以在相同的組件中這樣做,并公開一個將切換到transition-group實現的group prop,那會怎么樣呢?幸運的是,我們可以通過render函數或component和is屬性來實現這一點。
// FadeTransition.vue
<template>
<component :is="type"
:tag="tag"
enter-active-class="fadeIn"
leave-active-class="fadeOut"
move-class="fade-move"
v-bind="$attrs"
v-on="hooks">
<slot></slot>
</component>
</template>
<script>
export default {
props: {
duration: {
type: Number,
default: 300
},
group: {
type: Boolean,
default: false
},
tag: {
type: String,
default: "div"
}
},
computed: {
type() {
return this.group ? "transition-group" : "transition";
},
hooks() {
return {
beforeEnter: this.setDuration,
afterEnter: this.cleanUpDuration,
beforeLeave: this.setDuration,
afterLeave: this.cleanUpDuration,
leave: this.setAbsolutePosition,
...this.$listeners
};
}
},
methods: {
setDuration(el) {
el.style.animationDuration = `${this.duration}ms`;
},
cleanUpDuration(el) {
el.style.animationDuration = "";
},
setAbsolutePosition(el) {
if (this.group) {
el.style.position = "absolute";
}
}
}
};
</script>
<style>
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.fadeIn {
animation-name: fadeIn;
}
@keyframes fadeOut {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.fadeOut {
animation-name: fadeOut;
}
.fade-move {
transition: transform 0.3s ease-out;
}
</style>
// App.vue
...
<div class="box-wrapper">
<fade-transition group :duration="300">
<div class="box"
v-for="(item, index) in list"
@click="remove(index)"
:key="item"
>
</div>
</fade-transition>
</div>
...
圖片描述
完整事例地址:https://codesandbox.io/s/pk9r...
文檔中介紹了一個帶有transition-group元素的警告。 我們基本上必須在元素離開時將每個項目的定位設置為absolute,以實現其他項目的平滑移動動畫。 我們也必須添加一個move-class并手動指定過渡持續時間,因為沒有用于移動的 JS hook。我們將這些調整添加到我們的上一個示例中。
再做一些調整,通過在mixin中提取 JS 邏輯,我們可以將其應用于輕松創建新的transition組件,只需將其放入下一個項目中即可。
Vue Transition
在此之前描述的所有內容基本上都是這個小型 transition 集合所包含的內容。它有 10 個封裝的transition組件,每個約1kb(縮小)。我認為它非常方便,可以輕松地在不同的項目中使用。你可以試一試:)
總結
我們從一個基本的過渡示例開始,并最終通過可調整的持續時間和transition-group支持來創建可重用的過渡組件。 我們可以使用這些技巧根據并根據自身的需求創建自己的過渡組件。 希望讀者從本文中學到了一些知識,并且可以幫助你們建立功能更好的過渡組件。
XSS
跨站腳本攻擊(Cross Site Script),本來縮寫是 CSS, 但是為了和層疊樣式表(Cascading Style Sheet, CSS)有所區分,所以安全領域叫做 “XSS”;
XSS攻擊,通常是指攻擊者通過 “HTML注入”篡改了網頁,插入了惡意的腳本,從而在用戶瀏覽網頁時,對用戶的瀏覽器進行控制或者獲取用戶的敏感信息(Cookie, SessionID等)的一種攻擊方式。
頁面被注入了惡意JavaScript腳本,瀏覽器無法判斷區分這些腳本是被惡意注入的,還是正常的頁面內容,所以惡意注入Javascript腳本也擁有了所有的腳本權限。如果頁面被注入了惡意 JavaScript腳本,它可以做哪些事情呢?
可以竊取 cookie信息。惡意 JavaScript可以通過 ”doccument.cookie“獲取cookie信息,然后通過 XMLHttpRequest或者Fetch加上CORS功能將數據發送給惡意服務器;惡意服務器拿到用戶的cookie信息之后,就可以在其他電腦上模擬用戶的登陸,然后進行轉賬操作。
可以監聽用戶行為。惡意JavaScript可以使用 "addEventListener"接口來監聽鍵盤事件,比如可以獲取用戶輸入的銀行卡等信息,又可以做很多違法的事情。
可以修改DOM 偽造假的登陸窗口,用來欺騙用戶輸入用戶名和密碼等信息。
還可以在頁面內生成浮窗廣告,這些廣告會嚴重影響用戶體驗。
XSS攻擊可以分為三類:反射型,存儲型,基于DOM型(DOM based XSS)
反射型
惡意腳本作為網絡請求的一部分。
const Koa = require("koa");
const app = new Koa();
app.use(async ctx => {
// ctx.body 即服務端響應的數據
ctx.body = '<script>alert("反射型 XSS 攻擊")</script>';
})
app.listen(3000, () => {
console.log('啟動成功');
});
訪問 http://127.0.0.1:3000/ 可以看到 alert執行
反射型XSS1
舉一個常見的場景,我們通過頁面的url的一個參數來控制頁面的展示內容,比如我們把上面的一部分代碼改成下面這樣
app.use(async ctx => {
// ctx.body 即服務端響應的數據
ctx.body = ctx.query.userName;
})
此時訪問 http://127.0.0.1:3000?userName=xiaoming 可以看到頁面上展示了xiaoming,此時我們訪問 http://127.0.0.1:3000?userName=<script>alert("反射型 XSS 攻擊")</script>, 可以看到頁面彈出 alert。
反射型XSS2
通過這個操作,我們會發現用戶將一段含有惡意代碼的請求提交給服務器,服務器在接收到請求時,又將惡意代碼反射給瀏覽器端,這就是反射型XSS攻擊。另外一點需要注意的是,Web 服務器不會存儲反射型 XSS 攻擊的惡意腳本,這是和存儲型 XSS 攻擊不同的地方。
在實際的開發過程中,我們會碰到這樣的場景,在頁面A中點擊某個操作,這個按鈕操作是需要登錄權限的,所以需要跳轉到登錄頁面,登錄完成之后再跳轉會A頁面,我們是這么處理的,跳轉登錄頁面的時候,會加一個參數 returnUrl,表示登錄完成之后需要跳轉到哪個頁面,即這個地址是這樣的 http://xxx.com/login?returnUrl=http://xxx.com/A,假如這個時候把returnUrl改成一個script腳本,而你在登錄完成之后,如果沒有對returnUrl進行合法性判斷,而直接通過window.location.href=returnUrl,這個時候這個惡意腳本就會執行。
存儲型
存儲型會把用戶輸入的數據“存儲”在服務器。
比較常見的一個場景就是,攻擊者在社區或論壇寫下一篇包含惡意 JavaScript代碼的博客文章或評論,文章或評論發表后,所有訪問該博客文章或評論的用戶,都會在他們的瀏覽器中執行這段惡意的JavaScript代碼。
存儲型攻擊大致需要經歷以下幾個步驟
首先攻擊者利用站點漏洞將一段惡意JavaScript代碼提交到網站數據庫中
然后用戶向網站請求包含了惡意 JavaScript腳本的頁面
當用戶瀏覽該頁面的時候,惡意腳本就會將用戶的cookie信息等數據上傳到服務器
存儲型XSS
舉一個簡單的例子,一個登陸頁面,點擊登陸的時候,把數據存儲在后端,登陸完成之后跳轉到首頁,首頁請求一個接口將當前的用戶名顯示到頁面
客戶端代碼
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>XSS-demo</title>
<style>
.login-wrap {
height: 180px;
width: 300px;
border: 1px solid #ccc;
padding: 20px;
margin-bottom: 20px;
}
input {
width: 300px;
}
</style>
</head>
<body>
<div class="login-wrap">
<input type="text" placeholder="用戶名" class="userName">
<br>
<input type="password" placeholder="密碼" class="password">
<br>
<br>
<button class="btn">登陸</button>
</div>
</body>
<script>
var btn = document.querySelector('.btn');
btn.onclick = function () {
var userName = document.querySelector('.userName').value;
var password = document.querySelector('.password').value;
fetch('http://localhost:3200/login', {
method: 'POST',
body: JSON.stringify({
userName,
password
}),
headers:{
'Content-Type': 'application/json'
},
mode: 'cors'
})
.then(function (response) {
return response.json();
})
.then(function (res) {
alert(res.msg);
window.location.href= "http://localhost:3200/home";
})
.catch(err => {
message.error(`本地測試錯誤 ${err.message}`);
console.error('本地測試錯誤', err);
});
}
</script>
</html>
服務端代碼
const Koa = require("koa");
const app = new Koa();
const route = require('koa-route');
var bodyParser = require('koa-bodyparser');
const cors = require('@koa/cors');
// 臨時用一個變量來存儲,實際應該存在數據庫中
let currentUserName = '';
app.use(bodyParser()); // 處理post請求的參數
const login = ctx => {
const req = ctx.request.body;
const userName = req.userName;
currentUserName = userName;
ctx.response.body = {
msg: '登陸成功'
};
}
const home = ctx => {
ctx.body = currentUserName;
}
app.use(cors());
app.use(route.post('/login', login));
app.use(route.get('/home', home));
app.listen(3200, () => {
console.log('啟動成功');
});
點擊登陸將輸入信息提交大服務端,服務端使用變量 currentUserName來存儲當前的輸入內容,登陸成功后,跳轉到 首頁, 服務端會返回當前的用戶名。如果用戶輸入了惡意腳本內容,則惡意腳本就會在瀏覽器端執行。
在用戶名的輸入框輸入 <script>alert('存儲型 XSS 攻擊')</script>,執行結果如下
存儲型XSS
基于DOM(DOM based XSS)
通過惡意腳本修改頁面的DOM節點,是發生在前端的攻擊
基于DOM攻擊大致需要經歷以下幾個步驟
攻擊者構造出特殊的URL,其中包含惡意代碼
用戶打開帶有惡意代碼的URL
用戶瀏覽器接受到響應后執行解析,前端JavaScript取出URL中的惡意代碼并執行
惡意代碼竊取用戶數據并發送到攻擊者的網站,冒充用戶行為,調用目標網站接口執行攻擊者指定的操作。
舉個例子:
<body>
<div class="login-wrap">
<input type="text" placeholder="輸入url" class="url">
<br>
<br>
<button class="btn">提交</button>
<div class="content"></div>
</div>
</body>
<script>
var btn = document.querySelector('.btn');
var content = document.querySelector('.content');
btn.onclick = function () {
var url = document.querySelector('.url').value;
content.innerHTML = `<a href=${url}>跳轉到輸入的url</a>`
}
</script>
點擊提交按鈕,會在當前頁面插入一個超鏈接,其地址為文本框的內容。
在輸入框輸入 如下內容
'' onclick=alert('哈哈,你被攻擊了')
執行結果如下
基于DOM型XSS
首先用兩個單引號閉合調 href屬性,然后插入一個onclick事件。點擊這個新生成的鏈接,腳本將被執行。
上面的代碼是通過執行 執行 alert來演示的攻擊類型,同樣你可以把上面的腳本代碼修改為任何你想執行的代碼,比如獲取 用戶的 cookie等信息,<script>alert(document.cookie)</script>,同樣也是可以的.
防御XSS
HttpOnly
由于很多XSS攻擊都是來盜用Cookie的,因此可以通過 使用HttpOnly屬性來防止直接通過 document.cookie 來獲取 cookie。
一個Cookie的使用過程如下
瀏覽器向服務器發起請求,這時候沒有 Cookie
服務器返回時設置 Set-Cookie 頭,向客戶端瀏覽器寫入Cookie
在該 Cookie 到期前,瀏覽器訪問該域下的所有頁面,都將發送該Cookie
HttpOnly是在 Set-Cookie時標記的:
通常服務器可以將某些 Cookie 設置為 HttpOnly 標志,HttpOnly 是服務器通過 HTTP 響應頭來設置的。
const login = ctx => {
// 簡單設置一個cookie
ctx.cookies.set(
'cid',
'hello world',
{
domain: 'localhost', // 寫cookie所在的域名
path: '/home', // 寫cookie所在的路徑
maxAge: 10 * 60 * 1000, // cookie有效時長
expires: new Date('2021-02-15'), // cookie失效時間
httpOnly: true, // 是否只用于http請求中獲取
overwrite: false // 是否允許重寫
}
)
}
HttpOnly
需要注意的一點是:HttpOnly 并非阻止 XSS 攻擊,而是能阻止 XSS 攻擊后的 Cookie 劫持攻擊。
輸入和輸出的檢查
永遠不要相信用戶的輸入。
輸入檢查一般是檢查用戶輸入的數據是都包含一些特殊字符,如 <、>, '及"等。如果發現特殊字符,則將這些字符過濾或編碼。這種可以稱為 “XSS Filter”。
安全的編碼函數
針對HTML代碼的編碼方式是 HtmlEncode(是一種函數實現,將字符串轉成 HTMLEntrities)
& --> &
< --> <
> --> >
" --> "
相應的, JavaScript的編碼方式可以使用 JavascriptEncode。
假如說用戶輸入了 <script>alert("你被攻擊了")</script>,我們要對用戶輸入的內容進行過濾(如果包含了 <script> 等敏感字符,就過濾掉)或者對其編碼,如果是惡意的腳本,則會變成下面這樣
<script>alert("你被攻擊了");</script>
經過轉碼之后的內容,如 <script>標簽被轉換為 <script>,即使這段腳本返回給頁面,頁面也不會指向這段代碼。
防御 DOM Based XSS
我們可以回看一下上面的例子
btn.onclick = function () {
var url = document.querySelector('.url').value;
content.innerHTML = `<a href=${url}>跳轉到輸入的url</a>`
}
事實上,DOM Based XSS 是從 JavaScript中輸出數據到HTML頁面里。
用戶輸入 '' onclick=alert('哈哈,你被攻擊了'),然后通過 innerHTML 修改DOM的內容,就變成了 <a href='' onclick=alert('哈哈,你被攻擊了')>跳轉到輸入的url</a>, XSS因此產生。
那么正確的防御方法是什么呢?
從JavaScript輸出到HTML頁面,相當于一次 XSS輸出的過程,需要根據不同場景進行不同的編碼處理
變量輸出到 <script>,執行一次 JavascriptEncode
通過JS輸出到HTML頁面
輸出事件或者腳本,做 JavascriptEncode 處理
輸出 HTML內容或者屬性,做 HtmlEncode 處理
會觸發 DOM Based XSS的地方有很多,比如
xxx.interHTML
xxx.outerHTML
document.write
頁面中所有的inputs框
XMLHttpRequest返回的數據
...
項目中如果用到,一定要避免在字符串中拼接不可信的數據。
利用CSP
CSP (Content Security Policy) 即內容安全策略,是一種可信白名單機制,可以在服務端配置瀏覽器哪些外部資源可以加載和執行。我們只需要配置規則,如何攔截是由瀏覽器自己實現的。我們可以通過這種方式來盡量減少 XSS 攻擊。
通常可以通過兩種方式來開啟 CSP:
設置 HTTP Header 的 Content-Security-Policy
Content-Security-Policy: default-src 'self'; // 只允許加載本站資源
Content-Security-Policy: img-src https://* // 只允許加載 HTTPS 協議圖片
Content-Security-Policy: child-src 'none' // 允許加載任何來源框架
設置 meta 標簽的方式
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src https://*; child-src 'none';">
css 偽類是用于向某些選擇器添加特殊的效果,是動態的,指當前元素所處的狀態或者特性。只有一個元素達到一個特定狀態時,它可能得到一個偽類的樣式;當狀態改變時,它又會失去這個樣式。
這篇文章在一定程度上鼓勵你在構建UI時使用更簡單的CSS和更少的 JS。熟悉 CSS 所提供的一切是實現這一目標的一種方法,另一種方法是實現最佳實踐并盡可能多地重用代碼。
接下介紹一些大家可能還不熟悉的一些偽類及其用例,希望對大家日后有所幫助。
::first-line | 選擇文本的第一行
::first-line 偽元素在某塊級元素的第一行應用樣式。第一行的長度取決于很多因素,包括元素寬度,文檔寬度和文本的文字大小。
::first-line 偽元素只能在塊容器中,所以,::first-line偽元素只能在一個display值為block, inline-block, table-cell 或者 table-caption中有用。在其他的類型中,::first-line 是不起作用的。
用法如下:
p:first-line {
color: lightcoral;
}
::first-letter | 選擇這一行的第一字
CSS 偽元素 ::first-letter會選中某塊級元素第一行的第一個字母。用法如下:
<style>
p::first-letter{
color: red;
font-size: 2em;
}
</style>
<p>前端小智,不斷努,終身學習者!</p>
clipboard.png
::selection| 被用戶高亮的部分
::selection 偽元素應用于文檔中被用戶高亮的部分(比如使用鼠標或其他選擇設備選中的部分)。
div::selection {
color: #409EFF;
}
clipboard.png
:root | 根元素
:root 偽類匹配文檔樹的根元素。對于 HTML 來說,:root 表示 <html> 元素,除了優先級更高之外,與 html 選擇器相同。
在聲明全局 CSS 變量時 :root 會很有用:
:root {
--main-color: hotpink;
--pane-padding: 5px 42px;
}
:empty | 僅當子項為空時才有作用
:empty 偽類代表沒有子元素的元素。子元素只可以是元素節點或文本(包括空格),注釋或處理指令都不會產生影響。
div:empty {
border: 2px solid orange;
margin-bottom: 10px;
}
<div></div>
<div></div>
<div>
</div>
clipboard.png
只有第一個和第二個div有作用,因為它們確實是空的,第三個 div 沒有作用,因為它有一個換行。
:only-child | 只有一個子元素才有作用
:only-child 匹配沒有任何兄弟元素的元素.等效的選擇器還可以寫成 :first-child:last-child或者:nth-child(1):nth-last-child(1),當然,前者的權重會低一點。
p:only-child{
background: #409EFF;
}
<div>
<p>第一個沒有任何兄弟元素的元素</p>
</div>
<div>
<p>第二個</p>
<p>第二個</p>
</div>
clipboard.png
:first-of-type | 選擇指定類型的第一個子元素
:first-of-type表示一組兄弟元素中其類型的第一個元素。
.innerDiv p:first-of-type {
color: orangered;
}
上面表示將 .innerDiv 內的第一個元素為 p 的顏色設置為橘色。
<div class="innerDiv">
<div>Div1</div>
<p>These are the necessary steps</p>
<p>hiya</p>
<p>
Do <em>not</em> push the brake at the same time as the accelerator.
</p>
<div>Div2</div>
</div>
clipboard.png
:last-of-type | 選擇指定類型的最后一個子元素
:last-of-type CSS 偽類 表示了在(它父元素的)子元素列表中,最后一個給定類型的元素。當代碼類似Parent tagName:last-of-type的作用區域包含父元素的所有子元素中的最后一個選定元素,也包括子元素的最后一個子元素并以此類推。
.innerDiv p:last-of-type {
color: orangered;
}
上面表示將 .innerDiv 內的的最后一個元素為 p 的顏色設置為橘色。
clipboard.png
nth-of-type() | 選擇指定類型的子元素
:nth-of-type() 這個 CSS 偽類是針對具有一組兄弟節點的標簽, 用 n 來篩選出在一組兄弟節點的位置。
.innerDiv p:nth-of-type(1) {
color: orangered;
}
<div class="innerDiv">
<div>Div1</div>
<p>These are the necessary steps</p>
<p>hiya</p>
<p>
Do <em>not</em> push the brake at the same time as the accelerator.
</p>
<div>Div2</div>
</div>
clipboard.png
:nth-last-of-type() | 在列表末尾選擇類型的子元素
:nth-last-of-type(an+b) 這個 CSS 偽類 匹配那些在它之后有 an+b-1 個相同類型兄弟節點的元素,其中 n 為正值或零值。它基本上和 :nth-of-type 一樣,只是它從結尾處反序計數,而不是從開頭處。
.innerDiv p:nth-last-of-type(1) {
color: orangered;
}
這會選擇innerDiv元素中包含的類型為p元素的列表中的最后一個子元素。
<div class="innerDiv">
<p>These are the necessary steps</p>
<p>hiya</p>
<div>Div1</div>
<p>
Do the same.
</p>
<div>Div2</div>
</div>
clipboard.png
:link | 選擇一個未訪問的超鏈接
:link偽類選擇器是用來選中元素當中的鏈接。它將會選中所有尚未訪問的鏈接,包括那些已經給定了其他偽類選擇器的鏈接(例如:hover選擇器,:active選擇器,:visited選擇器)。
為了可以正確地渲染鏈接元素的樣式,:link偽類選擇器應當放在其他偽類選擇器的前面,并且遵循LVHA的先后順序,即::link — :visited — :hover — :active。:focus偽類選擇器常伴隨在:hover偽類選擇器左右,需要根據你想要實現的效果確定它們的順序。
a:link {
color: orangered;
}
<a href="/login">Login<a>
clipboard.png
:checked | 選擇一個選中的復選框
:checked CSS 偽類選擇器表示任何處于選中狀態的radio(<input type="radio">), checkbox (<input type="checkbox">) 或("select") 元素中的option HTML元素("option")。
input:checked {
box-shadow: 0 0 0 3px hotpink;
}
<input type="checkbox" />
clipboard.png
大家都說簡歷沒項目寫,我就幫大家找了一個項目,還附贈【搭建教程】。
:valid | 選擇一個有效的元素
:valid CSS 偽類表示內容驗證正確的<input> 或其他 <form> 元素。這能簡單地將校驗字段展示為一種能讓用戶辨別出其輸入數據的正確性的樣式。
input:valid {
box-shadow: 0 0 0 3px hotpink;
}
clipboard.png
:invalid | 選擇一個無效的元素
:invalid CSS 偽類 表示任意內容未通過驗證的 <input> 或其他 <form> 元素。
input[type="text"]:invalid {
border-color: red;
}
:lang() | 通過指定的lang值選擇一個元素
:lang() CSS 偽類基于元素語言來匹配頁面元素。
/* 選取任意的英文(en)段落 */
p:lang(en) {
quotes: '\201C' '\201D' '\2018' '\2019';
}
:not() | 用來匹配不符合一組選擇器的元素
CSS 偽類 :not() 用來匹配不符合一組選擇器的元素。由于它的作用是防止特定的元素被選中,它也被稱為反選偽類(negation pseudo-class)。
來看一個例子:
.innerDiv :not(p) {
color: lightcoral;
}
<div class="innerDiv">
<p>Paragraph 1</p>
<p>Paragraph 2</p>
<div>Div 1</div>
<p>Paragraph 3</p>
<div>Div 2</div>
</div>
clipboard.png
Div 1 和 Div 2會被選中,p 不會被選 中。
原文:https://blog.bitsrc.io/css-ps...
代碼部署后可能存在的BUG沒法實時知道,事后為了解決這些BUG,花了大量的時間進行log 調試,這邊順便給大家推薦一個好用的BUG監控工具 Fundebug。
什么是圖片懶加載
當我們向下滾動的時候圖片資源才被請求到,這也就是我們本次要實現的效果,進入頁面的時候,只請求可視區域的圖片資源這也就是懶加載。
比如我們加載一個頁面,這個頁面很長很長,長到我們的瀏覽器可視區域裝不下,那么懶加載就是優先加載可視區域的內容,其他部分等進入了可視區域在加載。
這個功能非常常見,你打開淘寶的首頁,向下滾動,就會看到會有圖片不斷的加載;你在百度中搜索圖片,結果肯定成千上萬條,不可能所有的都一下子加載出來的,很重要的原因就是會有性能問題。你可以在Network中查看,在頁面滾動的時候,會看到圖片一張張加載出來。
lazyLoad
為什么要做圖片懶加載
懶加載是一種網頁性能優化的方式,它能極大的提升用戶體驗。就比如說圖片,圖片一直是影響網頁性能的主要元兇,現在一張圖片超過幾兆已經是很經常的事了。如果每次進入頁面就請求所有的圖片資源,那么可能等圖片加載出來用戶也早就走了。所以,我們需要懶加載,進入頁面的時候,只請求可視區域的圖片資源。
總結出來就兩個點:
1.全部加載的話會影響用戶體驗
2.浪費用戶的流量,有些用戶并不像全部看完,全部加載會耗費大量流量。
懶加載原理
圖片的標簽是 img標簽,圖片的來源主要是 src屬性,瀏覽器是否發起加載圖片的請求是根據是否有src屬性決定的。
所以可以從 img標簽的 src屬性入手,在沒進到可視區域的時候,就先不給 img 標簽的 src屬性賦值。
懶加載實現
實現效果圖:
imgLazyLoad
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
div {
display: flex;
flex-direction: column;
}
img {
width: 100%;
height: 300px;
}
</style>
</head>
<body>
<div>
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657907683.jpeg">
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657913523.jpeg">
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657925550.jpeg">
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657930289.jpeg">
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657934750.jpeg">
<img data-src="https://cdn.suisuijiang.com/ImageMessage/5adad39555703565e79040fa_1590657918315.jpeg">
</div>
</body>
</html>
監聽 scroll 事件判斷元素是否進入視口
const imgs = [...document.getElementsByTagName('img')];
let n = 0;
lazyload();
function throttle(fn, wait) {
let timer = null;
return function(...args) {
if(!timer) {
timer = setTimeout(() => {
timer = null;
fn.apply(this, args)
}, wait)
}
}
}
function lazyload() {
var innerHeight = window.innerHeight;
var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
for(let i = n; i < imgs.length; i++) {
if(imgs[i].offsetTop < innerHeight + scrollTop) {
imgs[i].src = imgs[i].getAttribute("data-src");
n = i + 1;
}
}
}
window.addEventListener('scroll', throttle(lazyload, 200));
可能會存在下面幾個問題:
每次滑動都要執行一次循環,如果有1000多個圖片,性能會很差
每次讀取 scrollTop 都會引起回流
scrollTop跟DOM的嵌套關系有關,應該根據getboundingclientrect獲取
滑到最后的時候刷新,會看到所有的圖片都加載了
IntersectionObserver
Intersection Observer API提供了一種異步觀察目標元素與祖先元素或文檔viewport的交集中的變化的方法。
創建一個 IntersectionObserver對象,并傳入相應參數和回調用函數,該回調函數將會在目標(target)元素和根(root)元素的交集大小超過閾值(threshold)規定的大小時候被執行。
var observer = new IntersectionObserver(callback, options);
IntersectionObserver是瀏覽器原生提供的構造函數,接受兩個參數:callback是可見性變化時的回調函數(即目標元素出現在root選項指定的元素中可見時,回調函數將會被執行),option是配置對象(該參數可選)。
返回的 observer是一個觀察器實例。實例的 observe 方法可以指定觀察哪個DOM節點。
具體的用法可以 查看 MDN文檔
const imgs = [...document.getElementsByTagName('img')];
// 當監聽的元素進入可視范圍內的會觸發回調
if(IntersectionObserver) {
// 創建一個 intersection observer
let lazyImageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach((entry, index) => {
let lazyImage = entry.target;
// 相交率,默認是相對于瀏覽器視窗
if(entry.intersectionRatio > 0) {
lazyImage.src = lazyImage.getAttribute('data-src');
// 當前圖片加載完之后需要去掉監聽
lazyImageObserver.unobserve(lazyImage);
}
})
})
for(let i = 0; i < imgs.length; i++) {
lazyImageObserver.observe(imgs[i]);
}
}
源碼地址-codePen點擊預覽
vue自定義指令-懶加載
Vue自定義指令
下面的api來自官網自定義指令:
鉤子函數
bind: 只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
inserted: 被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
update: 所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新
componentUpdated: 指令所在組件的 VNode 及其子 VNode 全部更新后調用。
unbind: 只調用一次,指令與元素解綁時調用。
鉤子函數參數
指令鉤子函數會被傳入以下參數:
el:指令所綁定的元素,可以用來直接操作 DOM。
binding:一個對象,包含以下 property:
name:指令名,不包括 v- 前綴。
value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。
oldValue:指令綁定的前一個值,僅在 update 和 componentUpdated 鉤子中可用。無論值是否改變都可用。
expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式為 "1 + 1"。
arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數為 "foo"。
modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }。
vnode:Vue 編譯生成的虛擬節點。
oldVnode:上一個虛擬節點,僅在 update 和 componentUpdated 鉤子中可用。
實現 v-lazyload 指令
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
img {
width: 100%;
height: 300px;
}
</style>
</head>
<body>
<div id="app">
<p v-for="item in imgs" :key="item">
<img v-lazyload="item">
</p>
</div>
</body>
<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
Vue.directive("lazyload", {
// 指令的定義
bind: function(el, binding) {
火車車次
/^[GCDZTSPKXLY1-9]\d{1,4}$/
手機機身碼(IMEI)
/^\d{15,17}$/
必須帶端口號的網址(或ip)
/^((ht|f)tps?:\/\/)?[\w-]+(\.[\w-]+)+:\d{1,5}\/?$/
網址(url,支持端口和"?+參數"和"#+參數)
/^(((ht|f)tps?):\/\/)?[\w-]+(\.[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?$/
統一社會信用代碼
/^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/
迅雷鏈接
/^thunderx?:\/\/[a-zA-Z\d]+=$/
ed2k鏈接(寬松匹配)
/^ed2k:\/\/\|file\|.+\|\/$/
磁力鏈接(寬松匹配)
/^magnet:\?xt=urn:btih:[0-9a-fA-F]{40,}.*$/
子網掩碼
/^(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])(?:\.(?:\d{1,2}|1\d\d|2[0-4]\d|25[0-5])){3}$/
linux"隱藏文件"路徑
/^\/(?:[^\/]+\/)*\.[^\/]*/
linux文件夾路徑
/^\/(?:[^\/]+\/)*$/
linux文件路徑
/^\/(?:[^\/]+\/)*[^\/]+$/
window"文件夾"路徑
/^[a-zA-Z]:\\(?:\w+\\?)*$/
window下"文件"路徑
/^[a-zA-Z]:\\(?:\w+\\)*\w+\.\w+$/
股票代碼(A股)
/^(s[hz]|S[HZ])(000[\d]{3}|002[\d]{3}|300[\d]{3}|600[\d]{3}|60[\d]{4})$/
大于等于0, 小于等于150, 支持小數位出現5, 如145.5, 用于判斷考卷分數
/^150$|^(?:\d|[1-9]\d|1[0-4]\d)(?:.5)?$/
html注釋
/^<!--[\s\S]*?-->$/
md5格式(32位)
/^([a-f\d]{32}|[A-F\d]{32})$/
版本號(version)格式必須為X.Y.Z
/^\d+(?:\.\d+){2}$/
視頻(video)鏈接地址(視頻格式可按需增刪)
/^https?:\/\/(.+\/)+.+(\.(swf|avi|flv|mpg|rm|mov|wav|asf|3gp|mkv|rmvb|mp4))$/i
圖片(image)鏈接地址(圖片格式可按需增刪)
/^https?:\/\/(.+\/)+.+(\.(gif|png|jpg|jpeg|webp|svg|psd|bmp|tif))$/i
24小時制時間(HH:mm:ss)
/^(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/
12小時制時間(hh:mm:ss)
/^(?:1[0-2]|0?[1-9]):[0-5]\d:[0-5]\d$/
base64格式
/^\s*data:(?:[a-z]+\/[a-z0-9-+.]+(?:;[a-z-]+=[a-z0-9-]+)?)?(?:;base64)?,([a-z0-9!$&',()*+;=\-._~:@\/?%\s]*?)\s*$/i
數字/貨幣金額(支持負數、千分位分隔符)
/^-?\d+(,\d{3})*(\.\d{1,2})?$/
數字/貨幣金額 (只支持正數、不支持校驗千分位分隔符)
/(?:^[1-9]([0-9]+)?(?:\.[0-9]{1,2})?$)|(?:^(?:0){1}$)|(?:^[0-9]\.[0-9](?:[0-9])?$)/
銀行卡號(10到30位, 覆蓋對公/私賬戶, 參考微信支付)
/^[1-9]\d{9,29}$/
中文姓名
/^(?:[\u4e00-\u9fa5·]{2,16})$/
英文姓名
/(^[a-zA-Z]{1}[a-zA-Z\s]{0,20}[a-zA-Z]{1}$)/
車牌號(新能源)
/[京津滬渝冀豫云遼黑湘皖魯新蘇浙贛鄂桂甘晉蒙陜吉閩貴粵青藏川寧瓊使領 A-Z]{1}[A-HJ-NP-Z]{1}(([0-9]{5}[DF])|([DF][A-HJ-NP-Z0-9][0-9]{4}))$/
車牌號(非新能源)
/^[京津滬渝冀豫云遼黑湘皖魯新蘇浙贛鄂桂甘晉蒙陜吉閩貴粵青藏川寧瓊使領 A-Z]{1}[A-HJ-NP-Z]{1}[A-Z0-9]{4}[A-Z0-9掛學警港澳]{1}$/
車牌號(新能源+非新能源)
/^(?:[京津滬渝冀豫云遼黑湘皖魯新蘇浙贛鄂桂甘晉蒙陜吉閩貴粵青藏川寧瓊使領 A-Z]{1}[A-HJ-NP-Z]{1}(?:(?:[0-9]{5}[DF])|(?:[DF](?:[A-HJ-NP-Z0-9])[0-9]{4})))|(?:[京津滬渝冀豫云遼黑湘皖魯新蘇浙贛鄂桂甘晉蒙陜吉閩貴粵青藏川寧瓊使領 A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9 掛學警港澳]{1})$/
手機號(mobile phone)中國(嚴謹), 根據工信部2019年公布的手機號段
/^(?:(?:\+|00)86)?1(?:(?:3[\d])|(?:4[5-7|9])|(?:5[0-3|5-9])|(?:6[5-7])|(?:7[0-8])|(?:8[\d])|(?:9[1|8|9]))\d{8}$/
手機號(mobile phone)中國(寬松), 只要是13,14,15,16,17,18,19開頭即可
/^(?:(?:\+|00)86)?1[3-9]\d{9}$/
手機號(mobile phone)中國(最寬松), 只要是1開頭即可, 如果你的手機號是用來接收短信, 優先建議選擇這一條
/^(?:(?:\+|00)86)?1\d{10}$/
date(日期)
/^\d{4}(-)(1[0-2]|0?\d)\1([0-2]\d|\d|30|31)$/
email(郵箱)
/^[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/
座機(tel phone)電話(國內),如: 0341-86091234
/^\d{3}-\d{8}$|^\d{4}-\d{7}$/
身份證號(1代,15位數字)
/^[1-9]\d{7}(?:0\d|10|11|12)(?:0[1-9]|[1-2][\d]|30|31)\d{3}$/
身份證號(2代,18位數字),最后一位是校驗位,可能為數字或字符X
/^[1-9]\d{5}(?:18|19|20)\d{2}(?:0\d|10|11|12)(?:0[1-9]|[1-2]\d|30|31)\d{3}[\dXx]$/
身份證號, 支持1/2代(15位/18位數字)
/(^\d{8}(0\d|10|11|12)([0-2]\d|30|31)\d{3}$)|(^\d{6}(18|19|20)\d{2}(0\d|10|11|12)([0-2]\d|30|31)\d{3}(\d|X|x)$)/
護照(包含香港、澳門)
/(^[EeKkGgDdSsPpHh]\d{8}$)|(^(([Ee][a-fA-F])|([DdSsPp][Ee])|([Kk][Jj])|([Mm][Aa])|(1[45]))\d{7}$)/
帳號是否合法(字母開頭,允許5-16字節,允許字母數字下劃線組合
/^[a-zA-Z]\w{4,15}$/
中文/漢字
/^(?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])+$/
小數
/^\d+\.\d+$/
數字
/^\d{1,}$/
html標簽(寬松匹配)
/<(\w+)[^>]*>(.*?<\/\1>)?/
qq號格式正確
/^[1-9][0-9]{4,10}$/
數字和字母組成
/^[A-Za-z0-9]+$/
英文字母
/^[a-zA-Z]+$/
小寫英文字母組成
/^[a-z]+$/
大寫英文字母
/^[A-Z]+$/
密碼強度校驗,最少6位,包括至少1個大寫字母,1個小寫字母,1個數字,1個特殊字符
/^\S*(?=\S{6,})(?=\S*\d)(?=\S*[A-Z])(?=\S*[a-z])(?=\S*[!@#$%^&*? ])\S*$/
用戶名校驗,4到16位(字母,數字,下劃線,減號)
/^[a-zA-Z0-9_-]{4,16}$/
ip-v4
/^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
ip-v6
/^((([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){5}:([0-9A-Fa-f]{1,4}:)?[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){4}:([0-9A-Fa-f]{1,4}:){0,2}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){3}:([0-9A-Fa-f]{1,4}:){0,3}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){2}:([0-9A-Fa-f]{1,4}:){0,4}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){6}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(([0-9A-Fa-f]{1,4}:){0,5}:((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|(::([0-9A-Fa-f]{1,4}:){0,5}((\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b)\.){3}(\b((25[0-5])|(1\d{2})|(2[0-4]\d)|(\d{1,2}))\b))|([0-9A-Fa-f]{1,4}::([0-9A-Fa-f]{1,4}:){0,5}[0-9A-Fa-f]{1,4})|(::([0-9A-Fa-f]{1,4}:){0,6}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:))$/i
16進制顏色
/^#?([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/
微信號(wx),6至20位,以字母開頭,字母,數字,減號,下劃線
/^[a-zA-Z][-_a-zA-Z0-9]{5,19}$/
郵政編碼(中國)
/^(0[1-7]|1[0-356]|2[0-7]|3[0-6]|4[0-7]|5[1-7]|6[1-7]|7[0-5]|8[013-6])\d{4}$/
中文和數字
/^((?:[\u3400-\u4DB5\u4E00-\u9FEA\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD840-\uD868\uD86A-\uD86C\uD86F-\uD872\uD874-\uD879][\uDC00-\uDFFF]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D\uDC20-\uDFFF]|\uD873[\uDC00-\uDEA1\uDEB0-\uDFFF]|\uD87A[\uDC00-\uDFE0])|(\d))+$/
不能包含字母
/^[^A-Za-z]*$/
java包名
/^([a-zA-Z_][a-zA-Z0-9_]*)+([.][a-zA-Z_][a-zA-Z0-9_]*)+$/
mac地址
/^((([a-f0-9]{2}:){5})|(([a-f0-9]{2}-){5}))[a-f0-9]{2}$/i
使用 vue-router 的導航守衛鉤子函數,某些鉤子函數可以讓開發者根據業務邏輯,控制是否進行下一步,或者進入到指定的路由。
例如,后臺管理頁面,會在進入路由前,進行必要登錄、權限判斷,來決定去往哪個路由,以下是偽代碼:
// 全局導航守衛
router.beforEach((to, from, next) => {
if('no login'){
next('/login')
}else if('admin') {
next('/admin')
}else {
next()
}
})
// 路由配置鉤子函數
{
path: '',
component: component,
beforeEnter: (to, from, next) => {
next()
}
}
// 組件中配置鉤子函數
{
template: '',
beforeRouteEnter(to, from, next) {
next()
}
}
調用 next,意味著繼續進行下面的流程;不調用,則直接終止,導致路由中設置的組件無法渲染,會出現頁面一片空白的現象。
鉤子函數有不同的作用,例如 beforEach,afterEach,beforeEnter,beforeRouteEnter,beforeRouteUpdate,beforeRouteLeave,針對這些注冊的鉤子函數,要依次進行執行,并且在必要環節有控制權決定是否繼續進入到下一個鉤子函數中。
以下分析下源碼中實現的方式,而源碼中處理的邊界情況比較多,需要抓住核心點,去掉冗余代碼,精簡出便于理解的實現。
精簡源碼核心功能
總結下核心點:鉤子函數注冊的回調函數,能順序執行,同時會將控制權交給開發者。
先來一個能夠注冊回調函數的類:
class VueRouter {
constructor(){
this.beforeHooks = []
this.beforeEnterHooks = []
this.afterHooks = []
}
beforEach(callback){
return registerHook(this.beforeHooks, callback)
}
beforeEnter(callback){
return registerHook(this.beforeEnterHooks, callback)
}
afterEach(callback){
return registerHook(this.afterHooks, callback)
}
}
function registerHook (list, fn) {
list.push(fn)
return () => {
const i = list.indexOf(fn)
if (i > -1) list.splice(i, 1)
}
}
聲明的類,提供了 beforEach 、beforeEnter 和 afterEach 來注冊必要的回調函數。
抽象出一個 registerHook 公共方法,作用:
注冊回調函數
返回的函數,可以取消注冊的回調函數
使用一下:
const router = new VueRouter()
const beforEach = router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
// 取消注冊的函數
beforEach()
以上的回調函數會被取消,意味著不會執行了。
router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
router.afterEach(() => {
console.log('afterEach');
})
以上注冊的鉤子函數會依次執行。beforEach 和 beforeEnter 的回調接收內部傳來的參數,同時通過調用 next 可繼續走下面的回調函數,如果不調用,則直接被終止了。
最后一個 afterEach 在上面的回調函數都執行后,才被執行,且不接收任何參數。
先來實現依次執行,這是最簡單的方式,在類中增加 run 方法,手動調用:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.afterHooks
)
for(let i = 0; i < queue.length; i++){
if(queue(i)) {
queue(i)('to', 'from', () => {})
}
}
}
}
// 手動調用
router.run()
打印:
'beforEach'
'beforeEnter'
上面把要依次執行的回調函數聚合在一個隊列中執行,并傳入必要的參數,但這樣開發者不能控制是否進行下一步,即便不執行 next 函數,依然會依次執行完隊列的函數。
改進一下:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.afterHooks
)
queue[0]('to', 'from', () => {
queue[1]('to', 'from', () => {
console.log('調用結束');
})
})
}
}
router.beforEach((to, from, next) => {
console.log('beforEach');
// next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
傳入的 next 函數會有調用下一個回調函數的行為,把控制權交給了開發者,調用了 next 函數會繼續執行下一個回調函數;不調用 next 函數,則終止了隊列的執行,所以打印結果是:
'beforEach'
上面實現有個弊端,代碼不夠靈活,手動一個個調用,在真實場景中無法確定注冊了多少個回調函數,所以需要繼續抽象成一個功能更強的方法:
function runQueue (queue, fn, cb) {
const step = index => {
// 隊列執行結束了
if (index >= queue.length) {
cb()
} else {
// 隊列有值
if (queue[index]) {
// 傳入隊列中回調,做一些必要的操作,第二個參數是為了進行下一個回調函數
fn(queue[index], () => {
step(index + 1)
})
} else {
step(index + 1)
}
}
}
// 初次調用,從第一個開始
step(0)
}
runQueue 就是執行隊列的通用方法。
第一個參數為回調函數隊列, 會依次取出來;
第二個參數是函數,它接受隊列中的函數,進行一些其他處理;并能進行下個回調函數的執行;
第三個參數是隊列執行結束后調用。
知道了這個函數的含義,來使用一下:
class VueRouter {
// ... 其他省略,增加 run 函數
run(){
// 把需要依次執行的回調存放在一個隊列中
let queue = [].concat(
this.beforeHooks,
this.beforeEnterHooks
)
// 接收回到函數,和進行下一個的執行函數
const iterator = (hook, next) => {
// 傳給回調函數的參數,第三個參數是函數,交給開發者調用,調用后進行下一個
hook('to', 'from', () => {
console.log('執行下一個回調時,處理一些相關信息');
next()
})
}
runQueue(queue, iterator, () => {
console.log('執行結束');
// 執行 afterEach 中的回調函數
this.afterHooks.forEach((fn) => {
fn()
})
})
}
}
// 注冊
router.beforEach((to, from, next) => {
console.log('beforEach');
next()
})
router.beforeEnter((to, from, next) => {
console.log('beforeEnter');
next()
})
router.afterEach(() => {
console.log('afterEach');
})
router.run();
從上面代碼可以看出來,每次把隊列 queue 中的回調函數傳給 iterator , 用 hook 接收,并調用。
傳給 hook 必要的參數,尤其是第三個參數,開發者在注冊的回調函數中調用,來控制進行下一步。
在隊列執行完畢后,依次執行 afterHooks 的回調函數,不傳入任何參數。
所以打印結果為:
beforEach
執行下一個回調時,處理一些相關信息
beforeEnter
執行下一個回調時,處理一些相關信息
執行結束
afterEach
以上實現的非常巧妙,再看 Vue-router 源碼這塊的實現方式,相信你會豁然開朗。
文章目錄
小白學VUE——快速入門
前言:什么是VUE?
環境準備:
vue的js文件
vscode
Vue入門程序
抽取代碼片段
vue標準語法:
什么是vue指令?
v-bind指令
事件單向綁定
v-model:事件雙向綁定
v-on事件監聽指令
v: on:submit.prevent指令
v-if 判斷指令
v-for 循環渲染指令
Vue.js(讀音 /vju?/, 類似于 view) 是一套構建用戶界面的漸進式框架。 Vue 只關注視圖層, 采用自底向上增量開發的設計。 Vue 的目標是通過盡可能簡單的 API 實現響應的數據綁定和組合的視圖組件。
環境準備:
vue的js文件
使用CDN外部導入方法
以下推薦國外比較穩定的兩個 CDN,把這些網址放進script標簽的src屬性下即可,國內還沒發現哪一家比較好,目前還是建議下載到本地。
Staticfile CDN(國內) : https://cdn.staticfile.org/vue/2.2.2/vue.min.js
unpkg:https://unpkg.com/vue/dist/vue.js, 會保持和 npm 發布的的版本一致。
cdnjs : https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.8/vue.min.js
2.VSCODE軟件
(2).使用內部導入方法(自行下載js文件放進工作區js文件夾即可)
前往vscode官網下載對應版本的vscode
Vue入門程序
首先了解一下什么是插值
插值:數據綁定最常見的形式就是使用 **{{…}}(雙大括號)**的文本插值:
單獨抽出這段來看一下:
Vue即是vue內置的對象,el(element)指的是綁定的元素,可以用#id綁定元素,data指的是定義頁面中顯示的模型數據,還有未展示的methods,指的是方法
var app = new Vue({
el: "#app",//綁定VUE作用的范圍
data: {//定義頁面中顯示的模型數據
message: 'hello vue'
}
});
代碼如下:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="js/vue.min.js"></script>
</head>
<body>
<!-- 插值表達式 獲取data里面定義的值 {{message}} -->
<div id="app">{{ message }}</div>
<script>
//創建一個VUE對象
var app = new Vue({
el: "#app",//綁定VUE作用的范圍
data: {//定義頁面中顯示的模型數據
message: 'hello vue'
}
});
</script>
</body>
</html>
步驟:文件-首選項-用戶片段
輸入片段名稱回車
{
"vh": {
"prefix": "vh", // 觸發的關鍵字 輸入vh按下tab鍵
"body": [
"<!DOCTYPE html>",
"<html lang=\"en\">",
"",
"<head>",
" <meta charset=\"UTF-8\">",
" <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">",
" <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">",
" <title>Document</title>",
" <script src=\"js/vue.min.js\"></script>",
"</head>",
"",
"<body>",
" <div id=\"app\"></div>",
" <script>",
" var vm=new Vue({",
" el:'#app',",
" data:{},",
" methods:{}",
" });",
" </script>",
"</body>",
"",
"</html>",
],
"description": "vh components"
}
}
此時,新建一個html文件,輸入vh在按下tab鍵即可快速填充內容
vue標準語法:
什么是vue指令?
在vue中提供了一些對于頁面 + 數據的更為方便的輸出,這些操作就叫做指令, 以v-xxx表示
類似于html頁面中的屬性 `
比如在angular中 以ng-xxx開頭的就叫做指令
在vue中 以v-xxx開頭的就叫做指令
指令中封裝了一些DOM行為, 結合屬性作為一個暗號, 暗號有對應的值,根據不同的值,框架會進行相關DOM操作的綁定
下面簡單介紹一下vue的幾個基礎指令: v-bind v-if v-for v-on等
v-bind指令
作用:
給元素的屬性賦值
可以給已經存在的屬性賦值 input value
也可以給自定義屬性賦值 mydata
語法
在元素上 v-bind:屬性名="常量||變量名"
簡寫形式 :屬性名="變量名"
例:
<div v-bind:原屬性名="變量"></div> <div :屬性名="變量"></div>
事件單向綁定,可以用 v-bind:屬性名="常量||變量名,綁定事件,用插值表達式取出值
<body>
————————————————
版權聲明:本文為CSDN博主「熱愛旅行的小李同學」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/m0_46275020/java/article/details/106055312
1. 訪問內部屬性
JavaScript 對象無法以常規方式訪問的內部屬性。內部屬性名由雙方括號[[]]包圍,在創建對象時可用。
內部屬性不能動態地添加到現有對象。
內部屬性可以在某些內置 JavaScript 對象中使用,它們存儲ECMAScript規范指定的內部狀態。
有兩種內部屬性,一種操作對象的方法,另一種是存儲數據的方法。例如:
[[Prototype]] — 對象的原型,可以為null或對象
[[Extensible]] — 表示是否允許在對象中動態添加新的屬性
[[PrivateFieldValues]] — 用于管理私有類字段
2. 屬性描述符對象
數據屬性包含了一個數據值的位置,在這個位置可以讀取和寫入值。也就是說,數據屬性可以通過 對象.屬性 訪問,就是我么平常接觸的用戶賦什么值,它們就返回什么,不會做額外的事情。
數據屬性有4個描述其行為的特性(為了表示內部值,把屬性放在兩對方括號中),稱為描述符對象。
屬性 解釋 默認值
[[Configurable]] 能否通過delete刪除屬性從而重新定義屬性;
能否修改屬性的特性;
能否把屬性修改為訪問器屬性 true
[[Enumerable]] 能否通過for-in循環返回屬性 true
[[Writable]] 能否修改屬性的值 true
[[Value]] 包含這個屬性的數據值 undefined
value 描述符是屬性的數據值,例如,我們有以下對象 :
let foo = {
a: 1
}
那么,a 的value屬性描述符為1。
writable是指該屬性的值是否可以更改。 默認值為true,表示屬性是可寫的。 但是,我們可以通過多種方式將其設置為不可寫。
configurable 的意思是可以刪除對象的屬性還是可以更改其屬性描述符。 默認值為true,這意味著它是可配置的。
enumerable 意味著它可以被for ... in循環遍歷。 默認值為true,說明能通過for-in循環返回屬性
將屬性鍵添加到返回的數組之前,Object.keys方法還檢查enumerable 描述符。 但是,Reflect.ownKeys方法不會檢查此屬性描述符,而是返回所有自己的屬性鍵。
Prototype描述符有其他方法,get和set分別用于獲取和設置值。
在創建新對象, 我們可以使用Object.defineProperty方法設置的描述符,如下所示:
let foo = {
a: 1
}
Object.defineProperty(foo, 'b', {
value: 2,
writable: true,
enumerable: true,
configurable: true,
});
這樣得到foo的新值是{a: 1, b: 2}。
我們還可以使用defineProperty更改現有屬性的描述符。 例如:
let foo = {
a: 1
}
Object.defineProperty(foo, 'a', {
value: 2,
writable: false,
enumerable: true,
configurable: true,
});
這樣當我們嘗試給 foo.a 賦值時,如:
foo.a = 2;
如果關閉了嚴格模式,瀏覽器將忽略,否則將拋出一個錯誤,因為我們將 writable 設置為 false, 表示該屬性不可寫。
我們還可以使用defineProperty將屬性轉換為getter,如下所示:
'use strict'
let foo = {
a: 1
}
Object.defineProperty(foo, 'b', {
get() {
return 1;
}
})
當我們這樣寫的時候:
foo.b = 2;
因為b屬性是getter屬性,所以當使用嚴格模式時,我們會得到一個錯誤:Getter 屬性不能重新賦值。
3.無法分配繼承的只讀屬性
繼承的只讀屬性不能再賦值。這是有道理的,因為我們這樣設置它,它是繼承的,所以它應該傳播到繼承屬性的對象。
我們可以使用Object.create創建一個從原型對象繼承屬性的對象,如下所示:
const proto = Object.defineProperties({}, {
a: {
value: 1,
writable: false
}
})
const foo = Object.create(proto)
在上面的代碼中,我們將proto.a的 writable 描述符設置為false,因此我們無法為其分配其他值。
如果我們這樣寫:
foo.a = 2;
在嚴格模式下,我們會收到錯誤消息。
總結
我們可以用 JavaScript 對象做很多我們可能不知道的事情。
首先,某些 JavaScript 對象(例如內置瀏覽器對象)具有內部屬性,這些屬性由雙方括號包圍,它們具有內部狀態,對象創建無法動態添加。
JavaScript對象屬性還具有屬性描述符,該屬性描述符使我們可以控制其值以及可以設置它們的值,還是可以更改其屬性描述符等。
我們可以使用defineProperty更改屬性的屬性描述符,它還用于添加新屬性及其屬性描述符。
最后,繼承的只讀屬性保持只讀狀態,這是有道理的,因為它是從父原型對象繼承而來的。
web中開發的三個基本技術(html5,css3,JavaScript)
html簡介:html語言是純文本類型的語言,是internet上用來編寫網頁的主要語言,使用HTML語言編寫的網頁文件也是標準的純文本文件(簡單說告訴瀏覽器顯示什么)
.
css簡介:css是一種網頁控制技術,采用css技術,可以有效地對頁面、字體、顏色、背景和其他效果實現更加精準的控制
(簡單的說告訴瀏覽器如何顯示)
.
JavaScript:JavaScript是web頁面中的一種腳本編程語言,也是一種通用的、跨平臺的、基于對象和事件驅動并具有安全性的腳本語言。它不需要進行編譯,而是直接嵌入HTML頁面中,把靜態頁面變成動態頁面。(簡單的來說告訴瀏覽器如何交互)
簡單HTML文件結構
<html>/*文件開始*/ <head>/*文件頭*/ <title>標題</title>/*文件標題*/ </head> <body>內容</body> </html>/*文件結束*/
HTML常用的標記
<br>換行 <p></p>段落 <s></s>刪除線 <b></b>字體粗體 <u></u>下劃線 <em></em>斜體內容 <sub></sub> 下標 <sup></sup>上標 <hr></hr>水平線 <a></a>超鏈接 .....
Elasticsearch(下面簡稱ES)中的bool查詢在業務中使用也是比較多的。在一些非實時的分頁查詢,導出的場景,我們經常使用bool查詢組合各種查詢條件。
Bool查詢包括四種子句,
must
filter
should
must_not
我這里只介紹下must和filter兩種子句,因為是我們今天要講的重點。其它的可以自行查詢官方文檔。
must, 返回的文檔必須滿足must子句的條件,并且參與計算分值
filter, 返回的文檔必須滿足filter子句的條件。但是跟Must不一樣的是,不會計算分值, 并且可以使用緩存
從上面的描述來看,你應該已經知道,如果只看查詢的結果,must和filter是一樣的。區別是場景不一樣。如果結果需要算分就使用must,否則可以考慮使用filter。
光說比較抽象,看個例子,下面兩個語句,查詢的結果是一樣的。
使用filter過濾時間范圍,
GET kibana_sample_data_ecommerce/_search { "size": 1000, "query": { "bool": { "must": [ {"term": { "currency": "EUR" }} ], "filter": { "range": { "order_date": { "gte": "2020-01-25T23:45:36.000+00:00", "lte": "2020-02-01T23:45:36.000+00:00" } } } } } }
filter比較的原理
上一節你已經知道了must和filter的基本用法和區別。簡單來講,如果你的業務場景不需要算分,使用filter可以真的讓你的查詢效率飛起來。
為了說明filter查詢的原因,我們需要引入ES的一個概念 query context和 filter context。
query context
query context關注的是,文檔到底有多匹配查詢的條件,這個匹配的程度是由相關性分數決定的,分數越高自然就越匹配。所以這種查詢除了關注文檔是否滿足查詢條件,還需要額外的計算相關性分數.
filter context
filter context關注的是,文檔是否匹配查詢條件,結果只有兩個,是和否。沒有其它額外的計算。它常用的一個場景就是過濾時間范圍。
并且filter context會自動被ES緩存結果,效率進一步提高。
對于bool查詢,must使用的就是query context,而filter使用的就是filter context。
我們可以通過一個示例驗證下。繼續使用第一節的例子,我們通過kibana自帶的search profiler來看看ES的查詢的詳細過程。
使用must查詢的執行過程是這樣的:
可以明顯看到,此次查詢計算了相關性分數,而且score的部分占據了查詢時間的10分之一左右。
filter的查詢我就不截圖了,區別就是score這部分是0,也就是不計算相關性分數。
除了是否計算相關性算分的差別,經常使用的過濾器將被Elasticsearch自動緩存,以提高性能。
我自己曾經在一個項目中,對一個業務查詢場景做了這種優化,當時線上的索引文檔數量大概是3000萬左右,改成filter之后,查詢的速度幾乎快了一倍。
我們應該根據自己的實際業務場景選擇合適的查詢語句,在某些不需要相關性算分的查詢場景,盡量使用filter context
可以讓你的查詢更加。
藍藍設計的小編 http://www.syprn.cn