vuejs 貼合官方文檔 源碼分析

vdom 虛擬dom

使用傳統jquery庫操作dom都是整塊整塊的操作,虛擬dom通過js模擬dom結構,使用diff算法對比dom,局部更新dom,提高瀏覽重繪能力。

使用snabbdom實現vdom,關鍵API爲:1、h函數創建節點;2、patch函數渲染dom

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <div id="container"></div>
    <button id="btn-change">change</button>

    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-class.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-props.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-style.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/snabbdom-eventlisteners.js"></script>
    <script src="https://cdn.bootcss.com/snabbdom/0.7.0/h.js"></script>
    <script type="text/javascript">
        var snabbdom = window.snabbdom
        // 定義關鍵函數 patch
        var patch = snabbdom.init([
            snabbdom_class,
            snabbdom_props,
            snabbdom_style,
            snabbdom_eventlisteners
        ])

        // 定義關鍵函數 h
        var h = snabbdom.h

        // 原始數據
        var data = [
            {
                name: '張三',
                age: '20',
                address: '北京'
            },
            {
                name: '李四',
                age: '21',
                address: '上海'
            },
            {
                name: '王五',
                age: '22',
                address: '廣州'
            }
        ]
        // 把表頭也放在 data 中
        data.unshift({
            name: '姓名',
            age: '年齡',
            address: '地址'
        })

        var container = document.getElementById('container')

        // 渲染函數
        var vnode
        function render(data) {
            var newVnode = h('table', {}, data.map(function (item) {
                var tds = []
                var i
                for (i in item) {
                    if (item.hasOwnProperty(i)) {  //是否是自己創建的而不是原型裏的
                        tds.push(h('td', {}, item[i] + ''))
                    }
                }
                return h('tr', {}, tds)
            }))

            if (vnode) {
                // re-render
                patch(vnode, newVnode)
            } else {
                // 初次渲染
                patch(container, newVnode)
            }

            // 存儲當前的 vnode 結果
            vnode = newVnode
        }

        // 初次渲染
        render(data)


        var btnChange = document.getElementById('btn-change')
        btnChange.addEventListener('click', function () {
            data[1].age = 30
            data[2].address = '深圳'
            // re-render
            render(data)
        })

    </script>
</body>
</html>

在vue中模塊的引入是用es6的語法,import...from...

vue

響應式:vue如何監聽數據變化

通過object.defineProperty,接收三個參數(代理到的對象、數據的key,對象裏面是get、set方法)來劫持各個屬性的 setter / getter,在數據變動時發佈消息給訂閱者,觸發相應的監聽回調

數據對象定義的屬性都是靜態的,for循環的時候使用key就會命中閉包注意下面的處理方法

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <p>Object.defineProperty test</p>
    <p>模擬</p>

    <script type="text/javascript">
        // var obj = {
        //     name: 'zhangsan',
        //     age: 25
        // }
        // console.log(obj)

        // var obj = {}
        // var _name = 'shangsan'
        // Object.defineProperty(obj, 'name', {
        //     get: function () {
        //         console.log('get', _name) // 監聽
        //         return _name
        //     },
        //     set: function (newVal) {
        //         console.log('set', newVal)  // 監聽
        //         _name = newVal
        //     }
        // })




        // var vm = new Vue({
        //     el: '#app',
        //     data: {
        //         name: 'zhangsan',
        //         age: 20
        //     }
        // })

        var vm = {}
        var data = {
            name: 'zhangsan',
            age: 20
        }

        var key, value
        for (key in data) {
            (function (key) {
                Object.defineProperty(vm, key, {
                    get: function () {
                        console.log('get', data[key]) // 監聽
                        return data[key]
                    },
                    set: function (newVal) {
                        console.log('set', newVal) // 監聽
                        data[key] = newVal
                    }
                })
            })(key)
        }


    </script>
</body>
</html>

模版引擎及渲染:vue模版如何渲染成html

