整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

JavaScript的赋值、深拷贝和浅拷贝

JavaScript的赋值、深拷贝和浅拷贝

存空间

在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中引用类型进行操作的时候,都是操作其对象的引用(保存在栈内存中的指针)

赋值、深拷贝和浅拷贝 (Assignment, deep copy and shallow copy)

赋值:两个变量的值(指针)都指向同一个对象,改变其中一个,另一个也会受到影响

所谓拷贝就是复制,通过复制原对象生成一个新的对象

浅拷贝:重新在堆内存中开辟一个空间,拷贝后新对象获得一个独立的基本数据类型数据,和原对象共用一个原对象内的引用类型数据,改变基本类型数据,两个对象互不影响,改变其中一个对象内的引用类型数据,另一个对象会受到影响

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 文件。

1. 对象和数组

在 JavaScript 语言中,一切都是对象。因此,任何支持的类型都可以通过 JSON 来表示,例如字符串、数字、对象、数组等,但是对象和数组是比较特殊且常用的两种类型,下面简要介绍一下它们。

  • 对象:它在 JavaScript 中是使用花括号 {} 包裹起来的内容,数据结构为 {key1:value1, key2:value2, ...} 的键值对结构。在面向对象的语言中,key 为对象的属性,value 为对应的值。键名可以使用整数和字符串来表示。值的类型可以是任意类型。
  • 数组:数组在 JavaScript 中是方括号 [] 包裹起来的内容,数据结构为 ["java", "javascript", "vb", ...] 的索引结构。在 JavaScript 中,数组是一种比较特殊的数据类型,它也可以像对象那样使用键值对,但还是索引用得多。同样,值的类型可以是任意类型。

所以,一个 JSON 对象可以写为如下形式:

[
  {
    name: "Bob",
    gender: "male",
    birthday: "1992-10-18",
  },
  {
    name: "Selina",
    gender: "female",
    birthday: "1995-10-18",
  },
];

由中括号包围的就相当于列表类型,列表中的每个元素可以是任意类型,这个示例中它是字典类型,由大括号包围。

JSON 可以由以上两种形式自由组合而成,可以无限次嵌套,结构清晰,是数据交换的极佳方式。

2. 读取 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 方法可以更灵活地控制要转化的内容。两种方法可以在适当的场景下使用。

3. 输出 JSON

另外,我们还可以调用 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 对象还是保持不变,运行效果是一样的。

4. 总结

本节中,我们了解了用 Python 进行 JSON 文件读写的方法,后面做数据解析时经常会用到,建议熟练掌握。

本节代码:https://github.com/Python3WebSpider/FileStorageTest。

开始分析之前,我们先简单回顾一下上一个章节中讲到的Gin框架中的几个核心的结构.

Gin框架中的几个核心结构

go语言中文文档:www.topgoer.com

转自:https://www.jianshu.com/p/9d1886b70ed9

Gin框架中的几个重要的模型:

  • Engine: 用来初始化一个gin对象实例,在该对象实例中主要包含了一些框架的基础功能,比如日志,中间件设置,路由控制(组),以及handlercontext等相关方法.源码文件
  • Router: 用来定义各种路由规则和条件,并通过HTTP服务将具体的路由注册到一个由context实现的handler中
  • Context: Context是框架中非常重要的一点,它允许我们在中间件间共享变量,管理整个流程,验证请求的json以及提供一个json的响应体. 通常情况下我们的业务逻辑处理也是在整个Context引用对象中进行实现的.
  • Bind: 在Context中我们已经可以获取到请求的详细信息,比如HTTP请求头和请求体,但是我们需要根据不同的HTTP协议参数来获取相应的格式化
    数据来处理底层的业务逻辑,就需要使用Bind相关的结构方法来解析context中的HTTP数据

1.Gin框架对HTTP响应数据的处理

我们在深入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结构模型。

2.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结构体常用的一些方法

基本方法:

  • Copy(): 返回当前正在使用的context的拷贝(context指针),当这个context必须在goroutine中用时,该方法比较有用
  • HandlerName(): 返回当前主handler的名称(比如:handler为handleGetUsers(),该方法将返回"main.handleGetUsers")
  • HandlerNames(): 返回所有注册的handler的名称
  • Handler(): 返回当前的主handler(func (c *Context) Handler() HandlerFunc)
  • FullPath(): 返回一个匹配路由的全路径(uri: "/user/:id",c.FullPath()=="/user/:id" )

