在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小时内与您取得联系。