前端笔试总结

  1. 简单讲讲http2的多路复用:

    在 HTTP/1 中,每次请求都会建立一次HTTP连接,也就是我们常说的3次握手4次挥手,这个过程在一次请求过程中占用了相当长的时间,即使开启了 Keep-Alive ,解决了多次连接的问题,但是依然有两个效率上的问题:

    • 第一个:串行的文件传输。当请求a文件时,b文件只能等待,等待a连接到服务器、服务器处理文件、服务器返回文件,这三个步骤。我们假设这三步用时都是1秒,那么a文件用时为3秒,b文件传输完成用时为6秒,依此类推。(注:此项计算有一个前提条件,就是浏览器和服务器是单通道传输)
    • 第二个:连接数过多。我们假设Apache设置了最大并发数为300,因为浏览器限制,浏览器发起的最大请求数为6,也就是服务器能承载的最高并发为50,当第51个人访问时,就需要等待前面某个请求处理完成。

    HTTP/2的多路复用就是为了解决上述的两个性能问题。
    在 HTTP/2 中,有两个非常重要的概念,分别是帧(frame)和流(stream)。
    帧代表着最小的数据单位,每个帧会标识出该帧属于哪个流,流也就是多个帧组成的数据流。
    多路复用,就是在一个 TCP 连接中可以存在多条流。换句话说,也就是可以发送多个请求,对端可以通过帧中的标识知道属于哪个请求。通过这个技术,可以避免 HTTP 旧版本中的队头阻塞问题,极大的提高传输性能。

  2. Symbol(ES6中新的基础数据类型)
    let s2 = Symbol('another symbol')
    • 你也可以在调用Symbol()函数时传入一个可选的字符串参数,相当于给你创建的Symbol实例一个描述信息
    • 由于Symbol是一种基础数据类型,所以当我们使用typeof去检查它的类型的时候,它会返回一个属于自己的类型symbol,而不是什么stringobject之类的
    • 我们需要重点记住的一点是:每个Symbol实例都是唯一的。因此,当你比较两个Symbol实例的时候,将会返回false
    • 应用场景:

      ①.使用Symbol来作为对象属性名

    const PROP_NAME = Symbol()
    const PROP_AGE = Symbol()
    
    let obj = {
      [PROP_NAME]: "一斤代码"
    }
    obj[PROP_AGE] = 18
    
    obj[PROP_NAME] // '一斤代码'
    obj[PROP_AGE] // 18 
    随之而来的是另一个非常值得注意的问题:就是当使用了Symbol作为对象的属性key后,在对该对象进行key的枚举时,会有什么不同?在实际应用中,我们经常会需要使用Object.keys()或者for...in来枚举对象的属性名,那在这方面,Symbol类型的key表现的会有什么不同之处呢?
    let obj = {
       [Symbol('name')]: '一斤代码',
       age: 18,
       title: 'Engineer'
    }
    
    Object.keys(obj)   // ['age', 'title']
    
    for (let p in obj) {
       console.log(p)   // 分别会输出:'age' 和 'title'
    }
    
    Object.getOwnPropertyNames(obj)   // ['age', 'title']
    由上代码可知,Symbol类型的key是不能通过Object.keys()或者for...in来枚举的,它未被包含在对象自身的属性名集合(property names)之中。所以,利用该特性,我们可以把一些不需要对外操作和访问的属性使用Symbol来定义。
    也正因为这样一个特性,当使用JSON.stringify()将对象转换成JSON字符串的时候,Symbol属性也会被排除在输出内容之外:
    JSON.stringify(obj)  // {"age":18,"title":"Engineer"}

     然而,这样的话,我们就没办法获取以Symbol方式定义的对象属性了么?非也。还是会有一些专门针对Symbol的API,比如:

    // 使用Object的API
    Object.getOwnPropertySymbols(obj) // [Symbol(name)]
    
    // 使用新增的反射API
    Reflect.ownKeys(obj) // [Symbol(name), 'age', 'title']

     ②.使用Symbol代替常量:

    const TYPE_AUDIO = 'AUDIO'
    const TYPE_VIDEO = 'VIDEO'
    const TYPE_IMAGE = 'IMAGE'
    
    function handleFileResource(resource) {
      switch(resource.type) {
        case TYPE_AUDIO:
          playAudio(resource)
          break
        case TYPE_VIDEO:
          playVideo(resource)
          break
        case TYPE_IMAGE:
          previewImage(resource)
          break
        default:
          throw new Error('Unknown type of resource')
      }
    }

     

    ③.使用Symbol定义类的私有属性
    //在a.js中

    const PASSWORD = Symbol() class Login { constructor(username, password) { this.username = username this[PASSWORD] = password } checkPassword(pwd) { return this[PASSWORD] === pwd } } export default Login

     

    //在b.js中
    
    import Login from './a'
    
    const login = new Login('admin', '123456')
    
    login.checkPassword('123456')  // true
    
    login.PASSWORD  // oh!no!
    login[PASSWORD] // oh!no!
    login["PASSWORD"] // oh!no!
    由于Symbol常量PASSWORD被定义在a.js所在的模块中,外面的模块获取不到这个Symbol,也不可能再创建一个一模一样的Symbol出来(因为Symbol是唯一的),因此这个PASSWORD的Symbol只能被限制在a.js内部使用,所以使用它来定义的类属性是没有办法被模块外访问到的,达到了一个私有化的效果。
  3. JS算法之深度优先遍历(DFS)和广度优先遍历(BFS)
    <div id="root">
        <ul>
            <li>
              <a>
                 <img src="" alt="">
              </a>  
            </li>
            <li>
              <span></span>
            </li>
            <li></li>
        </ul>        
    </div>    
    让我们看看这个dom结构生成的数的样子:

     

     ①.深度优先的遍历:

    /*深度优先遍历三种方式*/
    let deepTraversal1 = (node, nodeList = []) => {
    if (node !== null) {
    nodeList.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
    deepTraversal1(children[i], nodeList)
    }
    }
    return nodeList
    }
    let deepTraversal2 = (node) => {
    let nodes = []
    if (node !== null) {
    nodes.push(node)
    let children = node.children
    for (let i = 0; i < children.length; i++) {
    nodes = nodes.concat(deepTraversal2(children[i]))
    }
    }
    return nodes
    }
    // 非递归
    let deepTraversal3 = (node) => {
    let stack = []
    let nodes = []
    if (node) {
    // 推入当前处理的node
    stack.push(node)
    while (stack.length) {
    let item = stack.pop()
    let children = item.children
    nodes.push(item)
    // node = [] stack = [parent]
    // node = [parent] stack = [child3,child2,child1]
    // node = [parent, child1] stack = [child3,child2,child1-2,child1-1]
    // node = [parent, child1-1] stack = [child3,child2,child1-2]
    for (let i = children.length - 1; i >= 0; i--) {
    stack.push(children[i])
    }
    }
    }
    return nodes
    }

     

     ②.广度优先遍历:

    let widthTraversal2 = (node) => {
    let nodes = []
    let stack = []
    if (node) {
    stack.push(node)
    while (stack.length) {
    let item = stack.shift()
    let children = item.children
    nodes.push(item)
    // 队列,先进先出
    // nodes = [] stack = [parent]
    // nodes = [parent] stack = [child1,child2,child3]
    // nodes = [parent, child1] stack = [child2,child3,child1-1,child1-2]
    // nodes = [parent,child1,child2]
    for (let i = 0; i < children.length; i++) {
    stack.push(children[i])
    }
    }
    }
    return nodes

  4.   节流和防抖?有什么区别?怎么实现?

    防抖(debounce)

    所谓防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

    节流(throttle)

     

    所谓节流,就是指连续触发事件但是在 n 秒中只执行一次函数。节流会稀释函数的执行频率。

     

    对于节流,一般有两种方式可以实现,分别是时间戳版和定时器版。
    https://www.jianshu.com/p/c8b86b09daf0

  5.  父子组件之间的传值

    父组件向子组件传值

    <!DOCTYPE html>
    <html lang="en">
      <head>
        <title></title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1">
      </head>
      <body>
        <div id="app">
          <h2>{{msg}}</h2>
          <!-- 使用子组件(以子组件的名字当做标签即可使用子组件),并向子组件传递了一个msg值 -->
          <son :info="msg"></son>
        </div>
        <script src="./lib/vue-2.4.0.js"></script>
        <script>
          var vm = new Vue({
            el: '#app',
            data: {
              // 父组件中的msg值,要传递给子组件
              msg: "我是父组件"
            },
            // 定义一个私有的子组件
            components: {
              // 子组件的名字为son
              son: {
                // 子组件显示的内容
                template: "<h2>我是子组件+++{{info}}</h2>",
                // 接收父组件传递过来的值info,里面保存的是父组件的msg
                props: ["info"]
              }
            }
          })
        </script>
      </body>
    </html>

    子组件向父组件传值

    <!DOCTYPE html>
    <html lang="en">
    <head>    
        <meta charset="UTF-8">
        <title>子传父</title>
    </head>
    <body>
    <div id="app">
    <!-- 在父组件中使用子组件,并用v-on接收子组件传递过来的方法 -->
        <son @chuandi="fangfa"></son>
    </div>
    
    
    <script src="./lib/vue-2.4.0.js"></script>    
    <script>
        let vm = new Vue({
            el: '#app',
            data: {
                msg: '我是父组件的数据'
            },
            methods: {
                // 在父组件中定义方法,接收子组件传递过来的值
                fangfa(res){
                    console.log(res);
                }
            },
            // 定义一个私有的子组件
            components: {
                // 子组件的名字为son
                son: {
                    // 子组件的内容
                    template: "<button>子组件标签</button>"
                    // 子组件中的数据
                    data(){
                        return {
                            message: "我是子组件的数据"
                        }
                    },
                    // 在created钩子函数中用$emit注册一个方法,第一个参数就是父组件用v-on绑定的属性,第二个参数是子组件要传递给父组件的值
                    created(){
                        this.$emit("chuandi",this.message);
                    }
                }
            }
        });
    </script>
    </body>
    </html>
  6. Vue的生命周期

    创建期间的生命周期函数:

     

    beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性

     

    created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
    beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
    mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
    运行期间的生命周期函数:

     

    beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
    updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
    销毁期间的生命周期函数:

     

    beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。

  7.   krrp-alive属性

    <keep-alive>是Vue的内置组件,能在组件切换过程中将状态保留在内存中,防止重复渲染DOM。

     <keep-alive> 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们。和 <transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

      最简单的使用方法就是使用<keep-alive>标签包裹将要渲染的组件(自己的理解)   

<keep-alive include="test-keep-alive">  //include就是包括哪个组件需要缓存,后边的text-keep-alive是组件的名称(也就是设置路由时的那个name)
  <!-- 将缓存name为test-keep-alive的组件 -->
  <component></component>
</keep-alive>

<keep-alive include="a,b">
  <!-- 将缓存name为a或者b的组件,结合动态组件使用 -->
  <component :is="view"></component>
</keep-alive>

<!-- 使用正则表达式,需使用v-bind -->
<keep-alive :include="/a|b/">
  <component :is="view"></component>
</keep-alive>

<!-- 动态判断 -->
<keep-alive :include="includedComponents">
  <router-view></router-view>
</keep-alive>

<keep-alive exclude="test-keep-alive">
  <!-- 将不缓存name为test-keep-alive的组件 -->
  <component></component>

 

   其次还可以结合router来缓存页面:
<keep-alive>
    <router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
//...router.js
export default new Router({
  routes: [
    {
      path: '/',
      name: 'Hello',
      component: Hello,
      meta: {
        keepAlive: false // 不需要缓存
      }
    },
    {
      path: '/page1',
      name: 'Page1',
      component: Page1,
      meta: {
        keepAlive: true // 需要被缓存
      }
    }
  ]
})

 

这样在设置路由的时候就对需要缓存的路由进行了设计   
 
 

 

posted @ 2020-07-11 17:01  杨超(yc)  阅读(175)  评论(0编辑  收藏  举报