92国产精品视频_亚洲a级在线观看_国产精品电影观看_国产精品免费观看在线_精品伊人久久97_亚洲人成在线观_尤物九九久久国产精品的特点_成人激情在线播放_成人黄色大片在线免费观看_亚洲成人精品久久久_久久免费视频在线观看_久久精品国产一区_国产一区二区三区18_亚洲欧美中文字幕在线一区_日韩美女中文字幕_日韩视频免费在线

首頁

在Vue中創建可重用的 Transition

seo達人

原始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支持來創建可重用的過渡組件。 我們可以使用這些技巧根據并根據自身的需求創建自己的過渡組件。 希望讀者從本文中學到了一些知識,并且可以幫助你們建立功能更好的過渡組件。

web安全之XSS實例解析

seo達人

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)


& --> &amp;

< --> &lt;

> --> &gt;

" --> &quot;

相應的, JavaScript的編碼方式可以使用 JavascriptEncode。


假如說用戶輸入了 <script>alert("你被攻擊了")</script>,我們要對用戶輸入的內容進行過濾(如果包含了 <script> 等敏感字符,就過濾掉)或者對其編碼,如果是惡意的腳本,則會變成下面這樣


&lt;script&gt;alert("你被攻擊了");&lt;/script&gt;

經過轉碼之后的內容,如 <script>標簽被轉換為 &lt;script&gt;,即使這段腳本返回給頁面,頁面也不會指向這段代碼。


防御 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 偽類,你可能還不知道,可以用起來了!

seo達人

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。



實現一個Vue自定義指令懶加載

seo達人

什么是圖片懶加載

當我們向下滾動的時候圖片資源才被請求到,這也就是我們本次要實現的效果,進入頁面的時候,只請求可視區域的圖片資源這也就是懶加載。


比如我們加載一個頁面,這個頁面很長很長,長到我們的瀏覽器可視區域裝不下,那么懶加載就是優先加載可視區域的內容,其他部分等進入了可視區域在加載。


