9.前端框架vue-3

1.13 js是如何监听HistoryRouter的变化的

参考答案:

通过浏览器的地址栏来改变切换页面,前端实现主要有两种方式:

  1. 通过hash改变,利用window.onhashchange 监听。

  2. HistoryRouter:通过history的改变,进行js操作加载页面,然而history并不像hash那样简单,因为history的改变,除了浏览器的几个前进后退(使用 history.back(), history.forward()和 history.go() 方法来完成在用户历史记录中向后和向前的跳转。)等操作会主动触发popstate 事件,pushState,replaceState 并不会触发popstate事件,要解决history监听的问题,方法是:

    首先完成一个订阅-发布模式,然后重写history.pushState, history.replaceState,并添加消息通知,这样一来只要history的无法实现监听函数就被我们加上了事件通知,只不过这里用的不是浏览器原生事件,而是通过我们创建的event-bus 来实现通知,然后触发事件订阅函数的执行。

具体操作如下:

  1. 订阅-发布模式示例
class Dep {                  // 订阅池
    constructor(name){
        this.id = new Date() //这里简单的运用时间戳做订阅池的ID
        this.subs = []       //该事件下被订阅对象的集合
    }
    defined(){              // 添加订阅者
        Dep.watch.add(this);
    }
    notify() {              //通知订阅者有变化
        this.subs.forEach((e, i) => {
            if(typeof e.update === 'function'){
                try {
                   e.update.apply(e)  //触发订阅者更新函数
                } catch(err){
                    console.warr(err)
                }
            }
        })
    }
}
Dep.watch = null;
class Watch {
    constructor(name, fn){
        this.name = name;       //订阅消息的名称
        this.id = new Date();   //这里简单的运用时间戳做订阅者的ID
        this.callBack = fn;     //订阅消息发送改变时->订阅者执行的回调函数     
    }
    add(dep) {                  //将订阅者放入dep订阅池
       dep.subs.push(this);
    }
    update() {                  //将订阅者更新方法
        var cb = this.callBack; //赋值为了不改变函数内调用的this
        cb(this.name);         
    }
}
  1. 重写history方法,并添加window.addHistoryListener事件机制。
var addHistoryMethod = (function(){
        var historyDep = new Dep();
        return function(name) {
            if(name === 'historychange'){
                return function(name, fn){
                    var event = new Watch(name, fn)
                    Dep.watch = event;
                    historyDep.defined();
                    Dep.watch = null;       //置空供下一个订阅者使用
                }
            } else if(name === 'pushState' || name === 'replaceState') {
                var method = history[name];
                return function(){
                    method.apply(history, arguments);
                    historyDep.notify();
                }
            }
        }
}())
window.addHistoryListener = addHistoryMethod('historychange');
history.pushState =  addHistoryMethod('pushState');
history.replaceState =  addHistoryMethod('replaceState');

1.14 HashRouter 和 HistoryRouter的区别和原理

参考答案:

vue-router是Vue官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。vue-router 默认 hash 模式,还有一种是history模式。

原理:

  1. hash路由:hash模式的工作原理是hashchange事件,可以在window监听hash的变化。我们在url后面随便添加一个#xx触发这个事件。vue-router默认的是hash模式—使用URL的hash来模拟一个完整的URL,于是当URL改变的时候,页面不会重新加载,也就是单页应用了,当#后面的hash发生变化,不会导致浏览器向服务器发出请求,浏览器不发出请求就不会刷新页面,并且会触发hasChange这个事件,通过监听hash值的变化来实现更新页面部分内容的操作

    对于hash模式会创建hashHistory对象,在访问不同的路由的时候,会发生两件事:
    HashHistory.push()将新的路由添加到浏览器访问的历史的栈顶,和HasHistory.replace()替换到当前栈顶的路由

  2. history路由:

    主要使用HTML5的pushState()和replaceState()这两个api结合window.popstate事件(监听浏览器前进后退)来实现的,pushState()可以改变url地址且不会发送请求,replaceState()可以读取历史记录栈,还可以对浏览器记录进行修改