模版本質就是字符串、有邏輯,html是靜態的沒有邏輯,在vue源碼搜索code.render 大約一萬行的位置,在var code = ......;下面打印code.render,能看到模版的渲染

_c相當於vdom中的h函數、_v相當與vdom中的patch函數

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>to-do-list by vue</title>
    <script src="./vue-2.5.13.js"></script>
</head>
<body>
    <div id="app">
        <div>
            <input v-model="title">
            <button v-on:click="add">submit</button>
        </div>
        <div>
            <ul>
                <li v-for="item in list">{{item}}</li>
            </ul>
        </div>
    </div>

    <script type="text/javascript">
        // data 獨立
        var data = {
            title: '',
            list: []
        }
        // 初始化 Vue 實例
        var vm = new Vue({
            el: '#app',
            data: data,
            methods: {
                add: function () {
                    this.list.push(this.title)
                    this.title = ''
                }
            }
        })

        /*
        
        with(this){  // this 就是 vm
            return _c(
                'div',
                {
                    attrs:{"id":"app"}
                },
                [
                    _c(
                        'div',
                        [
                            _c(
                                'input',
                                {
                                    directives:[
                                        {
                                            name:"model",
                                            rawName:"v-model",
                                            value:(title),
                                            expression:"title"
                                        }
                                    ],
                                    domProps:{
                                        "value":(title)
                                    },
                                    on:{
                                        "input":function($event){
                                            if($event.target.composing)return;
                                            title=$event.target.value
                                        }
                                    }
                                }
                            ),
                            _v(" "),
                            _c(
                                'button',
                                {
                                    on:{
                                        "click":add
                                    }
                                },
                                [_v("submit")]
                            )
                        ]
                    ),
                    _v(" "),
                    _c('div',
                        [
                            _c(
                                'ul',
                                _l((list),function(item){return _c('li',[_v(_s(item))])})
                            )
                        ]
                    )
                ]
            )
        }

        */
    </script>
</body>
</html>

mvvm的理解

數據、視圖、vmodel他把數據和視圖關聯、區別於傳統的mvc模式 View 和 ViewModel 之間通過雙向綁定(data-binding)建立聯繫。與 MVC 不同的是,它沒有 Controller 層,而是演變爲 ViewModel。ViewModel 通過雙向數據綁定把 View 層和 Model 層連接了起來

常見的指令:v-on(事件綁定)v-bind(動態屬性設置)v-if v-else v-for v-model v-show v-html v-text,還有事件的修改器v-on:keydown.enter,v-model.lazy,v-model.number,v-model.trim 內置組件:router-view keep-active transition slot

<a v-bind:href="url">...</a>
<!-- 縮寫 -->
<a :href="url">...</a>

<a v-on:click="doSomething">...</a>
<!-- 縮寫 -->
<a @click="doSomething">...</a>

計算屬性是基於它們的依賴進行緩存的,對性能優化有很大的幫助。
計算屬性默認只有 getter ,不過在需要時你也可以提供一個 setter

Vue雙向綁定的實現原理Vue使用的發佈訂閱模式,是點對點的綁定數據。Vue的data裏,每個屬性都有set和get屬性,像下面的name值每次改變都必須經過set,其他方式是改變不了它的,相當於一個萬能的監聽器

var Coder = function () {
        var that = this;
        return {
            get name() {
                if (that.name) {
                    return that.name
                }
                return '你還沒有取名'
            },
            set name(val) {
                console.log('你把名字修成了' + val)
                that.name = val
            }
        }
    }
    var isMe = new Coder()
    console.log(isMe.name)
    isMe.name = '神'
    console.log(isMe.name)
    console.log(isMe)

    //你還沒有取名
    //你把名字修成了神
    //神
    //name: "神" get name: ƒ name() set name: ƒ name(val)__proto__: Object

在<template> 元素上使用 v-if 條件渲染分組,相當於小程序上的block

用 key 管理可複用的元素
這兩個元素是完全獨立的,複用它們”。只需添加一個具有唯一值的 key 屬性即可