這個功能非常常見,你打開淘寶的首頁,向下滾動,就會看到會有圖片不斷的加載;你在百度中搜索圖片,結果肯定成千上萬條,不可能所有的都一下子加載出來的,很重要的原因就是會有性能問題。你可以在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) {

2020年這些

seo達人

火車車次

/^[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 導航守衛中 next 控制實現

seo達人

使用 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?

環境準備:

vue的js文件

vscode

Vue入門程序

抽取代碼片段

vue標準語法:

什么是vue指令?

v-bind指令

事件單向綁定

v-model:事件雙向綁定

v-on事件監聽指令

v: on:submit.prevent指令

v-if 判斷指令

v-for 循環渲染指令

前言:什么是VUE?

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文件夾即可)

2.png

vscode

前往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>
    <div id="app">
    
        <h1 v-bind:title="message">
            {{content}}
        </h1>

        <!-- 簡寫方式 -->
        <h2 :title="content">{{message}}</h2>


    </div>
    <script>
        var vm=new Vue({
           el:'#app',
           data:{
               content: '我是標題',
               message: '頁面加載于' + new Date().toDateString()
           }
           
        });
    </script>
</body>

效果:
20200511203222885.png


————————————————
版權聲明:本文為CSDN博主「熱愛旅行的小李同學」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/m0_46275020/java/article/details/106055312


JavaScript 對象可以做到的三件事

seo達人

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前端

前端達人

web前端

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>超鏈接 .....





bool查詢簡介

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可以讓你的查詢更加。


Web安全之CSRF實例解析

seo達人

前言

文章首次發表在 個人博客


之前寫過一篇 web安全之XSS實例解析,是通過舉的幾個簡單例子講解的,同樣通過簡單得例子來理解和學習CSRF,有小伙伴問實際開發中有沒有遇到過XSS和CSRF,答案是有遇到過,不過被測試同學發現了,還有安全掃描發現了可能的問題,這兩篇文章就是簡化了一下當時實際遇到的問題。


CSRF

跨站請求偽造(Cross Site Request Forgery),是指黑客誘導用戶打開黑客的網站,在黑客的網站中,利用用戶的登陸狀態發起的跨站請求。CSRF攻擊就是利用了用戶的登陸狀態,并通過第三方的站點來做一個壞事。


要完成一次CSRF攻擊,受害者依次完成兩個步驟:


登錄受信任網站A,并在本地生成Cookie

在不登出A的情況,訪問危險網站B

CSRF攻擊


在a.com登陸后種下cookie, 然后有個支付的頁面,支付頁面有個誘導點擊的按鈕或者圖片,第三方網站域名為 b.com,中的頁面請求 a.com的接口,b.com 其實拿不到cookie,請求 a.com會把Cookie自動帶上(因為Cookie種在 a.com域下)。這就是為什么在服務端要判斷請求的來源,及限制跨域(只允許信任的域名訪問),然后除了這些還有一些方法來防止 CSRF 攻擊,下面會通過幾個簡單的例子來詳細介紹 CSRF 攻擊的表現及如何防御。


下面會通過一個例子來講解 CSRF 攻擊的表現是什么樣子的。

實現的例子:

在前后端同域的情況下,前后端的域名都為 http://127.0.0.1:3200, 第三方網站的域名為 http://127.0.0.1:3100,釣魚網站頁面為 http://127.0.0.1:3100/bad.html。


平時自己寫例子中會用到下面這兩個工具,非常方便好用:

http-server: 是基于node.js的HTTP 服務器,它最大的好處就是:可以使用任意一個目錄成為服務器的目錄,完全拋開后端的沉重工程,直接運行想要的js代碼;

nodemon: nodemon是一種工具,通過在檢測到目錄中的文件更改時自動重新啟動節點應用程序來幫助開發基于node.js的應用程序

前端頁面: client.html


<!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>CSRF-demo</title>

   <style>

       .wrap {

           height: 500px;

           width: 300px;

           border: 1px solid #ccc;

           padding: 20px;

           margin-bottom: 20px;

       }

       input {

           width: 300px;

       }

       .payInfo {

           display: none;

       }

       .money {

           font-size: 16px;

       }

   </style>

</head>


<body>

   <div class="wrap">

       <div class="loginInfo">

           <h3>登陸</h3>

           <input type="text" placeholder="用戶名" class="userName">

           <br>

           <input type="password" placeholder="密碼" class="password">

           <br>

           <br>

           <button class="btn">登陸</button>

       </div>

       

       

       <div class="payInfo">

           <h3>轉賬信息</h3>

           <p >當前賬戶余額為 <span class="money">0</span>元</p>

           <!-- <input type="text" placeholder="收款方" class="account"> -->

           <button class="pay">支付10元</button>

           <br>

           <br>

           <a target="_blank">

               聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么

           </a>

       </div>

   </div>

</body>

<script>

   const btn = document.querySelector('.btn');

   const loginInfo = document.querySelector('.loginInfo');

   const payInfo = document.querySelector('.payInfo');

   const money = document.querySelector('.money');

   let currentName = '';

   // 第一次進入判斷是否已經登陸

   Fetch('http://127.0.0.1:3200/isLogin', 'POST', {})

   .then((res) => {

       if(res.data) {

           payInfo.style.display = "block"

           loginInfo.style.display = 'none';

           Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 0})

           .then((res) => {

               money.innerHTML = res.data.money;

           })

       } else {

           payInfo.style.display = "none"

           loginInfo.style.display = 'block';

       }

       

   })

   // 點擊登陸

   btn.onclick = function () {

       var userName = document.querySelector('.userName').value;

       currentName = userName;

       var password = document.querySelector('.password').value;

       Fetch('http://127.0.0.1:3200/login', 'POST', {userName, password})

       .then((res) => {

           payInfo.style.display = "block";

           loginInfo.style.display = 'none';

           money.innerHTML = res.data.money;

       })

   }

   // 點擊支付10元

   const pay = document.querySelector('.pay');

   pay.onclick = function () {

       Fetch('http://127.0.0.1:3200/pay', 'POST', {userName: currentName, money: 10})

       .then((res) => {

           console.log(res);

           money.innerHTML = res.data.money;

       })

   }

   // 封裝的請求方法

   function Fetch(url, method = 'POST', data) {

       return new Promise((resolve, reject) => {

           let options = {};

           if (method !== 'GET') {

               options = {

                   headers: {

                       'Content-Type': 'application/json',

                   },

                   body: JSON.stringify(data),

               }

           }

           fetch(url, {

               mode: 'cors', // no-cors, cors, *same-origin

               method,

               ...options,

               credentials: 'include',

           }).then((res) => {

               return res.json();

           }).then(res => {

               resolve(res);

           }).catch(err => {

               reject(err);

           });

       })

   }

   

