函數節流與函數防抖是我們解決頻繁觸發DOM事件的兩種常用解決方案,但是經常傻傻分不清楚。。。這不,在項目中又用遇到了,在此處記錄一下
函數防抖 debounce
原理:將若干函數調用合成為一次,并在給定時間過去之后,或者連續事件完全觸發完成之后,調用一次(僅僅只會調用一次?。。。。。。。。?!)。
舉個栗子:滾動scroll事件,不?;瑒訚L輪會連續觸發多次滾動事件,從而調用綁定的回調函數,我們希望當我們停止滾動的時,才觸發一次回調,這時可以使用函數防抖。
原理性代碼及測試:
// 給盒子較大的height,容易看到效果
<style>
* {
padding: 0;
margin: 0;
}
.box {
width: 800px;
height: 1200px;
}
</style>
<body>
<div class="container">
<div class="box" style="background: tomato"></div>
<div class="box" style="background: skyblue"></div>
<div class="box" style="background: red"></div>
<div class="box" style="background: yellow"></div>
</div>
<script>
window.onload = function() {
const decounce = function(fn, delay) {
let timer = null
return function() {
const context = this
let args = arguments
clearTimeout(timer) // 每次調用debounce函數都會將前一次的timer清空,確保只執行一次
timer = setTimeout(() => {
fn.apply(context, args)
}, delay)
}
}
let num = 0
function scrollTap() {
num++
console.log(看看num吧 ${num})
}
// 此處的觸發時間間隔設置的很小
document.addEventListener('scroll', decounce(scrollTap, 500))
// document.addEventListener('scroll', scrollTap)
}
</script>
</body>
此處的觸發時間間隔設置的很小,如果勻速不間斷的滾動,不斷觸發scroll事件,如果不用debounce處理,可以發現num改變了很多次,用了debounce函數防抖,num在一次上時間的滾動中只改變了一次。
調用debouce使scrollTap防抖之后的結果:
直接調用scrollTap的結果:
補充:瀏覽器在處理setTimeout和setInterval時,有最小時間間隔。
setTimeout的最短時間間隔是4毫秒;
setInterval的最短間隔時間是10毫秒,也就是說,小于10毫秒的時間間隔會被調整到10毫秒。
事實上,未優化時,scroll事件頻繁觸發的時間間隔也是這個最小時間間隔。
也就是說,當我們在debounce函數中的間隔事件設置不恰當(小于這個最小時間間隔),會使debounce無效。
函數節流 throttle
原理:當達到了一定的時間間隔就會執行一次;可以理解為是縮減執行頻率
舉個栗子:還是以scroll滾動事件來說吧,滾動事件是及其消耗瀏覽器性能的,不停觸發。以我在項目中碰到的問題,移動端通過scroll實現分頁,不斷滾動,我們不希望不斷發送請求,只有當達到某個條件,比如,距離手機窗口底部150px才發送一個請求,接下來就是展示新頁面的請求,不停滾動,如此反復;這個時候就得用到函數節流。
原理性代碼及實現
// 函數節流 throttle
// 方法一:定時器實現
const throttle = function(fn,delay) {
let timer = null
return function() {
const context = this
let args = arguments
if(!timer) {
timer = setTimeout(() => {
fn.apply(context,args)
clearTimeout(timer)
},delay)
}
}
}
// 方法二:時間戳
const throttle2 = function(fn, delay) {
let preTime = Date.now()
return function() {
const context = this
let args = arguments
let doTime = Date.now()
if (doTime - preTime >= delay) {
fn.apply(context, args)
preTime = Date.now()
}
}
}
需要注意的是定時器方法實現throttle方法和debounce方法的不同:
在debounce中:在執行setTimeout函數之前總會將timer用setTimeout清除,取消延遲代碼塊,確保只執行一次
在throttle中:只要timer存在就會執行setTimeout,在setTimeout內部每次清空這個timer,但是延遲代碼塊已經執行啦,確保一定頻率執行一次
我們依舊可以在html頁面中進行測試scroll事件,html和css代碼同debounce,此處不贅述,運行結果是(可以說是一場漫長的滾輪滾動了):
最后再來瞅瞅項目中封裝好的debounce和throttle函數,可以說是很優秀了,考慮的特別全面,希望自己以后封裝的函數也能考慮的這么全面吧,加油!
/*
空閑控制 返回函數連續調用時,空閑時間必須大于或等于 wait,func 才會執行
@param {function} func 傳入函數,最后一個參數是額外增加的this對象,.apply(this, args) 這種方式,this無法傳遞進函數
@param {number} wait 表示時間窗口的間隔
@param {boolean} immediate 設置為ture時,調用觸發于開始邊界而不是結束邊界
@return {function} 返回客戶調用函數
/
const debounce = function(func, wait, immediate) {
let timeout, args, context, timestamp, result;
const later = function() {
// 據上一次觸發時間間隔
let last = Number(new Date()) - timestamp;
// 上次被包裝函數被調用時間間隔last小于設定時間間隔wait
if (last < wait && last > 0) {
timeout = setTimeout(later, wait - last);
} else {
timeout = null;
// 如果設定為immediate===true,因為開始邊界已經調用過了此處無需調用
if (!immediate) {
result = func.call(context, ...args, context);
if (!timeout) {
context = args = null;
}
}
}
};
return function(..._args) {
context = this;
args = _args;
timestamp = Number(new Date());
const callNow = immediate && !timeout;
// 如果延時不存在,重新設定延時
if (!timeout) {
timeout = setTimeout(later, wait);
}
if (callNow) {
result = func.call(context, ...args, context);
context = args = null;
}
return result;
};
};
/*
頻率控制 返回函數連續調用時,func 執行頻率限定為 次 / wait
@param {function} func 傳入函數
@param {number} wait 表示時間窗口的間隔
@param {object} options 如果想忽略開始邊界上的調用,傳入{leading: false}。
如果想忽略結尾邊界上的調用,傳入{trailing: false}
@return {function} 返回客戶調用函數
*/
const throttle = function(func, wait, options) {
let context, args, result;
let timeout = null;
// 上次執行時間點
let previous = 0;
if (!options) options = {};
// 延遲執行函數
let later = function() {
// 若設定了開始邊界不執行選項,上次執行時間始終為0
previous = options.leading === false ? 0 : Number(new Date());
timeout = null;
result = func.apply(context, args);
if (!timeout) context = args = null;
};
return function(..._args) {
let now = Number(new Date());
// 首次執行時,如果設定了開始邊界不執行選項,將上次執行時間設定為當前時間。
if (!previous && options.leading === false) previous = now;
// 延遲執行時間間隔
let remaining = wait - (now - previous);
context = this;
args = _args;
// 延遲時間間隔remaining小于等于0,表示上次執行至此所間隔時間已經超過一個時間窗口
// remaining大于時間窗口wait,表示客戶端系統時間被調整過
if (remaining <= 0 || remaining > wait) {
clearTimeout(timeout);
timeout = null;
previous = now;
result = func.apply(context, args);
if (!timeout) context = args = null;
//如果延遲執行不存在,且沒有設定結尾邊界不執行選項
} else if (!timeout && options.trailing !== false) {
timeout = setTimeout(later, remaining);
}
return result;
};
};
本文旨在通過一個簡單的例子,練習vuex的幾個常用方法,使初學者以最快的速度跑起來一個vue + vuex的示例。
學習vuex需要你知道vue的一些基礎知識和用法。相信點開本文的同學都具備這個基礎。
另外對vuex已經比較熟悉的大佬可以忽略本文。
基于vue-cli腳手架生成一個vue項目
常用npm命令:
npm i vue-vli -g vue --version vue init webpack 項目名
進入項目目錄,使用npm run dev先試著跑一下。
一般不會出現問題,試跑成功后,就可以寫我們的vuex程序了。
使用vuex首先得安裝vuex,命令:
npm i vuex --save
介紹一下我們的超簡單Demo,一個父組件,一個子組件,父組件有一個數據,子組件有一個數據,想要將這兩個數據都放置到vuex的state中,然后父組件可以修改自己的和子組件的數據。子組件可以修改父組件和自己的數據。
先放效果圖,初始化效果如下:
如果想通過父組件觸發子組件的數據,就點“改變子組件文本”按鈕,點擊后效果如下:
如果想通過子組件修改父組件的數據,就在子組件點擊“修改父組件文本”按鈕,點擊后效果如下:
首先是Parent.vue組件
<template> <div class="parent"> <h3>這里是父組件</h3> <button type="button" @click="clickHandler">修改自己文本</button> <button type="button" @click="clickHandler2">修改子組件文本</button> <div>Test: {{msg}}</div> <child></child> </div> </template> <script> import store from '../vuex' import Child from './Child.vue' export default { computed: {
msg(){ return store.state.testMsg;
}
}, methods:{
clickHandler(){
store.commit('changeTestMsg', '父組件修改自己后的文本')
},
clickHandler2(){
store.commit('changeChildText', '父組件修改子組件后的文本')
}
}, components:{ 'child': Child
},
store,
} </script> <style scoped> .parent{ background-color: #00BBFF; height: 400px;
} </style>
下面是Child.vue子組件
<template> <div class="child"> <h3>這里是子組件</h3> <div>childText: {{msg}}</div> <button type="button" @click="clickHandler">修改父組件文本</button> <button type="button" @click="clickHandler2">修改自己文本</button> </div> </template> <script> import store from '../vuex' export default { name: "Child", computed:{
msg(){ return store.state.childText;
}
}, methods: {
clickHandler(){
store.commit("changeTestMsg", "子組件修改父組件后的文本");
},
clickHandler2(){
store.commit("changeChildText", "子組件修改自己后的文本");
}
},
store
} </script> <style scoped> .child{ background-color: palegreen; border:1px solid black; height:200px; margin:10px;
} </style>
最后是vuex的配置文件
import Vue from 'vue' import Vuex from 'vuex';
Vue.use(Vuex) const state = { testMsg: '原始文本', childText:"子組件原始文本" } const mutations = {
changeTestMsg(state, str){
state.testMsg = str;
},
changeChildText(state, str){
state.childText = str;
}
} const store = new Vuex.Store({ state: state, mutations: mutations
}) export default store;
通過該vuex示例,了解vuex的常用配置及方法調用。希望對不怎么熟悉vuex的同學快速上手vuex項目有點幫助。
因為沒太多東西,我自己也是剛接觸,本例就不往GitHub扔了,如果嘗試了本例,但是沒有跑起來的同學,可以一起交流下。
無論是 pc 端還是移動端,無可避免都會涉及到列表查詢有關的操作,但對于這兩種不同的設備,其列表查詢的最佳處理方式也是完全不同。
對于 pc 端列表查詢來說,前端通常是給與服務端當前需要獲取的數據量(如 pageCount,limit 等參數)以及所需要獲取數據的位置(如 pageSize,offset 等參數)作為查詢條件。然后服務端然后返回數據總數,以及當前數據,前端再結合這些數據顯示頁面總數等信息。這里我稱為相對位置取數。
對于移動端而言,沒有pc 端那么大的空間展示以及操作,所以基本上都會采用下拉取數這種方案。
那么我們在處理移動端列表查詢時候使用這種相對位置取數會有什么問題呢?
通過相對位置取數會具有性能問題,因為一旦使用 offset 信息來獲取數據,隨著頁數的增加,響應速度也會變的越來越慢。因為在數據庫層面,我們每次所獲取的數據都是“從頭開始第幾條”,每次我們都需要從第一條開始計算,計算后舍棄前面的數據,只取最后多條數據返回前端。
當然了,對于相對位置取數來說,數據庫優化是必然的,這里我就不多做贅述了。對于前端開發來說,優秀的的查詢條件設計可以在一定方面解決此問題。
事實上,對于一個實際運行的項目而言,數據更新才是常態,如果數據更新的頻率很高或者你在當前頁停留的時間過久的話,會導致當前獲取的數據出現一定的偏差。
例如:當你在獲取最開始的 20 條數據后,正準備獲取緊接著的后 20 條數據時,在這段時間內 ,發生了數據增加,此時移動端列表就可能會出現重復數據。雖然這個問題在 pc 端也存在,但是 pc 端只會展示當前頁的信息,這樣就避免了該問題所帶來的負面影響。
我們在上面的問題中說明了,移動端下拉加載中使用相對位置查詢取數是有問題的。
那么,如果當前不能迅速結合前后端進行修改 api 的情況下,當服務端傳遞過來的數據與用戶想要得的數據不一致,我們必須在前端進行處理,至少處理數據重復問題所帶來的負面影響。
因為當前分頁請求時無狀態的。在分頁取到數據之后前端可以對取得的數據進行過濾,過濾掉當前頁面已經存在的 key(例如 id 等能夠確定的唯一鍵)。
通過這種處理方式,我們至少可以保證當前用戶看到的數據不會出現重復。同時當列表數據可以編輯修改的時候,也不會出現因為 key 值相同而導致數據錯亂。
如果不使用相對位置獲取數據,前端可以利用當前列表中的最后一條數據作為請求源參數。前端事先記錄最后一條數據的信息。例如當前的排序條件為創建時間,那么記錄最后一條數據的創建時間為主查詢條件(如果列表對應的數據不屬于個人,可能創建時間不能唯一決定當前數據位置,同時還需要添加 ID 等信息作為次要查詢條件)。
當我們使用絕對位置獲取數據時候,雖然我們無法提供類似于從第 1 頁直接跳轉 100 頁的查詢請求,但對于下拉加載這種類型的請求,我們不必擔心性能以及數據重復顯示的問題。
對于相對位置取數來說,前端可以根據返回數據的總數來判斷。但當使用絕對位置取數時,即使獲取數據總數,也無法判斷當前查詢是否存在后續數據。
從服務器端實現的角度來說,當用戶想要得到 20 條數據時候,服務端如果僅僅只向數據庫請求 20 條數據,是無法得知是否有后續數據的。服務端可以嘗試獲取當前請求的數據條數 + 1, 如向數據庫請求 21 條數據,如果成功獲得 21 條數據,則說明至少存在著 1 條后續數據,這時候,我們就可以返回 20 條數據以及具有后續數據的信息。但如果我們請求 21 條數據卻僅僅只能獲取 20 條數據(及以下),則說明沒有后續數據。
如可以通過 “hasMore” 字段來表示是否能夠繼續下拉加載的信息。
{ data: [], hasMore: true }
事實上,前面我們已經解決了移動端處理列表查詢的問題。但是我們做的還不夠好,前端還需要結合排序條件來處理并提供請求參數,這個操作對于前端來說也是一種負擔。那么我們就聊一下 HATEOAS 。
HATEOAS (Hypermedia As The Engine Of Application State, 超媒體即應用狀態引起) 這個概念最早出現在 Roy Fielding 的論文中。REST 設計級別如下所示:
HATEOAS 會在 API 返回的數據中添加下一步要執行的行為,要獲取的數據等 URI 的鏈接信息??蛻舳酥灰@取這些信息以及行為鏈接,就可以根據這些信息進行接下來的操作。
對于當前的請求來說,服務端可以直接返回下一頁的信息,如
{ data: [], hasMore: true, nextPageParams: {}
}
服務端如此傳遞數據,前端就不需要對其進行多余的請求處理,如果當前沒有修改之前的查詢以及排序條件,則只需要直接返回 “nextPageParams” 作為下一頁的查詢條件即可。
這樣做的好處不但符合 REST LEVEL 3,同時也減輕了前端的心智模型。前端無需配置下一頁請求參數。只需要在最開始查詢的時候提供查詢條件即可。
當然,如果前端已經實現了所有排序添加以及查詢條件由服務端提供,前端僅僅提供組件,那么該方案更能體現優勢。 前端是不需要知道當前業務究竟需要什么查詢條件,自然也不需要根據查詢條件來組織下一頁的條件。同時,該方案的輸入和輸出都由后端提供,當涉及到業務替換( 查詢條件,排序條件修改)時候,前端無需任何修改便可以直接替換和使用。
一旦涉及到移動端請求,不可避免的會有網絡問題,當用戶在火車或者偏遠地區時候,一旦下拉就會涉及取數,但是當前數據沒有返回之前,用戶多次下拉可能會有多次取數請求,雖然前端可以結合 key 使得渲染不出錯,但是還是會在緩慢的網絡下請求多次,無疑雪上加霜。這時候我們需要增加條件變量 loading。
偽代碼如下所示:
// 查詢 function search(cond) {
loading = true api.then(res => {
loading = false }).catch(err => {
loading = false })
} // 獲取下一頁數據 function queryNextPage() { if (!nextPageParams) return if (!loading) return search(nextPageParams)
}
為了降低開發的復雜度,以后端為出發點,比如:Struts、SpringMVC 等框架的使用,就是后端的 MVC 時代;
以 SpringMVC 流程為例:
優點:
前后端職責很清晰: 前端工作在瀏覽器端,后端工作在服務端。清晰的分工,可以讓開發并行,測 試數據的模擬不難,前端可以本地開發。后端則可以專注于業務邏輯的處理,輸出 RESTful等接 口。
前端開發的復雜度可控: 前端代碼很重,但合理的分層,讓前端代碼能各司其職。這一塊蠻有意思 的,簡單如模板特性的選擇,就有很多很多講究。并非越強大越好,限制什么,留下哪些自由,代 碼應該如何組織,所有這一切設計,得花一本書的厚度去說明。
-部署相對獨立: 可以快速改進產品體驗
缺點:
代碼不能復用。比如后端依舊需要對數據做各種校驗,校驗邏輯無法復用瀏覽器端的代碼。如果可 以復用,那么后端的數據校驗可以相對簡單化。
全異步,對 SEO 不利。往往還需要服務端做同步渲染的降級方案。 性能并非最佳,特別是移動互聯網環境下。
SPA 不能滿足所有需求,依舊存在大量多頁面應用。URL Design 需要后端配合,前端無法完全掌控。
NodeJS 帶來的全棧時代
前端為主的 MV* 模式解決了很多很多問題,但如上所述,依舊存在不少不足之處。隨著 NodeJS 的興 起,JavaScript 開始有能力運行在服務端。這意味著可以有一種新的研發模式:
————————————————
版權聲明:本文為CSDN博主「叁有三分之一」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/iME_cho/article/details/105654633
塊級元素(inline):
塊級元素可以包含行內元素和其它塊級元素,且占據父元素的整個空間,可以設置 width 和 height 屬性,瀏覽器通常會在塊級元素前后另起一個新行。
常見塊級元素:
<!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{
width: 150px;
height: 150px;
background-color: cadetblue;
}
</style>
</head>
<body>
<div>塊級元素1</div>
<div>塊級元素2</div>
</body>
</html>
分析:
塊級元素的高和寬可以被修改,而且塊級元素會在一個塊級元素之后另起一行。
行級元素
行級元素(block):
一般情況下,行內元素只能包含內容或者其它行內元素,寬度和長度依據內容而定,不可以設置,可以和其它元素和平共處于一行。
常見行級元素:
a,b,strong,span,img,label,button,input,select,textarea
特點:
和相鄰的行內元素在一行上
高度和寬度無效,但是水平方向上的padding和margin可以設置,垂直方向上的無效
默認的寬度就是它本身的寬度
行內元素只能容納純文本或者是其他的行內元素(a標簽除外)
例如:
<!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>
span{
width: 150px;
height: 150px;
font-size: 40px;
background-color: cadetblue;
}
</style>
</head>
<body>
<span>行級元素1</span>
<span>行級元素2</span>
</body>
</html>
<!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>
span{
width: 150px;
height: 150px;
font-size: 20px;
background-color: cadetblue;
display: inline-block;
}
</style>
</head>
<body>
<span>以前我是行級元素,</span>
<span>現在我只想做行內塊級元素。</span>
</body>
</html>
<!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{
width: 150px;
height: 150px;
font-size: 30px;
background-color: cadetblue;
display: inline;
}
</style>
</head>
<body>
<div>我以前是塊級元素,</div>
<div>現在我是行級元素!</div>
</body>
</html>
分析:
在VSC中,修改寬高的代碼已經出現了波浪線,證明他是錯誤的,所以現在的div已經變成了行級元素。
————————————————
版權聲明:本文為CSDN博主「董小宇」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lolly1023/article/details/105715892
其實VSCode編輯器本身自帶了一個功能(Interactive Editor Playground :可以讓你快速了解VSCode的特性,并且是可以交互的),
但很可惜它的內容是全英文的(將VSCode設置為中文也沒用哦~),
我將每一部分截圖下來,并為你說明關鍵內容,教你學會使用 Interactive Editor Playground
還有一些顯而易見的特性,我不會再用文字敘述一遍(它們都是潛移默化的)
在下文中會涉及到大量快捷鍵的介紹,如果看不懂快捷鍵請自行百度
鼠標 = 文本光標 = 光標
本文成于2020年4月22日,隨著VSCode的版本更迭,此部分內容可能會略有差異(小更改不影響觀看,有較大影響的更新請在評論區告之,我會及時更新的)
打開VSCode > Help > Interactive Playground
你將會打開 Interactive Editor Playground 頁面
VS代碼中的核心編輯器包含許多特性。此頁高亮顯示了10個特性,每個特性介紹中都提供了代碼行供你編輯
接下來的10行內容(你可以理解為目錄,對應10個特性)
多光標編輯(Multi-Cursor Editing)- 選擇一塊區域,選擇所有匹配項,添加其余光標等
智能感應(intelliSense)- 獲取代碼和外部模塊的代碼幫助和參數建議
行操作(Line Actions )- 快速移動行以重新排序代碼
重命名重構(Rename Refactoring)- 快速重命名代碼庫中的符號(比如變量名、函數名)
格式化(Formatting)- 使用內置文檔和選擇格式使代碼看起來很棒
代碼折疊(Code Folding) - 通過折疊其他代碼區域,關注代碼中最相關的部分
錯誤和警告(Errors and Warnings)- 寫代碼時請參閱錯誤和警告
片段(Snippets)- 花更少的時間輸入片段
Emmet - 只需要敲一行代碼就能生成你想要的完整HTML結構等(極大方便前端開發)
JavaScript Type Checking- 使用零配置的TypeScript對JavaScript文件執行類型檢查。
Multi-Cursor Editing
使用多光標編輯可以同時編輯文檔的多個部分,極大地提高了工作效率
框式選擇
鍵盤同時按下 Shift + DownArrow(下鍵)、Shift + RightArrow(右鍵)、Shift + UpArrow(上鍵)、Shift + LeftArrow(左鍵) 的任意組合可選擇文本塊
也可以用鼠標選擇文本時按 Shift + Alt 鍵
或使用鼠標中鍵拖動選擇(可用性很高)
添加光標
按 Ctrl + Alt + UpArrow 在行上方添加新光標
或按 Ctrl + Alt + DownArrow 在行下方添加新光標
您也可以使用鼠標和 Alt + Click 在任何地方添加光標(可用性很高)
在所有出現的字符串上創建光標
選擇字符串的一個實例,例如我用鼠標選中所有background,然后按 Ctrl + Shift + L,文本中所有的background都將被選中(可用性很高)
IntelliSense
Visual Studio Code 預裝了強大的JavaScript和TypeScript智能感知。
在代碼示例中,將文本光標放在錯誤下劃線的上面,會自動調用IntelliSense
這只是智能提示的冰山一角,還有懸停在函數名上可以看到參數及其注釋(如果有)等等,它會潛移默化的帶給你極大幫助
其他語言在安裝對應插件后,會附帶對應語言的IntelliSense
Line Actions
分別使用 Shift + Alt + DownArrow 或 Shift + Alt + UpArrow 復制光標所在行并將其插入當前光標位置的上方或下方
分別使用 Alt + UpArrow 和 Alt + DownArrow 向上或向下移動選定行(可用性很高)
用 Ctrl + Shift + K 刪除整行(可用性很高)
通過按 Ctrl + / 來注釋掉光標所在行、切換注釋(可用性很高)
Rename Refactoring
重命名符號(如函數名或變量名)
重命名操作將在項目中的所有文件中發生(可用性很高)
代碼如果沒有良好的編寫格式,閱讀起來是一個折磨
Formatting可以解決編寫格式問題:無論你的代碼的格式寫的有多么糟糕,它可以將代碼格式化為閱讀性良好的格式
格式化整個文檔 Shift + Alt + F (可用性很高)
格式化當前行 Ctrl + K Ctrl + F(即先按Ctrl,再按K,最后按F)
鼠標右鍵 > Format Document (格式化整個文檔)
將格式化操作設置為自動化(保存時自動格式化整個文檔):Ctrl + , 輸入 editor.formatOnSave
鼠標操作,自己嘗試一下,秒懂
快捷鍵:
折疊代碼段是基于基于縮進
錯誤和警告將在你出現錯誤時,高亮該代碼行
在代碼示例中可以看到許多語法錯誤(如果沒有,請你隨便修改它,讓它出現錯誤)
按F8鍵可以按順序在錯誤之間導航,并查看詳細的錯誤消息(可用性很高)
Snippets
通過使用代碼片段,可以大大加快編輯速度
在代碼編輯區,你可以嘗試輸入try并從建議列表中選擇try catch,
然后按Tab鍵或者Enter,創建try->catch塊
你的光標將放在文本error上,以便編輯。如果存在多個參數,請按Tab鍵跳轉到該參數。
Emmet
Emmet將代碼片段的概念提升到了一個全新的層次(前端開發的大寶貝)
你可以鍵入類似Css的可動態解析表達式,并根據在abrevision中鍵入的內容生成輸出
比如說:
然后Enter
————————————————
版權聲明:本文為CSDN博主「索兒呀」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Zhangguohao666/article/details/105676173
隨著項目體積的增加,參與到項目中的同學越來越多,每個人都有自己的打 git log 的習慣:
add: 添加...
[add]: 添加...
Add 添加...
為了形成統一的規范,達成共識,從而降低協作開發成本,需要對 git commit 記錄進行規范。
規范 git commit 記錄,需要做兩件事情:
問:既然已經交互式生成了規范記錄,為什么需要在 hooks 進行檢查?
交互式生成 commit 記錄,需要用戶調用自定義的 npm scripts,例如npm run commit。但還是可以直接調用原生 git 命令 git commit 來提交記錄。而檢查是在正式提交前進行的,因此不符合要求的記錄不會生效,需要重新 commit。
前期調研結果,關于 commit 提示有兩種做法:
方法 1 的優缺點:
優點 1: 直接安裝對應的 adapter 即可
優點 2: 無開發成本
缺點 1: 無法定制,不一定滿足團隊需要
方法 2 的優缺點:
優點 1: 可定制,滿足開發需求
優點 2: 單獨成庫,發布 tnpm,作為技術建設
缺點 1: 需要單獨一個倉庫(但開發成本不高)
在實際工作中,發現方法 1 中的常用規范,足夠覆蓋團隊日常開發場景。所以,選擇了方法 1.
step1: 安裝 npm 包
npm i --save-dev commitizen cz-conventional-changelog @commitlint/cli @commitlint/config-conventional husky
添加 package.json 的配置:
"scripts": { "commit": "git-cz" }, "husky": { "hooks": { "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" }
}, "config": { "commitizen": { "path": "./node_modules/cz-conventional-changelog" }
}
在項目根目錄下創建commitlint.config.js:
module.exports = { extends: ["@commitlint/config-conventional"]
};
使用方法:不再使用git commit -m ...,而是調用npm run commit。
<img src="https://tva1.sinaimg.cn/large/006tNbRwly1gbjcfr3xb5j30cw00tjrd.jpg" style="width: 100% !important;"/>
微信小程序的wxss、阿里旗下淘寶、支付寶小程序的acss等等語法很類似原生css,但是在web開發里用慣了動態css語言,再寫回原生css很不習慣,尤其是父子樣式的嵌套寫法非常繁瑣。
因此,我希望能有一個自動化構建方案,能夠簡單地將scss轉換成小程序的樣式語言。
以前寫微信小程序的依賴庫時用過,使用gulp編譯,將源碼和編譯后的代碼分別放到src和dist兩個目錄。gulp會處理src下面的所有文件,將其中的scss轉換成css,并將其他所有文件原封不動挪到dist下相應位置。
這里就不詳細說了,代碼參考Wux。
非常簡單直接,使用Webstorm/IDEA的File Watchers功能實時轉換。
確保命令行輸入sass -v能出現版本號,安裝過程略。
File Watchers
到插件市場上搜索并安裝(已安裝則跳過)
現在安裝完插件打開項目會自動彈出scss轉css的向導,方便了很多。但還需要做一些修改,配置如下:
首先要將生成文件的后綴名改掉,比如這里我的淘寶小程序就得是acss。
其次,將Arguments改為:
$FileName$:$FileNameWithoutExtension$.acss --no-cache --sourcemap=none --default-encoding utf-8 --style expanded
如果不加--no-cache,scss文件同目錄下會出現一個.sass-cache目錄。
如果不加--sourcemap=none, scss文件同目錄下會出現一個.map文件。
如果不加--default-encoding utf-8, scss文件如果有中文注釋轉換就會報錯。
style可不加,這里用的是無縮進和壓縮的風格,反正小程序打包發布時還會壓,這里保持可讀性。
現在這個scss轉換是單獨作用于項目的,如果新建一個小程序項目,就需要重新添加(不建議設置成global,容易誤傷)。
注意到File Watchers列表的右側操作欄下方有導入導出按鈕,可以將現在配好的設置導出保存,將來新建項目時只要導入一下就行了。
之后還有一個問題,如果我手動將編譯后的css(即wxss或者acss,下略)文件刪除,scss文件不改動的話,就不會重新編譯出css文件。
或者萬一監聽失效或者不夠及時,css還有可能是舊的。
所以還需要一個命令,用來將整個目錄下的scss文件統一轉換,確保沒有遺漏和保持代碼。
不過我看了半天sass和sass-convert的文檔,沒有找到一個可用的寫法,能讓命令行遍歷指定目錄下的所有scss文件,將其轉換成css放到源文件所在目錄,并且將后綴名改為wxss或者acss。
所以遍歷這個行為只能交給nodejs來實現,代碼如下:
創建編譯腳本build/scss-convert.js:
var path = require("path") var fs = require("fs") const { exec } = require('child_process') const basePath = path.resolve(__dirname, '../') function mapDir(dir, callback, finish) {
fs.readdir(dir, function(err, files) { if (err) { console.error(err) return }
files.forEach((filename, index) => { let pathname = path.join(dir, filename)
fs.stat(pathname, (err, stats) => { // 讀取文件信息 if (err) { console.log('獲取文件stats失敗') return } if (stats.isDirectory()) {
mapDir(pathname, callback, finish)
} else if (stats.isFile()) { if (!['.scss'].includes(path.extname(pathname))) { return }
callback(pathname)
}
}) if (index === files.length - 1) {
finish && finish()
}
})
})
}
mapDir(
basePath, function (file) { const newFileWithoutExt = path.basename(file, '.scss') if (newFileWithoutExt.startsWith('_')) { return // 按照scss規則,下劃線開頭的文件不會生成css } // exec可以讓nodejs執行外部命令 exec(`sass --no-cache --sourcemap=none --default-encoding utf-8 --style expanded ${file}:${newFileWithoutExt}.acss`, { cwd: path.dirname(file) // 不寫這個會導致生成的文件出現在根目錄 }, (err, stdout, stderr) => { if (err) { console.log(err) return } console.log(`stdout: ${stdout}`)
})
}, function() { // console.log('xxx文件目錄遍歷完了') }
)
在package.json里添加一條script:
"scripts": { "scss": "node build/scss-convert",
},
ES6 允許按照一定模式,從數組和對象中提取值,對變量進行賦值,這被稱為解構(Destructuring)
本質上,這種寫法屬于“模式匹配”,只要等號兩邊的模式相同,左邊的變量就會被賦予對應的值
如果解構不成功,變量的值就等于undefined
解構賦值的規則是,只要等號右邊的值不是對象或數組,就先將其轉為對象。由于undefined和null無法轉為對象,所以對它們進行解構賦值,都會報錯、
交換變量的值
例如:let x=1,y=2;[x,y] = [y,x]
從函數返回多個值
函數只能返回一個值,如果要返回多個值,只能將它們放在數組或對象里返回。有了解構賦值,取出這些值就非常方便
函數參數的定義
解構賦值可以方便地將一組參數與變量名對應起來
提取 JSON 數據,很多接口數據只需要其中某部分
例如aa.axios.get(res=>{let {data:result}=res;}),則res.data.result = result了
函數參數的默認值
指定參數的默認值,就避免了在函數體內部再寫var foo = config.foo || ‘default foo’;這樣的語句
遍歷 Map 結構
Map 結構原生支持 Iterator 接口,配合變量的解構賦值,獲取鍵名和鍵值就非常方便
輸入模塊的指定方法
加載模塊時,往往需要指定輸入哪些方法。解構賦值使得輸入語句非常清晰。* const { SourceMapConsumer, SourceNode } = require(“source-map”);
左右兩側數據解構須得吻合,或者等號左邊的模式,只匹配一部分的等號右邊的數組(屬于不完全解構)
特殊情況使用…擴展運算符,無值是空數組
左右兩邊等式的性質要相同,等號的右邊不是數組(或者嚴格地說,不是可遍歷的結構),那么將會報錯,只要某種數據結構具有 Iterator
接口,都可以采用數組形式的解構賦值,例如Set結構
解構賦值允許指定默認值,當一個數組成員嚴格等于undefined,默認值才會生效,否則取賦值的值;如果默認值是一個表達式,那么這個表達式是惰性求值的,即只有在用到的時候,才會求值;默認值可以引用解構賦值的其他變量,但該變量必須已經聲明
// 數組的解構賦值
let [a,b] = [1,2];
console.log([a,b],a);//[1, 2] 1
let [aa] = [11,22];
console.log(aa)//11
let [aaa,bbb] = [111];
console.log(aaa,bbb)//111 undefined
let [head, ...tail] = [1, 2, 3, 4];
console.log(head,tail)//1,[2,3,4]
let [x, y, ...z] = ['a'];
console.log(x,y,z)//a undefined []
// 等號右邊不是數組會報錯
// let [ab] = 121;
// conosle.log(ab)//TypeError: 121 is not iterable
// let [abc] = {}
// conosle.log(abc)//TypeError: {} is not iterable
// 默認值賦值
let [zz = 1] = [undefined];
console.log(zz)//1
let [zzz = 1] = [null];
console.log(zzz)//null
let [foo = true] = [];
console.log(foo)// true
let [xxx, yyy = 'b'] = ['a'];
console.log(xxx,yyy)//a,b
let [xxxx, yyyy = 'b'] = ['a', undefined];
console.log(xxxx,yyyy)//a,b
function f() {
console.log('aaa');
}
let [xx = f()] = [1];
console.log(xx)//1
let [qq=ww,ww=11] = [23,44];
console.log(qq,ww)//23,44,因為ww申明比qq晚所以是undefined;
2、對象的解構賦值
對象的解構賦值的內部機制,是先找到同名屬性,然后再賦給對應的變量。真正被賦值的是后者,而不是前者
數組是按照位置區分,對象則是按照鍵名區分的,同樣的解構失敗則為undefine
可將已有方法對象解構賦值
嵌套賦值,注意是變量是否被賦值是模式還是鍵值
對象的解構賦值可以取到繼承的屬性
如果要將一個已經聲明的變量用于解構賦值,必須非常小心
let xx; // {xx} = {xx: 1}這樣會報錯,
解構賦值允許等號左邊的模式之中,不放置任何變量名。因此,可以寫出非常古怪的賦值表達式
({} = [true, false]);//可執行
由于數組本質是特殊的對象,因此可以對數組進行對象屬性的解構
objFuc(){
// 對象解構賦值
let {b,a} = {a:1}
console.log(a,b)//1 undefined
// 已有對象解構賦值
let { sin, cos } = Math;//將Math對象的對數、正弦、余弦三個方法,賦值到對應的變量上
console.log(sin);//log() { [native code] }
const { log } = console;
log('hello') // hello
//
let { foo: baz } = { foo: 'aaa', bar: 'bbb' };
console.log(baz);//aaa
// 嵌套賦值
let obj = {
p: [
'Hello',
{ y: 'World' }
]
};
let { p,p:[x, { y }] } = obj;
console.log(x,y,p)//Hello World p: ['Hello',{ y: 'World' }]
//繼承賦值
const obj1 = {};
const obj2 = { foo: 'bar' };
Object.setPrototypeOf(obj1, obj2);//obj1繼承obj2
const { foo } = obj1;
console.log(foo) // "bar"
// 默認值
// 錯誤的寫法
let xx;
// {xx} = {xx: 1};// SyntaxError: syntax error,Uncaught SyntaxError: Unexpected token '='
({xx} = {xx: 1});//正確寫法
console.log(xx)
// 古怪的,等式左邊可為空
// ({} = [true, false]);
// 對象可解構數組
let arr = [1, 2, 3];
let {0 : first, [arr.length - 1] : last} = arr;
console.log(first,last)//1 3
},
strFuc(){ // str:'yan_yan' let [a,b,c,d,e,f,g] = this.str; console.log(a,b,c,d,e,f,g)//y a n _ y a n // 對數組屬性解構賦值 let {length} = this.str; console.log(length)//7 },
4、數值和布爾值的解構賦值
let {toString: s} = 123; console.log(s === Number.prototype.toString,s)//true ? toString() { [native code] } let {toString: ss} = true; console.log(ss === Boolean.prototype.toString,ss)// true ? toString() { [native code] } // 右側必須是數組或對象,undefined和null無法轉為對象,所以對它們進行解構賦值,都會報錯 // let { prop: x } = undefined; // TypeError // let { prop: y } = null; // TypeError
5、函數參數的解構賦值
// 函數的解構賦值可使用默認值,注意默認值是指實參的默認值而不是形參的默認值
function move({x=1, y=1}={}) {
return [x, y];
}
function move1({x, y} = { x: 0, y: 0 }) {
return [x, y];
}
function move2({x, y=1} = { x: 0, y: 0 }) {
return [x, y];
}
console.log(move({x: 3, y: 8})); // [3, 8]
console.log(move({x: 3})); // [3, 1]
console.log(move({})); // [1, 1]
console.log(move()); // [1,1]
console.log(move1({x: 3, y: 8})); // [3, 8]
console.log(move1({x: 3})); // [3, 1]
console.log(move1({})); // [undefined, 1]
console.log(move1()); // [0,0]
console.log(move2({x: 3, y: 8})); // [3, 8]
console.log(move2({x: 3})); // [3, 1]
console.log(move2({})); // [undefined, 1]
console.log(move2()); // [0,0]
6、圓括號問題 解構賦值雖然很方便,但是解析起來并不容易。對于編譯器來說,一個式子到底是模式,還是表達式,沒有辦法從一開始就知道,必須解析到(或解析不到)等號才能知道。 由此帶來的問題是,如果模式中出現圓括號怎么處理。ES6 的規則是,只要有可能導致解構的歧義,就不得使用圓括號。 可以使用圓括號的情況只有一種:賦值語句的非模式部分,可以使用圓括號 總結: 不管是哪一類的解構賦值,等式右邊的數據必須是對象形式(數組也是一種對象形式) ———————————————— 版權聲明:本文為CSDN博主「Yan_an_n」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。 原文鏈接:https://blog.csdn.net/weixin_44258964/article/details/105643553
目錄
HTTP協議
HTTP請求:
HTTP響應:
會話與會話狀態:
Cookie
Session
Cookie和Session的區別
HTTP協議
HTTP請求:
Post /test.php HTTP/1.1 //請求行以一個方法符號開頭,以空格分開,后面跟著請求的URI和協議的版本
Host: www.test.com //請求頭
User-agent:mozilla/5.0(windows NT 6.1: rv: 15.0)
Gecko/20100101 firefox15.0
//空白行,代表請求頭結束
Username=admin&passwd=admin //請求正文
HTTP請求方法
GET 請求獲取Request-URI所標識的資源
POST 在Request-URI所標識的資源后附加新的數據
HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭
PUT 請求服務器存儲一個資源,并用Request-URI作為其標識
常用的為GET和POST;GET和POST的區別:
GET提交的內容會直接顯示在URL中,私密性較差,可以用于顯示一些公共資源;但是GET效率會比較高。
POST不會將內容顯示在URL中,可以用于提交一些敏感數據,例如用戶名或密碼。
HTTP響應:
HTTP/1.1 200 OK //響應行由協議版本號,響應狀態碼和文本描述組成
Data:sun,15 nov 2018 11:02:04 GMT //響應頭
Server:bfe/1.0.8.9
……
Connection: keep-alive
//空白行,代表響應頭結束
<html>
</html><title>index.heml</title> //響應正文
HTTP的狀態碼:
狀態代碼由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值。
1xx:指示信息 —— 表示請求已接收,繼續處理。
2xx:成功 —— 表示請求已被成功接收、理解、接受。
3xx:重定向 —— 要完成請求必須進行更進一步的操作。
4xx:客戶端錯誤 —— 請求有語法錯誤或請求無法實現。
5xx:服務器端錯誤 —— 服務器未能實現合法的請求。
常見狀態代碼、狀態描述的說明如下。
200 OK:客戶端請求成功。
400 Bad Request:客戶端請求有語法錯誤,不能被服務器所理解。
401 Unauthorized:請求未經授權,這個狀態代碼必須和 WWW-Authenticate 報頭域一起使用。
403 Forbidden:服務器收到請求,但是拒絕提供服務。
404 Not Found:請求資源不存在,舉個例子:輸入了錯誤的URL。
500 Internal Server Error:服務器發生不可預期的錯誤。
503 Server Unavailable:服務器當前不能處理客戶端的請求,一段時間后可能恢復正常。
會話與會話狀態:
Web中的會話是指一個客戶端瀏覽器與web服務器之間連續發生一系列請求和響應過程。會話狀態是指在會話過程中產生的狀態信息;借助會話狀態,web服務器能夠把屬于同一會話中的一系列的請求和響應關聯起來。
Cookie
概述
Cookie是一種在客戶端保持HTTP狀態信息的技術,它好比商場發放的優惠卡。在瀏覽器訪問Web服務器的某個資源時,由Web服務器在在HTTP響應頭中附帶傳送給瀏覽器一片數據,web服務器傳送給各個客戶端瀏覽器的數據是可以各不相同的。
一旦Web瀏覽器保存了某個Cookie,那么它在以后每次訪問該Web服務器是都應在HTTP請求頭中將這個Cookie回傳個Web服務器。Web服務器通過在HTTP響應消息中增加Set-Cookie響應頭字段將CooKie信息發送給瀏覽器,瀏覽器則通過在HTTP請求消息中增加Cookie請求頭字段將Cookie回傳給Web服務器。
一個Cookie只能標識一種信息,它至少含有一個標識該消息的名稱(NAME)和和設置值(VALUE)。一個Web瀏覽器也可以存儲多個Web站點提供的Cookie。瀏覽器一般只允許存放300個Cookie,每個站點最多存放20個Cookie,每個Cookie的大小限制為4KB。
傳送示意圖
特點
存儲于瀏覽器頭部/傳輸與HTTP頭部,寫時帶屬性,讀時無屬性。由三元【name,domain,path】唯一確定Cookie。
Set-Cookie2響應頭字段
Set-Cookie2頭字段用于指定WEB服務器向客戶端傳送的Cookie內容,但是按照Netscape規范實現Cookie功能的WEB服務器, 使用的是Set-Cookie頭字段,兩者的語法和作用類似。Set-Cookie2頭字段中設置的cookie內容是具有一定格式的字符串,它必須以Cookie的名稱和設置值開頭,格式為"名稱=值”,后面可以加上0個或多個以分號(;) 和空格分隔的其它可選屬性,屬性格式一般為 "屬性名=值”。
除了“名稱=值”對必須位于最前面外,其他的可選屬性可以任意。Cookie的名稱只能由普通的英文ASCII字符組成,瀏覽器不用關心和理解Cookie的值部分的意義和格式,只要WEB服務器能理解值部分的意義就行。大多數現有的WEB服務器都是采用某種編碼方式將值部分的內容編碼成可打印的ASCII字符,RFC 2965規范中沒有明確限定編碼方式。
舉例: Set-Cookie2: user-hello; Version=1; Path=/
Cookie請求頭字段
Cookie請求頭字段中的每個Cookie之間用逗號(,)或分號(;)分隔。在Cookie請求字段中除了必須有“名稱=值”的設置外,還可以有Version、path、domain、port等屬性;在Version、path、domain、port等屬性名之前,都要增加一個“$”字符作為前綴。Version屬性只能出現一次,且要位于Cookie請求頭字段設置值的最前面,如果需要設置某個Cookie信息的Path、Domain、Port等屬性,它們必須位于該Cookie信息的“名稱=值”設置之后。
瀏覽器使用Cookie請求頭字段將Cookie信息會送給Web服務器;多個Cookie信息通過一個Cookie請求頭字段會送給Web服務器。
瀏覽器會根據下面幾個規則決定是否發送某個Cookie信息:
1、請求主機名是否與某個存儲的Cookie的Domain屬性匹配
2、請求的端口號是否在該Cookie的Port屬性列表中
3、請求的資源路徑是否在該Cookie的Path屬性指定的目錄及子目錄中
4、該Cookie的有效期是否已過
Path屬性的指向子目錄的Cookie排在Path屬性指向父目錄的Cookie之前
舉例: Cookie: $Version=1; Course=Java; $Path=/hello/lesson;Course=vc; $Path=/hello
Cookie的安全屬性
secure屬性
當設置為true時,表示創建的Cookie會被以安全的形式向服務器傳輸,也就是只能在HTTPS連接中被瀏覽器傳遞到服務器端進行會話驗證,如果是HTTP連接則不會傳遞該信息,所以不會被竊取到Cookie的具體內容。
HttpOnly屬性
如果在Cookie中設置了"HttpOnly"屬性,那么通過程序(JS腳本、Applet等)將無法讀取到Cookie信息,這樣能有效的防止XSS攻擊。
總結:secure屬性 是防止信息在傳遞的過程中被監聽捕獲后信息泄漏,HttpOnly屬性的目的是防止程序獲取cookie后進行攻擊這兩個屬性并不能解決cookie在本機出現的信息泄漏的問題(FireFox的插件FireBug能直接看到cookie的相關信息)。
Session
使用Cookie和附加URL參數都可以將上一-次請求的狀態信息傳遞到下一次請求中,但是如果傳遞的狀態信息較多,將極大降低網絡傳輸效率和增大服務器端程序處理的難度。
概述
Session技術是一種將會話狀態保存在服務器端的技術,它可以比喻成是醫院發放給病人的病歷卡和醫院為每個病人保留的病歷檔案的結合方式??蛻舳诵枰邮?、記憶和回送Session的會話標識號,Session可以且通常是借助Cookie來傳遞會話標識號。
Session的跟蹤機制
HttpSession對象是保持會話狀態信息的存儲結構,一個客戶端在WEB服務器端對應一個各自的HttpSession對象。WEB服務器并不會在客戶端開始訪問它時就創建HttpSession對象,只有客戶端訪問某個能與客戶端開啟會話的服務端程序時,WEB應用程序才會創建一個與該客戶端對應的HttpSession對象。WEB服務器為HttpSession對象分配一個獨一無的會話標識號, 然后在響應消息中將這個會話標識號傳遞給客戶端??蛻舳诵枰涀挊俗R號,并在后續的每次訪問請求中都把這個會話標識號傳送給WEB服務器,WEB服務器端程序依據回傳的會話標識號就知道這次請求是哪個客戶端發出的,從而選擇與之對應的HttpSession對象。
WEB應用程序創建了與某個客戶端對應的HttpSession對象后,只要沒有超出一個限定的空閑時間段,HttpSession對象就駐留在WEB服務器內存之中,該客戶端此后訪問任意的Servlet程序時,它們都使用與客戶端對應的那個已存在的HttpSession對象。
Session是實現網上商城的購物車的最佳方案,存儲在某個客戶Session中的一個集合對象就可充當該客戶的一個購物車。
超時管理
WEB服務器無法判斷當前的客戶端瀏覽器是否還會繼續訪問,也無法檢測客戶端瀏覽器是否關閉,所以,即使客戶已經離開或關閉了瀏覽器,WEB服務器還要保留與之對應的HttpSession對象。隨著時間的推移而不斷增加新的訪問客戶端,WEB服務器內存中將會因此積累起大量的不再被使用的HttpSession對象,并將最終導致服務器內存耗盡。WEB服務器采用“超時限制”的辦法來判斷客戶端是否還在繼續訪問如果某個客戶端在一定的時間之 內沒有發出后續請求,WEB服務器則認為客戶端已經停止了活動,結束與該客戶端的會話并將與之對應的HttpSession對象變成垃圾。
如果客戶端瀏覽器超時后再次發出訪問請求,Web服務器則認為這是一個新的會話開始,將為之創建新的Httpsession對象和分配新的會話標識號。
利用Cookie實現Session的跟蹤
如果WEB服務器處理某個訪問請求時創建了新的HttpSession對象,它將把會話標識號作為一個Cookie項加入到響應消息中,通常情況下,瀏覽器在隨后發出的訪問請求中又將會話標識號以Cookie的形式回傳給WEB服務器。WEB服務器端程序依據回傳的會話標識號就知道以前已經為該客戶端創建了HttpSession對象,不必再為該客戶端創建新的HttpSession對象,而是直接使用與該會話標識號匹配的HttpSession對象,通過這種方式就實現了對同一個客戶端的會話狀態的跟蹤。
利用URL重寫實現Session跟蹤
Servlet規范中引入了一種補充的會話管理機制,它允許不支持Cookie的瀏覽器也可以與WEB服務器保持連續的會話。這種補充機制要求在響應消息的實體內容中必須包含下一 次請求的超鏈接,并將會話標識號作為超鏈接的URL地址的一個特殊參數。將會話標識號以參數形式附加在超鏈接的URL地址后面的技術稱為URL重寫。 如果在瀏覽器不支持Cookie或者關閉了Cookie功能的情況下,WEB服務器還要能夠與瀏覽器實現有狀態的會話,就必須對所有能被客戶端訪問的請求路徑(包括超鏈接、form表單的action屬性設置和重定向的URL)進行URL重寫。
Cookie和Session的區別
session和cookies同樣都是針對單獨用戶的變量(或者說是對象好像更合適點),不同的用戶在訪問網站的時候都會擁有各自的session或者cookies,不同用戶之間互不干擾。
他們的不同點是:
1,存儲位置不同
session在服務器端存儲,比較安全,但是如果session較多則會影響性能
cookies在客戶端存儲,存在較大的安全隱患
2,生命周期不同
session生命周期在指定的時間(如20分鐘) 到了之后會結束,不到指定的時間,也會隨著瀏覽器進程的結束而結束。
cookies默認情況下也隨著瀏覽器進程結束而結束,但如果手動指定時間,則不受瀏覽器進程結束的影響。
總結:簡而言之,兩者都是保存了用戶操作的歷史信息,但是存在的地方不同;而且session和cookie的目的相同,都是為了克服HTTP協議無狀態的缺陷,但是完成方法不同。Session通過cookie在客戶端保存session id,將用戶的其他會話消息保存在服務端的session對象中;而cookie需要將所有信息都保存在客戶端,因此存在著一定的安全隱患,例如本地Cookie中可能保存著用戶名和密碼,容易泄露。
————————————————
版權聲明:本文為CSDN博主「悲觀的樂觀主義者」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/weixin_43997530/article/details/105650267
藍藍設計的小編 http://m.skdbbs.com