做者:Peter 谭金杰
转发链接:https://mp.weixin.电话.com/s/xjtUK9yZVIK9-OL_WxDH7Q
媒介能优化是一门大学问,本文仅对小我一些积累常识的论述,欢送下面弥补。
抛出一个问题,从输入url地址栏到所有内容显示到界面上做了哪些事?
1.阅读器向DNS办事器恳求解析该 URL 中的域名所对应的 IP 地址;2.成立TCP毗连(三次握手);3.阅读器发出读取文件(URL 中域名后面部门对应的文件)的HTTP 恳求,该恳求报文做为 TCP 三次握手的第三个报文的数据发送给办事器;4.办事器对阅读器恳求做出响应,并把对应的 html 文本发送给阅读器;5.阅读器将该 html 文本并显示内容;6.释放 TCP毗连(四次挥手);上面那个问题是一个面试官十分喜好问的问题,我们下面把那6个步调合成,逐渐细谈优化。
更多的前端优化相关文章,请见本篇文章底部
一、DNS 解析DNS`解析:将域名解析为ip地址 ,由上往下婚配,只要射中便停行- 走缓存- 阅读器DNS缓存- 本机DNS缓存- 路由器DNS缓存- 收集运营商办事器DNS缓存 (80%的DNS解析在那完成的)- 递归查询优化战略:尽量允许利用阅读器的缓存,能给我们节省大量时间,下面有dns-prefetch的介绍,每次dns解析大要需要20-120秒
二、TCP的三次握手SYN (同步序列编号)ACK(确认字符)第一次握手:Client将标记位SYN置为1,随机产生一个值seq=J,并将该数据包发送给Server,Client进入SYN_SENT形态,等 待Server确认。第二次握手:Server收到数据包后由标记位SYN=1晓得Client恳求成立毗连,Server将标记位SYN和ACK都置为1,ack=J+1,随机产生一个值seq=K,并将该数据包发送给Client以确认毗连恳求,Server进入SYN_RCVD形态。第三次握手:Client收到确认后,查抄ack能否为J+1,ACK能否为1,若是准确则将标记位ACK置为1,ack=K+1,并将该数据包发送给Server,Server查抄ack能否为K+1,ACK能否为1,若是准确则毗连成立胜利,Client和Server进入ESTABLISHED形态,完成三次握手,随后Client与Server之间能够起头传输数据了。三、阅读器发送恳求优化战略:
1.HTTP协议通信最消耗时间的是成立TCP毗连的过程,那我们就能够利用HTTP Keep-Alive,在HTTP早期,每个HTTP恳求都要求翻开一个TCP socket毗连,而且利用一次之后就断开那个TCP毗连。 利用keep-alive能够改善那种形态,即在一次TCP毗连中能够持续发送多份数据而不会断开毗连。通过利用keep-alive机造,能够削减TCP毗连成立次数,也意味着能够削减TIME_WAIT形态毗连,以此进步性能和进步http办事器的吞吐率(更少的tcp毗连意味着更少的系统内核挪用2.但是,keep-alive并非免费的午餐,长时间的TCP毗连容易招致系统资本无效占用。设置装备摆设不妥的keep-alive,有时比反复操纵毗连带来的丧失还更大。所以,准确地设置keep-alive timeout时间十分重要。(那个keep-alive_timout时间值意味着:一个http产生的tcp毗连在传送完最初一个响应后,还需要hold住keepalive_timeout秒后,才起头封闭那个毗连),若是想更详细领会能够看那篇文章[keep-alve性能优化的测试成果][1]3.利用webScoket通信协议,仅一次TCP握手就不断连结毗连,并且它对二进造数据的传输有更好的撑持,能够应用于立即通信,海量高并发场景。[webSocket的原理以及详解][2]4.削减HTTP恳求次数,每次HTTP恳求城市有恳求头,返回响应城市有响应头,屡次恳求不只浪费时间并且会让收集传输良多无效的资本,利用前端模块化手艺 AMD CMD commonJS ES6等模块化计划将多个文件压缩打包成一个,当然也不克不及都放在一个文件中,因为如许传输起来可能会很慢,权衡取一个中间值5.设置装备摆设利用懒加载,关于一些用户不立即利用到的文件到特定的事务触发再恳求,也许用户只是想看到你首页上半屏的内容,但是你却恳求了整个页面的所有图片,若是用户量很大,那么那是一种极大的浪费6.办事器资本的摆设尽量利用同源战略7.在需要多个cookie去辨识用户的多种情况时,利用session替代,把数据贮存在办事器端或者办事器端的数据库中,如许只需要一个cookie传输,节省大量的无效传输,并且贮存的数据能够是永久无线大的。8.利用preload和dns-prefetch、prefetch,预恳求资本,那种恳求体例不会阻塞阅读器的解析,并且能将预恳求的资本缓存起来,并且能够设置crossorgin停止跨域资本的缓存,不会推延首屏的衬着时间,还会加快后面的加载时间,因为后面的自己需要的资本会间接从缓存中读取,而不会走收集恳求。9.利用defer和async属性的脚本,异步加载的体例,会先发恳求,然后JS引擎继续解析下面的内容。async的属性脚本会无需加载,谁先恳求回来就立即加载谁,当恳求回来的时候,无论是在DOM解析仍是脚本的解析,接下来都先会解析那个asncy脚本,它会阻塞DOM的解析。defer属性的会按HTML构造的按挨次加载,在DOMContentLoad前加载,但是加载之前所有的DOM解析必定已经完成了,defer属性的脚本不会阻塞DOM的解析,它也叫延迟脚本。因为现实中它不确定能否在DOMContentLoaded前加载,所以一般只放一个defer的脚本,参考挪动端京东网页。 [async和defer详解][3]详情参考[preload和prefetch详解][4]四、办事器返回响应,阅读器领受到响应数据不断没想到那里利用什么优化手段,今晚想到了,利用Nginx反向代办署理办事器,次要是对办事器端的优化。
Nginx是一款轻量级的Web 办事器/反向代办署理办事器及电子邮件(IMAP/POP3)代办署理办事器,并在一个BSD-like 协议下发行。其特点是占有内存少,并发才能强,事实上nginx的并发才能确其实同类型的网页办事器中表示较好,中国大陆利用nginx网站用户有:百度、京东、新浪、网易、腾讯、淘宝等。Nginx 是一个安拆十分的简单、设置装备摆设文件十分简洁(还可以撑持perl语法)、Bug十分少的办事。Nginx 启动出格容易,而且几乎能够做到7*24不连续运行,即便运行数个月也不需要从头启动。你还可以不连续办事的情况下停止软件版本的晋级。它能够:处理跨域,恳求过滤,设置装备摆设gzip,负载平衡,静态资本办事器 等...把办事窗口想像成我们的后端办事器,然后面末端的人则是无数个客户规矩在倡议恳求。负载平衡就是用来帮忙我们将浩瀚的客户端恳求合理的分配到各个办事器,以到达办事端资本的充实操纵和更少的恳求时间。Nginx若何实现负载平衡nginx若何实现负载平衡Upstream指定后端办事器地址列表upstreambalanceServer{server10.1.22.33:12345;server10.1.22.34:12345;server10.1.22.35:12345;}复造代码在server中拦截响应恳求,并将恳求转发到Upstream中设置装备摆设的办事器列表。server{server_namefe.server.com;listen80;location/api{proxy_passhttp://balanceServer;}}上面的设置装备摆设只是指定了nginx需要转发的办事端列表,并没有指定分配战略。默认情况下接纳的战略,将所有客户端恳求轮询分配给办事端。那种战略是能够一般工做的,但是若是此中某一台办事器压力太大,呈现延迟,会影响所有分配在那台办事器下的用户。最小毗连数战略 将恳求优先分配给压力较小的办事器,它能够平衡每个队列的长度,并制止向压力大的办事器添加更多的恳求。upstreambalanceServer{least_conn;//设置装备摆设压力较小的办事器server10.1.22.33:12345;server10.1.22.34:12345;server10.1.22.35:12345;}依赖于NGINX Plus,优先分配给响应时间最短的办事器。upstreambalanceServer{fair;//设置装备摆设响应时间最短的办事器server10.1.22.33:12345;server10.1.22.34:12345;server10.1.22.35:12345;}客户端ip绑定来自统一个ip的恳求永久只分配一台办事器,有效处理了动态网页存在的session共享问题。upstreambalanceServer{ip_hash;//设置装备摆设1个IP永久只分配一台办事器server10.1.22.33:12345;server10.1.22.34:12345;server10.1.22.35:12345;}设置装备摆设静态资本办事器location~* \.(png|gif|jpg|jpeg)$ {root/root/static/;autoindexon;access_logoff;expires10h; 设置过时时间为10小时}复造代码婚配以png|gif|jpg|jpeg为结尾的恳求,并将恳求转发到当地途径,root中指定的途径即nginx当地途径。同时也能够停止一些缓存的设置。Nginx处理跨域nginx处理跨域的原理例如:前端server的域名为:fe.server.com后端办事的域名为:dev.server.com如今我在fe.server.com对dev.server.com倡议恳求必然会呈现跨域。如今我们只需要启动一个nginx办事器,将server_name设置为fe.server.com,然后设置响应的location以拦截前端需要跨域的恳求,最初将恳求代办署理回dev.server.com。如下面的设置装备摆设:server{listen80;server_namefe.server.com;location/ {proxy_passdev.server.com;}}复造代码如许能够完美绕过阅读器的同源战略:fe.server.com拜候nginx的fe.server.com属于同源拜候,而nginx对办事端转发的恳求不会触发阅读器的同源战略。最重要的一点来了,如今的BATJ大都利用了那种设置装备摆设:设置装备摆设GZIPGZIP是规定的三种尺度HTTP压缩格局之一。目前绝大大都的网站都在利用GZIP传输 HTML、CSS、JavaScript 等资本文件。关于文本文件,GZip 的效果十分明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。启用 GZip所需的HTTP 更低版本默认值为HTTP/1.1启用gzip同时需要客户端和办事端的撑持,若是客户端撑持gzip的解析,那么只要办事端可以返回gzip的文件就能够启用gzip了,我们能够通过nginx的设置装备摆设来让办事端撑持gzip。下面的respone中content-encoding:gzip,指办事端开启了gzip的压缩体例。详细能够看那篇文字文章 [Nginx设置装备摆设GZIP][5]关于文本文件,GZip 的效果十分明显,开启后传输所需流量大约会降至 1/4 ~ 1/3。
Nginx功用十分强大,设置装备摆设也十分便利,有兴趣的能够多看看那篇文章 [Nginx解析][6]
五、阅读器解析数据,绘造衬着页面的过程先预解析(将需要发送恳求的标签的恳求发进来)从上到下解析html文件碰到HTML标签,挪用html解析器将其解析DOM树碰到css标识表记标帜,挪用css解析器将其解析CSSOM树link 阻塞 - 为领会决闪屏,所有处理闪屏的款式style 非阻塞,与闪屏的款式不相关的将DOM树和CSSOM树连系在一路,构成render树layout规划 render衬着碰到script标签,阻塞,挪用js解析器解析js代码,可能会修改DOM树,也可能会修改CSSOM树将DOM树和CSSOM树连系在一路,构成render树layout规划 render衬着(重排重绘)script标签的属性 asnyc defer性能优化战略:
需要阻塞的款式利用link引入,不需要的利用style标签(详细能否需要阻塞看营业场景)图片比力多的时候,必然要利用懒加载,图片是最需要优化的,webpack4中也要设置装备摆设图片压缩,能极大压缩图片大小,关于新版本阅读器能够利用webp格局图片[webP详解][7],图片优化对性能提拔更大。webpack4设置装备摆设 代码朋分,提取公共代码成零丁模块。便利缓存/*runtimeChunk 设置为 true, webpack 就会把 chunk 文件名全数存到一个零丁的 chunk 中,如许更新一个文件只会影响到它所在的 chunk 和 runtimeChunk,制止了引用那个 chunk 的文件也发作改动。*/runtimeChunk: true,splitChunks: {chunks:all// 默认 entry 的 chunk 不会被拆分, 设置装备摆设成 all, 就能够了}}//因为是单入口文件设置装备摆设,所以没有考虑多入口的情况,多入口是应该别离停止处置。关于需要事务驱动的webpack4设置装备摆设懒加载的,能够看那篇[webpack4优化教程][8],写得十分全面一些原生javaScript的DOM操做等优化会鄙人面总结六、TCP的四次挥手,断开毗连末结篇:性能只是 load 时间或者 DOMContentLoaded 时间的问题吗?RAILResponce 响应,研究表白,100ms内对用户的输入操做停止响应,凡是会被人类认为是立即响应。时间再长,操做与反响之间的毗连就会中断,人们就会觉得它的操做有延迟。例如:当用户点击一个按钮,若是100ms内给出响应,那么用户就会觉得响应很及时,不会察觉到丝毫延迟感。Animaton 现现在大大都设备的屏幕刷新频次是60Hz,也就是每秒钟屏幕刷新60次;因而网页动画的运行速度只要到达60FPS,我们就会觉得动画很流利。Idle RAIL规定,空闲周期内运行的使命不得超越50ms,当然不行RAIL规定,W3C性能工做组的Longtasks尺度也规定了超越50毫秒的使命属于长使命,那么50ms那个数字是怎么得来的呢?阅读器是单线程的,那意味着统一时间主线程只能处置一个使命,若是一个使命施行时间过长,阅读器则无法施行其他使命,用户会觉得到阅读器被卡死了,因为它的输入得不到任何响应。为了到达100ms内给出响应,将空闲周期施行的使命限造为50ms意味着,即便用户的输入行为发作在空闲使命刚起头施行,阅读器仍有剩余的50ms时间用来响应用户输入,而不会产生用户可察觉的延迟。Load若是不克不及在1秒钟内加载网页并让用户看到内容,用户的留意力就会分离。用户会觉得他要做的工作被打断,若是10秒钟还打不开网页,用户会感应绝望,会放弃他们想做的事,以后他们或许都不会再回来。若何使网页更丝滑?
利用requestAnimationFrame即使你能包管每一帧的总耗时都小于16ms,也无法包管必然不会呈现丢帧的情况,那取决于触发JS施行的体例。假设利用 setTimeout 或 setInterval 来触发JS施行并修改款式从而招致视觉变革;那么会有如许一种情况,因为setTimeout 或 setInterval没有法子包管回调函数什么时候施行,它可能在每一帧的中间施行,也可能在每一帧的最初施行。所以会招致即使我们能保障每一帧的总耗时小于16ms,但是施行的时机若是在每一帧的中间或最初,最初的成果仍然是没有法子每隔16ms让屏幕产生一次变革,也就是说,即使我们能包管每一帧总体时间小于16ms,但若是利用按时器触策动画,那么因为按时器的触发时机不确定,所以仍是会招致动画丢帧。如今整个Web只要一个API能够处理那个问题,那就是requestAnimationFrame,它能够包管回调函数不变的在每一帧最起头触发。制止FSL先施行JS,然后在JS中修改了款式从而招致款式计算,然后款式的改动触发了规划、绘造、合成。但JavaScript能够强迫阅读器将规划提早施行,那就叫 强迫同步规划FSL。 //读取offsetWidth的值会招致重绘const newWidth = container.offsetWidth;//设置width的值会招致重排,但是for轮回内部代码施行速度极快,当上面的查询操做招致的重绘还没有完成,下面的代码又会招致重排,并且那个重排会强迫完毕上面的重绘,间接重排,如许对性能影响十分大。所以我们一般会在轮回外部定义一个变量,那里面利用变量取代container.offsetWidth;boxes[i].style.width = newWidth + px;}利用transform属性去操做动画,那个属性是由合成器零丁处置的,所以利用那个属性能够制止规划与绘造。利用translateZ(0)开启图层,削减重绘重排。出格在挪动端,尽量利用transform取代absolute。创建图层的更佳体例是利用will-change,但某些不撑持那个属性的阅读器能够利用3D 变形(transform: translateZ(0))来强迫创建一个新层。有兴趣的能够看看那篇文字 [前端页面优化][9]款式的切换更好提早定义好class,通过class的切换批量修改款式,制止屡次重绘重排能够先切换display:none再修改款式屡次的append操做能够先插入到一个重生成的元素中,再一次性插入到页面中。代码复用,函数柯里化,封拆高阶函数,将屡次复用代码封拆成通俗函数(俗称办法),React中封拆成高阶组件,ES6中能够利用继承,TypeScript中接口继承,类继承,接口合并,类合并。在把数据贮存在localstorage和sessionstorage中时,能够再本身定义一个模块,把那些数据在内存中存储一份,如许只要能够间接从内存中读书,速度更快,性能更好。能不定义全局变量就不定义全局变量,更好利用部分变量取代全局变量,查找的速度要高一倍。强烈保举阅读:[阮一峰ES6教程][10]以及[什么是TypeScript以及入门][11]下面参加React的性能优化计划:
在生命周期函数shouldComponentUpdate中对this.state和prev state停止浅比力,利用for-in轮回遍历两者, 只要得到他们每一项值,只要有一个纷歧样就返回true,更新组件。定义组件时不适用React.component , 利用PureComponent取代,如许React机造会主动在shouldComponentUpdate中停止浅比力,决定能否更新。上面两条优化计划只停止浅比力,只比照间接属性的值,当然你还能够在上面参加this.props和prevprops的遍历比力,因为shouldComponentUpdate的生命周期函数自带那两个参数。若是props 和 state的值比力复杂,那么能够利用下面那种体例去停止深比力。处理:包管每次都是新的值利用 immutable-js 库,那个库包管生成的值都是独一的var map1 = Immutable.Map({ a: 1, b: 2, c: 3 });var map2 = map1.set(b, 50);map1.get(b); // 2map2.get(b); // 50总结:利用以上体例,能够削减没必要要的反复衬着。React的JSX语法要求必需包裹一层根标签,为了削减没必要要的DOM层级,我们利用Fragment标签取代,如许衬着时候不会衬着多余的DOM节点,让DIFF算法更快遍历。利用Redux办理全局多个组件复用的形态。React构建的是SPA应用,对SEO不敷友好,能够选择部门SSR手艺停止SEO优化。对Ant-design那类的UI组件库,停止按需加载设置装备摆设,从import Button from antd 的引入体例,酿成import {Button} from antd的体例引入。(类似Babel7中的runtime和polifill的区别).在React中一些数据的需要更新,但是却不急着利用,或者说每次更新的那个数据不需要更新组件从头衬着的,能够按期成类的实例上的属性,如许能削减屡次的反复无意义的DIFF和衬着。Redux的利用要看情况利用,若是只是一个部分形态(仅仅是一个组件或者父子组件利用就不要利用Redux)。关于一个父子、父子孙多层组件需要用到的state数据,也能够利用context上下文去传递. [Context上下文详解][12],但是复杂项目标多个差别条理组件利用到的state,必需上Redux。所有的原生监听事务,按时器等,必需在componentWillUnmount中肃清,不然大型项目肯定会发作内存泄露,极度影响性能!!!React Hooks?React Hooks是什么? * 用来定义有形态和生命周期函数的纯函数组件(在过去纯函数组件是没有形态和生命周期函数的~) Hooks是React v16.7.0-alpha中参加的新特征,并向后兼容。 * 什么是钩子(Hook)素质就是函数,能让你利用React组件的形态和生命周期函数 * 让代码愈加可复用,不消在定义冗杂的HOC(高阶组件)和class组件。 * 利用:```useState(initValue)- const [ state, setState ] = React.useState(initValue);- 用来定义形态数据和操做形态数据的办法useEffect(function)- useEffect(() => { do something })- 副感化函数(发恳求获取数据、订阅事务、修改DOM等)- 素质上就是一个生命周期函数,相当于componentDidMount 、 componentDidUpdate 和 componentWillUnmountuseContext(Context)- context指的是React.createContext返回值------ 以下Hooks只利用于特殊场景,需要时在用 -----useReducer- const [state, dispatch] = useReducer(reducer, initialState);- 一个 useState 替代计划,相当于reduxuseCallback- useCallback(fn, inputs)- 相当于 shouldComponentUpdate,只要inputs的值发作变革才会挪用fnuseMemo(create, inputs)- 相当于useCallback```更多详见官方文档:[HOOKS文档][13] 留意只能在顶层挪用钩子。不要在轮回,控造流和嵌套的函数中挪用钩子。只能从React的函数式组件中挪用钩子。不要在常规的JavaScript函数中挪用钩子。-(此外,你也能够在你的自定义钩子中挪用钩子。)原生JavaScript实现懒加载:
懒加载,从字面意思就能够简单的理解为不到用时就不去加载,关于页面中的元素,我们能够如许理解:只要当滚动页面内容使得本元素进入到阅读器视窗时(或者略微提早,需给定提早量),我们才起头加载图片;不给img元素的src属性赋值时,不会发出恳求【不克不及使src="",如许即便只给src赋了空值也会发出恳求】,而一旦给src属性付与资本地址值,那么该恳求发出,使得图片显示;所以那里我们操纵那一点控造img元素的加载时机。在起头的时候将资本url放置在自定义属性data-src傍边,然后在需要加载的时候获取该属性并赋值给元素的src属性从上面的阐发能够看出来,次要要处理的问题就是怎么检测到元素能否在视窗傍边,那里我们要借助于dom操做api傍边的el.getBoundingClientRect()来获取其位置,并判断能否在视窗内,那里简单描述。Element.getBoundingClientRect()办法返回元素的大小及其相关于视口的位置。返回值是一个 DOMRect对象,那个对象是由该元素的 getClientRects()办法返回的一组矩形的集合, 即:是与该元素相关的CSS 边框集合 。DOMRect 对象包罗了一组用于描述边框的只读属性——left、top、right和bottom,单元为像素。除了 width 和 height 外的属性都是相关于视口的左上角位置而言的。因而我们能够利用以下逻辑判断元素能否进入视窗:functionisInSight(el){vareldom =typeofel ==object?el:document.querySelector(el);varbound = eldom.getBoundingClientRect();// 那里的bound包罗了el间隔视窗的间隔;// bound.left是元素间隔窗口左侧的间隔值;// bound.top是袁术间隔窗口顶端的间隔值;// 以以上两个数值判断元素能否进入视窗;varclientHeigt =window.innerHeight;varclientWidth =window.innerWidth;// return (bound.top>=0&&bound.left>=0)&&(bound.top<=window.innerHeight+20)&&(bound.left<=window.innerWidth+20);return!((bound.top>clientHeigt)||(bound.bottom<0)||(bound.left>clientWidth)||(bound.right<0))}此中window.innerHeight和window.innerWidth别离为视窗的高度和宽度,之所以加上20是为了让懒加载稍稍提早,利用户体验更好;添加scroll事务监听:那么什么时候去检测元素能否在视窗内,并判断能否加载呢,那里因为页面的滚动会使得元素相关于视窗的位置发作变革,也就是说滚动会改动isInSight的成果,所以那里我们在window上添加scroll事务监听:// 当加载完成,检测并加载可视范畴内的图片window.onload= checkAllImgs;// 添加滚动监听,即可视范畴变革时检测当前范畴内的图片能否能够加载了window.addEventListener("scroll",function(){checkAllImgs();})// 检测所有图片,并给视窗中的图片的src属性赋值,即起头加载;functioncheckAllImgs(){varimgs =document.querySelectorAll("img");Array.prototype.forEach.call(imgs,function(el){if(isInSight(el)){loadImg(el);}})}// 起头加载指定el的资本functionloadImg(el){vareldom =typeofel ==object?el:document.querySelector(el);if(!eldom.src){// 懒加载img定义如:varsource = eldom.getAttribute("data-src");varindex = eldom.getAttribute("data-index");eldom.src = source;console.log("第"+index+"张图片进入视窗,起头加载。。。。")}}如许就实现了图片的懒加载的简单实现,当然还能够对scroll停止优化等操做。如今最新版本的谷歌阅读器也要撑持 标签的内部 loading属性了,相信将来开发会越来越便利。以上都是按照本人的常识点总结得出,后期还会有更多性能优化计划等出来,路过点个赞保藏保藏~,欢送提出问题弥补~
保举前端性能优化相关文章前端性能优化之雅虎35条军规
手把手教你深切Vue3.0(Vue-cli4)项目打包性能优化理论
2020前端性能优化清单(一)
2020前端性能优化清单(二)
2020前端性能优化清单(三)
2020前端性能优化清单(四)
2020前端性能优化清单(五)
2020前端性能优化清单(六)
Chrome 运行时性能瓶颈阐发
前端性能优化之操纵 Chrome Dev Tools 停止页面性能阐发
Vue项目摆设及性能优化指点篇「理论」
React 函数式组件性能优化常识点指南汇总
深切前端tree优化衬着速度从14.65s到0.49s【实战】
手把手教你若何处置Web站点图片优化
Vue高性能衬着大数据Tree组件「理论」
手把手教你通过图片优化,将网站大小削减一半以上
做者:Peter 谭金杰
转发链接:https://mp.weixin.电话.com/s/xjtUK9yZVIK9-OL_WxDH7Q

评论列表