</script>


</html>

實現一個簡單的支付功能:


會首先判斷有沒有登錄,如果已經登陸過,就直接展示轉賬信息,未登錄,展示登陸信息

登陸完成之后,會展示轉賬信息,點擊支付,可以實現金額的扣減

后端服務: server.js


const Koa = require("koa");

const app = new Koa();

const route = require('koa-route');

const bodyParser = require('koa-bodyparser');

const cors = require('@koa/cors');

const KoaStatic = require('koa-static');


let currentUserName = '';


// 使用  koa-static  使得前后端都在同一個服務下

app.use(KoaStatic(__dirname));


app.use(bodyParser()); // 處理post請求的參數


// 初始金額為 1000

let money = 1000;


// 調用登陸的接口

const login = ctx => {

   const req = ctx.request.body;

   const userName = req.userName;

   currentUserName = userName;

   // 簡單設置一個cookie

   ctx.cookies.set(

       'name',

       userName,

       {

         domain: '127.0.0.1', // 寫cookie所在的域名

         path: '/',       // 寫cookie所在的路徑

         maxAge: 10 * 60 * 1000, // cookie有效時長

         expires: new Date('2021-02-15'),  // cookie失效時間

         overwrite: false,  // 是否允許重寫

         SameSite: 'None',

       }

     )

   ctx.response.body = {

       data: {

           money,

       },

       msg: '登陸成功'

   };

}

// 調用支付的接口

const pay = ctx => {

   if(ctx.method === 'GET') {

       money = money - Number(ctx.request.query.money);

   } else {

       money = money - Number(ctx.request.body.money);

   }

   ctx.set('Access-Control-Allow-Credentials', 'true');

   // 根據有沒有 cookie 來簡單判斷是否登錄

   if(ctx.cookies.get('name')){

       ctx.response.body = {

           data: {

               money: money,

           },

           msg: '支付成功'

       };

   }else{

       ctx.body = '未登錄';

   }

}


// 判斷是否登陸

const isLogin = ctx => {

   ctx.set('Access-Control-Allow-Credentials', 'true');


   if(ctx.cookies.get('name')){

       ctx.response.body = {

           data: true,

           msg: '登陸成功'

       };


   }else{

       ctx.response.body = {

           data: false,

           msg: '未登錄'

       };

   }

}

// 處理 options 請求

app.use((ctx, next)=> {

   const headers = ctx.request.headers;

   if(ctx.method === 'OPTIONS') {

       ctx.set('Access-Control-Allow-Origin', headers.origin);

       ctx.set('Access-Control-Allow-Headers', 'Content-Type');

       ctx.set('Access-Control-Allow-Credentials', 'true');

       ctx.status = 204;

   } else {

       next();

   }

})


app.use(cors());

app.use(route.post('/login', login));

app.use(route.post('/pay', pay));

app.use(route.get('/pay', pay));

app.use(route.post('/isLogin', isLogin));


app.listen(3200, () => {

   console.log('啟動成功');

});

執行 nodemon server.js,訪問頁面 http://127.0.0.1:3200/client.html


CSRF-demo


登陸完成之后,可以看到Cookie是種到 http://127.0.0.1:3200 這個域下面的。


第三方頁面 bad.html


<!DOCTYPE html>

<html lang="en">