v-if vs v-show 的區別

v-if 是“真正”的條件渲染,因爲它會確保在切換過程中條件塊內的事件監聽器和子組件適當地被銷燬和重建。
v-if 也是惰性的:如果在初始渲染時條件爲假,則什麼也不做——直到條件第一次變爲真時,纔會開始渲染條件塊。
相比之下,v-show 就簡單得多——不管初始條件是什麼,元素總是會被渲染,並且只是簡單地基於 CSS 進行切換。
一般來說,v-if 有更高的切換開銷,而 v-show 有更高的初始渲染開銷。因此,如果需要非常頻繁地切換,則使用 v-show 較好;如果在運行時條件很少改變,則使用 v-if 較好。

vm.$set 實例方法替換數組、對象

由於 JavaScript 的限制,Vue 不能檢測以下變動的數組:
當你利用索引直接設置一個項時,例如:vm.items[indexOfItem] = newValue
當你修改數組的長度時,例如:vm.items.length = newLength

用 v-on 指令監聽 DOM 事件

<button v-on:click="warn('Form cannot be submitted yet.', $event)">
  Submit
</button>
// ...
methods: {
  warn: function (message, event) {
    // 現在我們可以訪問原生事件對象
    if (event) event.preventDefault()
    alert(message)
  }
}

動畫&過渡

v-enter:定義進入過渡的開始狀態。在元素被插入之前生效,在元素被插入之後的下一幀移除。
v-enter-active:定義進入過渡生效時的狀態。在整個進入過渡的階段中應用,在元素被插入之前生效,在過渡/動畫完成之後移除。這個類可以被用來定義進入過渡的過程時間,延遲和曲線函數。
v-enter-to: 2.1.8版及以上 定義進入過渡的結束狀態。在元素被插入之後下一幀生效 (與此同時 v-enter 被移除),在過渡/動畫完成之後移除。
v-leave: 定義離開過渡的開始狀態。在離開過渡被觸發時立刻生效,下一幀被移除。
v-leave-active:定義離開過渡生效時的狀態。在整個離開過渡的階段中應用,在離開過渡被觸發時立刻生效,在過渡/動畫完成之後移除。這個類可以被用來定義離開過渡的過程時間,延遲和曲線函數。

Vue 提供了 過渡模式
in-out:新元素先進行過渡,完成之後當前元素過渡離開。
out-in:當前元素先進行過渡,完成之後新元素過渡進入。

用 out-in 重寫之前的開關按鈕過渡:
<transition name="fade" mode="out-in">
  <!-- ... the buttons ... -->
</transition>

指令v-el的作用,作爲 Vue 實例的掛載目標

Vue中常用的生命週期鉤子函數

beforecreate : 舉個栗子:可以在這加個loading事件
created :在這結束loading,還做一些初始化,實現函數自執行
mounted : 在這發起後端請求,拿回數據,配合路由鉤子做一些事情
beforeDestroy: 你確認刪除XX嗎? destroyed :當前組件已被刪除,清空相關內容

  beforeCreate () {
    console.log(this.$el, 'beforeCreate')
  },
  created () {
    console.log(this.$el, 'created')
  },
  beforeMount () {
    console.log(this.$el, 'beforeMount')  
    // 渲染dom 可以拿到$el的實例
  },
  mounted () {
    console.log(this.$el, 'mounted')
  },
  beforeUpdate () {
    console.log(this, 'beforeUpdate')
  },
  updated () {
    console.log(this, 'updated')
  },
  activated () { // 在組件章節講解  keep-alive
    console.log(this, 'activated')
  },
  deactivated () { // 在組件章節講解 keep-alive
    console.log(this, 'deactivated')
  },
  beforeDestroy () {
    console.log(this, 'beforeDestroy')
  },
  destroyed () {
    console.log(this, 'destroyed')
  },