区别:

  1. hash模式较丑,history模式较优雅
  2. pushState设置的新URL可以是与当前URL同源的任意URL;而hash只可修改#后面的部分,故只可设置与当前同文档的URL
  3. pushState设置的新URL可以与当前URL一模一样,这样也会把记录添加到栈中;而hash设置的新值必须与原来不一样才会触发记录添加到栈中
  4. pushState通过stateObject可以添加任意类型的数据到记录中;而hash只可添加短字符串
  5. pushState可额外设置title属性供后续使用
  6. hash兼容IE8以上,history兼容IE10以上
  7. history模式需要后端配合将所有访问都指向index.html,否则用户刷新页面,会导致404错误

使用方法:

<script>
        // hash路由原理***************************
        // 监听hashchange方法
        window.addEventListener('hashchange',()=>{
            div.innerHTML = location.hash.slice(1)
        })
        // history路由原理************************
        // 利用html5的history的pushState方法结合window.popstate事件(监听浏览器前进后退)
        function routerChange (pathname){
            history.pushState(null,null,pathname)
            div.innerHTML = location.pathname
        }
        window.addEventListener('popstate',()=>{
            div.innerHTML = location.pathname
        })
</script>

1.15 Vue router 原理, 哪个模式不会请求服务器

参考答案:

Vue router 的两种方法,hash模式不会请求服务器

解析:

  1. url的hash,就是通常所说的锚点#,javascript通过hashChange事件来监听url的变化,IE7以下需要轮询。比如这个 URL:http://www.abc.com/#/hello,hash 的值为 #/hello。它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面

  2. HTML5的History模式,它使url看起来像普通网站那样,以“/”分割,没有#,单页面并没有跳转。不过使用这种模式需要服务端支持,服务端在接收到所有请求后,都只想同一个html文件,不然会出现404。因此单页面应用只有一个html,整个网站的内容都在这一个html里,通过js来处理。

1.16 组件通信的方式

参考答案:

组件通信的方式的方式有以下8种方法:

  1. props和$emit

    这是最最常用的父子组件通信方式,父组件向子组件传递数据是通过prop传递的,子组件传递数据给父组件是通过$emit触发事件来做到的

  2. listeners

    第一种方式处理父子组件之间的数据传输有一个问题:如果多层嵌套,父组件A下面有子组件B,组件B下面有组件C,这时如果组件A想传递数据给组件C怎么办呢?

    如果采用第一种方法,我们必须让组件A通过prop传递消息给组件B,组件B在通过prop传递消息给组件C;要是组件A和组件C之间有更多的组件,那采用这种方式就很复杂了。从Vue 2.4开始,提供了listeners来解决这个问题,能够让组件A之间传递消息给组件C。

  3. v-model

    父组件通过v-model传递值给子组件时,会自动传递一个value的prop属性,在子组件中通过this.$emit(‘input',val)自动修改v-model绑定的值

  4. provide和inject

    父组件中通过provider来提供变量,然后在子组件中通过inject来注入变量。不论子组件有多深,只要调用了inject那么就可以注入provider中的数据。而不是局限于只能从当前父组件的prop属性来获取数据,只要在父组件的生命周期内,子组件都可以调用。

  5. 中央事件总线

    上面方式都是处理的父子组件之间的数据传递,那如果两个组件不是父子关系呢?也就是兄弟组件如何通信?

    这种情况下可以使用中央事件总线的方式。新建一个Vue事件bus对象,然后通过bus.on监听触发的事件。

  6. parent和children

  7. boradcast和dispatch

    vue1.0中提供了这种方式,但vue2.0中没有,但很多开源软件都自己封装了这种方式,比如min ui、element ui和iview等。 比如如下代码,一般都作为一个mixins去使用, broadcast是向特定的父组件,触发事件,dispatch是向特定的子组件触发事件,本质上这种方式还是on和on和emit的封装,但在一些基础组件中却很实用

  8. vuex处理组件之间的数据交互

    如果业务逻辑复杂,很多组件之间需要同时处理一些公共的数据,这个时候才有上面这一些方法可能不利于项目的维护,vuex的做法就是将这一些公共的数据抽离出来,然后其他组件就可以对这个公共数据进行读写操作,这样达到了解耦的目的

1.17 vue组件间传值, attrs和listeners 了解过吗?

参考答案:

listeners的作用:解决多层嵌套情况下,父组件A下面有子组件B,组件B下面有组件C,组件A传递数据给组件B的问题,这个方法是在Vue 2.4提出的。

listeners解决问题的过程:

C组件

Vue.component('C',{ 
     template:` 
     <div> 
     <input type="text" v-model="$attrs.messageC" @input="passCData($attrs.messageC)"> 
     </div> 
     `, 
     methods:{ 
         passCData(val){ 
             //触发父组件A中的事件 
             this.$emit('getCData',val) 
         } 
     } 
}) 

B组件

Vue.component('B',{ 
 data(){ 
     return { 
         myMessage:this.message 
     } 
 }, 
 template:` 
 <div> 
 <input type="text" v-model="myMessage" @input="passData(myMessage)"> 
 <C v-bind="$attrs" v-on="$listeners"></C> 
 </div> 
 `, 
 //得到父组件传递过来的数据 
 props:['message'], 
 methods:{ 
     passData(val){ 
         //触发父组件中的事件 
         this.$emit('getChildData',val) 
     } 
 } 
}) 

A组件

Vue.component('A',{ 
 template:` 
 <div> 
 <p>this is parent compoent!</p> 
 <B  
 :messageC="messageC"  
 :message="message"  
 v-on:getCData="getCData"  
 v-on:getChildData="getChildData(message)"> 
 </B> 
 </div> 
 `, 
 data(){ 
     return { 
         message:'Hello', 
         messageC:'Hello c' 
     } 
 }, 
 methods:{ 
     getChildData(val){ 
         console.log('这是来自B组件的数据') 
     }, 
     //执行C子组件触发的事件 
     getCData(val){ 
            console.log("这是来自C组件的数据:"+val) 
     } 
 } 
}) 
var app=new Vue({ 
 el:'#app', 
 template:` 
 <div> 
 <A></A> 
 </div> 
 ` 
}) 

解析:

  • C组件中能直接触发getCData的原因在于 B组件调用C组件时 使用 v-on 绑定了$listeners 属性
  • 通过v-bind 绑定$attrs属性,C组件可以直接获取到A组件中传递下来的props(除了B组件中props声明的)

1.18 组建传值,事件总线是怎么用的

参考答案:

中央事件总线主要用来解决兄弟组件通信的问题。

实现方式:新建一个Vue事件bus对象,然后通过bus.on监听触发的事件。

Vue.component('brother1',{ 
 data(){ 
     return { 
        myMessage:'Hello brother1' 
     } 
 }, 
 template:` 
 <div> 
 <p>this is brother1 compoent!</p> 
 <input type="text" v-model="myMessage" @input="passData(myMessage)"> 
 </div> 
 `, 
 methods:{ 
     passData(val){ 
         //触发全局事件globalEvent 
         bus.$emit('globalEvent',val) 
     } 
 } 
}) 
Vue.component('brother2',{ 
 template:` 
 <div> 
 <p>this is brother2 compoent!</p> 
 <p>brother1传递过来的数据:{{brothermessage}}</p> 
 </div> 
 `, 
 data(){ 
     return { 
         myMessage:'Hello brother2', 
         brothermessage:'' 
     } 
 }, 
 mounted(){ 
      //绑定全局事件globalEvent 
     bus.$on('globalEvent',(val)=>{ 
        this.brothermessage=val; 
     }) 
 } 
}) 
//中央事件总线 
var bus=new Vue(); 
var app=new Vue({ 
 el:'#app', 
 template:` 
 <div> 
 <brother1></brother1> 
 <brother2></brother2> 
 </div> 
 ` 
}) 

1.19 vue生命周期中异步加载在mouted还是create里实现

参考答案:

最常用的是在 created 钩子函数中调用异步请求

解析:

一般来说,可以在,created,mounted中都可以发送数据请求,但是,大部分时候,会在created发送请求。
Created的使用场景:如果页面首次渲染的就来自后端数据。因为,此时data已经挂载到vue实例了。
在 created(如果希望首次选的数据来自于后端,就在此处发请求)(只发了异步请求,渲染是在后端响应之后才进行的)、beforeMount、mounted(在mounted中发请求会进行二次渲染) 这三个钩子函数中进行调用。
因为在这三个钩子函数中,data 已经创建,可以将服务端端返回的数据进行赋值。但是最常用的是在 created 钩子函数中调用异步请求,因为在 created 钩子函数中调用异步请求有两个优点:
第一点:能更快获取到服务端数据,减少页面 loading 时间;
第二点:放在 created 中有助于一致性,因为ssr 不支持 beforeMount 、mounted 钩子函数。

1.20 vue钩子函数(重点问了keep-alive)

参考答案:

Vue生命周期经历哪些阶段:

  1. 总体来说:初始化、运行中、销毁
  2. 详细来说:开始创建、初始化数据、编译模板、挂载Dom、渲染→更新→渲染、销毁等一系列过程

生命周期经历的阶段和钩子函数:

  1. 实例化vue(组件)对象:new Vue()

  2. 初始化事件和生命周期 init events 和 init cycle

  3. beforeCreate函数:

    在实例初始化之后,数据观测 (data observer) 和 event/watcher 事件配置之前被调用。

    即此时vue(组件)对象被创建了,但是vue对象的属性还没有绑定,如data属性,computed属性还没有绑定,即没有值。

    此时还没有数据和真实DOM。

    即:属性还没有赋值,也没有动态创建template属性对应的HTML元素(二阶段的createUI函数还没有执行)

  4. 挂载数据(属性赋值)

    包括 属性和computed的运算

  5. Created函数:

    vue对象的属性有值了,但是DOM还没有生成,$el属性还不存在。

    此时有数据了,但是还没有真实的DOM

    ​ 即:data,computed都执行了。属性已经赋值,但没有动态创建template属性对应的HTML元素,所以,此时如果更改数据不会触发updated函数

    ​ 如果:数据的初始值就来自于后端,可以发送ajax,或者fetch请求获取数据,但是,此时不会触发updated函数

    1. 检查

​ 6.1 检查是否有el属性
​ 检查vue配置,即new Vue{}里面的el项是否存在,有就继续检查template项。没有则等到手动绑定调用 vm.el的绑定。

​ 6.2 检查是否有template属性

​ 检查配置中的template项,如果没有template进行填充被绑定区域,则被绑定区域的el对outerHTML(即 整个#app DOM对象,包括

标签)都作为被填充对象替换掉填充区域。即: 如果vue对象中有 template属性,那么,template后面的HTML会替换$el对应的内容。如果有render属 性,那么render就会替换template。 即:优先关系时: render > template > el

  1. beforeMount函数:

    模板编译(template)、数据挂载(把数据显示在模板里)之前执行的钩子函数

    此时 this.$el有值,但是数据还没有挂载到页面上。即此时页面中的{{}}里的变量还没有被数据替换

  2. 模板编译:用vue对象的数据(属性)替换模板中的内容

  3. Mounted函数:

    模板编译完成,数据挂载完毕

    即:此时已经把数据挂载到了页面上,所以,页面上能够看到正确的数据了。

    一般来说,我们在此处发送异步请求(ajax,fetch,axios等),获取服务器上的数据,显示在DOM里。

  4. beforeUpdate函数:

    组件更新之前执行的函数,只有数据更新后,才能调用(触发)beforeUpdate,注意:此数据一定是在模板上出现的数据,否则,不会,也没有必要触发组件更新(因为数据不出现在模板里,就没有必要再次渲染)

    数据更新了,但是,vue(组件)对象对应的dom中的内部(innerHTML)没有变,所以叫作组件更新前

  5. updated函数:

    组件更新之后执行的函数

    vue(组件)对象对应的dom中的内部(innerHTML)改变了,所以,叫作组件更新之后

  6. activated函数:keep-alive组件激活时调用

  7. activated函数:keep-alive组件停用时调用

  8. beforeDestroy:vue(组件)对象销毁之前

  9. destroyed:vue组件销毁后

keep-alive

<keep-alive></keep-alive>包裹动态组件时,会缓存不活动的组件实例,主要用于保留组件状态或避免重新渲染。

解析: 比如有一个列表和一个详情,那么用户就会经常执行打开详情=>返回列表=>打开详情…这样的话列表和详情都是一个频率很高的页面,那么就可以对列表组件使用<keep-alive></keep-alive>进行缓存,这样用户每次返回列表的时候,都能从缓存中快速渲染,而不是重新渲染

全部评论

相关推荐

头像
10-15 22:27
已编辑
门头沟学院 C++
罗格镇的小镇做题家:我投了hr打电话来说学历太低了不符合要求,建议投荣耀,结果荣耀也投了一定水花没有,非本211硕
投递华为等公司10个岗位
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务