<head>

   <meta charset="UTF-8">

   <meta name="viewport" content="width=device-width, initial-scale=1.0">

   <title>第三方網站</title>

</head>

<body>

   <div>

       哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

       <!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->

       <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr" style="display: none">

           <input type="text" name="userName" value="xiaoming">

           <input type="text" name="money" value="100">

       </form>

       <iframe name="targetIfr" style="display:none"></iframe>

   </div>

</body>

<script>

   document.querySelector('.form').submit();

</script>

</html>

使用 HTTP-server 起一個 本地端口為 3100的服務,就可以通過 http://127.0.0.1:3100/bad.html 這個鏈接來訪問,CSRF攻擊需要做的就是在正常的頁面上誘導用戶點擊鏈接進入這個頁面

CSRF-DEMO


點擊誘導鏈接,跳轉到第三方的頁面,第三方頁面自動發了一個扣款的請求,所以在回到正常頁面的時候,刷新,發現錢變少了。

我們可以看到在第三方頁面調用 http://127.0.0.1:3200/pay 這個接口的時候,Cookie自動加在了請求頭上,這就是為什么 http://127.0.0.1:3100/bad.html 這個頁面拿不到 Cookie,但是卻能正常請求 http://127.0.0.1:3200/pay 這個接口的原因。


CSRF攻擊大致可以分為三種情況,自動發起Get請求, 自動發起POST請求,引導用戶點擊鏈接。下面會分別對上面例子進行簡單的改造來說明這三種情況


自動發起Get請求

在上面的 bad.html中,我們把代碼改成下面這樣


<!DOCTYPE html>

<html>

 <body>

   <img src="http://127.0.0.1:3200/payMoney?money=1000">

 </body>

</html>

當用戶訪問含有這個img的頁面后,瀏覽器會自動向自動發起 img 的資源請求,如果服務器沒有對該請求做判斷的話,那么會認為這是一個正常的鏈接。


自動發起POST請求

上面例子中演示的就是這種情況。


<body>

   <div>

       哈哈,小樣兒,哪有賺大錢的方法,還是踏實努力工作吧!

       <!-- form 表單的提交會伴隨著跳轉到action中指定 的url 鏈接,為了阻止這一行為,可以通過設置一個隱藏的iframe 頁面,并將form 的target 屬性指向這個iframe,當前頁面iframe則不會刷新頁面 -->

       <form action="http://127.0.0.1:3200/pay" method="POST" class="form" target="targetIfr">

           <input type="text" name="userName" value="xiaoming">

           <input type="text" name="money" value="100">

       </form>

       <iframe name="targetIfr" style="display:none"></iframe>

   </div>

</body>

<script>

   document.querySelector('.form').submit();

</script>

上面這段代碼中構建了一個隱藏的表單,表單的內容就是自動發起支付的接口請求。當用戶打開該頁面時,這個表單會被自動執行提交。當表單被提交之后,服務器就會執行轉賬操作。因此使用構建自動提交表單這種方式,就可以自動實現跨站點 POST 數據提交。


引導用戶點擊鏈接

誘惑用戶點擊鏈接跳轉到黑客自己的網站,示例代碼如圖所示


<a >聽說點擊這個鏈接的人都賺大錢了,你還不來看一下么</a>

用戶點擊這個地址就會跳到黑客的網站,黑客的網站可能會自動發送一些請求,比如上面提到的自動發起Get或Post請求。


如何防御CSRF

利用cookie的SameSite

SameSite有3個值: Strict, Lax和None


Strict。瀏覽器會完全禁止第三方cookie。比如a.com的頁面中訪問 b.com 的資源,那么a.com中的cookie不會被發送到 b.com服務器,只有從b.com的站點去請求b.com的資源,才會帶上這些Cookie

Lax。相對寬松一些,在跨站點的情況下,從第三方站點鏈接打開和從第三方站點提交 Get方式的表單這兩種方式都會攜帶Cookie。但如果在第三方站點中使用POST方法或者通過 img、Iframe等標簽加載的URL,這些場景都不會攜帶Cookie。

None。任何情況下都會發送 Cookie數據