http常用方法:

  • ClientIP() string: 返回客户端ip(该方法会解析X-Real-IP,X-Forwarded-For)
  • ContentType() string: 返回HTTP的Content-Type头
  • IsWebsocket() bool: 返回是否为ws链接

流控相关的方法:

  • Next(): 该方法仅被使用在middleware中,它会在被调用的handler链内部执行pending handler
  • IsAborted(): 如果当前的context被终止了,该方法返回true
  • Abort(): 该函数可以从正在被调用中保护pending handler. 该方法停止后不会停止当前正在执行的handler. 比如我们有一个鉴权的中间件来验证请求是否有权限,如果认证失败了(用户信息异常等),此时调用Abort()来确保后面的handler不再被调用
  • AbortWithStatus(code int): 同上,在会写入状态码。context.AbortWithStatus(401)即可表示上述的鉴权失败
  • AbortWithStatusJSON(code int, jsonObj interface{}): 同上,会再加响应数据.该方法会停止整个handler链,再写入状态码和json的响应体,同时也会设置Content-Type="application/json"
  • AbortWithError(code int, err error) *Error: 同上返回错误信息

错误管理:

  • Error(err error) *Error: 返回一些错误对象

元数据管理:

  • Set(key string, value interface{}): 给当前这个context设置一个新的键值对
  • Get(key string) (value interface{}, exists bool): 返回指定的key的值,以及是否存在
  • MustGet(key string) interface{}: 返回指定key的值,不存在则panic
  • GetString(key string) (s string): 以string类型返回指定的key
  • GetBool(key string) (b bool): 返回分配给该key的值(bool类型)
  • GetInt(key string) (i int):
  • GetStringSlice(key string) (ss []string): 返回key的slice类型
  • GetStringMap(key string) (sm map[string]interface{}): 返回interface{}类型的map结构
  • GetStringMapString(key string) (sms map[string]string): 返回string类型的map结构
  • GetStringMapStringSlice(key string) (smss map[string][]string): 同理

输入数据:

  • Param(key string) string: 返回URL的参数值(uri_patten: "/user/:id",url: "/user/john",c.Param("id")="john")
  • Query(key string) string: 返回url中的查询参数值(url: "/path?id=1234&name=Manu&value=",c.Query("id")为1234,c.Query("name")为Manu,c.Query("value")为空)
  • DefaultQuery(key, defaultValue string) string: 返回url中的查询参数的默认值(同上,但是c.Query("value")就没有值,该方法可以设置默认值)
  • GetQuery(key string) (string, bool): 同Query()方法,并且会返回状态,如果对应的key不存在,返回("",false)
  • QueryArray(key string) []string: 返回指定key的对应的array(slice的长度取决于给定key的参数的数量)
  • GetQueryArray(key string) ([]string, bool): 同上,会返回状态
  • QueryMap(key string) map[string]string: 返回指定key对应map类型
  • GetQueryMap(key string) (map[string]string, bool): 同上,并且会返回状态
  • PostForm(key string) string: 该方法返回一个从POST 请求的urlencode表单或者multipart表单数据,不存在时返回空字符串
  • DefaultPostForm(key, defaultValue string) string: 同上,key不存在时返回默认值
  • GetPostForm(key string) (string, bool): 同PostForm()方法,并且会返回状态
  • PostFormArray(key string) []string: 该方法返回指定key的字符串类型的slice
  • GetPostFormArray(key string) ([]string, bool): 同上,并返回状态
  • PostFormMap(key string) map[string]string: 返回指定key的map类型
  • GetPostFormMap(key string) (map[string]string, bool): 同上,并返回状态
  • FormFile(name string) (*multipart.FileHeader, error): 返回指定key的第一个文件(用作文件上传)
  • MultipartForm() (*multipart.Form, error): 该方法解析multipart表单,包含file文件上传
  • SaveUploadedFile(file *multipart.FileHeader, dst string) error: 该方法用来上传指定的文件头到目标路径(dst)

Bind家族相关方法:

  • Bind(obj interface{}) error: 自动解析Content-Type并绑定到指定的binding引擎
  • BindJSON(obj interface{}) error: 同上,binding引擎为binding.JSON
  • BindXML(obj interface{}) error:
  • BindQuery(obj interface{}) error:
  • BindYAML(obj interface{}) error:
  • BindHeader(obj interface{}) error:
  • BindUri(obj interface{}) error: 使用binding.Uri来绑定传递的结构体指针
  • MustBindWith(obj interface{}, b binding.Binding) error: 使用指定的binding引擎来绑定传递的结构体指针(当有任何错误时,终止请求并返回400)

