在JavaScript中,每一個數據都需要一個內存空間。內存空間分為兩種,棧內存(stack)與堆內存(heap)
棧是系統自動分配的內存空間,由系統自動釋放,堆則是動態分配的內存,大小不定不會自動釋放。
JavaScript中的基本數據類型,這些值都有固定的大小,保存在棧內存中,由系統自動分配存儲空間在棧內存空間的值,我們可以直接進行操作,因此基礎數據類型都是按照值訪問
在棧內存中的數據發生復制的行為時,系統會自動為新變量開辟一個新的內存空間,當復制執行后,兩個內存空間的值就互不影響,改變其中一個不會影響另一個
var a=`I am variable a`; var b=a; console.log(b); //`I am variable a` b=`I am variable b`; console.log(a); //`I am variable a` console.log(b); //`I am variable b`
引用類型的值是保存在堆內存中的對象,在JavaScript中我們不能直接操作對象的堆內存空間。因為引用類型的值都是按引用訪問的,所以在操作對象時,實際上是操作對象的引用而不是實際的對象。引用可以理解為保存在棧內存中的一個地址,該地址指向堆內存中的一個實際對象
引用類型值的復制,系統會為新的變量自動分配一個新的棧內存空間這個棧內存空間保存著與被復制變量相同的指針,盡管他們在棧內存中的內存空間的位置互相獨立但是在堆內存中訪問到的對象實際上是同一個,因此,當我們改變其中一個對象的值時,實際上就是改變原來的對象
棧內存空間保存指針(地址),堆內存空間保存實際的對象,我們通過變量訪問對象時,實際上訪問的是對象的引用(地址)
內存中的棧區域存放變量(基本類型的變量包括變量聲明和值)以及指向堆區域存儲位置的指針(引用類型的變量包括變量聲明和指向內容的指針)
var a={ name : `I am object a`, type : 'object' } var b=a; console.log(b); // {name: "I am object a", type: "object"} b.name=`I am object b`; console.log(a); // {name: "I am object b", type: "object"} console.log(b); // {name: "I am object b", type: "object"}
基本數據類型:
包括:null、undefined、number、string、boolean、symbol(es6)
存放位置:內存中的棧區域中
比較:值的比較,判斷是否相等,如果值相等,就相等。一般使用===進行比較,因為==會進行類型的轉換
拷貝:賦值(通過(=)賦值操作符 賦值),賦值完成后,兩個變量之間就沒有任何關系了,改變其中一個變量的值對另一個沒有任何影響
引用數據類型:
包括:數組、對象、函數
存放位置:內存的棧區域中存放變量和指針,堆區域存儲實際的對象
比較:是引用的比較(就是地址的比較,變量在棧內存中對應的指針地址相等就指向同一個對象)判斷是否為同一個對象,示例如下
變量a和變量b的引用不同,對象就不是同一個對象 var a={name:'Jay'}; var b={name:'Jay'}; a===b //false
我們對JavaScript中引用類型進行操作的時候,都是操作其對象的引用(保存在棧內存中的指針)
賦值:兩個變量的值(指針)都指向同一個對象,改變其中一個,另一個也會受到影響
所謂拷貝就是復制,通過復制原對象生成一個新的對象
淺拷貝:重新在堆內存中開辟一個空間,拷貝后新對象獲得一個獨立的基本數據類型數據,和原對象共用一個原對象內的引用類型數據,改變基本類型數據,兩個對象互不影響,改變其中一個對象內的引用類型數據,另一個對象會受到影響
var obj={ name: 'Jay Chou', age: 32, song:{ name:'菊花臺', year:2007 } } var obj1=obj; function shallowCopy(obj){ if(!obj || typeof obj !=='object'){ return ; } var scObj=Array.isArray(obj) ? [] : {}; //var scObj=(obj instanceof Array) ? [] :{}; //var scObj=obj.constructor===Array ? [] : {}; for(var prop in obj){ if(obj.hasOwnProperty(prop)){ scObj[prop]=obj[prop] } } return scObj; } var obj2=shallowCopy(obj); console.log(obj===obj1,'obj===obj1','賦值'); console.log(obj===obj2,'obj===obj2','淺拷貝'); // true "obj===obj1" "賦值" // false "obj===obj2" "淺拷貝" console.log(obj.song===obj2.song); //true obj2.song.name='菊花臺'; obj2.name='Jay'; console.log(obj) // {name: "Jay Chou", age: 32, song: {name:'菊花臺',year:2007}} console.log(obj1); // {name: "Jay Chou", age: 32, song: {name:'菊花臺',year:2007}} console.log(obj2); {name: "Jay", age: 32, song: {name:'菊花臺',year:2007}} console.log(obj===obj1) //true console.log(obj===obj2) //false
深拷貝:不論是對象內的基本類型還是引用類型都被完全拷貝,拷貝后兩個對象互不影響
一種比較簡單實現方法是使用 var dataObj=JSON.parse(JSON.stringify(data))
var obj={ name: 'Jay Chou', age: 32, song:{ name:'菊花臺', year:2007 } } var dcObj=JSON.parse(JSON.stringify(obj)); console.log(dcObj); // {name: "Jay Chou", age: 32, song: {name:'菊花臺',year:2007}} console.log(dcObj.song===obj.song); //false dcObj.name='Jay'; dcObj.song.name='雙截棍'; console.log(obj); // {name: "Jay Chou", age: 32, song: {name:'菊花臺',year:2007}} console.log(dcObj); //{name: "Jay", age: 32, song: {name:'雙截棍',year:2007}}
需要注意的是,使用JSON.Stringify()序列化對象時會把對象內的function和原型成員忽略掉,示例如下
var obj={ name: 'Jay Chou', job: 'artist', say:function(){ alert(this.job); } } JSON.stringify(obj); //"{"name":"Jay Chou","job":"artist"}"
通過遞歸淺拷貝函數實現深拷貝
function deepCopy(obj){ if(!obj || typeof obj !=='object'){ return ; } var dcObj=Array.isArray(obj) ? [] : {}; for(var key in obj){ if(obj.hasOwnProperty(key)){ if(obj[key] && typeof obj[key]==='object'){ dcObj[key]=Array.isArray(obj[key]) ? [] : {}; dcObj[key]=deepCopy(obj[key]); } dcObj[key]=obj[key] } } return dcObj; }
賦值: 變量獲得原對象的引用,改變該引用指向的對象的值(基本類型和引用類型)其實就是修改原對象的值
淺拷貝: 改變新對象基本類型的值不會使原對象對應的值一起改變,但是改變新對象引用類型的值會使原對象對應的值一同改變
深拷貝: 改變新對象基本類型和引用類型的值,都不會影響原對象,兩者互相獨立,互不影響
SON,全稱為 JavaScript Object Notation, 也就是 JavaScript 對象標記,它通過對象和數組的組合來表示數據,構造簡潔但是結構化程度非常高,是一種輕量級的數據交換格式。
本節中,我們就來了解如何利用 Python 保存數據到 JSON 文件。
在 JavaScript 語言中,一切都是對象。因此,任何支持的類型都可以通過 JSON 來表示,例如字符串、數字、對象、數組等,但是對象和數組是比較特殊且常用的兩種類型,下面簡要介紹一下它們。
所以,一個 JSON 對象可以寫為如下形式:
[
{
name: "Bob",
gender: "male",
birthday: "1992-10-18",
},
{
name: "Selina",
gender: "female",
birthday: "1995-10-18",
},
];
由中括號包圍的就相當于列表類型,列表中的每個元素可以是任意類型,這個示例中它是字典類型,由大括號包圍。
JSON 可以由以上兩種形式自由組合而成,可以無限次嵌套,結構清晰,是數據交換的極佳方式。
Python 為我們提供了簡單易用的 JSON 庫來實現 JSON 文件的讀寫操作,我們可以調用 JSON 庫的 loads 方法將 JSON 文本字符串轉為 JSON 對象,實際上 JSON 對象為 Python 中的 list 和 dict 的嵌套和組合,這里稱之為 JSON 對象。另外我們還可以通過 dumps 方法將 JSON 對象轉為文本字符串。
例如,這里有一段 JSON 形式的字符串,它是 str 類型,我們用 Python 將其轉換為可操作的數據結構,如列表或字典:
import json
str='''
[{
"name": "Bob",
"gender": "male",
"birthday": "1992-10-18"
}, {
"name": "Selina",
"gender": "female",
"birthday": "1995-10-18"
}]
'''
print(type(str))
data=json.loads(str)
print(data)
print(type(data))
運行結果如下:
<class'str'>
[{'name': 'Bob', 'gender': 'male', 'birthday': '1992-10-18'}, {'name': 'Selina', 'gender': 'female', 'birthday': '1995-10-18'}]
<class 'list'>
這里使用 loads 方法將字符串轉為 JSON 對象。由于最外層是中括號,所以最終的類型是列表類型。
這樣一來,我們就可以用索引來獲取對應的內容了。例如,如果想取第一個元素里的 name 屬性,就可以使用如下方式:
data[0]['name']
data[0].get('name')
得到的結果都是:
Bob
通過中括號加 0 索引,可以得到第一個字典元素,然后再調用其鍵名即可得到相應的鍵值。獲取鍵值時有兩種方式,一種是中括號加鍵名,另一種是通過 get 方法傳入鍵名。這里推薦使用 get 方法,這樣如果鍵名不存在,則不會報錯,會返回 None。另外,get 方法還可以傳入第二個參數(即默認值),示例如下:
data[0].get('age')
data[0].get('age', 25)
運行結果如下:
None
25
這里我們嘗試獲取年齡 age,其實在原字典中該鍵名不存在,此時默認會返回 None。如果傳入第二個參數(即默認值),那么在不存在的情況下返回該默認值。
值得注意的是,JSON 的數據需要用雙引號來包圍,不能使用單引號。例如,若使用如下形式表示,則會出現錯誤:
import json
str='''
[{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
'''
data=json.loads(str)
運行結果如下:
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 3 column 5 (char 8)
這里會出現 JSON 解析錯誤的提示。這是因為這里數據用單引號來包圍,請千萬注意 JSON 字符串的表示需要用雙引號,否則 loads 方法會解析失敗。
如果從 JSON 文本中讀取內容,例如這里有一個 data.json 文本文件,其內容是剛才定義的 JSON 字符串,我們可以先將文本文件內容讀出,然后再利用 loads 方法轉化:
import json
with open('data.json', encoding='utf-8') as file:
str=file.read()
data=json.loads(str)
print(data)
運行結果如下:
[{'name': 'Bob', 'gender': 'male', 'birthday': '1992-10-18'}, {'name': 'Selina', 'gender': 'female', 'birthday': '1995-10-18'}]
這里我們用 open 方法讀取了文本文件,同時使用了默認的讀模式,編碼指定為 utf-8,文件操作對象賦值為 file。接著我們調用了 file 對象的 read 方法讀取了文本的所有內容,賦值為 str。然后再調用 loads 方法解析 JSON 字符串,將其轉化為 JSON 對象。
這里其實也有更簡便的寫法,我們可以直接使用 load 方法傳入文件操作對象,同樣也可以將文本轉化為 JSON 對象,寫法如下:
import json
data=json.load(open('data.json', encoding='utf-8'))
print(data)
注意這里使用的是 load 方法,而不是 loads 方法。前者的參數是一個文件操作對象,后者的參數是一個 JSON 字符串。
這兩種寫法的運行結果也是完全一樣的。只不過 load 方法是將整個文件的內容轉化為 JSON 對象,而使用 loads 方法可以更靈活地控制要轉化的內容。兩種方法可以在適當的場景下使用。
另外,我們還可以調用 dumps 方法將 JSON 對象轉化為字符串。例如,將上例中的列表重新寫入文本:
import json
data=[{
'name': 'Bob',
'gender': 'male',
'birthday': '1992-10-18'
}]
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data))
利用 dumps 方法,我們可以將 JSON 對象轉為字符串,然后再調用文件的 write 方法寫入文本,結果如圖所示。
另外,如果想保存 JSON 的格式縮進,可以再加一個參數 indent,代表縮進字符個數。示例如下:
with open('data.json', 'w') as file:
file.write(json.dumps(data, indent=2))
此時寫入結果如圖所示。
這樣得到的內容會自動帶縮進,格式會更加清晰。
另外,如果 JSON 中包含中文字符,會怎么樣呢?例如,我們將之前的 JSON 的部分值改為中文,再用之前的方法寫入到文本:
import json
data=[{
'name': '王偉',
'gender': '男',
'birthday': '1992-10-18'
}]
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2))
寫入結果如圖所示。
可以看到,中文字符都變成了 Unicode 字符,這并不是我們想要的結果。
為了輸出中文,還需要指定參數 ensure_ascii 為 False,另外還要規定文件輸出的編碼:
with open('data.json', 'w', encoding='utf-8') as file:
file.write(json.dumps(data, indent=2, ensure_ascii=False))
寫入結果如圖所示。
可以發現,這樣就可以輸出 JSON 為中文了。
同樣地,類比 loads 與 load 方法,dumps 也有對應的 dump 方法,它可以直接將 JSON 對象全部寫入到文件中,因此上述的寫法也可以寫為如下形式:
json.dump(data, open('data.json', 'w', encoding='utf-8'), indent=2, ensure_ascii=False)
這里第一個參數就是 JSON 對象,第二個參數可以傳入文件操作對象,其他的 indent、ensure_ascii 對象還是保持不變,運行效果是一樣的。
本節中,我們了解了用 Python 進行 JSON 文件讀寫的方法,后面做數據解析時經常會用到,建議熟練掌握。
本節代碼:https://github.com/Python3WebSpider/FileStorageTest。
開始分析之前,我們先簡單回顧一下上一個章節中講到的Gin框架中的幾個核心的結構.
go語言中文文檔:www.topgoer.com
轉自:https://www.jianshu.com/p/9d1886b70ed9
Gin框架中的幾個重要的模型:
我們在深入Gin框架內幕(一)中,以一個簡單的Gin實例來具體講解它內部是如何創建一個Http服務,并且注冊一個路由來接收用戶的請求,在示例程序中我們使用了Context引用對象的String方法來處理HTTP服務的數據響應,所以在整個Gin框架中緊跟Router模型結構的就要屬Context結構了,該結構體主要用來處理整個HTTP請求的上下文數據,也是我們在開發HTTP服務中相對比較重要的一個結構體了。
# 深入Gin框架內幕(一)中的示例
$ cat case1.go
package main
import (
"net/http"
"github.com/gin-gonic/gin"
)
func main() {
ginObj :=gin.Default()
ginObj.Any("/hello",func(c *gin.Context){
c.String(http.StatusOK,"Hello BGBiao.")
})
ginObj.Run("localhost:8080")
}
我們可以看到,在使用Gin框架后,我們只需要很簡單的代碼,即可以快速運行一個返回Hello BGBiao.的HTTP服務,而在ginObj.Any方法中,我們傳入了一個參數為Context引用類型的匿名函數,并在該函數內部采用String(code,data)方法來處理HTTP服務的響應數據(返回Hello BGBiao字符串),這個時候,你可能會想,我們在企業內部都是前后端分離,通常情況下后端僅會提供RESTful API,并通過JSON格式的數據和前端進行交互,那么Gin是如何處理其他非字符串類型的數據響應呢,這也是我們接下來要主要講的Context結構模型。
注意: 在Gin框架中由Router結構體來負責路由和方法(URL和HTTP方法)的綁定,內的Handler采用Context結構體來處理具體的HTTP數據傳輸方式,比如HTTP頭部,請求體參數,狀態碼以及響應體和其他的一些常見HTTP行為。
Context結構體:
type Context struct {
// 一個包含size,status和ResponseWriter的結構體
writermem responseWriter
// http的請求體(指向原生的http.Request指針)
Request *http.Request
// ResonseWriter接口
Writer ResponseWriter
// 請求參數[]{"Key":"Value"}
Params Params
handlers HandlersChain
index int8
// http請求的全路徑地址
fullPath string
// gin框架的Engine結構體指針
engine *Engine
// 每個請求的context中的唯一鍵值對
Keys map[string]interface{}
// 綁定到所有使用該context的handler/middlewares的錯誤列表
Errors errorMsgs
// 定義了允許的格式被用于內容協商(content)
Accepted []string
// queryCache 使用url.ParseQuery來緩存參數查詢結果(c.Request.URL.Query())
queryCache url.Values
// formCache 使用url.ParseQuery來緩存PostForm包含的表單數據(來自POST,PATCH,PUT請求體參數)
formCache url.Values
}
Context結構體常用的一些方法
基本方法:
http常用方法:
流控相關的方法:
錯誤管理:
元數據管理:
輸入數據:
Bind家族相關方法:
ShouldBind家族相關方法:
HTTP響應相關的方法:
3.1返回json格式的數據
為了解決我們在開頭提到的問題,我們將使用context引用對象的JSON家族方法來處理該需求
# 使用context來返回json格式的數據
$ cat case2.go
package main
import (
"github.com/gin-gonic/gin"
)
// 我們定義一個通用的格式化的響應數據
// 在Data字段中采用空接口類型來實際存放我們的業務數據
type restData struct {
Data interface{} `json:"data"`
Message string `json:"message"`
Status bool `json:"status"`
}
func main() {
// mock一個http響應數據
restdata :=&restData{"Hello,BGBiao","",true}
restdata1 :=&restData{map[string]string{"name":"BGBiao","website":"https://bgbiao.top"},"",true}
// 使用Gin框架啟動一個http接口服務
ginObj :=gin.Default()
ginObj.GET("/api/test",func(c *gin.Context){
// 我們的handlerFunc中入參是一個Context結構的引用對象c
// 因此我們可以使用Context中的JSON方法來返回一個json結構的數據
// 可用的方法有如下幾種,我們可以根據實際需求進行選擇
/*
IndentedJSON(code int, obj interface{}): 帶縮進的json(消耗cpu和mem)
SecureJSON(code int, obj interface{}): 安全化json
JSONP(code int, obj interface{})
JSON(code int, obj interface{}): 序列化為JSON,并寫Content-Type:"application/json"頭
*/
c.JSON(200,restdata)
})
ginObj.GET("/api/test1",func(c *gin.Context){
c.IndentedJSON(200,restdata1)
})
ginObj.Run("localhost:8080")
}
# 實例運行(這里成功將我們寫的兩個api接口進行對外暴露)
$ go run case2.go
....
....
[GIN-debug] GET /api/test --> main.main.func1 (3 handlers)
[GIN-debug] GET /api/test1 --> main.main.func2 (3 handlers)
# 接口測試訪問
$ curl localhost:8080/api/test
{"data":"Hello,BGBiao","message":"","status":true}
$ curl localhost:8080/api/test1
{
"data": {
"name": "BGBiao",
"website": "https://bgbiao.top"
},
"message": "",
"status": true
}%
當然上面我們僅以JSON格式來示例,類似的方式我們可以使用XML,YAML,ProtoBuf等方法來輸出指定格式化后的數據。
3.2其他常用的基本方法
注意:在其他基本方法中我們仍然使用上述示例代碼中的主邏輯,主要用來測試基本的方法.
# 我們在/api/test這個路由中增加如下兩行代碼
// 設置響應體中的自定義header(通常我們可以通過自定義頭來實現一個內部標識)
c.Header("Api-Author","BGBiao")
// GetHeader方法用來獲取指定的請求頭,比如我們經常會使用請求中的token來進行接口的認證和鑒權
// 這里由于我們使用的restdata的指針,通過GetHeader方法獲取到token賦值給Message
// ClientIP()方法用于獲取客戶端的ip地址
restdata.Message=fmt.Sprintf("token:%s 當前有效,客戶端ip:%s",c.GetHeader("token"),c.ClientIP())
# 訪問接口示例(我們可以看到在響應體中多了一個我們自定義的Api-Author頭,并且我們將請求頭token的值)
$ curl -H 'token:xxxxxxxx' localhost:8080/api/test -i
HTTP/1.1 200 OK
Api-Author: BGBiao
Content-Type: application/json; charset=utf-8
Date: Sun, 12 Jan 2020 14:41:01 GMT
Content-Length: 66
{"data":"Hello,BGBiao","message":"token:xxxxxxxx 當前有效,客戶端ip:127.0.0.1","status":true}
3.3用戶數據輸入
當然到這里后,你可能還會有新的疑問,就是通常情況下,我們開發后端接口會提供一些具體的參數,通過一些具體數據提交來實現具體的業務邏輯處理,這些參數通常會分為如下三類:
以上的基本需求,幾乎都可以在Context結構體的輸入數據中找到響應的方法.
*請認真填寫需求信息,我們會在24小時內與您取得聯系。