我們可以根據實際情況將一些關鍵的Cookie設置 Stirct或者 Lax模式,這樣在跨站點請求的時候,這些關鍵的Cookie就不會被發送到服務器,從而使得CSRF攻擊失敗。


驗證請求的來源點

由于CSRF攻擊大多來自第三方站點,可以在服務器端驗證請求來源的站點,禁止第三方站點的請求。

可以通過HTTP請求頭中的 Referer和Origin屬性。


HTTP請求頭


但是這種 Referer和Origin屬性是可以被偽造的,碰上黑客高手,這種判斷就是不安全的了。


CSRF Token

最開始瀏覽器向服務器發起請求時,服務器生成一個CSRF Token。CSRF Token其實就是服務器生成的字符串,然后將該字符串種植到返回的頁面中(可以通過Cookie)

瀏覽器之后再發起請求的時候,需要帶上頁面中的 CSRF Token(在request中要帶上之前獲取到的Token,比如 x-csrf-token:xxxx), 然后服務器會驗證該Token是否合法。第三方網站發出去的請求是無法獲取到 CSRF Token的值的。

其他知識點補充

1. 第三方cookie

Cookie是種在服務端的域名下的,比如客戶端域名是 a.com,服務端的域名是 b.com, Cookie是種在 b.com域名下的,在 Chrome的 Application下是看到的是 a.com下面的Cookie,是沒有的,之后,在a.com下發送b.com的接口請求會自動帶上Cookie(因為Cookie是種在b.com下的)


2. 簡單請求和復雜請求

復雜請求需要處理option請求。


之前寫過一篇特別詳細的文章 CORS原理及@koa/cors源碼解析,有空可以看一下。


3. Fetch的 credentials 參數

如果沒有配置credential 這個參數,fetch是不會發送Cookie的


credential的參數如下


include:不論是不是跨域的請求,總是發送請求資源域在本地的Cookies、HTTP Basic anthentication等驗證信息

same-origin:只有當URL與響應腳本同源才發送 cookies、 HTTP Basic authentication 等驗證信息

omit: 從不發送cookies.

平常寫一些簡單的例子,從很多細節問題上也能補充自己的一些知識盲點。

日歷

鏈接

個人資料

藍藍設計的小編 http://m.skdbbs.com

存檔