axios的特點有哪些
一、Axios 是一個基於 promise 的 HTTP 庫,支持promise所有的API
二、它可以攔截請求和響應
三、它可以轉換請求數據和響應數據,並對響應回來的內容自動轉換成 JSON類型的數據
四、安全性更高,客戶端支持防禦 XSRF

Vuex的原理和使用方法

文章下面有詳細的代碼演示vuex的使用流程

所有組件的數據中心,做狀態管理。
一個實例化的Vuex.Store由state, mutations和actions三個屬性組成,
• state中保存着共有數據
• 改變state中的數據可以通過mutations方法,同步處理
• 如果要寫異步的方法,需要寫在actions中

keep-alive

當組件在keep-alive內被切換時組件的activated、deactivated這兩個生命週期鉤子函數會被執行,在keep-alive激活會觸發activated鉤子函數

同樣也存在一個問題就是被keep-alive包裹的組件我們請求獲取的數據不會再重新渲染頁面:

<keep-alive include="bookLists,bookLists">
      <router-view></router-view>
</keep-alive>
<keep-alive exclude="indexLists">
      <router-view></router-view>
</keep-alive>

include屬性表示只有name屬性爲bookLists,bookLists的組件會被緩存,(注意是組件的名字,不是路由的名字)其它組件不會被緩存exclude屬性表示除了name屬性爲indexLists的組件不會被緩存,其它組件都會被緩存

利用meta屬性:

