摘要:常见内存泄漏情形全局变量被忘记的或者闭包引用闭包概念有权访问另一个函数作用域的变量的函数。会话存储刷新页面依旧存在,与在持久上不同外,其余一致。请求向指定的资源提交被处理的数据,数据量和类型没限制,不主动缓存,页面刷新数据会被重新提交。
defer 脚本延迟执行,适用于外部脚本文件
async 立即下载,不保证顺序(建议不修改DOM,避免重绘)
CDN加速 (Content Delivery Network,内容分发网络) 提高访问网站的响应速度
内存
js内存回收机制:解除引用
内存溢出:内存不够,系统会提示内存溢出,有时候会自动关闭软件
内存泄漏:未能释放已经不再使用的内存,不是指内存在物理上的消失,而是内存的浪费。
常见内存泄漏情形:1.全局变量、2.被忘记的Timers或者callbacks、3.闭包、4.DOM引用
闭包
概念:有权访问另一个函数作用域的变量的函数。
作用:匿名自执行函数、结果缓存、封装、实现类和继承
函数封装模式
//工厂模式:在函数中返回一个创建并返回一个对象 function createClass0(params){ var o = new Object(); o.name = params; o.fn0 = function(){ console.log(this.name); } return o; } var obj = createClass0(params); console.log(obj.constructor == createClass0); //false console.log(obj instanceof createClass0); //false //构造函数模式:es6的类也是通过该模式实现 function Class0(params){ this.name = params; this.fn0 = fn0; this.fn1 = function(){ console.log(this.name) } } function fn0(){ console.log(this.name) } var obj = new Class0(params); console.log(obj.constructor == Class0); //true console.log(obj instanceof Class0); //true //原型模式:属性与方法挂在原型链上 function Class0(){} Class0.prototype = { name: params, fn0: function(){ console.log(this.name) } } var obj = new Class0(); console.log(obj.constructor == Class0);//true console.log(obj instanceof Class0); //true Object.defineProperty(Class0.prototype,"constructor",{ enumerable: false, value: Class0 }) console.log(obj.constructor == Class0); //false //动态原型模式: function Class0(params){ this.name = params; if(typeof this.fn0 != "function"){ Class0.prototype.fn0 = function(){ console.log(this.name) } } } var obj = new Class0(params); console.log(obj.constructor == Class0); //true console.log(obj instanceof Class0); //true //寄生构造函数模式: function Class0(params){ var o = new Object(); o.name = params; return o; } var obj = new Class0(params); console.log(obj.constructor == Class0); //false console.log(obj instanceof Class0); //false //稳妥构造函数模式:无法访问其属性 function Class0(params){ var o = new Object(); var name = params; o.fn0 = function(){ console.log(name); } return o; } var obj = Class0(params); console.log(obj.constructor == Class0); //false console.log(obj instanceof Class0); //false //继承:js没有借口继承,只有实现继承,依靠原型链实现 //经典继承:借用构造函数 function Class0(params){ this.name0 = params; this.fn0 = function(){ console.log(this.name); } } function subClass0(params){ Class0.call(this,params); this.name1 = params; subClass0.prototype.fn1 = function(){ console.log(this.name1); } } var obj = new subClass0(params); console.log(obj.constructor == subClass0); //true console.log(obj instanceof subClass0); //true console.log(obj.constructor == Class0); //false console.log(obj instanceof Class0); //false //组合继承:es6类的继承通过该模式实现 function Class0(params){ this.name0 = params; this.fn0 = function(){ console.log(this.name0); } } function subClass0(params){ Class0.call(this,params); this.name1 = params; subClass0.prototype.fn1 = function(){ console.log(this.name1); } } subClass0.prototype = new Class0(); subClass0.prototype.constructor = subClass0; var obj = new subClass0(params); console.log(obj.constructor == subClass0); //true console.log(obj instanceof subClass0); //true console.log(obj.constructor == Class0); //false console.log(obj instanceof Class0); //true
BOM
概念:浏览器对象模型
核心对象:window,通过js访问浏览器窗口的一个接口,也是ECMAScript规定的Global对象。
窗口框架:frame、iframe
窗口位置:window.screenLeft/window.screenX,window.moveBy(x,y); window.moveTo(x,y);
窗口大小:window.resizeTo(width,height); window.resizeBy(dWidth,dHeight);
导航弹窗:let win = window.open("..."); win.close();
location
hash:URL的hash字符串
host:服务器名称和端口号
hostname:服务器名称
href:当前加载页面的完整URL
pathname:URL中的目录
port:URL中制定的端口号
prptocol:页面使用的协议
search:URL的查询字符串,以?开头
改变URL会生成新记录,禁用历史记录跳转可通过location.replace("...")实现
刷新页面:location.reload(); location.reload(true);
navigator
离线检测:onLine 地理信息:geolocation navigator.geolocation.getCurrentPosition((position)=>{ //成功的回调 },(error)=>{ //错误的回调 },{ enableHighAccuracy: true, //尽可能使用最准确的位置信息 timeout: 5000, //等待位置信息的最长时间 maximumAge: 25000 //上一次取得坐标信息的有效时间,时间到则重新获取 }); //跟踪监控 let watchId = navigator.geolocation.watchPosition(position)=>{ //成功的回调 },(error)=>{ //错误的回调 }); clearWatch(watchId); //关闭跟踪 浏览器中安装的插件信息:plugins //IE浏览器无效 function hasPlugin(name){ return Array.from(navigator.plugins).some(item => item.name.toLowerCase().includes(name.toLowerCase()) ) } hasPlugin("Flash"); //IE浏览器 function hasIEPlugin(name){ try{ new ActiveXObject(name); return true; }catch(ex){ return false; } } hasIEPlugin("ShockwaveFlash.ShockwaveFlash"); cookie是否启用:cookieEnabled 浏览器所在的系统平台:platform 用户代理:userAgent(可用于判断浏览器引擎、版本、终端等) //简易的浏览器类型判断 function browserType(){ //判断浏览器类型 let userAgent = navigator.userAgent; let type; switch(userAgent){ case userAgent.includes("MSIE"): type = "IE"; break; case userAgent.includes("Firefox"): type = "Firefox"; break; case userAgent.includes("Chrome"): type = "Chrome"; break; case userAgent.includes("Opera"): type = "Opera"; break; case userAgent.includes("Safari"): type = "Safari"; break; case userAgent.includes("Netscape"): type = "Netscape"; break; default: console.log(userAgent); break; } return type; }
history
页面前进:history.forward(); history.go(1);
页面后退:history.back(); history.go(-1);
跳转至可能存在的最近某个页面(不存在则什么都不做):history.go("...");
历史记录数量:history.length(hash改变会使历史记录增加)
系统对话框
alert(val); //警告窗 confirm(val); //确认窗,选择OK,则返回true prompt(val,""); //输入窗,选择OK,则返回输入框内容,否则返回null
数据存储
cookie:信息越大,对服务器的请求时间越长,故浏览器对其大小有限制,总量不到4K
//创建cookie function setCookie(name, value, expires, path, domain, secure) { var cookieText = encodeURIComponent(name) + "=" + encodeURIComponent(value);//防止中文乱码,所以需要编码 (expires instanceof Date) && (cookieText += "; expires=" + expires); //有效期 path && (cookieText += "; path=" + path); //设置域的路径 domain && (cookieText += "; domain=" + domain); //设置哪个域的请求中都会包含该cookie信息,默认为设置cookie的域 secure && (cookieText += "; secure"); //只有在使用SSL连接的时候才发送到服务器 document.cookie = cookieText; } //获取cookie function getCookie(name) { var cookieName = encodeURIComponent(name) + "="; var cookieStart = document.cookie.indexOf(cookieName); var cookieValue = null; if (cookieStart > -1) { var cookieEnd = document.cookie.indexOf(";", cookieStart); if (cookieEnd == -1) { cookieEnd = document.cookie.length; } cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd)); } return cookieValue; } //删除cookie function unsetCookie(name) { document.cookie = name + "= ; expires=" + new Date(0); }
localStorage:本地存储,容量5M左右,值类型限定为string类型,localStorage本质上是对字符串的读取,如果存储内容多的话会消耗内存空间,会导致页面变卡,另外在部分浏览器隐私模式下不可读取。
sessionStorage:会话存储(刷新页面依旧存在),与localStorage在持久上不同外,其余一致。
indexedDB:储存空间不少于250M,没有上限,异步设计,支持二进制储存(ArrayBuffer对象和Blob对象)
localforage:js库,异步操作(自动加载最佳驱动程序,从IndexedDB到localStorage)
//存储 localforage.setItem("key", "value"); localforage.setItem("key", "value").then(()=>{ ... }).catch(err=>{ console.log(err); }); localforage.setItem("key", "value").then(()=>{ return localforage.getItem("key"); }).then(val=>{ console.log(val); }).catch(err=>{ console.log(err); }); //获取 localforage.getItem("key").then(()=>{ ... }).catch(err=>{ console.log(err); }); //删除 localforage.removeItem("key"); localforage.removeItem("key").then(()=>{ ... }).catch(err=>{ console.log(err); }); //清空 localforage.clear(); localforage.clear().then(()=>{ ... }).catch(err=>{ console.log(err); }) //遍历 localforage.iterate((value, key)=>{ console.log(key,value); }).then(()=>{ ... }).catch(err=>{ console.log(err); });
DOM
//querySelector选择器,返回匹配的一个元素 let parentElement = document.querySelector(".container"); //创建元素 let element = document.createElement("button"); //设置元素的属性 element.setAttribute("onclick", `javascript:alert("click the span!");`); //设置内容,innerText有兼容性问题,采用innerHTML较为方便 element.innerHTML = "创建元素添加的元素"; //设置内联样式 element.style.color = "blue"; //在容器中末尾加入元素 parentElement.appendChild(element); //添加元素的另一种方式,该方式适合一次性创建多节点 let elementHtml = `` parentElement.innerHTML += elementHtml; //元素的克隆.cloneNode(true),父节点.parentNode,首个子节点.firstChild、下一个兄弟节点.nextElementSibling,加入到某个元素的前面refNode.parentNode.insertBefore(newNode,refNode) parentElement.insertBefore(parentElement.parentNode.cloneNode(true), parentElement.firstChild.nextElementSibling);//克隆一份parentElement的父节点,并将其放在parentElement的第一个子元素的下一个元素的前面 let newElement = element.cloneNode(true); newElement.innerHTML = "newElement"; //容器的最后一个子元素.lastChild,上一个兄弟节点.previousElementSibling,替换元素refNode.parentNode.insertBefore(newNode,refNode) parentElement.replaceChild(newElement, parentElement.lastChild.previousElementSibling);//替换parentElement最后一个子元素的上一个元素 //querySelectorAll选择器返回NodeList let firstContainer = document.querySelectorAll(".container")[0]; //删除元素node.parentNode.removeChild(node),元素子节点.children firstContainer.removeChild(firstContainer.children[0]);//删除firstContainer这个元素的第一个子元素 //动态添加样式规则 document.querySelector("head").innerHTML += `` //移动滚动条至firstContainer这个元素可见,true或不传表示元素顶端对齐,false表示底端在可视区域中间 firstContainer.scrollIntoView(true); //获取元素的真正样式 window.getComputedStyle(element) console.log(window.getComputedStyle(firstContainer)) //获取元素的真正样式 console.log(firstContainer.style); //获取元素的内联样式 //获取元素的尺寸及相对于浏览器可视窗口的位置(非文档,故页面发生滚动会改变).getBoundingClientRect(),在计算坐标使用时可用clientX配合getBoundingClientRect().left console.log(firstContainer.getBoundingClientRect());
事件流
事件流过程:捕获过程、目标、冒泡过程
事件捕获:从大到小,极少使用
事件冒泡:从小到大,常用,虽然各浏览器有些差异
阻止冒泡:e.stopPropagation(),IE9前使用e.cancelBubble=true
阻止默认行为:e.preventDefault(),IE9前使用e.returnValue=false
默认行为:如表单按钮的提交,标签的跳转等
事件委托:利用事件冒泡,监听顶级的DOM(事件处理程序数量关系到页面的整体运行性能)
页面事件
pageshow事件:页面显示时触发,事件目标为document,但必须将其事件处理程序添加到window,参数为event
pagehide事件:页面卸载时触发,事件目标为document,但必须将其事件处理程序添加到window,参数为event
haschange事件:URL的参数列表发生变化时触发,但必须将其事件处理程序添加到window,参数为event
visibilitychange事件:绑定在document上,当页面最小化或切换浏览器标签等可见状态改变触发,documen.hidden可查看页面可见度状态
document.addEventListener("visibilitychange",function(){ console.log(document.hidden); })
表单事件
默认行为:表单中的最后一个按钮会自动提交表单
form.submit()主动提交表单
form.reset()重置表单
form.checkValidity()表单验证配合require和pattern使用
noValidate属性禁用表单验证
过滤输入,屏蔽某些词的默认行为:监听keypress事件,e.preventDefault();
富文本编辑的实现:
①iframe实现,空文档的designMode设置为on,getSeclection事件获取具体信息;
②设置contenteditable属性实现,通过document.execCommand()实现富文本操作,如document.execCommand("blod", false, null);实现粗体文本。
鼠标事件
双击事件顺序:mousedown、mouseup、click、mousedown、mouseup、click、dblclick
移入容器事件顺序过程:mouseover(在不同元素间触发)、mouseenter、mousemove、mouseout(在不同元素间触发)、mouseleave
右键点击事件:contextmenu(H5事件)
位置信息:
clientX(鼠标相对于浏览器窗口可视区域的X坐标)
offsetX(鼠标相对于事件源元素的X坐标,存在兼容性问题)
screenX(鼠标相对于用户显示器屏幕左上角的X坐标)
pageX(鼠标相对于文档区域的X坐标,存在兼容性问题)
event对象属性:
type(事件类型)
detail(点击次数)
target(事件源元素)
path(数组,冒泡的路径)
拖放事件
相关事件:dragstart、drag、dragend、dragenter、dragover、dragleave、drop
投放目标 被拖拽的物体dragover(e){ e.preventDefault(); } drag(e){ e.dataTransfer.setData("id",e.target.id); } drop(e){ e.preventDefault(); let data = e.dataTransfer.getData("id"); e.target.appendChild(document.getElementById(data)); }
触摸与手势事件
移动端点击事件顺序:touchstart、touchend、mouseover、mouseenter、mousemove、mousedown、mouseup、click
touchmove事件:需要阻止默认行为以防止屏幕滚动
touchcancel事件:当系统停止跟踪触摸时触发
gesturestart事件:当一个手指已经按在屏幕上而另一个手指又触摸时触发
gesturechange事件:当触摸屏幕的任何一个手指的位置发生变化时改变
gestureend事件:当任何一个手指从屏幕上面移开时触发
触摸事件特有的event对象属性:
touches(表示当前跟踪的触摸操作的Touch对象的数组)
targetTouchs(特定于事件目标的Touch对象的数组)
changeTouches(上次触摸以来发生了什么改变的Touch对象的数组)
rotation(手势变化引起的旋转角度,从0开始,顺正逆负)
scal(手指间的距离变化,从1开始,与距离呈正相关)
模拟事件
UIEvents UI事件
MouseEvents 鼠标事件
MutationEvents DOM变动事件
HTMLEvents HTML事件
模拟鼠标点击事件: 1.创建鼠标事件的event对象 let event = document.createEvent("MouseEvents"); 2.设置触发的参数 //可简写,如event.initMouseEvent("click"); event.initMouseEvent(事件类型,...event对象其他参数); /*参数顺序:type、bubbles(是否冒泡)、cancelable(事件是否可取消)、view(document.defaultView)、detail、screenX、screenY、clientX、clientY、ctrlKey(是否按下ctrl)、altKey(是否按下alt)、shiftKey(是否按下shiftKey)、metaKey(是否按下metaKey)、button(表示按下哪个鼠标键)、relatedTarget(事件相关的对象,用于模拟mouseover/mouseout)*/ 3.触发事件 document.getElementById("btn").dispatchEvent(event);
媒体元素
音频播放器:audio
视频播放器:video
通过play()和pause()可手工控制媒体文件的播放和暂停,ended事件当播放结束时触发
绘图技术
canvas:2D绘图,可进行图片与图像的互转,通过变换和合成绘制复杂图像
//获取canvas内容生成的URI let imgURI = canvas.toDataURL("image/png"); let img = document.createElement("img"); img.src = imgURI; document.body.appendChild(img);
svg:支持事件驱动,通过foreignObject可实现svg和普通HTML元素的融合
WebGL:3D绘图,非W3C标准
ajax
Asynchronous Javascript And XML,即异步JavaScript和XML,是一种创建交互式网页应用的网页开发技术。
//封装ajax function ajax(obj) { var xhr = new XMLHttpRequest(); //创建XMLHttpRequest实例 obj.url = obj.url + "?rand=" + Math.random(); //为url加时间戳 if(obj.method === "get") obj.url += "&" + obj.data; if(obj.async) xhr.onreadystatechange = ()=>{ (xhr.readyState == 4) && callback(); } //异步请求当readyState == 4才执行回调 xhr.open(obj.method, obj.url, obj.async); //发送请求 if(obj.method === "post"){ xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); xhr.send(obj.data); }else{ xhr.send(null); } if(!obj.async) callback(); //同步请求 function callback() { if(xhr.status == 200){ obj.success(xhr.responseText); } //回调传递参数 else{ console.log("获取数据错误!错误代号:" + xhr.status + ",错误信息:" + xhr.statusText); } } } ajax({ method : "post/get", url : "...", data : "", success : (res)=>{ console.log(res); }, async : true });
get与post请求
get请求:从指定的资源请求数据,数据量和类型有限制(因为数据放在请求头中,浏览器对其长度有限制),会被缓存,且数据在url上可见,导致安全性相对低点。
post请求:向指定的资源提交被处理的数据,数据量和类型没限制,不主动缓存,页面刷新数据会被重新提交。
底层分析:对于get请求,浏览器会把http header和data一并发送出去,服务器响应200(返回数据),整个过程产生一个TCP数据包;对于post请求,浏览器先发送header,服务器响应100 continue,浏览器再发送data,服务器响应200 ok(返回数据),整个过程产生两个TCP数据包,导致时间消耗,网络环境畅通情况下可无视。
File API
let reader = new FileReader(); reader.readAsText(file,encoding); //以纯文本的形式读取文件,读取到的文本保存在result属性中 //reader.readAsDataURL(file); //读取文件以数据URI的形式保存在result属性中 //reader.readAsBinaryString(file); //读取文件并将一个字符串保存在result属性中,字符串中的每个字符表示一字节 //reader.readAsArrayBuffer(file); //读取文件并将一个包含文件内容的ArrayBuffer保存在result属性中 reader.progress = (event)=>{ if(event.lengthComputable){ console.log(event.loaded + "/" + event.total); } } reader.onerror = ()=>{ console.log(reader.error.code); } reader.onload = function(){ console.log(reader.result); }
对象防篡改
Object.preventExtensions(obj); //对象不可拓展,禁止添加属性或方法 Object.seal(obj); //密封对象,不可删除属性和方法 Object.freeze(obj); //冻结对象,不可拓展、且密封、不可编辑
String、JSON、Object相互转化
//js对象转JSON function jsToJSON(obj){ let objFnToStr = fnToStr(obj); return JSON.stringify(objFnToStr); } //字符串转JSON function strToJSON(str){ let obj = eval("("+str+")"); return jsToJSON(obj); } //JSON转js对象 function JSONToJs(json){ let obj = JSON.parse(json); return strToFn(obj); } //将js对象中的函数字符串解析为函数 function strToFn(obj){ var o = obj instanceof Array ? [] : {}; for(var k in obj) { switch(typeof(obj[k])){ case "object": o[k] = obj[k] ? strToFn(obj[k]) : null; break; case "string": o[k] = obj[k].match(/^s*(functions*(.*)s*)|((.*)s*=>s*)/) ? eval("("+obj[k]+")") : obj[k]; break; default: o[k] = obj[k]; break; } } return o; } //将js对象中的函数类型转为字符串 function fnToStr(obj){ var o = obj instanceof Array ? [] : {}; for(var k in obj) { switch(typeof(obj[k])){ case "object": o[k] = obj[k] ? fnToStr(obj[k]) : null; break; case "function": o[k] = obj[k] + ""; break; default: o[k] = obj[k]; break; } } return o; }
定时器
//延迟调用 let timeId = setTimeout(fn,time); clearTimeout(timeId); //间歇调用 let timeId = setInterval(fn,time); clearInterval(timeId); //定时器中的定时器 setTimeout(function(){ //... setTimeout(arguments.callee, interval); }) //沉睡排序法 [1,5,2,4,100,3].forEach(n=>{ setTimeout(()=>{ console.log(n) }, n); })
html与文本的转换
//字符串转html字符串,参数:html传入的字符串,type是否返回标签结构,默认只返回内容,escape表示是否需要转义 function escapeHtml(html,type,escape){ //type:content/html,escape:转义/不转义 let objE = document.createElement("div"); objE.innerHTML = html; type ? (escape ? (html.includes("&") ? (objE.innerText = html) : (objE.innerText = new Option(html).innerHTML)) : (html.includes("<") && (objE.innerText = html))) : (objE.innerHTML = objE.innerText); return objE.innerText; }
错误处理
//监听除 try catch 捕捉的错误 window.onerror = (...error)=>{ window.open("http://stackoverflow.com/search?q=[js] + "+error[0]); } //强制报错 var err = new Error("错误信息"); console.error(err); //单纯日志输出,不影响代码执行 throw err; //显示报错,导致后面代码不解析
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/98138.html
摘要:菜鸟教程这是一个属性其值是字符串菜鸟教程同上这是一个属性其值是字符串用于定义的函数,可以通过来返回函数值。它们都有前缀,以便与用户定义的属性区分开来。 开篇语 我最近学习了js,取得进步,现在学习vue.js.建议新手学习,请不要用npm的方式(vue-cli,vue脚手架),太复杂了. 请直接下载vue.js文件本地引入,就上手学习吧参照菜鸟教程网站的vue.js教程http://...
1、对象具有唯一标识性,即使完全相同的两个对象也不是同一个对象。 (js创建的对象内存地址不同)2、对象具有状态 同一对象可能处于不同的状态下 (js对象的属性)3、对象具有行为 对象的状态 可能因为他的行为发生改变 (js对象的属性) js对象独特性:具有高度动态性,js赋予使用者再运行时修改对象状态和行为的能力 属性描述对象 数据属性 value writable enumerable con...
摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...
摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...
摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...
摘要:鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇下的使用方法,传送门使用构建单页应用新篇华丽的分割线原文地址前言在最近学习的时候,看到国外一篇讲述了如何使用和来构建一个简单笔记的单页应用的文章。 鉴于该篇文章阅读量大,回复的同学也挺多的,特地抽空写了一篇 vue2.0 下的 vuex 使用方法,传送门:使用 Vuex + Vue.js 构建单页应用【新篇】 ---------...
阅读 472·2021-11-22 12:05
阅读 1531·2021-11-17 09:33
阅读 3578·2021-11-11 16:54
阅读 2658·2021-10-14 09:49
阅读 4023·2021-09-06 15:01
阅读 1820·2019-08-29 17:23
阅读 693·2019-08-29 14:09
阅读 712·2019-08-29 12:28