Vue

179 阅读4分钟
function 数据双向绑定(){
    // 数据劫持和发布订阅一起实现的数据双向绑定
    // 把一个普通 Javascript 对象传给 Vue 实例的 data 选项,
    // Vue 将遍历此对象所有的属性,
    // 并使用 Object.defineProperty 把这些属性全部转为 getter/setter。
    // Object.defineProperty 是仅 ES5 支持,且无法 shim 的特性,
    // 这也就是为什么 Vue 不支持 IE8 以及更低版本浏览器的原因。
    Object.defineProperty
    var Book = {}
    var name = '';
    Object.defineProperty(Book, 'name', {
    set: function (value) {
        name = value;
        console.log('你取了一个书名叫做' + value);
    },
    get: function () {
        return '《' + name + '》'
    }
    })
    
    Book.name = 'vue权威指南';  // 你取了一个书名叫做vue权威指南
    console.log(Book.name);  // 《vue权威指南》
    // 它是es5一个方法,可以直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 
    // 并返回这个对象,对象里目前存在的属性描述符有两种主要形式:
    // 数据描述符和存取描述符。
    // 数据描述符是一个拥有可写或不可写值的属性。
    // 存取描述符是由一对 getter-setter 函数功能来描述的属性。
    // 描述符必须是两种形式之一;不能同时是两者
}
function newVue(){
    // new 关键字在 JavaScript 中代表实例化一个对象,而 Vue 实际上是一个类,就是一个 Function。 源码在 src/core/instance/index.js 中
    function Vue (options) {
        if (process.env.NODE_ENV !== 'production' &&
          !(this instanceof Vue)
        ) {
          warn('Vue is a constructor and should be called with the `new` keyword')
        }
        this._init(options)
      }
    // 可以看到 Vue 通过 new 关键字初始化,然后会调用 this._init 方法, 在 src/core/instance/init.js 中:

    Vue.prototype._init = function (options?: Object) {
        const vm: Component = this
        // a uid
        vm._uid = uid++
    
        let startTag, endTag
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          startTag = `vue-perf-start:${vm._uid}`
          endTag = `vue-perf-end:${vm._uid}`
          mark(startTag)
        }
    
        // a flag to avoid this being observed
        vm._isVue = true
        // merge options
        if (options && options._isComponent) {
          // optimize internal component instantiation
          // since dynamic options merging is pretty slow, and none of the
          // internal component options needs special treatment.
          initInternalComponent(vm, options)
        } else {
          vm.$options = mergeOptions(
            resolveConstructorOptions(vm.constructor),
            options || {},
            vm
          )
        }
        /* istanbul ignore else */
        if (process.env.NODE_ENV !== 'production') {
          initProxy(vm)
        } else {
          vm._renderProxy = vm
        }
        // expose real self
        vm._self = vm
        initLifecycle(vm)
        initEvents(vm)
        initRender(vm)
        callHook(vm, 'beforeCreate')
        initInjections(vm) // resolve injections before data/props
        initState(vm)
        initProvide(vm) // resolve provide after data/props
        callHook(vm, 'created')
    
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
          vm._name = formatComponentName(vm, false)
          mark(endTag)
          measure(`vue ${vm._name} init`, startTag, endTag)
        }
    
        if (vm.$options.el) {
          vm.$mount(vm.$options.el)
        }
      }
    
    // Vue 初始化只要就是干了这几件事情,合并配置,初始化生命周期,初始化事件中心,初始化渲染,初始化 data、props、computed、watcher 等。
    // 初始化的最后,检测到如果有 el 属性,则调用 vm.$mount 方法挂载 vm,挂载的目标就是把模板渲染成最终的 DOM。


}
function v-model原理(){
    // 其实在vue中,在使用v-model绑定数据之后,既绑定了数据,又添加了事件监听,这个事件就是input事件
    // <input v-model="something"></input>
    // 这不过是以下示例的语法糖
    // <input
    //     v-bind:value="something"
    //     v-on:input="something = $event.target.value">
     /* 这就解释了为什么在input元素使用v-model绑定数据后,可以实现双向绑定,因为输入的时候会触发元素的input事件      */


}
computed(){
    // 计算属性将被混入到vue实例中,
    // 所有的getter和setter的this上下文自动地绑定为vue实例
    // 就算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算
}
watch(){
    // 一个对象,键是需要观察的表达式,值是对应回调函数

}
组件通信(){
    1.父子组件通信--->父向子{
        // 父组件通过v-model绑定index到子组件上,而通过v-model绑定的数据在子组件中默认的key是value,
        // 所以在props上用value取值,最后通过点击事件执行$emit,而$emit上触发的事件是input,
        // 前面我们说过v-model绑定的事件是input,从而在子组件上触发了父组件的input事件,而在触发事件的时候可以传值,
        // 所以就实现了父子组件数据双向绑定,如果用的是v-bind,还需要自己去定义事情,所以使用v-model可以减少代码量

        // 父组件:
        // 1.import son from './son.js' 引入子组件 son

        // 2.在components : {"son"} 里注册所有子组件名称
        
        // 3.在父组件的template应用子组件, <son></son>
        
        // 4.如果需要传递数据给子组件,就在template模板里写 <son :num="number"></son>

        // 子组件:
        // 1.用props接受数据,就可以直接使用数据

        // 2.子组件接受到的数据,不能去修改。如果你的确需要修改,可以用计算属性,或者把数据赋值给子组件data里的一个变量
    } 
    2.父子组件通信--->子向父{
        // 父组件:
        // 在template里定义事件

        // 在methods里写函数,监听子组件的事件触发
        // 子组件:
        // 在数据变化后,用$emit触发即可
    }
    3.eventBus:{
        import Vue from 'vue'
        new Vue({
              el: '#app',
              components: { App }, 
              template: '<App/>',
              data(){    
                return {
                  Bus : new Vue()
                }
              }
            })
        // 通过在根组件,也就是#app组件上定义了一个所有组件都可以访问到的组件
        // 给app组件添加Bus属性 (这样所有组件都可以通过this.$root.Bus访问到它,而且不需要引入任何文件)

        // 2.在组件1里,this.$root.Bus.$emit触发事件
        emitincrement(){
            this.number++
            this.$root.Bus.$emit('eventName', this.number)
        },

        // 3.在组件2里,this.$root.Bus.$on监听事件
        mounted(){    
            this.$root.Bus.$on('eventName', value => {        
                this.number = value
                    console.log('busEvent');
               })
        }
    }
    4.利用localStorage或者sessionStorage
    5. 利用Vuex:{
        初始化VueX
        {
            // 新建一个store文件夹(这个不是必须的),并在文件夹下新建store.js文件,文件中引入我们的vue和vuex
            import Vue from 'vue';
            import Vuex from 'vuex';
            Vue.use(Vuex);
            //初始化数据
            const state = {
            count: 0
            };
            var vuexStore = new Vuex.Store({
            state
            });
            // 使用 export default 封装,让外部可以访问
        }
        在需要使用的组件中引入store.js,并使用
        {
            // 这里有3中方法去得到VueX里面的值 
            1.// 使用this获取或通过computed的计算属性直接赋值
            {
                import store from "../store/store.js";
                export default {
                store,//使用
                data(){
                    return{
                    //取值
                    count:this.$store.state.count
                    }
                }
                };
                //或则
                computed:{
                    count(){
                        return this.$store.state.count;
                    }
                }
            }
            2.//通过mapState的对象来赋值
            {
                import {mapState} from 'vuex';
                computed:mapState({
                //理解为传入state对象,修改state.count属性
                count:state=>state.count  
                })
            }
            3.//通过mapState的数组来赋值
            {
                computed:mapState(["count"])
            }
        }           
        创建改变状态的方法-->mutations
        {
            import Vue from 'vue'
            import Vuex from 'vuex'
            Vue.use(Vuex);
            
            const state = {
              count: 0
            };
            //改变状态的方法
            const mutations = {
              add(state) {
                state.count++;
              },
              mul(state) {
                state.count--;
              }
            };
            var vuexStore = new Vuex.Store({
              state,
              mutations //引入
            });
            
            // 使用 export default 封装,让外部可以访问
            
        }
        使用改变状态的方法
        {
            // 在需要使用的组件中
            // <button @click="$store.commit('add')">add</button>

        }
        使用getters的方式更新数据
        {
            const getters = {
                count:function(state){
                  return state.count +=100;
                }
            };
            var vuexStore = new Vuex.Store({
                state,
                mutations,
                getters
            });       

            // 组件中导入mapGetters,并使用
            // 1.导入
            // import { mapGetters } from 'vuex';
            // 2.在methods中加入
            // ...mapGetters(["count"])
            // 3.在页面中使用
            // <button @click="count()">mapGetters模式的点击事件</button>

        }
        actions异步修改状态
        {
            1.在store中创建方法 
            - context:上下文对象,这里你可以理解称store本身 
            - {commit}:直接把commit对象传递过来,可以让方法体逻辑和代码更清晰明了
            const actions ={
                addAction(context){
                  context.commit('add',10)
                },
                reduceAction({commit}){
                  commit('reduce')
                }
            }
            2.组件中import导入mapActions
                import { mapActions } from 'vuex';
            3.methods中加入mapActions
                ...mapActions(['addAction','reduceAction'])
            4.页面中使用
                // <button @click="addAction">mapActions模式点击事件</button>
        }

        State
        // State负责存储整个应用的状态数据,一般需要在使用的时候在跟节点注入store对象,后期就可以使用this.$store.state直接获取状态
        {
            //store为实例化生成的
            import store from './store'

            new Vue({
            el: '#app',
            store,
            render: h => h(App)
            })
            // 这个store可以理解为一个容器,包含着应用中的state等。实例化生成store的过程是:
            const mutations = {...};
            const actions = {...};
            const state = {...};

            Vuex.Store({
            state,
            actions,
            mutation
            });
            // 后续在组件中使用的过程中,如果想要获取对应的状态你就可以直接使用this.$store.state获取,
            // 当然,也可以利用vuex提供的mapState辅助函数将state映射到计算属性中去,如
            {
                //我是组件
                import {mapState} from 'vuex'

                export default {
                computed: mapState({
                    count: state => state.count
                })
                }
            }
        }
        Mutations
        //Mutations的中文意思是“变化”,利用它可以更改状态,本质就是用来处理数据的函数,
        // 其接收唯一参数值state。store.commit(mutationName)是用来触发一个mutation的方法。
        // 需要记住的是,定义的mutation必须是同步函数,否则devtool中的数据将可能出现问题,使状态改变变得难以跟踪。
        {
            const mutations = {
                mutationName(state) {
                  //在这里改变state中的数据
                }
              }
            //   在组件中触发:
            //我是一个组件
            export default {
                methods: {
                handleClick() {
                    this.$store.commit('mutationName')
                }
                }
            }
            // 或者使用辅助函数mapMutations直接将触发函数映射到methods上,这样就能在元素事件绑定上直接使用了。如:
            import {mapMutations} from 'vuex'
            //我是一个组件
            export default {
            methods: mapMutations([
                'mutationName'
            ])
            }
        }

        Actions
        //Actions也可以用于改变状态,不过是通过触发mutation实现的,重要的是可以包含异步操作。
        // 其辅助函数是mapActions与mapMutations类似,也是绑定在组件的methods上的。
        // 如果选择直接触发的话,使用this.$store.dispatch(actionName)方法
        {
        //定义Actions
            const actions = {
                actionName({ commit }) {
                //dosomething
                commit('mutationName')
                }
            }

        //在组件中使用
            import {mapActions} from 'vuex'
            //我是一个组件
            export default {
            methods: mapActions([
                'actionName',
            ])
            }
        }

// Getters // 有些状态需要做二次处理,就可以使用getters。 // 通过this.$store.getters.valueName对派生出来的状态进行访问。 // 或者直接使用辅助函数mapGetters将其映射到本地计算属性中去。 { const getters = { strLength: state => state.aString.length } //上面的代码根据aString状态派生出了一个strLength状态 //在组件中使用 import {mapGetters} from 'vuex' //我是一个组件 export default { computed: mapGetters([ 'strLength' ]) } } }

}
动态组件(){
    // 有的时候,在不同组件之间进行动态切换是非常有用的,比如在一个多标签的界面里:
    // 上述内容可以通过 Vue 的 <component> 元素加一个特殊的 is 特性来实现:
    // <component v-bind:is="currentTabComponent"></component>
    // 在上述示例中,currentTabComponent 可以包括

    // 已注册组件的名字,或
    // 一个组件的选项对象
}
Vue组件注册可分为全局注册和局部注册
全局注册(){
    需在初始化根实例之前注册组件
    //html
    <div id="example">
    <my-component></my-component>
    </div>
    // 注册
    Vue.component('my-component', {
    template: '<div>A custom component!</div>'
    })
    // 创建根实例
    new Vue({
    el: '#example'
    })
}
局部注册(){
    // 通过Vue 实例/组件的实例选项 components 注册仅在其作用域中可用的组件。这种封装也适用于其它可注册的 Vue 功能,比如指令
    var Child = {
        template: '<div>A custom component!</div>'
      }
      
      new Vue({
        // ...
        components: {
          // <my-component> 将只在父组件模板中可用
          'my-component': Child
        }
      })
      
}
function 与react对比() {
    // Vue中的props更灵活,对于class和Style特性,采用合并的策略,
    // 并且需要在子组件中显示声明props,相同的地方是都有props验证,单项prop数据流
    // props是可以动态变化的,子组件也实时更新,在react中官方建议props要像纯函数那样,输入输出一致对应,而且不太建议通过props来更改视图
    // 子组件一般要显示地调用props选项来声明它期待获得的数据。而在react中不必需,另两者都有props校验机制
    // 每个Vue实例都实现了事件接口,方便父子组件通信,小型项目中不需要引入状态管理机制,而react必需自己实现
    // 多了指令系统,让模版可以实现更丰富的功能,而React只能使用JSX语法
    // Vue增加的语法糖computed和watch,而在React中需要自己写一套逻辑来实现
}
function vue与JQ() {
    // jQuery是使用选择器($)选取DOM对象,对其进行赋值、取值、事件绑定等操作,
    // 其实和原生的HTML的区别只在于可以更方便的选取和操作DOM对象,而数据和界面是在一起的。
    // 比如需要获取label标签的内容:$("lable").val();,它还是依赖DOM元素的值。 
    // Vue则是通过Vue对象将数据和View完全分离开来了。对数据进行操作不再需要引用相应的DOM对象,
    // 可以说数据和View是分离的,他们通过Vue对象这个vm实现相互的绑定。这就是传说中的MVVM。
}
function MVVM() {
    // 1. 低耦合。View可以独立于Model变化和修改,一个ViewModel可以绑定到不同的View上,
    // 当View变化的时候Model可以不变,当Model变化的时候View也可以不变。  
    // 2. 可重用性。可以把一些视图的逻辑放在ViewModel里面,让很多View重用这段视图逻辑。   
    // 3.独立开发。开发人员可以专注与业务逻辑和数据的开发(ViewModel)。
    //   设计人员可以专注于界面(View)的设计。   
    // 4. 可测试性。可以针对ViewModel来对界面(View)进行测试
}
function 生命周期() {
    // beforeCreate
    组件实例刚被创建,组件属性计算之前,如data属性等
    // created
    组件实例创建完成,属性已绑定,但dom还未生成,$el属性还不存在
    // beforeMount
    模板编译/挂载之前
    // mounted
    模板编译/挂载之后
    // beforeUpdate
    组件更新之前
    // updated
    组件更新之后
    // beforeDestroy
    组件销毁前调用
    // destroyed
    组件销毁后调用

    beforecreate : 举个栗子:可以在这加个loading事件 
    created :在这结束loading,还做一些初始化,实现函数自执行 
    mounted : 在这发起后端请求,拿回数据,配合路由钩子做一些事情
    beforeDestroy: 你确认删除XX吗? 
    destroyed :当前组件已被删除,清空相关内容
}