export default[
 {
  path:'/',
  name:'home',
  components:Home,
  meta:{
    keepAlive:true //需要被緩存的組件
 },
 {
  path:'/book',
  name:'book',
  components:Book,
  meta:{
     keepAlive:false //不需要被緩存的組件
 } 
]

<keep-alive>
  <router-view v-if="this.$route.meat.keepAlive"></router-view>
  <!--這裏是會被緩存的組件-->
</keep-alive>

<keep-alive>
  <router-view v-if="$route.meta.keepAlive">
    <!-- 這裏是會被緩存的視圖組件 -->
  </router-view>
</keep-alive>

<router-view v-if="!$route.meta.keepAlive">
  <!-- 這裏是不被緩存的視圖組件 -->
</router-view>

被包裹在keep-alive中的組件的狀態將會被保留,例如我們將某個列表類組件內容滑動到第100條位置,那麼我們在切換到一個組件後再次切換回到該組件,該組件的位置狀態依舊會保持在第100條列表處,產品可能會要求在每一次進入一個組件時頁面的初始位置都是保持在頂部的,這裏可以利用Vue中的滾動行爲,但是前提是你是HTML5 history模式history.pushState
創建一個router實例的時候,可以提供一個scrollBehavior方法

v-model

v-model 是一個語法糖,可以拆解爲 props: value 和 events: input

v-model語法糖如下面的代碼

<input
  :value="something"
  @:input="something = $event.target.value">

實際應用:父子組件通信,一般都是單項的;通過v-model原理實現雙向通信

一般都會有一個 currentValue 的內部 data,初始時從 value 獲取一次值,當 value 修改時,也通過 watch 監聽到及時更新;組件不會修改 value 的值,而是修改 currentValue

<template>
  <div>
    <button @click="increase(-1)">1</button>
    <span style="color: red;padding: 6px">{{ currentValue }}</span>
    <button @click="increase(1)">1</button>
  </div>
</template>
<script>
  export default {
    name: 'InputNumber',
    props: {
      value: {
        type: Number
      }
    },
    data () {
      return {
        currentValue: this.value
      }
    },
    watch: {
      value (val) {
        this.currentValue = val;
      }
    },
    methods: {
      increase (val) {
        this.currentValue += val;
        this.$emit('input', this.currentValue);
      }
    }
  }
</script>

上面的數字選擇器組件可以有下面兩種使用方式:

<template>
  <InputNumber v-model="value" />
</template>
<script>
  import InputNumber from '../components/input-number/input-number.vue';

  export default {
    components: { InputNumber },
    data () {
      return {
        value: 1
      }
    }
  }
</script>

<template>
  <InputNumber :value="value" @input="handleChange" />
</template>
<script>
  import InputNumber from '../components/input-number/input-number.vue';

  export default {
    components: { InputNumber },
    data () {
      return {
        value: 1
      }
    },
    methods: {
      handleChange (val) {
        this.value = val;
      }
    }
  }
</script>

如果你不想用 valueinput 這兩個名字,從 Vue.js 2.2.0 版本開始,提供了一個 model 的選項,可以指定它們的名字,所以數字選擇器組件也可以這樣寫

<template>
  <div>
    <button @click="increase(-1)">1</button>
    <span style="color: red;padding: 6px">{{ currentValue }}</span>
    <button @click="increase(1)">1</button>
  </div>
</template>
<script>
  export default {
    name: 'InputNumber',
    props: {
      number: {
        type: Number
      }
    },
    model: {
      prop: 'number',
      event: 'change'
    },
    data () {
      return {
        currentValue: this.number
      }
    },
    watch: {
      value (val) {
        this.currentValue = val;
      }
    },
    methods: {
      increase (val) {
        this.currentValue += val;
        this.$emit('change', this.currentValue);
      }
    }
  }
</script>

.sync 修飾符

<template>
  <div>
    <button @click="increase(-1)">1</button>
    <span style="color: red;padding: 6px">{{ value }}</span>
    <button @click="increase(1)">1</button>
  </div>
</template>
<script>
  export default {
    name: 'InputNumber',
    props: {
      value: {
        type: Number
      }
    },
    methods: {
      increase (val) {
        this.$emit('update:value', this.value + val);
      }
    }
  }
</script>
<template>
  <InputNumber :value.sync="value" />
</template>
<script>
  import InputNumber from '../components/input-number/input-number.vue';

  export default {
    components: { InputNumber },
    data () {
      return {
        value: 1
      }
    }
  }
</script>

===========v-model 在一個組件中只能有一個,但 .sync 可以設置很多個=============

vue 跳轉方式

router-link-active類爲路由激活是的狀態,
在路由文件中添加redirect定義默認頁面

router-link默認是a標籤,我們通過tag指定爲div .router-link-active這個class是組件自帶的

<router-link :to="{name:'home', params: {id:1}}">  
 
// params傳參數 (類似post)
// 路由配置 path: "/home/:id" 或者 path: "/home:id" 
// 不配置path ,第一次可請求,刷新頁面id會消失
// 配置path,刷新頁面id會保留
 
// html 取參  $route.params.id
// script 取參  this.$route.params.id
 
<router-link tag="div" class="tab-item" to="/recommend">
  <span class="tab-link">推薦</span>
</router-link>

<router-link :to="{name:'home', query: {id:1}}"> 
 
// query傳參數 (類似get,url後面會顯示參數)
// 路由可不配置
 
// html 取參  $route.query.id
// script 取參  this.$route.query.id
this.$router.push
跳轉到指定url路徑,並想history棧中添加一個記錄,點擊後退會返回到上一個頁面
this.$router.replace
跳轉到指定url路徑,但是history棧中不會有記錄,
點擊返回會跳轉到上上個頁面 (就是直接替換了當前頁面)
this.$router.go(n)
向前或者向後跳轉n個頁面,n可爲正整數或負整數

路由鉤子函數

路由全局的鉤子

router.beforeEach((to, from, next) => {
  --做數據的校驗 驗證頁面需要用戶登錄才能訪問--
  console.log('before each invoked')
  next()
})

router.beforeResolve((to, from, next) => {
  console.log('before resolve invoked')
  next()
})

router.afterEach((to, from) => {
  console.log('after each invoked')
})

應用內部的鉤子

export default {
  metaInfo: {
    title: 'The Todo App'
  },
  // 路由加載完成
  beforeRouteEnter (to, from, next) {
    console.log('todo before enter', this)
    next(vm => {
      console.log('after enter vm.id is ', vm.id)
    })
  },
  // 如果此組件 是一個公共組件 反覆進入(詳情頁)纔會觸發  路由更新
  beforeRouteUpdate (to, from, next) {
    console.log('todo update enter')
    next()
  },
  // 如果用戶的表單 信息修改沒有保存 提示用戶是否保存 再離開頁面
  beforeRouteLeave (to, from, next) {
    console.log('todo leave enter')
    next()
  },

使用 $set

export default {
  data () {
    return {
      item: {
        a: 1
      }
    }
  },
  methods: {
    handler () {
      this.$set(this.item, 'b', 2);  // 是響應性的
    }
  }
}

還有一種小技巧,就是先 copy 一個數組,然後通過 index 修改後,再把原數組整個替換,比如:

handler () {
  const data = [...this.items];
  data[1] = 'x';
  this.items = data;
}

計算屬性和 watch 的區別

區別來源於用法,只是需要動態值,那就用計算屬性;需要知道值的改變後執行業務邏輯,才用 watch


computed 和 methods 有什麼區別?

methods 是一個方法,它可以接受參數,而 computed 不能;computed 是可以緩存的,methods 不會


watch 是一個對象時,它有哪些選項?

  • handler 執行的函數
  • deep 是否深度
  • immediate 是否立即執行

computed 是一個對象時,它有哪些選項?

但大多數時候,我們只是用它默認的 get 方法,也就是平時的常規寫法

事實上可以寫爲一個 Object,而非 Function,只是 Function 形式是我們默認使用它的 get 方法,當寫爲 Object 時,還能使用它的 set 方法

computed: {
  fullName: {
    get () {
      return `${this.firstName} ${this.lastName}`;
    },
    set (val) {
      const names = val.split(' ');
      this.firstName = names[0];
      this.lastName = names[names.length - 1];
    }
  }
}

怎樣給這個自定義組件 custom-component 綁定一個原生的 click 事件?

<custom-component @click.native="xxx">內容</custom-component>

修飾符 .exact

.exact 是 Vue.js 2.5.0 新加的,它允許你控制由精確的系統修飾符組合觸發的事件

<button @click.ctrl=“onClick”>A

<button @click.ctrl.exact=“onCtrlClick”>A

<button @click.exact=“onClick”>A

你可能還需要了解常用的幾個事件修飾符:

  • .stop
  • .prevent
  • .capture
  • .self

組件中 data 爲什麼是函數

因爲組件是用來複用的,JS 裏對象是引用關係,這樣作用域沒有隔離,而 new Vue 的實例,是不會被複用的,因此不存在引用對象的問題


遞歸組件的要求

要給組件設置 name; 要有一個明確的結束條件。


Vuex 中 mutations 和 actions 的區別

主要的區別是,actions 可以執行異步。actions 是調用 mutations 可以批量修改mutations,而 mutations 來修改 store。


怎樣理解單向數據流

父組件是通過 prop 把數據傳遞到子組件的,但是這個 prop 只能由父組件修改,子組件不能修改,否則會報錯。子組件想修改時,只能通過 $emit 派發一個自定義事件,父組件接收到後,由父組件修改。

slot也是 一種單向流動


nextTick

nextTick 是 Vue.js 提供的一個函數,並非瀏覽器內置。nextTick 函數接收一個回調函數 cb

<template>
  <div>
    <p v-if="show" ref="node">內容</p>
    <button @click="handleShow">顯示</button>
  </div>
</template>
<script>
  export default {
    data () {
      return {
        show: false
      }
    },
    methods: {
      handleShow () {
        this.show = true;
        console.log(this.$refs.node);  // undefined
        this.$nextTick(() => {
          console.log(this.$refs.node);  // <p>內容</p>
        });
      }
    }
  }
</script>

組件之間傳遞數據的幾種方式

  1. props/$emit
  2. .sync 修飾符
  3. $root / $parent / $children / ref。 通過 $root 屬性訪問根實例 new Vue()。
  4. vuex
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章