92国产精品视频_亚洲a级在线观看_国产精品电影观看_国产精品免费观看在线_精品伊人久久97_亚洲人成在线观_尤物九九久久国产精品的特点_成人激情在线播放_成人黄色大片在线免费观看_亚洲成人精品久久久_久久免费视频在线观看_久久精品国产一区_国产一区二区三区18_亚洲欧美中文字幕在线一区_日韩美女中文字幕_日韩视频免费在线
天堂影院一区二区| 成人影院网站| 视频一区视频二区视频三区视频四区国产| 69**夜色精品国产69乱| 免费看成人av| 欧美日韩中国免费专区在线看| 91超碰中文字幕久久精品| 国产亚洲网站| 欧美日韩精品免费观看视欧美高清免费大片| 俺来俺也去www色在线观看| 国产伦精品一区二区三区免费视频| 亚洲一区二区三区四区中文字幕| 7777精品久久久大香线蕉| 成人福利一区| 2019中文字幕在线观看| sm久久捆绑调教精品一区| 国产女主播一区二区| 久久aⅴ国产紧身牛仔裤| 9色在线视频| 欧美日本亚洲韩国国产| 亚洲夜晚福利在线观看| 欧美xfplay| 国产精品毛片一区视频| 国产99精品国产| 国产精自产拍久久久久久| 黄色欧美视频| 国模私拍国内精品国内av| 欧洲一区二区三区在线| 国产人伦精品一区二区| 欧美性一二三区| 久久久久国色av免费观看性色| 国产一区二区视频在线播放| 欧美成人亚洲| 亚洲香蕉网站| 亚洲狼人精品一区二区三区| 日韩在线免费视频观看| 欧美aa一级| 日韩av在线播放中文字幕| 久久精彩免费视频| 色哟哟一区二区| 亚洲欧美综合另类在线卡通| 日韩在线影视| 欧美日韩在线播| 欧洲美女7788成人免费视频| 久久久久久久国产精品视频| www.亚洲免费av| 国产三级精品三级在线专区| 99一区二区三区| 精品欧美久久| 国产91免费看片| 99久热这里只有精品视频免费观看| 欧美男男tv网站在线播放| av成人手机在线| 日本大香伊一区二区三区| 欧美天天综合色影久久精品| 亚洲成人直播| 久久精品中文| 国产精品免费一区二区三区| 亚洲免费在线观看视频| 精品国产中文字幕第一页| 久久不见久久见中文字幕免费| 久草精品电影| 亚洲欧美日本视频在线观看| 99久久777色| 亚洲麻豆精品| 日韩激情视频在线播放| 国产在线精品不卡| 在线观看爽视频| 91精品一区二区三区在线观看| 精品久久久久久久久国产字幕| 色综合久久88色综合天天| 海角国产乱辈乱精品视频| 亚洲激情在线视频| 国产午夜精品理论片a级探花| 人人澡人人澡人人看欧美| 91丝袜高跟美女视频| 99理论电影网| 成人性生交大片免费网站| 日韩精品一区二区三区视频播放| 亚洲三级欧美| 欧美一级理论性理论a| 捆绑调教美女网站视频一区| 久久精子c满五个校花| 国产精品久久久久久久久久久久| 在线免费三级电影网站| 亚洲福利天堂| 亚洲一区欧美激情| 91精品国产91久久综合| 精品亚洲成a人| 欧美怡红院视频| 日韩一级特黄| 国产精品免费不| 日本高清不卡一区| 欧美日韩另类图片| 夜夜精品浪潮av一区二区三区| 欧美88av| 精品国产乱码久久久久久夜甘婷婷| 亚洲欧美日韩国产中文专区| 国产精品久久久久久婷婷天堂| 欧美中文字幕在线| 国产精品91视频| 久久不卡国产精品一区二区| 色欧美乱欧美15图片| h网站久久久| 久久99日本精品| 美女视频黄免费的亚洲男人天堂| 亚洲成人aaa| 国产精品久久九九| 久久精品国产免费看久久精品| 欧美成人h版| 日本久久亚洲电影| 欧美无砖专区一中文字| 国产精品视频白浆免费视频| 欧美二区三区| av一区二区在线播放| 欧美大片免费久久精品三p| 在线精品播放av| 欧美高清视频一区二区三区在线观看| 老妇喷水一区二区三区| 99精品桃花视频在线观看| 日韩免费观看网站| 不卡的av一区| 欧美xxx久久| 91精品xxx在线观看| 狠狠久久五月精品中文字幕| 亚洲最新av在线网站| 亚洲最新在线观看| 欧美呦呦网站| 青娱乐精品视频在线| 亚洲国产精品一区二区久久恐怖片| 精品欧美一区二区在线观看| 九色porny丨首页在线| 97操在线视频| 国产精品99久久久久久宅男| yourporn久久国产精品| 日本在线看片免费人成视1000| 欧美成人亚洲成人| 猫咪成人在线观看| 成人免费看片| 午夜电影网亚洲视频| 亚洲综合社区| 黄色一级大片在线免费看产| 欧美一区久久久| 4438x成人网全国最大| 97欧洲一区二区精品免费| 91精彩视频在线观看| 在线观看三级视频欧美| 亚洲国产va精品久久久不卡综合| 91超碰caoporn97人人| 欧美日韩亚洲丝袜制服| 夜夜嗨av一区二区三区中文字幕| 2017欧美狠狠色| 亚洲精品欧美日韩| 中文文精品字幕一区二区| 久久人体大尺度| 在线观看的av| 久久精品免费播放| 中文字幕一区二区三区在线视频| 在线电影av不卡网址| 3344国产永久在线观看视频| 99免费精品| 好吊妞www.84com只有这里才有精品| 欧美区在线观看| 精品精品国产毛片在线看|