美文网首页
前端知识点总结

前端知识点总结

作者: fangtang0101 | 来源:发表于2019-10-07 22:15 被阅读0次

    title: 前端知识点总结
    author: 作者
    top: false
    toc: false
    date: 2019-08-19 11:13:48
    tags:

    1. var let const 的比较

      • let 是 块级作用域 ,其申明的变量只是在 其块级作用域中 有作用

        块级作用域:可以直接理解为 只在 {} 内生效

        **let 申明的 变量,用 window访问不了,var 申明的,window能访问 **

        {
          let a = 10;
          var b = 1;
        }
        a // ReferenceError: a is not defined.
        b // 1
        

        常见题目:

        var a = [];
        for (var i = 0; i < 10; i++) {
          a[i] = function () {
            console.log(i);
          };
        }
        a[6](); // 10
        
        var a = [];
        for (let i = 0; i < 10; i++) {
          a[i] = function () {
            console.log(i);
          };
        }
        a[6](); // 6
        
    • 变量提升 的区别

      结论:let const 不存在变量提升,var 有变量提升

      1. 直观的讲:变量提升就是 可以先 使用变量,后申明变量(注意这里只是说的表面上看,实际上 浏览器解析的时候,会将 所有的变量 先全部 拿出来 申明,然后在执行其他使用代码 这就是 变量提升)

      2. let const 不支持 变量提升 ,也就是说,必须 先申明 后使用

        // var 的情况
        console.log(foo); // 输出undefined
        var foo = 2;
        
        // let 的情况
        console.log(bar); // 报错ReferenceError
        let bar = 2;
        
    • 暂时性死区

      结论:只要在 块级作用域内 申明的 变量(let const),区域内部的 变量与外界完全隔离

      eg:

      var tmp = 123;
      if (true) {
        tmp = 'abc'; // ReferenceError
        let tmp;
      }
      
    • 变量是否可以重复定义

      结论:let的块级作用域内只能定义同样的变量一次,var 是可以定义多次的,const 是常量不可变(const也是块级作用域的)

      eg1:

      // 报错
      function func() {
        let a = 10;
        var a = 1;
      }
      
      // 报错
      function func() {
        let a = 10;
        let a = 1;
      }
      

      eg2:

      function func(arg) {
        let arg;
      }
      func() // 报错
      
      function func(arg) {
        {
          let arg;
        }
      }
      func() // 不报错
      
    1. 块级作用域有什么用

      1. 避免内部变量 覆盖 替换了 外部作用域的 变量,隔离作用

        匿名函数自执行,相当于起到同样的作用,现在有 {} 块级作用域就不用那么麻烦,很多框架就是这样的

      2. 避免 for循环时 计数的变量 泄露成全局变量,带来困恼

        eg

        var s = 'hello';
        for (var i = 0; i < s.length; i++) {
          console.log(s[i]);
        }
        console.log(i); // 5
        
    1. apply call bind 作用,如何实现

      总结:主要都是为了改变 函数执行 的 this 指向问题

      1. 为什么需要出现这几个 函数

        js中函数 在执行过程中 this 的指向是根据 执行时候 当时的上下文环境 决定的,所有只有在执行的时候,才会知道this 到底指向的是 什么对象

        apply call bind 最终都是为了改变 函数 执行时,this的指向问题

      2. 它们之间的区别

        • applycall g功能用法相似,区别在于

          fn.apply(obj,args)           // args 为数组 会将 args 全部传入 fn函数
          fn.call(obj,arg1,arg2,....)   // call 需要将参数 手动一个一个 传进去
          
          fn.call(obj,...args)        // 当然在 ES6 中 用 ... 的语法 同样可以解决这样的问题,功能就一样了
          
      - `apply call` 与 `bind` 的区别
    
        **总结:1.返回结果不一样 2.`bind` 可以实现颗粒化**
    
        1. `apply 与 call` 直接就是调用了 函数(fn 函数立刻执行了)
        2. `bind` 绑定函数,返回结果是一个新的函数(新函数是原函数的一个包装,将this 指向绑定的对象)
        3. `bind` 可以实现颗粒化,绑定时可传入 多个参数,当原函数 调用的时候,会将两次的 参数合并 传入原函数
    
      - 使用场景
    
        1. 用来实现 js 中的 **继承**
    
        2. 对象内置方法的跨对象调用(可以实现将一个对象的内部方法 给 任何 其他不具有此方法的对象调用)
    
           ```
           Math.max.apply(null,[1,2,4,7])
           Math.max.apply(Math,[1,2,4,7])
           ```
    
    1. 未解决的问题:

      apply call bind 一起使用的时候要,如何解释...

    2. 防抖和节流

      总结:防抖和节流不是一回事

      1. 防抖和节流的概念的区别

        • 防抖:在一个固定时间间隔内(比如10s),重复执行同一个函数,永远执行最后一个,并以此为时间为基础,未来10s内依旧执行最后的一次 (比喻:等公交)

        • 节流:在一个固定时间间隔内(比如10s),重复执行同一个函数,永远执行一次(第一次),忽略其他调用 (比喻:水龙头滴水)

    1. 防抖和节流如何实现

      • 利用 时间戳,每次执行的时候 对比 上次的时间戳 ,来处理是否要 执行当前的函数执行
      • 利用setTimeOut(时间延时函数), 每次在 执行函数之前,检查 上一次的 延时函数是否还存在(也就是判断上次函数是否已经执行),然后根据自己的逻辑 处理 (可以将上次的 延时函数 重置新的,也可以不执行)
    2. 场景:

      • 游戏领域
      • input 输入的同时 实时搜索结果,多次DOM的渲染,当data 在很短的时候内 ,修改的时候,会不断的渲染 DOM
      • 监听 scrollow 等事件执行函数
    3. 自己实现一个(一定要自己不参照情况下,手打代码)

        // 节流
        // 使用时间戳,比较当前时间 与 上次时间 ,如果 时间间隔 > 间隔时间,执行函数,若  < 时间间隔,什么也不做
        function throttle(fn,wait=80) {
            let timePre = 0;
            return function myFunc(){
                let now = + new Date();
                if(now - timePre > wait){
                    timePre = + new Date();
                    // 1.一定要传入 this,看下面 obj的调用(才能展示出来)
                    fn.apply(this,arguments)
                    // fn(arguments)
                }
            }
        }
        function test1(x,y){
            console.log('x y ....',this);
        }
        let fn1 = throttle(test1);
        fn1('cccc','yyyy');  // 此时内部 this 是window
          let obj ={x:'0000',y:'111111',func:fn1};
          obj.func('1','2'); // 此时内部 this 是 obj
      
    4. 原型、原型链和继承

      问题:还是没有完全懂

      1. 原型 与 原型链

        • 所有对象(引用类型)都有一个 __proto__ 属性 ,值即为 原型(一个普通对象而已)

        • 所有函数都有一个prototype属性,值即为 原型(一个普通对象而已)

        • **对象(引用类型)的 __proto__ === 其构造函数的 prototype **

        • 原型链的顶端是 Object.prototype.__proto__ === null,也就是 null

        **原型链:**当一个对象(引用类型)使用点语法的时候,首先从自己的内部寻找此属性,若没有从其原型(一个普通对象)寻找改属性,若没有,再次从该原型的原型中去寻找,直到顶层 null,若都没有,返回 `undefined`
    
    1. 继承

      js的继承实际上就是改变 构造函数的 原型(将原型指向 父类的一个实例)

      • js中的继承说明:

        1. js是一门面向对象的语言,但是 js 中没有严格意义的 类 与 实例的说法。而是通过 一个原型 的对象 达到 “继承”的目的。

        所以说 js 是比较另类的语言

        1. ES6 中 开始有了 class let const 等概念
        2. 趋势:更加接近 面向对象语言的 语法与概念 ,也更加严谨
      • 手写一个 js 继承 TODO

    1. 模块化

      根据历史知识来理解与记忆

      **对全局变量的影响 **

      命名的冲突与相互替换

      依赖的管理

      同步与异步 是否预下载,是否预执行

      1. 有哪些模块化,发展流程是什么(演变历史)

        • 起初 js 语言很弱,逻辑主要在 后端,所以没有什么模块的概念 都是 直接 写 function

          污染全局的变量、变量容易被外界改变、管理不方便

        • 匿名函数 + 闭包 典型的是 jQuery 等其他插件

        • node.js的兴起,前端可以没有 模块,但是 后端必须要有 模块 commonJs (nodejs社区创建,开始叫 serverjs)

          var abc = require('xxx') // 导入
          // 导出  是一个对象
          module.export = {
          }
          

          特点:导入是同步的,因为后端加载文件是在本地服务器,所以无所谓

          问题:推广到前端的时候,需要异步,因为前端是通过 网络请求来下来模块的

        • node.js 社区 出现了 分歧,有三个分支

          1. 第一个分支:在原有的commonJs的基础上 做兼容,新增 Transport规范 **Browserify compontes6-module-transpiler等 **

            通过工具 将现有node模块 转成 浏览器能用的模块

          2. 第二个分支:制定了AMD 标准(也就是异步)requestJs

            特点:预先下载、预先执行

          3. 第三个分支:module2.0,算是折中吧,融合上面两个的特点 FlyScript(推广不好,后期自己全部删除了)

            特点:预先下载,懒执行

        • 基于第三个方案的基础上 又融合了很多其他的方案 CMD 国内大牛 淘宝的 玉伯开发 seaJs

          特点:预先下载,懒执行。其他 语法上面的区别

          注意:后期发展 AMD CMD 都有相互兼容对方的优点

        • ES6 算是原生支持 模块化,但是浏览器还没有支持,期待中,目前需要 通过 babel-loader编译

          1. 语法

            // 导出
            export {}
            //导入
            import {x,y} from xx   
            import * as model form xx
            
        2. 特点:
    
           - `Commonjs` 模块运行时加载,`ES6`模块是编译时就输出接口
           - `Commonjs`输出的为 **值的浅拷贝** ;`ES6`输出的是 **值引用**
    
    1. 各自的 区别是什么

    2. js 中 异步 (比较大的模块)

      1. js 中异步的概念简述 (js 单线程

        • js 是单线程的,js 代码运行是单线程的
        • 当有耗时操作就会阻碍程序,使得浏览器 假死,所以 异步就出现了,弥补js 没有多线程的缺点
        • 异步基于 事件循环 eventLoop,也就是,只有当 不是异步的代码执行完了,才会 执行 异步代码
    1. 有哪些异步,简述各自的 特点(callback Promise async wait

      • callback 作为 js 实现异步的最基础的手段,使用简单,将函数作为参数传递给另外一个函数,在之后的某个条件下才会触发传入的函数,实现异步

        缺点:会出现回调地狱

      • Promise 为了解决 cb 的 回调地狱问题,链式操作,结构明了

      • async waitPromise 的基础上更加简单,将异步代码 写成 同步的逻辑,更加符合 人的阅读与理解

        基于genertor 与 Promise实现

      • xx

    2. 定时器函数(setTimeut setinterval requestAnimationFrame

      属于原生Js的内置函数,直接实现异步

    3. callback

    4. Promise (手写一个Primise)

    5. Generator

    6. async awit

    1. Event Loop (由 js 异步 延展到 js的 Event Loop)

      1. node 与 浏览器中的 Event Loop 不是一样的,差异在哪里
    2. 浏览器——基础知识

      1. BOM 操作

      2. DOM 操作

        增删改查

      3. 事件(事件绑定与代理

      4. Ajax

      5. 跨域问题

        • 什么是跨域

          同源策略:只允许 同一个 协议 域名 端口相同的网页 请求后端数据(请求已经发出,只是reponse被浏览器拦截)

          为什么:确保安全 (预防CSRF攻击,想象钓鱼网站)

        • 如何解决

          1. jsonP

            利用 <script>标签没有跨域的限制

            缺点:只允许get 请求

            <script>
                function jsonp(data) {
                 console.log(data)
             }
            </script>    
            
        2. CORS
    
           **主要后端设置 http请求的配置即可   允许 指定域名 访问接口**
    
           `Access-Controll-Allow-origin`
    
        3. postMessaege
    
           **用于iframe 嵌套的页面之间相互通信**
    
    1. 存储问题

      • cookie

        1. 生命周期:一般由 服务端 设置 过期时间,当前 也可以前端js 也可以清除

        2. 存储大小:4k

        3. 说明:主要用于 客户端与服务端通信的,识别用户身份,不建议作为存储使用

        4. 安全参数设置:可以设置不同的参数控制cookie

          • value:尽量用密文 并加密
          • http-only:只允许服务端修改,不能通过js修改,安全
          • secure: 只能在https 协议的请求中 携带
          • same-site:浏览器不能在跨域中携带 cookie
      - localStrong
    
        1. 生命周期:一直在,除非手动删除
        2. 存储大小:4M
    
      - sessionStrong
    
        1. 生命周期:页面关闭 就 消除
        2. 存储大小:4M
    
      - indexDB
    
        1. 生命周期:一直在,除非手动删除
        2. 存储大小:很大很大
        3. 说明:就是客户端的一个小型数据库
    
      - service worker
    
        service worker 是运行在 浏览器后台的 **独立线程** **必须是https请求**
    
        主要是为了 **缓存 网络请求**
    
    1. 缓存问题

      • 缓存位置的优先级(从前到后获取数据)

        1. service worker (上面已经讲过)

        2. Memory cache (内存缓存)

          速度快,持续时间短,随时就没有了

        3. Disk cache

          速度慢,持续时间长,随着进程而释放

        4. Push cache

          http2 的内容,了解即可 缓存时间短,会话结束就没有了

        5. 网络请求

          没有缓存,就自能网络请求

      • 缓存策略 注意:都是通过 HTTP header来设置的

        1. 强缓存

          • Expires:Wed, 22 Oct 2018 08:41:00 GMT(xx之后过期)
          • cache-control:max-age=30(30秒之后过期)
        2. 协议缓存

          当缓存过期,便发送数据的最后获取时间给服务端,服务端判断之后告诉服务器,数据没有变,304,便再次使用之前的缓存

          • Last-modified
          • Etag
        3. 场景使用

          • 频繁变化的文件

            cache-control:no-cache 配合 Last-modified每次校验是否需要跟新

          • html 代码

            cache-control:max-age100000(html代码一般很少修改,只有当html 文件改了,即hash值变了再发起请求,否则很长时间都是用的缓存)

    2. 浏览器——安全问题

      • XSS

        • 说明:注入可执行代码 并执行

          1. 持久性:代码被发送到 后端,后端执行了代码,不小心被 混入执行的 Mysql 的代码,可能影响到数据库
          2. 非持久性:代码发送到网页中并执行,影响程序,评论中写入js函数代码提交
        • 防御:

          1. 转义,将一些敏感的 字符等转义

          2. CSP:也就是白名单通

            • 过http header 设置 Content-Security-Policy

              //只允许加载本站资源
              Content-Security-Policy: default-src ‘self’
              //只允许加载 HTTPS 协议图片
              Content-Security-Policy: img-src https://*
              //允许加载任何来源框架
              Content-Security-Policy: child-src 'none'
              
           - meta 标签设置`<meta http-equiv="Content-Security-Policy">`
    
           
    
    - CSRF
    
      - 说明:
    
        **跨站请求攻击**也就是钓鱼网站
    
      - 防御:
    
        1. 设置第三方网站 得不到 cookie  `SameSite`
    
        2. 封掉第三方网络请求
    
        3. 每个请求 token 验证
    
        4. referce  表示请求是从哪个地方发过来的
    
            
    
    - 点击劫持
    
      - 说明
    
        **把目标页面嵌入firame,隐藏部分内容,引导点击**
    
      - 防御 **http header 设置参数限制  X-Frame-options**
    
        1. 值为 DENY:页面不允许 以  iframe 的方式展示
        2. 值为 SAMEORIGIN: 相同域名下 可以展示 iframe
        3. 值为ALLOW-FROM:允许指定 来源的网页 可以展示 iframe
    
    - 中间人劫持
    
      - 说明:
    
        **一般用公共的Wifi,有可能被劫持**
    
      - 防御:尽量用 HTTPS
    
    1. 浏览器——渲染机制

      a. 渲染由浏览器的渲染引擎完成,浏览器还有JS引擎,属于两个线程

      b.火狐是 Gekco 引擎 chorm sarfor 是 Webkit 引擎


      1. DOM 渲染

        • 浏览器将 html 代码 (其实就是一堆字符串)词法分析 打标记
        • 根据标记 转换成 node节点
        • 根据 node 节点 组成 DOM 树
      2. CSSOM 渲染

        • 浏览器根据 Css 代码 转换成 CSSOM 树
      3. 渲染树

        • 将 DOM树 与 CSSOM 树 结合起来,生成 渲染树
        • 根据渲染树 调用 GPU 进行 布局(layout) 显示
      4. 影响渲染的因素与解决办法

        • script 的位置,若在 body 之前,那么当 解析到 JS 代码,渲染便会 暂停 下载 JS 代码
        • 如何避免:
          1. 将 JS 代码 放在 body 之后加载
          2. 将 JS 代码延时 或者 异步加载 defer(下载完立即执行) 和 async(下载完不立即执行)
      5. 重绘与回流

        • 重绘:当改变 DOM 的样式 布局,DOM 会重绘

        • 回流:当改变 DOM 的样式,DOM 会回流(改变了布局)

        • 显然:

          1. 发生回流一定会触发重绘,发生重绘不一定触发回流
          2. 回流比重绘 消耗的资源 更大
    6. 如何尽量避免 回流
    
       - css 层级 **扁平化**(**渲染会从标签由内往外寻找,嵌套层级越少越好**)
       - 尽量 合并多次操作 为 一次 操作 DOM 结构(**减少操作DOM的次数**)
       - **少用table**
       - **达到同样的效果,尽量减少影响布局的属性**,( visibity 代替 diapaly:none)
    
    1. 性能优化(浏览器+js )

      • 图片加载优化(加载优化

        1. 格式: 大图尽量用 jpeg,小图尽量用 png(base64 压缩)
        2. 小图尽量用 css svg代替
        3. 采用雪碧图
        4. 用CDN ,按需请求所需尺寸的图片(移动端一般用小图,大图浪费)
      • DNS 预解析 dns-prefetch

        <link rel='dns-prefetch' href='xx.com'>
        
      • 预加载

        <link rel='preload' href='xx.com'>
        
      • 预渲染

        <link rel='prerender' href='xx.com'>
        
      • 懒加载

      • 懒执行

        图片懒加载(先用占位图片)

      • CDN

        静态资源尽量使用CDN

      • 节流

        水龙头,比方说3分钟一次,一段时间内,只允许一次事件的发生

        实现:

        1. 使用 setTimeout,若时间未到,则清除之前的 setTimeout
        2. 使用时间戳,时间戳相减,大于间隔时间才执行事件
      • 防抖

        从上次事件发生开始算,一段时间时间内,只允许一次事件发生(注意区别)

    2. webpack 性能优化

      尽量使用高版本(版本高,框架内部做了很多优化),打包 node = production ,内部也有很多优化

      • 缩短打包时间
        1. loader的优化 减少打包的范围
        2. 并行打包 happyPack(js是单线程,将内部任务转换成 并行)
        3. DllPlugin 将特定的库提前单独打成包,此包一般情况行下,很长时间不会变(eg,将Vue 提前打成包)
      • 缩小打包后的体积
        1. 压缩代码,也算节省时间的方式
        2. 按需加载,将不同路由 打包成 几个包,需要用到那个就加载哪个(对首页加载的速度有好处
        3. scope Hosting 分析依赖关系,尽量合并模块到一个函数中
        4. Tree Sharing 分析 并去除 没有用到的代码
    3. HTTP 协议

    4. 设计模式

    5. 框架 Vue

      • 生命周期

        • beforeCreated 无法获取到 props 与 data 的数据
        • created 能够获取到 props 与 data的数据
        • beforeMounted 创建VDOM(虚拟DOM)
        • mounted 将VDOM 转换成 真实的DOM
        • beforeUpdate 组件中数据更新前调用
        • update 组件中数据更新后调用
        • beforeDestroyed 组件销毁前 销毁事件 定时器等操作(否则有内存泄漏问题)
        • destroyed 组件全部销毁
      • 组件之间通信

        • propsemit 父子组件通信
        • v-model 父子间通信
        • $parent$children 父子通信,多层通信
        • provideinject 父子通信 ,多层通信
        • eventBus Vuex 任何通信
      • Vue 底层实现的原理

        definedProperty 对每个属性 进行 set get方法监听,对应的修改DOM结构

        缺点:Vue3.0 为何改成 proxy

        1. 无法监听数组,Vue内部自己实现了 Array的几种方法
    - Vue 底层是如何编译的
    
      - 响应式:根据 data 的变化 改变页面
    
      - 模板引擎:模板如何被解析(**render函数 vdom**)
    
        **模板就是字符串->js函数(render函数)->返回vnode(虚拟节点,也就是Vdom)**
    
      - 渲染:
    
        **根据vnode 渲染成 真实的节点,用的是专门的库**
    
    - 其余知识点
    
      1. `computed` 和 `watch` 的异同点
    
         `computed` **:有缓存,只要依赖的属性如果不变,不会触发**
    
         `watch`:**只要watch发生变化就执行**
    
      2. `keep-live` 的使用
    
         **当组件在页面消失,此组件仍然缓存着,会有两个钩子方法,隐藏于出现**
    
      3. `data`的值为何 返回 是个函数
    
         **保证每次创建的组件 数据都是 初始化的值,否则同一个组件创建出来的 公用一个data**
    
      4. `v-if` 与 `v-show` 的区别
    
         `v-if`:**false,页面不渲染,DOM不存在**
    
         `v-show`:**false,页面DOM结构还在,只是隐了而已**
    
      5. `nextTick` 是什么原理
    
         **Vue所有的数据变化,最终相应到页面的变化,但内部有一个 事件循环的,在一定事件内才会执行一次,所以当数据发生变化,瞬间又要获取对应的变化DOM是报错的,nextTick事件是所有数据变化对应的DOM 渲染都完成的时候的一个回调函数**
    
      6. xxx
    
          
    
    - Vuex
    
      1. 概念:核心就是**store(仓库)**,存放应用中的**很多状态(state)**,跟全局变量不一样
         - **响应式**,当store 中数据发生变化,那么会相应的很多组件发生变化
         - **commit(mutation)才能修改数据**,这样就能跟踪数据的变化
      2. state(仓库),可以想象成 一个**全局的 data**,注意是**响应式**的,可放在 组件的 computed 里面
      3. getter,**想象成对应的computed(计算属性,针对 state中的属性)**
      4. mutation: 改变state 状态的 **唯一方法**,必须是**同步**
      5. action:类似于 mutation的作用
         - 不是直接改变数据,而是 调用 mutation 改变数据
         - action 可以是异步的
      6. module:**模块化**,当state 数据太多时,可以做分割,每个模块 就是有单独的 store state getter mutation action 等
    
    1. 框架 React

      1. React 的生命周期

        • componentWillMount 组件将要渲染(第一次)
        • componentDidMount 组件已经渲染(首次)
        • componentWillReceiveProps 接受到父类 props,在调用另一个 渲染器之前
        • shouldComponentUpdate 返回 true false,组件是否要渲染
        • componentWillUpdate 组件即将跟新 ,Dom 渲染之前调用
        • componentDidupdate 组件已经跟新 ,Dom 渲染之后调用
        • componentWillUnmount DOM销毁后,用于清除内存 等
      2. shouldComponentUpdate 有什么用,有什么重要的 场景

        根据自己的实际需求 去 决定 是否需要 重新 渲染(因为重新渲染 是比较耗费资源的)

      3. setState 的方法,是异步还是同步,如何在异步之后 做些其他的操作

        异步操作this.setState({},()=>{页面渲染之后调用})

      4. this.state 和 props 有何异同点

        • props 是从父组件 传递下来的,不可以修改
        • 而state 是组件自身的,可以修改
      5. Virtual DOM 了解吗?在React 中是如何工作的,流程

        • 虚拟DOM,不是真实的DOM。而是一个 js 对象,树状的数据结构,用于描述 真实的DOM节点
        • 如何工作:如果state有改变,那么 先改 虚拟DOM,等到一定的时间,统一 根据 虚拟DOM 与 真实DOM 的对比,只更新 不一样的地方
      6. React 中箭头函数 怎么用

        允许函数绑定上下文,改变函数的this 的对象

        this.onChange.bind(this)

      7. HOC 高阶组件了解吗?

        类似于高阶函数

        将组建作为参数 传入函数,返回一个新的组件,在内部可以对 原始的组件 有很多的 特殊处理

      8. refs 是什么

        返回一个组件的对象,父组件可以拿到 子组件的实例,实现数据交互

      9. keys 有什么作用

        给组件标号,再次渲染的时候,可根据标号,从新排序,而不需要重新渲染

      10. redux 与 VueX

        https://segmentfault.com/a/1190000007753542

    2. xxx

    相关文章

      网友评论

          本文标题:前端知识点总结

          本文链接:https://www.haomeiwen.com/subject/tckvpctx.html