Ajax
客户端与服务器
服务器:负责存放和对外提供资源的电脑
客户端:负责获取和消费资源的电脑
URL地址:统一资源定位符(看作身份证号),表示互联网上每一个资源的唯一存放位置
由三部分组成
- 通信协议
- 服务器名称
- 具体的存放位置
客户端与服务器的 通信过程:请求-处理-响应
在网页中请求服务器上的数据资源,需要使用XMLHttpRequest对象
用法:var xhrObj=new XMLHttpRequest()
常见的两种请求方式
get请求用于获取服务器资源
例如获取图片,音频等
post请求常用语向服务器提交数据
例如:登陆、注册等
Ajax可以实现网页与服务器之间的数据交互
应用场景:
用户名检测:注册时,通过ajax形式,动态监测用户名是否被占用。
搜索提示:当输入关键字,通过ajax形式,动态提供列表。
数据的增删改查,实现数据的交互
$.get()函数
<button id="btnGET">发起不带参数的Get请求</button> <script> $(function () { $("#btnGET").on("click", function () { $.get('http://www.liulongbin.top:3006/api/getbooks', function (res) { console.log(res); }) }) }) </script>
通过XHR页签监听请求
<button id="btnGET">发起带参数的Get请求</button> <script> $(function () { $("#btnGET").on("click", function () { $.get('http://www.liulongbin.top:3006/api/getbooks', { id: 2 }, function (res) { console.log(res); }) }) }) </script>
$.post()函数
<button id="btnPOST">发起post请求</button> <script> $(function () { $("#btnPOST").on("click", function () { $.post("http://www.liulongbin.top:3006/api/addbook", { bookname: '活出生命的意义', author: '美国人', publisher: '天津出版社' }, function (res) { console.log(res); }) }) }) </script>
$.ajax()函数
功能综合
使用$.ajax()发起get请求
<button id="btnGET">发起get请求</button> <script> $(function () { $("#btnGET").on("click", function () { $.ajax({ type: 'GET', url: 'http://www.liulongbin.top:3006/api/getbooks', data: { id: 1 }, success: function (res) { console.log(res); } }) }) }) </script>
使用$.ajax()发起post请求
<button id="btnPOST">发起post请求</button> <script> $(function () { $("#btnPOST").on("click", function () { $.ajax({ type: 'POST', url: 'http://www.liulongbin.top:3006/api/addbook', data: { bookname: '活出生命的意义', author: '美国人', publisher: '天津出版社' }, success: function (res) { console.log(res); } }) }) }) </script>
接口
使用ajax请求数据时,被请求的URL地址,就叫做数据接口,同时每个接口必须有请求方式。
获取图书的接口:http://www.liulongbin.top:3006/api/getbooks
添加图书的接口:http://www.liulongbin.top:3006/api/addbook
Postman测试接口过程
- 选择请求方式
- 填写请求的URL地址
- 填写请求的参数
- 点击Send按钮发起GET请求
- 查看服务器相应的结果
Postman测试POST接口
步骤:
- 选择请求的方式
- 填写请求的URL地址
- 选择Body面板并勾选数据格式
- 填写要发送到服务器的数据
- 点击Send按钮发起POST请求
- 查看服务器响应的结果
接口文档
接口的说明文档,它是我们调用接口的依据,好的接口文档包含了对接口URL,参数以及输出内容的说明,我们参照接口就能方便的知道接口的作用,以及接口如何进调用。
接口文档的组成部分
接口名称:标识接口
接口URL:接口调用地址
调用方式:如get和post
参数格式:接口需压迫传递的参数,每个参数必须包括参数名称、参数类型、是否必选、参数说明这四项内容
响应格式:接口的返回值的详细描述,一般包含数据名称、数据类型、说明
返回事例(可选):通过对象的形式,例举返回数据的结构
案例:图书的增删改查
<link rel="stylesheet" href="bootstrap.min.css"> <script src="jquery.min.js"></script> <body style="padding: 15px;"> <!-- //添加图书的panel面板 --> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">添加新图书</h3> </div> <div class="panel-body form-inline"> <div class="input-group"> <div class="input-group-addon">书名</div> <input type="text" class="form-control" id="iptBookname" placeholder="请输入书名"> </div> <div class="input-group"> <div class="input-group-addon">作者</div> <input type="text" class="form-control" id="iptAuthor" placeholder="请输入作者"> </div> <div class="input-group"> <div class="input-group-addon">出版社</div> <input type="text" class="form-control" id="iptpublisher" placeholder="请输入出版社"> </div> <button id="btnAdd" class="btn btn-primary">添加</button> </div> </div> <!-- 图书的表格 --> <table class="table table-bordered table-hover"> <thead> <tr> <th>ID</th> <th>书名</th> <th>作者</th> <th>出版社</th> <th>操作</th> </tr> </thead> <tbody id="tb"> </tbody> </table> <script> $(function () { //获取图书列表的数据 function getBookList() { $.get("http://www.liulongbin.top:3006/api/getbooks", function (res) { // console.log(res); if (res.status !== 200) return alert("获取数据失败"); var rows = []; $.each(res.data, function (i, item) { rows.push("<tr><td>" + item.id + "</td><td>" + item.bookname + "</td><td>" + item.author + "</td><td>" + item.publisher + "</td ><td><a href='javascript:;' class='del' data-id='" + item.id + "'>删除</a></td></tr > "); }) $("#tb").empty().append(rows.join('')); }) } getBookList(); $("tbody").on("click", ".del", function () { var id = $(this).attr("data-id"); $.get('http://www.liulongbin.top:3006/api/delbook', { id: id }, function (res) { if (res.status !== 200) return alert("删除失败!"); getBookList(); }); }) $("#btnAdd").on("click", function () { var bookname = $("#iptBookname").val().trim(); var author = $("#iptAuthor").val().trim(); var publisher = $("#iptpublisher").val().trim(); if (bookname.length <= 0 || author.length <= 0 || publisher.length <= 0) { return alert('请填写完整的图书信息!'); }; $.post("http://www.liulongbin.top:3006/api/addbook", { bookname: bookname, author: author, publisher: publisher }, function (res) { if (res.status !== 201) { return alert("添加错误") } getBookList(); $("#iptBookname").val(''); $("#iptAuthor").val(''); $("#iptpublisher").val(''); }); }) }) </script> </body>
form表单
在网页中负责数据采集,HTML中form标签,用于采集用户输入的信息,并通过form标签提交操作,把采集到的信息提交到服务器端处理。
action属性:规定当提交表单时,提交的地址.
当form表单未指定URL,那么则将数据提交到当前页面的URL
也可以理解action的默认值为当前页面的URL
target属性:规定在何处打开action URL
一般情况下给target设置两种值:
_blank :在新窗口中打开
_self:默认在相同的框架中打开
method属性:规定以何种方式把表单提交到action URL
可选值为get和post
默认情况的值为get
enctype属性:规定在发送表单之前如何对数据进行编码
默认情况下值:application/x-www-form-urlencoded
如果涉及文件上传,必须要将enctype值设置为:multipart/form-data
表单的同步提交
通过点击submit按钮,触发表单提交的操作,从而使页面跳转到action URL的行为,叫做表单的同步提交。
缺点:
- 页面跳转
- 页面之前的状态和数据会丢失
采用ajax将数据提交到服务器,表单值负责采集数据
监听表单提交事件
两种方法
<form id="f1" action="/login" target="_blank" method="post"> <input type="text" name="email_or_mobile" /> <input type="password" name="password" /> <button type="submit">提交</button> </form> <script> $(function () { //one // $("#f1").submit(function () { // alert("监听到表单提交"); // }) //two $("#f1").on("submit", function () { alert("监听到表单提交"); }) }) </script>
阻止表单默认行为
当监听到表单的提交事件后,可以 调用事件对象的event.preventDefault()函数,来阻止表单的提交和页面的跳转。
//阻止表单的默认提交行为 //one // $("#f1").submit(function (e) { // e.preventDefault(); // }); //two $("#f1").on("submit", function (e) { e.preventDefault(); })
快速获取表单中的数据
//快速获取表单的值 //one $("#f1").submit(function (e) { e.preventDefault(); var data = $(this).serialize(); console.log(data); });
案例:评论发表
<body style="padding: 15px;"> <!-- //1.评论面板 --> <div class="panel panel-primary"> <div class="panel-heading"> <h3 class="panel-title">发表评论</h3> </div> <form class="panel-body" id="formAddcmt"> <div>评论人:</div> <input type="text" class="form-control" name="username" /> <div>评论内容:</div> <textarea class="form-control" name="content"></textarea> <button type="submit" class="btn btn-primary">发表评论</button> </form> </div> <!-- //评论列表 --> <ul class="list-group" id="cmt-list"> <li class="list-group-item"> <span class="badge" style="background-color:#f0ad4e;">评论时间</span> <span class="badge" style="background-color: #5bc0de;">评论人</span> Item 1 </li> </ul> <script> function getComment() { $.ajax({ method: 'GET', url: 'http://www.liulongbin.top:3006/api/cmtlist', data: {}, success: function (res) { console.log(res); if (res.status != 200) return alert('获取失败'); // console.log('success'); var rows = []; $.each(res.data, function (i, item) { var str = '<li class="list-group-item"><span class="badge" style="background-color:#f0ad4e;">评论时间' + item.time + '</span><span class="badge" style="background-color: #5bc0de;">评论人' + item.username + '</span>' + item.content + '</li > '; rows.push(str); }); $("#cmt-list").empty().append(rows.join('')); } }) } getComment(); $(function () { $("#formAddcmt").submit(function (e) { e.preventDefault(); var data = $(this).serialize(); $.post('http://www.liulongbin.top:3006/api/addcmt', data, function (res) { if (res.status !== 201) { return alert("发表评论失败"); } getComment(); $("#formAddcmt")[0].reset();//转成原生的dom对象 }) }) }) </script> </body>
模版引擎
可以根据程序员指定的模版结构和数据,自动生成一个完整的HTML页面
优点:
减少字符串的拼接操作
使代码结构更清晰
使代码更易于阅读与维护
art-template
简约、超快的模版引擎
<body> <div id="container"></div> <!-- 定义模版 --> <script type="text/html" id="tpl"> <h1>{{name}}----{{age}}</h1> <!-- //双花括号代表占位符 --> </script> <script> // 定义需要渲染的数据 var data = { name: "ss", age: 20 }; //调用template函数 var str = template('tpl', data); console.log(str); //渲染html结构 $("#container").html(str); </script> </body>
Art-template标准语法
{{}}内可以进行变量输出、对象属性的输出、三元表达式的输出,逻辑或输出、加减乘除表达式的输出、过滤器
<div id="container"></div> <!-- 定义模版 --> <script type="text/html" id="tpl"> <h1>{{name}}----{{age}}</h1> {{@test}} <!-- //双花括号代表占位符 --> <!-- 1.条件语句 --> <div> {{if flag===0 }} flag的值是0 {{else if flag===1}} flag的值是1 {{/if}} </div> <!--2. 循环输出 --> <ul> {{each hobby}} <li>索引是:{{$index}}:{{$value}}</li> {{/each}} </ul> <h3>{{regTime | dateFormat}}</h3> </script> <script> //3.过滤器函数 template.defaults.imports.dateFormat = function (date) { var y = date.getFullYear(); var m = date.getMonth() + 1; return y + '-' + m; } // 定义需要渲染的数据 var data = { name: "ss", age: 20, test: '<h3>测试原文输出</h3>', flag: 1, hobby: ['吃饭', '看书', '写代码'] , regTime: new Date() }; //调用template函数 var str = template('tpl', data); console.log(str); //渲染html结构 $("#container").html(str); </script>
正则与字符串操作
<script> var str = '<div>我是{{name}}</div>'; var pattern = /{{([a-zA-Z]+)}}/; var patternResult = pattern.exec(str); console.log(patternResult); </script>
replace函数
<script> var str = '<div>我是{{name}}</div>'; var pattern = /{{([a-zA-Z]+)}}/; var patternResult = pattern.exec(str); str = str.replace(patternResult[0], patternResult[1]); console.log(patternResult); console.log(str); </script>
多次replace操作
<script> var str = '<div>我是{{name}}年龄{{age}}</div>'; var pattern = /{{\s*([a-zA-Z]+)\s*}}/; var patternResult = pattern.exec(str); //第一次替换 str = str.replace(patternResult[0], patternResult[1]); console.log(patternResult); console.log(str); var patternResult = pattern.exec(str); //第二次替换 str = str.replace(patternResult[0], patternResult[1]); console.log(patternResult); console.log(str); </script>
换种思路:循环使用
var str = '<div>我是{{name}}年龄{{age}}</div>'; var pattern = /{{\s*([a-zA-Z]+)\s*}}/; //循环replace var patternResult = null; while (patternResult = pattern.exec(str)) { str = str.replace(patternResult[0], patternResult[1]); } console.log(str);
replace替换为真值
var data = { name: '张三', age: 20 }; var str = '<div>我是{{name}}年龄{{age}}</div>'; var pattern = /{{\s*([a-zA-Z]+)\s*}}/; //循环replace var patternResult = null; while (patternResult = pattern.exec(str)) { str = str.replace(patternResult[0], data[patternResult[1]]); } console.log(str);
调用自己的模版引擎
html文件
<div id="user-box"></div> <script type="text/html" id="tpl"> <div>姓名:{{name}}</div> <div>年龄:{{age}}</div> <div>性别:{{gender}}</div> <div>地址:{{address}}</div> </script> <script> //定义数据 var data = { name: '张三', age: 20, gender: '男', address: 'shanghai' }; //调用引擎模版 var htmlstr = template("tpl", data); //渲染html结构 document.getElementById("user-box").innerHTML = htmlstr; </script>
自己的template.js文件
function template(id, data) { var str = document.getElementById(id).innerHTML; var pattern = /{{\s*([a-zA-Z]+)\s*}}/; //循环replace var patternResult = null; while (patternResult = pattern.exec(str)) { str = str.replace(patternResult[0], data[patternResult[1]]); } return str; }
XMLHttpRequest的基本使用
可以请求服务器上的数据资源,ajax函数就是基于xhr对象封装出来的
<script> //使用XHR进行get请求 //1.创建XHR对象 var xhr = new XMLHttpRequest(); //2.调用open函数 xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks"); //3.调用send函数 xhr.send(); //4.监听 xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { //获取响应结果 console.log(xhr.responseText); } } </script>
带参数的get请求
xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks?id=1");
如何对URL进行编码和解码
<script> var str = '黑马程序员'; //编码 var str2 = encodeURI(str); console.log(str2); //解码 var str3 = decodeURI(str2); console.log(str3); </script>
使用XHR进行POST请求
//使用XHR进行post请求 //1.创建XHR对象 var xhr = new XMLHttpRequest(); //2.调用open函数 xhr.open("POST", "http://www.liulongbin.top:3006/api/addbook"); //3.设置content-type xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); //4.调用send函数 xhr.send('bookname=水浒传&author=施耐庵&publisher=上海版社'); //5.监听 xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { //获取响应结果 console.log(xhr.responseText); } }
数据交换格式
服务器端与客户端进行数据传输与交换的格式
主要使用JSON 和 XML
XML:可扩展标记语言
- HTM L是用来描述网页上的内容,是网页内容的载体
- XML是用来传输和存储数据,是数据的载体
缺点:格式复杂,和数据无关的代码多,体积大,传输效率低,在js中解析XML比较麻烦。
JSON:js对象和数组的字符串表示法,一种轻量级的文本数据交换格式,主流交换格式。
JSON的两种结构
对象结构和数组结构,字符串用双引号,数据类型可以是数字、字符串、布尔值、null、数组、对象
JSON的作用:在计算机与网络之间存储和传输数据
JSON的本质:用字符串来表示js对象数据和数组数据
JSON和JS对象的互转
//JSON-->JS var jsonStr = '{"a":"hello","b":"hi"}'; var jsStr = JSON.parse(jsonStr); console.log(jsStr); //JS-->JSON var jsStr = { a: 'hello', b: 'hi' }; var jsonStr = JSON.stringify(jsStr); console.log(jsonStr); //应用场景 var xhr = new XMLHttpRequest(); xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks"); xhr.send(); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.responseText); var result = JSON.parse(xhr.responseText); console.log(result); } }
序列化和反序列化
把数据对象转换为字符串的过程叫做序列化,
把字符串转换为数据对象的过程叫做反序列化,
封装自己的Ajax函数
处理data参数:需要把data对象,转化成查询字符串的格式,从而提交给服务器,因此提前定义resolve Data函数如下:
function resolveData(data) { var arr = []; for (var k in data) { var str = k + '=' + data[k]; arr.push(str); } return arr.join("&") } // var res = resolveData({ name: "ss", age: 20 }); // console.log(res); function itheima(options) { var xhr = new XMLHttpRequest(); //把外界传递过来的参数对象转换为查询字符串 var qs = resolveData(options.data); if (options.method.toUpperCase() == 'GET') { //发起get请求 xhr.open(options.method, options.url + '?' + qs); xhr.send(); } else if (options.method.toUpperCase() == 'POST') { //发起post请求 xhr.open(options.method, options.url); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencode'); xhr.send(qs); } xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { var result = JSON.parse(xhr.responseText); options.success(result); } } }
测试
<script> // //测试get // itheima({ // method: 'GET', // url: 'http://www.liulongbin.top:3006/api/getbooks', // data: { // id: 1 // }, // success: function (res) { // console.log(res); // } // }) //测试post itheima({ method: 'POST', url: 'http://www.liulongbin.top:3006/api/addbook', data: { bookname: '水浒传', author: '施耐庵', publisher: '上海出版社' }, success: function (res) { console.log(res); } }) </script>
XMLHttpRequest level2的新特性
1.设置HTTP请求时限
<script> var xhr = new XMLHttpRequest(); //设置超时函数 xhr.timeout = 3; //设置超时后的处理函数 xhr.ontimeout = function () { console.log('请求超时'); } // 1.创建XHR对象 var xhr = new XMLHttpRequest(); //2.调用open函数 xhr.open("GET", "http://www.liulongbin.top:3006/api/getbooks?id=1"); //3.调用send函数 xhr.send(); //4.监听 xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { //获取响应结果 console.log(xhr.responseText); } } </script>
使用Form Data对象管理表单数据
<script> //1.创建formdata实例 var fd = new FormData(); //2.调用append函数 向fd中追加数据 fd.append('uname', 'ss'); fd.append('upwd', '123456'); var xhr = new XMLHttpRequest(); xhr.open('POST', 'http://www.liulongbin.top:3006/api/formdata'); xhr.send(fd); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(xhr.responseText); } } </script>
获取网页表单的值
<form id="form1"> <input type="text" name="uname" autocomplete="off" /> <input type="password" name="upwd" /> <button type="submit"></button> </form> <script> var form = document.querySelector("#form1"); form.addEventListener('submit', function (e) { e.preventdefault(); //1.创建formdata实例 var fd = new FormData(); var xhr = new XMLHttpRequest(); xhr.open('GET', 'http://www.liulongbin.top:3006/api/formdata'); xhr.send(fd); xhr.onreadystatechange = function () { if (xhr.readyState == 4 && xhr.status == 200) { console.log(JSON.parse(xhr.responseText)); } } })
axios
专注于网络数据请求的库,只专注于网络数据请求
使用axios发送get请求
<button id="btn1">发起get请求</button> <script> document.querySelector("#btn1").addEventListener("click", function () { var url = "http://liulongbin.top:3306/api/get"; var paramsobj = { name: 'ss', age: 20 } axios.get(url, { params: paramsobj }) .then(function (res) { console.log(res); }) }) </script>
使用axios发送post请求
<button id="btn2">发起post请求</button> <script> document.querySelector("#btn2").addEventListener("click", function () { var url = "http://liulongbin.top:3306/api/post"; var dataobj = { address: '上海', location: '浦东' }; axios.post(url, { params: dataobj }) .then(function (res) { console.log(res.data); }) }) </script>
直接使用axios发送get请求
<button id="btn3">直接发起get请求</button> <script> document.querySelector("#btn3").addEventListener("click", function () { var url = "http://liulongbin.top:3306/api/get"; var paramsobj = { name: 'ss', age: 20 }; axios({ method: 'get', url: url, params: paramsobj }).then(function (res) { console.log(res.data); }) }) </script>
直接使用axios发送post请求
<button id="btn4">直接发起post请求</button> <script> document.querySelector("#btn4").addEventListener("click", function () { var url = "http://liulongbin.top:3306/api/post"; var dataobj = { name: 'ss', age: 20 }; axios({ method: 'post', url: url, data: dataobj }).then(function (res) { console.log(res.data); }) }) </script>
同源
如果两个页面的协议和域名和端口都相同,则两个页面具有相同的源
同源策略
浏览器提供的一个安全功能
跨域
与同源相反的就是跨域
出现跨域的根本原因:浏览器不允许非同源的URL之间进行资源交互
注:浏览器允许发起跨域请求,但是,跨域请求回来的数据会被浏览器拦截,无法被页面获取到
实现跨域请求JSONP和CORS
JSONP:出现早,兼容性好,只支持get
CORS:出现晚,W3C标准,支持get和post,兼容性不好
JSONP
实现原理:通过script标签的src属性,请求跨域的数据接口,并通过函数调用的形式,接收跨域接口响应回来的数据。
<body> <script> function success(data) { console.log('JSONP响应回来的数据是'); console.log(data); } </script> <script src="http://ajax.frontend.itheima.net:3006/api/jsonp?callback=success&name=ls&age=30"></script> </body>
jQuery中的JSONP
<script> $(function () { //发起JSONP的请求 $.ajax({ url: "http://ajax.frontend.itheima.net:3006/api/jsonp?name=ss&age=20", //代表我们发起JSONP的数据请求 dataType: 'jsonp', success: function (res) { console.log(res); } }) }) </script>
节流
指减少一段时间内事件的处罚频率
应用场景:当鼠标连续不断的触发某事件(如点击),只在单位时间内只触发一次;
懒加载要监听计算滚动条的位置,但不必每次滑动都触发,可以降低计算的频率,而不必浪费CPU的资源。
节流案例-鼠标跟随效果
节流阀:节流阀为空,表示可以执行下一次操作;不为空,表示不能执行下次操作。
当前操作操作执行完,必须将节流阀重置为空,表示可以执行下次操作了。
每次执行操作前,必须先判断节流是否为空。
<img src="cmq.JPG" id="cmq"> <script> $(function () { //不使用节流 //1.获取图片 var cmq = $('#cmq'); //(1)定义节流阀 var timer = null; //2.绑定事件 $(document).on('mousemove', function (e) { //(3)判断节流阀是否为空 if (timer) { return } //(2)开启延时器 timer = setTimeout(function () { $(cmq).css("top", e.pageY + 'px').css("left", e.pageX + 'px'); timer = null; }, 16) }) }) </script>
防抖:如果事件被频繁触发,防抖能保证只有一次出发生效,前面N多次的触发都会被忽略。
节流:如果事件被频繁触发,节流减少事件触发的频率,因此,节流是有选择性地执行一部分事件。
HTTP协议
客户端与服务器之间要实现网页内容的传输,则通信的双方必须遵守网页内容的传输协议,网页内容又叫做超文本,因此网页内容过的传输协议又叫做超文本传输协议
(hyper text transfer protocol)简称HTTP协议
HTTP协议采用请求/响应的交互模型
只有POST才有请求体,GET是没有请求体的
HTTP响应状态码

2**范围的状态码
3**范围的状态码
4**范围的状态码
5**范围的状态码
分类:
Ajax
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通