ShouldBind家族相关方法:

  • ShouldBind(obj interface{}) error: 同上述的Bind()方法,但是该方法在json结构无效时不会返回400
  • ShouldBindJSON(obj interface{}) error:
  • ShouldBindXML(obj interface{}) error:
  • ShouldBindQuery(obj interface{}) error:
  • ShouldBindYAML(obj interface{}) error:
  • ShouldBindHeader(obj interface{}) error:
  • ShouldBindUri(obj interface{}) error:
  • ShouldBindWith(obj interface{}, b binding.Binding) error: 等同于MustBindWith()方法
  • ShouldBindBodyWith(obj interface{}, bb binding.BindingBody) (err error): 和ShouldBindWith()方法相似,但是他会存储请求体到context中,当下次调用时可以重用(因为该方法是在binding之前读取body,因此在你只使用一次时,为了更好的性能还是使用ShouldBindWith会比较好)

HTTP响应相关的方法:

  • Status(code int): 设置http的响应码
  • Header(key, value string): 是c.Writer.Header().Set(key, value)的简单实现,在响应体重写入一个header,如果value为空,则相当于调用了c.Writer.Header().Del(key)
  • GetHeader(key string) string: 返回请求体重的header
  • GetRawData() ([]byte, error): 返回流式数据
  • SetCookie(name, value string, maxAge int, path, domain string, secure, httpOnly bool): 该方法将设置一个Set-Cookie到ResponseWriter的头中(注意:name必须是一个合法可用的名称,无效的coookie可能会被丢弃)
  • Cookie(name string) (string, error): 返回名称为name的cookie
  • Render(code int, r render.Render): 该方法写入响应头并调用render.Render去渲染数据
  • HTML(code int, name string, obj interface{}): 该方法使用指定文件模板名称去渲染http模板(同时会更新状态码并设置Content-Type as "text/html".)
  • IndentedJSON(code int, obj interface{}): 该方法会序列化对象obj为一个pretty JSON 数据到响应体中,同时设置Content-Type as "application/json"(pretty JSON需要消耗cpu和带宽,强烈建议生产使用Context.JSON())
  • SecureJSON(code int, obj interface{}): 同上,会序列化成Secure Json
  • JSONP(code int, obj interface{}):
  • JSON(code int, obj interface{}): 序列化为JSON,并写Content-Type:"application/json"头
  • AsciiJSON(code int, obj interface{}):
  • PureJSON(code int, obj interface{}):
  • XML(code int, obj interface{}): 序列化成xml格式,并写Content-Type:"application/xml"
  • YAML(code int, obj interface{}): 序列化成yaml
  • ProtoBuf(code int, obj interface{}): 序列化成probuf
  • String(code int, format string, values ...interface{}): 将制定的string写入响应体
  • Redirect(code int, location string): 重定向
  • Data(code int, contentType string, data []byte): 写一些数据到响应体重,并更新响应码
  • DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string): 写一些制定模板的数据到响应体中,并更新状态码
  • File(filepath string): 以一种高效方式将制定文件写入响应体数据中
  • FileAttachment(filepath, filename string): 同上,但是在客户端文件会被直接下载下来
  • SSEvent(name string, message interface{}): 写Server-Sent Event到响应数据中
  • Stream(step func(w io.Writer) bool) bool: 发送一个流式的响应数据并返回状态

3.Gin实例示例

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用户数据输入

当然到这里后,你可能还会有新的疑问,就是通常情况下,我们开发后端接口会提供一些具体的参数,通过一些具体数据提交来实现具体的业务逻辑处理,这些参数通常会分为如下三类:

  • 使用HTTP GET方法获取到的url中的一些查询参数来执行更具体的业务逻辑(比如我们查询数据的指定条数之类的)
  • 使用HTTP POST GET等其他方式以form表单方式提交的数据来验证和处理用户数据
  • 在URL中获取一些可变参数(比如通常我们的url会定义为"/api/uid/:id"来表示用户id相关的接口,这个时候通常需要获取到url中的id字段)

以上的基本需求,几乎都可以在Context结构体的输入数据中找到响应的方法.