vue2-3笔记

26.vue源码分析
    1.用到的js知识
        1)伪数组如何转成真数组
            const list2 = [...list1]
            const list3 = Array.from(list1)
            const list4 = Array.prototype.slice.call(list1) //改变数组方法的调用者,并将伪数组的元素全部截取并返回
!!            const list5 = [].slice.call(list1)
        2)节点类型
            console.log(eleNode.nodeType)  // 1 标签
            console.log(attrNode.nodeType)  // 2 属性
            console.log(txtNode.nodeType)  // 3 文本
        3) Object.definePrototype()方法---为对象添加属性并配置属性特性
            const person = {
              firstName: '东方',
              lastName: '华哥'
            }
            Object.defineProperty(person, 'fullName', {
              configurable: true, // 默认是false,不能被重设设置,是否可以被删除
              enumerable: false, // 默认是false,是否可以枚举
              // value:'哈_哈', // 该属性的默认值
              // writable:false , // 默认是false, 是否可以被重写
!!              get() {// 要使用get()和set()就不能设置默认值和writable
                // console.log(person.fullName) 就会进入到get
                return this.firstName + '_' + this.lastName
              },
              set(val) {
                // 什么时候会进入到set方法:   person.fullName='西门_华哥'
                const name = val.split('_')
                this.firstName = name[0]
                this.lastName = name[1]
              }
            })
        4) Object.keys()方法:将对象的key取出来放在数组中并返回
            const keys = Object.keys(person)
        5) 对象.hasOwnProperty()方法,判断当前对象中是否包含这个私有属性
            console.log(person.hasOwnProperty('fullName'))  // true
            console.log(person.hasOwnProperty('toString'))  // false
        6) 文档碎片对象模型----DoucmentFragment---(高效的批量处理多个节点)
            // 要求.通过文档碎片对象,把html标签容器中的内容更新
        
            // 1. 创建文档碎片对象
            var fragment = document.createDocumentFragment()
            // 2. 获取容器对象
            var divObj = document.getElementById('demo')
            // 3. 把容器对象中的所有的节点全部的存放在文档碎片对象中
            var child
            while (child = divObj.firstChild) { // 相当于剪切操作
              fragment.appendChild(child)
            }
            // 4. 遍历文档碎片对象中的节点,进行内容更新
            fragment.childNodes.forEach(node=>{
              node.innerHTML='我才是最帅的'
            })
            // 5. 把文档碎片对象放在容器中即可
            divObj.appendChild(fragment)
    2.vue源码文件
        1.mvvm.js文件
            1)MVVM:创建vue实例的构造函数
                1.调用方法实现数据代理:调用Object.keys()/原型上的_proxyData()方法将data对象的属性交给vue实例来代理
                2.调用方法实现数据劫持:模板解析前调用observer.js文件中的observe()方法劫持data对象的属性
                3.调用方法实现模板解析:调用原型上的$compile()方法解析模板
            2)MVVM.prototype原型
                1._proxyData():通过Object.defineProperty()方法把data对象中的每个属性添加到Vue的实例对象上,,并重写了Object.defineProperty()方法的set和get方法
        2.observer.js文件
            1)observe函数:判断传递过来的配置对象的data属性有没有值,或者是不是对象,创建Observer实例对象
            2)Observer构造函数
                1.把data对象的属性添加到Observer实例对象上
                2.调用原型的wall()方法,开始劫持数据,创建dep对象,并且当Dep.target中如果有值,就创建dep对象和Watcher实例对象的依赖关系
            3)Observer.prototype原型
                1.walk()方法:遍历data对象的属性的key
                2.convert()方法:内部调用劫持数据的方法
                3.defineReactive()方法
                    1)调用Dep构造函数创建dep实例对象,每个dep实例对象都有唯一的id标识和一个专门用来保存watcher实例对象的空数组subs
                    2)实现劫持数据,并重写了Object.defineProperty()方法的set和get方法
            4)Dep构造函数:
                1.创建dep实例对象,每个dep实例对象都有唯一的id标识,和一个专门用来保存watcher实例对象的空数组subs
                2.有一个target属性专门用来储存Watcher的实例对象,来建立dep对象和Watcher实例对象的依赖关系
            5)Dep.prototype原型
                1.depend():建立dep对象和Watcher实例对象的依赖关系
                2.addSub():建立dep对象和Watcher实例对象的依赖关系
                3.notify(): data对象属性发生变化通知watcher对象更新数据
        3.watcher.js文件
            1)Watcher构造函数:将表达式的值添加到watcher实例对象上
            2)Watcher.prototype原型
                1.parseGetter():返回获取表达式的值的函数
                2.get():调用获取表达式的值的函数来获取表达式的值
                3.addDep():建立dep对象和Watcher实例对象的依赖关系(把dep的id和dep对象以键值对的方式添加到watcher对象的depIds对象中)
                4.update(): data对象属性发生变化,watcher对象更新数据操作
        4.compile.js文件
            1)Compile编译对象构造函数:
                1.创建文档碎片对象,并把容器对象中所有的节点全都存放在文档碎片对象
                2.模版解析
                3.把模版解析后的文档碎片对象放在容器对象中
            2)Compile.prototype原型
                1.node2Fragment():创建文档碎片对象,并把容器对象中所有的节点全都存放在文档碎片对象,返回文档碎片对象
                2.init():内部调用真正解析模板的方法
                3.compileElement():真正解析模板的方法
                4.isElementNode():判断当前的节点是不是标签
                5.compile():判断标签属性是否是指令,指令是事件指令还是普通指令
                6.isDirective():判断是否是指令
                7.isEventDirective():判断当前的指令是不是事件指令
                8.isTextNode():判断当前的节点是不是文本            
                9.compileText():调用compileUtil方法来解析文本            
            3)compileUtil工具对象
                1.text属性:执行v-text指令的准备工作,内部会调用v-bind指令
                2.html属性:执行v-html指令的准备工作,内部会调用v-bind指令
                3.class属性:执行v-class指令的准备工作,内部会调用v-bind指令
                4.model属性:执行v-model指令的准备工作,内部会调用v-bind指令
                5.bind属性:获取表达式的值,创建Watcher实例对象
                6.eventHandler属性:给标签节点添加事件
                7._getVMVal:获取表达式的值,展示在页面上
            4)updater更新对象
                1.textUpdater属性:执行v-text指令
                2.htmlUpdater属性:执行v-html指令
                3.classUpdater属性:执行v-class指令
                4.modelUpdater属性:执行v-model指令
    3.剖析vue功能
        1)数据代理: 某个对象的属性,可以通过其他对象来访问,在Vue中是有数据代理,Vue的实例对象代理了data对象的属性
            创建Vue的实例对象的时候,把data对象中所有数据通过Object.keys()进行遍历,内部调用Object.defineProperty()把data对象中所有的数据一个一个添加到vm实例对象上,
            vm对象可以直接访问data对象中的数据了,vm代理了data,data是被代理者
        2)数据劫持    
            当Vue中数据代理结束后,就开始数据劫持,通过observe()方法开始进行数据的劫持,判断data是一个对象后,创建劫持的实例对象,内部遍历vm的data对象,然后把vm的data对象中所有的数据一个一个添加到劫持对象的data对象上,当前在正式添加之前,创建dep对象(id,subs数组),
            只要vm中的data对象有多少个属性就会创建多少个dep对象(将来和watcher建立关系)
        3)模板解析:把页面中html模版里面使用到的表达式(插值语法/事件指令/一般指令),解析为真正的数据的操作,并渲染界面
            1)在创建Vue实例对象时候,数据代理和数据劫持后,开始模版解析,会在MVVM的对象中实例化Compile对象
            2)Compile内部会把当前的Vue实例对象控制的容器对象中所有的节点全都存放在文档碎片对象中(文档碎片对象可以高效的批量操作DOM节点,在内存中进行节点的操作,这是所谓的虚拟DOM)
            3)取出文档碎片对象所有的子节点进行遍历,如果是文本节点,并且符合插值语法的正则,就要调用CompileUtil对象中的相关方法进而调用bind方法,
                 然后在调用updater对象中相关的方法把当前节点用到的表达式进行数据的替换,最后渲染页面即可
                 
            4)取出文档碎片对象所有的子节点进行遍历,如果当前的节点是标签节点,取出当前标签节点的所有属性,遍历所有属性
                 然后判断每个属性是不是Vue中的指令(以v-开头),然后判断当前的指令是事件指令(v-后面是:on)还是一般指令
            5)如果是事件指令,就把当前这个指令进行字符串切割,获取事件类型,还要拿着事件回调函数名去vue实例对象的methods属性中找对应的回调函数,
                 然后通过addEventListener方法为当前的标签节点绑定对应的事件,并将事件回调函数的this指向vue实例对象,最后在通过removeAttribute()移除当前标签节点的所有属性,最后渲染页面
            6)如果是普通指令,调用CompileUtil中的相关方法,进而调用bind方法,然后在调用updater对象中相关的方法把当前节点用到的表达式进行数据的替换,最后渲染页面即可
                1.updater对象中如何执行v-text和v-html指令?
                    通过文本节点的textContent和innerHTML属性替换表达式值
                2.updater对象中如何执行v-class指令?
                    获取当前标签节点的类样式的名字,如果有类名就添加一个空格在拼接上表达式的值,在把最终值添加给当前标签节点的类样式
                    
            7)模板解析中,当在内存中成功替换表达式的值之后,bind方法内部会创建watcher对象,会根据表达式的个数来创建对应个数的watcher对象    
                进入到watcher内部后,会调用get方法,进而完成开始建立dep对象和watcher对象的关系,进来会监视data对象属性的变化    
                dep和watcher的关系类型:
                    1对1的关系:1个dep对应一个watcher,data中只有一个属性,模版中只用了一个表达式
                    1对多的关系:1个dep对应多个watcher,data中只有一个属性,模版中用了多个表达式
                          多对1的关系:多个dep对应1个watcher,data中有多个属性,模版中用了一个表达式(data属性是对象,表达式:对象.属性)
                          多对多的关系:多个dep对应多个watcher,data中有多个属性,模版中用了多个表达式(data属性是对象,表达式:对象.属性)                                                                      
        4)双向数据绑定
                  创建Vue的实例的时候,除了数据代理和数据劫持以外,会进入到compile模版解析中,在内存中创建文档碎片对象,把html容器中所有的子级节点全部的存放在文档碎片对象中,
                  遍历所有的节点判断当前的节点是不是标签,然后获取当前节点标签中所有的属性,判断当前的属性是不是指令,然后再判断当前的指令是不是普通指令v-model,是的话就把把表达式的值赋值给节点value属性,再然后为当前的节点标签绑定input事件
                  如果标签中的数据发生变化,此时触发input事件,判断表达式之前的数据和现在输入的数据是否不同,之后会进入到MVVM的set方法内部再进入到observer.js的set方法内部,
                  根据当前的这个属性的dep对象通知当前dep对象中subs数组中的watcher进行数据的更新操作    
            (vue数据双向绑定是通过数据劫持结合发布者-订阅者模式的方式来实现的)
            
27.vue-property-decorator(简化ts在vue中的写法)
    vue-property-decorator是在Vue中使用TypeScript时,非常好用的一个库,使用装饰器来简化书写。(装饰器的作用就是接收vue组件,返回处理过后的vue组件)
    该库完全依赖于vue-class-component,因此在使用该库之前,请先阅读vue-class-component库。
    1)@component装饰器(来源于vue-class-component)
        @Component({
            name:'',
            components:{},
            filters:{},
            directives:{}
        })
        @Component装饰器可以接收一个对象作为参数,可以在对象中声明name , components ,mixins,filters,directives等装饰器选项,
        也可以声明computed,watch等,但并不推荐这么做,因为在访问this时,编译器会给出错误提示
        // 为了使用TypeScript,需要在script标签上添加 lang = ts
           
        // 等价于
         
    2)@Prop, Data, methods, computed, watch
        1.使用props
            //前面类型大写,后面可以小写
            @Prop(options: (PropOptions | Constructor[] | Constructor) = {})
            @Prop(String)
            @Prop([String,Number])
            @Prop({type: String, default: 'Developer',required: false})
            @Prop装饰器接收一个参数,这个参数可以有三种写法(字符串、数组、对象),可以添加 type, default, required为props指定验证要求,同样也可以使用 readonly 禁止操作
            
            // 等价于
            export default {
              props: {
                propA,
                propB:String,
                propC:[String,Number],
                propD: {
                  type: string,
                  default: 'Developer',
                  required: false
                }
              }
            }    
        2.使用data(data数据可以声明为类属性)
            export default class HelloWorld extends Vue {
              private msg: string = "welcome to my app"
              private list: Array = [
                {
                    name: 'Melody',
                    age: '20'
                },
                {
                    name: 'James',
                    age: '20'
                }
              ]
            }    
            //等价于
            export default {
              data() {
                return {
                  msg: "welcome to my app",
                  list: [
                    {
                      name: 'Melody',
                      age: '20'
                    },
                    {
                      name: 'James',
                      age: '20'
                    }
                  ]
                }
            }    
        3.使用Computed 属性(computed属性可以声明为类属性访问器)
            export default class HelloWorld extends Vue {
              get fullName(): string {
                return this.first+ ' '+ this.last
              }
            }
            // 等价于
            export default {
                computed:{
                    fullName() {
                        return this.first + ' ' + this.last
                    }
                }
            }
            
            当需要写一个稍微复杂点的涉及到setter和getter的 computed属性时,在ts中写法如下
            export default class HelloWorld extends Vue {
              get fullName(): string {
                return this.first+ ' '+ this.last
              }
              set fullName(newValue: string) {
                let names = newValue.split(' ')
                this.first = names[0]
                this.last = names[names.length - 1]
              }
            }
            //等价于
            export default {
                computed:{
                    fullName: {
                      get: function () {
                        return this.first + ' ' + this.last
                      },
                      set: function (newValue) {
                        let names = newValue.split(' ')
                        this.first = names[0]
                        this.last = names[names.length - 1]
                      }
                    }

                }
            }
        4.使用watch属性
            @Watch(path: string, options: WatchOptions = {}) 
            @Watch 装饰器接收两个参数
                path:被侦听的属性名
                options:可以包含两个属性            
                    immediate:侦听开始之后是否立即调用该回调函数
                    deep:是否开启深度监视
                    侦听开始,发生在beforeCreate勾子之后,created勾子之前
                                    
            @Watch('child')
            onChildChanged (val: string, oldVal: string) {
                if (val !== oldVal) {
                  window.console.log(val)
                }
            }
            //等价于
            watch: {
                'child': {
                    handler: 'onChildChanged',
                    immediate: false,
                    deep: false 
                }
            },
            method: {
                onChildChanged(val, oldVal) {
                    if (val !== oldVal) {
                      window.console.log(val)
                    }
                }
            }
        5.使用method属性(methods可以直接声明为类成员方法)
            export default class HelloWorld extends Vue {
              public clickMe(): void {
                console.log('clicked')
                console.log(this.addNum(4, 2))
              }
              public addNum(num1: number, num2: number): number {
                return num1 + num2
              }
            }
            //等价于
            export default {
              methods: {
                clickMe() {
                  console.log('clicked')
                  console.log(this.addNum(4, 2))
                }
                addNum(num1, num2) {
                  return num1 + num2
                }
              }
            }
        6.Lifecycle hooks(生命周期函数)
            所有Vue生命周期挂钩也可以直接声明为类成员方法
            export default class HelloWorld extends Vue {
              mounted() {}
              beforeUpdate() {}
            }
            //等价于
            export default {
              mounted() {}
              beforeUpdate() {}
            }
    3)@Emit 装饰器
        子组件触发父组件的自定义事件并传递数据,在TypeScript中使用@Emit 装饰器
        @Emit(event?: string)
        @Emit 装饰器接收一个可选参数,该参数是$Emit的第一个参数,充当事件名。如果没有提供这个参数,$Emit会将回调函数名的camelCase转为kebab-case,并将其作为事件名
        @Emit会将回调函数的返回值作为第二个参数,如果返回值是一个Promise对象,$emit会在Promise对象被标记为resolved之后触发,promise的结果值作为第二个参数
        @Emit的回调函数的参数,会放在其返回值之后,一起被$emit当做参数使用

        1.@Emit()没有指定参数,相当于触发'add-to-count',addToCount函数参数为传递给'add-to-count'的数据
            import { Vue, Component, Emit } from 'vue-property-decorator'
            @Component
            export default class YourComponent extends Vue {
                @Emit()
                addToCount(n: number) {
                  this.count += n
                }
            }            
            //等价于
            export default {
              methods: {
                addToCount(n) {
                  this.count += n
                  this.$emit('add-to-count', n)
                }
              }
            }
        2.@Emit()没有指定参数,相当于触发'return-value',returnValue函数返回值为传递给'return-value'的数据
            @Emit()
            returnValue() {
              return 10
            }
            returnValue() {
                this.$emit('return-value', 10)
            }
        3.@Emit()没有指定参数,相当于触发'on-input-change',onInputChange函数返回值和参数为传递给'return-value'的数据
            @Emit()
            onInputChange(e) {
              return e.target.value
            }
            onInputChange(e) {
              this.$emit('on-input-change', e.target.value, e)
            }
        4.@Emit()没有参数并且返回一个Promise对象,Promise对象的结果值为传递给'promise'的数据
            @Emit()
            promise() {
              return new Promise(resolve => {
                resolve(20)
              })
            }
            //等价于
            promise() {
              const promise = new Promise(resolve => {
                resolve(20)
              })
        
              promise.then(value => {
                this.$emit('promise', value)
              })
            }
        5.@Emit()有参数,那么触发'reset'
            @Emit('reset')
            resetCount() {
              this.count = 0
            }
            //等价于
            resetCount() {
              this.count = 0
              this.$emit('reset')
            }                                                        
    4)vuex
        1.首先安装
            npm install vuex-module-decorators -D
            npm install vuex-class -D
        2.如果想通过名字空间的形式来使用module, 需在@Module装饰器中添加额外的参数. 
             例如, 以下示例代码中添加一个namespaced为user的module
            import { VuexModule, Module, Mutation, Action } from 'vuex-module-decorators'
            @Module({ namespaced: true, name: 'user' })
            class User extends VuexModule {
              public name: string = ''
              @Mutation
              public setName(newName: string): void {
                this.name = newName
              }
              @Action
              public updateName(newName: string): void {
                this.context.commit('setName', newName)
              }
            }
            export default User
            注意:@Module装饰器的属性字段name值, 必须与new store({modules: {}})中注册module name名称保持一致
            //等价于
            export default {
              namespaced: true,
              state: {
                name: ''
              },
              mutations: {
                setName(state, newName) {
                  state.name = newName
                }
              },
              actions: {
                updateName(context, newName) {
                  context.commit('setName', newName)
                }
              }
            }
        3.在组件里面使用vuex
            要使用Vuex,可以使用vuex-class库。该库提供了装饰器,可以在Vue组件中绑定State,Getter,Mutation和Action。
            由于已经使用了命名空间的Vuex模块,因此我们首先从vuex-class导入命名空间,然后传递模块名称以访问该模块
            import { Component, Vue } from 'vue-property-decorator'
            import { namespace } from 'vuex-class'
            const user = namespace('user')
            @Component
            export default class User extends Vue {
              @user.State
              public name!: string
            
              @user.Getter
              public nameUpperCase!: string
            
              @user.Action
              public updateName!: (newName: string) => void
            }
            //等价于
            import { mapState, mapGetters, mapActions} from 'vuex'
            export default  {
              computed: {
                ...mapState('user', ['name']),
                ...mapGetters('user', ['nameUpperCase'])
              }  
              methods: {
                ...mapActions('user', ['updateName'])
              }
            }
    5.其它
        1)@Provide 提供 / @Inject 注入
            与 React的context组件间通信相似,这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代传递数据,
            数据通过Provide传递下去,然后子组件通过Inject来获取
            import { Component, Inject, Provide, Vue } from 'vue-property-decorator'
            const symbol = Symbol('baz')            
            @Component
            export class MyComponent extends Vue {
              @Inject() foo!: string
              @Inject('bar') bar!: string
              @Inject({ from: 'optional', default: 'default' }) optional!: string
              @Inject(symbol) readonly baz!: string
            
              @Provide() foo = 'foo'
              @Provide('bar') baz = 'bar'
            }
            //等价于
            const symbol = Symbol('baz')
            export const MyComponent = Vue.extend({
              inject: {
                foo: 'foo',
                bar: 'bar',
                optional: { from: 'optional', default: 'default' },
                baz: symbol,
              },
              data() {
                return {
                  foo: 'foo',
                  baz: 'bar',
                }
              },
              provide() {
                return {
                  foo: this.foo,
                  bar: this.baz,
                }
              },
            })
        2)@Ref(refKey?: string)
            @Ref 装饰器接收一个可选参数,用来指向元素或子组件的引用信息。如果没有提供这个参数,会使用装饰器后面的属性名充当参数
            import { Vue, Component, Ref } from 'vue-property-decorator'
            import { Form } from 'element-ui'
            @Componentexport default class MyComponent extends Vue {
              @Ref() readonly loginForm!: Form
              @Ref('changePasswordForm') readonly passwordForm!: Form            
              public handleLogin() {
                this.loginForm.validate(valide => {
                  if (valide) {
                    // login...
                  } else {
                    // error tips
                  }
                })
              }
            }
            //等价于
            export default {
              computed: {
                loginForm: {
                  cache: false,
                  get() {
                    return this.$refs.loginForm
                  }
                },
                passwordForm: {
                  cache: false,
                  get() {
                    return this.$refs.changePasswordForm
                  }
                }
              }
            }
        3)@Model装饰器允许我们在一个组件上自定义v-model
            @Model(event?: string, options: (PropOptions | Constructor[] | Constructor) = {})
            event:事件名。
            options:与@Prop的第一个参数一致,可以是字符串、数组、对象类型
            下面例子中指定的是change事件,所以我们还需要在template中加上相应的事件:
            
            import { Vue, Component, Model } from 'vue-property-decorator'            
            @Component
            export default class YourComponent extends Vue {
              @Model('change', { type: Boolean }) readonly checked!: boolean
            }
            //等价于
            export default {
              model: {
                prop: 'checked',
                event: 'change',
              },
              props: {
                checked: {
                  type: Boolean,
                },
              },
            }
        4)Mixins
            假设当前已经有一个mixins/ProjectMixin文件 如何在其他组件里面使用方式如下
            import { Component, Vue, Mixins } from 'vue-property-decorator'
            import ProjectMixin from '@/mixins/ProjectMixin'
            @Component
            export default class Project extends Mixins(ProjectMixin) {
              get projectDetail(): string {
                return this.projName + ' ' + 'HS'
              }
            }
            //等价于
            import ProjectMixin from '@/mixins/ProjectMixin'
            export default {
              mixins: [ ProjectMixin ],
              computed: {
                projectDetail() {
                  return this.projName + ' ' + 'HS'
                }
              }
            }    
        5)@PropSync装饰器与@prop用法类似,二者的区别在于:
            @PropSync(propName: string, options: (PropOptions | Constructor[] | Constructor) = {})装饰器接收两个参数:
            propName:表示父组件传递过来的属性名
            options:与@Prop的第一个参数一致,可以是字符串、数组、对象类型
            @PropSync 会生成一个新的计算属性。    
                
            import { Vue, Component, PropSync } from 'vue-property-decorator'
            @Component
            export default class MyComponent extends Vue {
              @PropSync('propA', { type: String, default: 'abc' }) public syncedPropA!: string
            }  
            //等价于
            export default {
              props: {
                propA: {
                  type: String,
                  default: 'abc'
                }
              },
              computed: {
                syncedPropA: {
                  get() {
                    return this.propA
                  },
                  set(value) {
                    this.$emit('update:propA', value)
                  }
                }
              }
            } 
            注意:@PropSync需要配合父组件的.sync修饰符使用 
    6.总结
        1)methods可以直接声明为类成员方法(使用public关键字)
        2)computed属性可以声明为类属性访问器(get/set)
        3)data数据可以声明为类属性(使用private关键字)
        4)render函数和所有Vue生命周期挂钩也可以直接声明为类成员方法,但不能在实例本身上调用它们。

28.服务器端渲染 (SSR)
    将一个组件渲染为服务器端的 HTML 字符串,将它们直接发送到浏览器,最后将这些静态标记"激活"为客户端上完全可交互的应用程序。
    1)为什么使用服务器端渲染
        1.更好的 SEO,由于搜索引擎爬虫抓取工具可以直接查看完全渲染的页面。
        2.更快的内容到达时间,无需等待所有的 JavaScript都完成下载并执行,才显示服务器渲染的标记,所以你的用户将会更快速地看到完整渲染的页面。
        3.服务端渲染, 解决首屏加载速度, 和 seo问题(总结)
    2)服务端渲染实例
        // 第 1 步:创建一个 Vue 实例
        const Vue = require('vue')
        const app = new Vue({
          template: `

{{myname}}-{{myage}}
`,
          data:{
              myname:"kerwin",
              myage:100
          }
        })
        // 第 2 步:创建一个 renderer
        const renderer = require('vue-server-renderer').createRenderer()
        // 第 3 步:将 Vue 实例渲染为 HTML
        // 旧写法
        // renderer.renderToString(app, (err, html) => {
        //   if (err) throw err
        //   console.log(html)
        //   // =>
Hello World

        // })
        
        // 新写法,在 2.5.0+,如果没有传入回调函数,则会返回 Promise:
        renderer.renderToString(app).then(html => {
          console.log(html)
        }).catch(err => {
          console.error(err)
        })
    3)Nuxt.js
        Nuxt.js是一个做vue ssr(服务端渲染)的框架
        Nuxt.js工作流:
        Incoming Request(客户端发送请求给客户端)-->nuxtServerLint(store action)(服务端检测有没有nuxtServerLint这个配置,有的话就执行这个函数用来操作vuex)
        -->middleware(操作路由相关的功能)-->validate(路由校验)-->asyncData(vue组件数据)和fetch(vuex数据)-->render(将数据渲染到页面)-->navigate(nuxt link)(组件中调用nuxt link重新从middleware开始循环)
        npm i -g create-nuxt-app
        create-nuxt-app 项目名
        //下面这个命令等价于上面两个命令,npx会检测全局和局部有没有create-nuxt-app命令,有的话就直接创建项目,没有的
        //话就先在本地下载create-nuxt-app命令,再去创建项目
        npx create-nuxt-app 项目名
        1.项目结构
            .nuxt
            assets        放静态资源
            components    放全局公共组件
            layouts        布局组件模板
            middleware    中间件做路由拦截
            pages        放路由组件
            plugins        放插件,如elememt-ui
            server        服务端配置
            static        放静态资源,小图标等
            store        放vuex
            nuxt.config.js(nuxt配置)
        2.路由
            1)Nuxt.js 依据 pages 目录结构自动生成 vue-router 模块的路由配置
            2)路口文件(layouts/default.vue)
            3)等价于,支持activeClass(通过activeClass给标签加一个类名,在样式中写触发时高亮样式),tag
              等价于
            4)嵌套路由
                1.在pages中创建一个与一级路由相同文件名的文件夹来放该路由的子路由
                2.在父组件中通过标签来显示子路由
            5)路由重定向
                1.配置nuxt.config.js文件
                    router:{
                        extendRoutes(routes){
                          routes.push({
                            path:"/",
                            redirect:'/film/nowplaying'
                          })
                        }
                      }
                2.利用中间件来处理
                    //middleware/redirect.js(名字可改)文件中
                    export default function({ isHMR,app,store,route,params,error,redirect })  {
                       if (isHMR) return
                       if(route.fullPath == '/film') {
                          return redirect('/film/nowplaying')
                       }
                    }
                    //配置nuxt.config.js文件
                    router: { 
                        middleware: 'redirect' // 即每次路由跳转会调用该中间件 ,redirect是文件名
                        //middleware: ['redirect'] //多个中间件写法
                    }
            6)动态路由
                1.创建
                    在pages创建路由文件夹,文件夹里面以下划线开头,下划线后面是路由参数
                    pages
                    ‐‐| detail(文件夹,不需要在外面创建.vue文件)
                    ‐‐‐‐‐| _id.vue
                2.获取路由参数        
                    this.$router.query.id
            7)视图
                在layout 里面 写好default.vue 可以认为这是根组件的模板了, 所有的组件都加在里面,但是有些页面 可能不一样,就可以使用 个性化定制页面。
                export default { 
                    layout: 'template' 
                }
            8)服务端渲染异步数据(ssr)
                  服务端渲染的异步数据可以通过右键查源码查到数据,而客户端渲染的异步数据则不可以
                  data() {
                    return {
                      list: []
                    }
                  },
                // 在asyncData()方法中实现服务端渲染异步数据,return回来的list会给data的list
                // 在当前页面刷新, 服务端执行此函数,从其他页面跳转过来,客户端执行此函数
                // data中可以拿到route、query、params等数据
                // 只有页面级别组件才有
                async asyncData(data) {
                    let {status, data: {list}} = await axios.get('http://localhost:3000/city/list')
                    if (status === 200) {
                      return {
                        list
                      }
                    }
                  }
                或者
                asyncData(data) {
                    return axios({
                      url:"https://m.maizuo.com/gateway?cityId=110100&pageNum=1&pageSize=10&type=1&k=6341699
                    }).then(res=>{
                      return {
                        datalist:res.data.data.films
                      }// 状态
                    })
                  }
            9)反向代理解决跨域
                1.npm i @nuxtjs/proxy ‐D
                2.在 nuxt.config.js 配置文件中添加对应的模块,并设置代理
                    modules: [
                        '@nuxtjs/axios',
                        '@nuxtjs/proxy'
                      ],
                      axios: {
                        proxy:true
                      },
                      proxy:{
                          //拦截/ajax开头的请求,拼接到http://m.maoyan.com上
                        '/ajax':{
                          target:"http://m.maoyan.com",
                          changeOrigin:true
                        }
                      },
                3.在组件中,上面代理只能解决前端跨域问题,但是如果asyncData函数在后端执行会出现请求路径不全问题
                    asyncData(data) {
                        return axios({
                          //process.server为true说明asyncData函数在服务端执行
                          url:process.server?"http://m.maoyan.com/ajax/movieOnInfoList?token=":"/ajax/movieOnInfoList?token=",
                        }).then(res=>{
                          console.log(res.data);
                          return {
                            datalist:res.data.movieList
                          }// 状态
                        })
                      }
                10)vuex在nuxt.js中的使用
                    直接在项目中创建store文件夹,配置store,nuxt.js会自动引入store,我们可以直接在组件中使用
                    const store = () => new Vuex.Store({
                      modules: {
                        city,
                        navbar
                      },
                      // 注意store中actions不能省略,nuxt.js会执行nuxtServerInit()方法实现vuex数据的服务端渲染
                      actions: {
                        // nuxtServerInit({ commit }, { req }) {
                        //   if (req.session.user) {
                        //     commit('city', req.session.user)
                        //   }
                        // }
                      }
                    })

29性能优化
    1)开发过程
        1.优先使用vIf
            v-if是将元素删除来达到隐藏的效果,v-show是将元素display:none来达到隐藏的效果,如果需要频繁切换才使用v-show
        2.vFor key避免使用index作为标识
            当index作为标识的时候,插入一条数据的时候,列表中它后面的key都发生了变化,那么当前的 vFor 都会对key变化的 Element 重新渲染,
            但是其实它们除了插入的 Element 数据都没有发生改变,这就导致了没有必要的开销。
        3.释放组件资源
            每创建出一个事物都需要消耗资源,资源不是凭空产生的,是分配出来的。所以说,当组件销毁后,尽量把我们开辟出来的资源块给销毁掉,
            比如 setInterval , addEventListener等
        4.长列表
            长列表渲染的时候,建议将DOM移除掉,类似于图片懒加载的模式,只有出现在视图上的DOM才是重要的DOM。网络上有一些很好的解决方案,如 vue-virtual-scroller 库等等
        5.图片合理的优化方式
            图片应该都不陌生吧,在网页中,往往存在大量的图片资源,这些资源或大或小。当我们页面中DOM中存在大量的图片时,
            难免不会碰到一些加载缓慢的问题,导致图片出现加载失败的问题。网络上大部分都在使用 懒加载 的使用方式,
            只有当 存在图片的DOM 出现在页面上才会进行图片的加载,无形中起到了分流的作用,下面就说一套实践的方案吧
            1)小图标使用 SVG 或者字体图标
            2)通过 base64 和 webp  的方式加载小型图片
            3)能通过cdn加速的大图尽量用cdn
            4)大部分框架都带有懒加载的图片
        6.使用路由懒加载
            component: () => import('@/components/HelloWorld')
        7.第三方模块(UI/工具插件)按需导入
        8.SPA 页面采用keep-alive缓存组件
        9.防抖、节流
        10.首屏优化
            众所周知,第一次打开Vue的时候,如果你的项目够大,那么首次加载资源时,会非常的久。由于资源没有加载完毕,
            界面的DOM也不会渲染,会造成白屏的问题。用户此时并不知道是加载的问题,所以会带来一个不好的体验。
            因此通常会在public下写一个加载动画,告诉用户,网页在加载中这个提示。当页面加载成功后,页面渲染出来的这一个体验比白屏等开机要好太多了。
        11.静态模板尽量使用函数组件替代普通组件
        12.render函数只要数据一改变就会触发,所以数据的处理尽量放在render函数外面,render函数里面的方法提前定义在外面
                因为方法是执行render函数临时创建的。消耗性能    
        13.结构显示隐藏加动画
    2)webpack
        1.优化打包构建速度
            1) HMR 热模块替换
            2) cache 缓存(针对js)
            3) oneOf            
            4) 多进程打包        
        2.优化打包代码体积和性能
            1) 兼容性处理    
            2) tree shaking 树摇
            3) code split 代码分割 / lazy loading 懒加载    
            4) preload 和 prefetch 预加载             
            5) cache 缓存(浏览器缓存)        
            6) PWA 渐进式网络应用程序(离线加载技术)
    3)其它
        1.服务端渲染SSR
            1)vuex中
                actions: {
                    //app相当于实例对象
                    nuxtServerInit({commit}, {req, app}) {
                    }
                }
            2)组件内
                //ctx相当于实例对象
                asyncData(ctx){}
        2.还可以使用缓存(客户端缓存、服务端缓存)优化、服务端开启gzip压缩等。
            //熊健静态服务器/utils/compress.js文件
            const { createGzip, createDeflate } = require("zlib");
            // 设置响应头,告诉客户端,内容经过了压缩
            res.setHeader("Content-Encoding", "gzip");
            // rs可读流会将数据读取传给gzip压缩,返回值还是一个rs(可读流中的数据已经压缩了~)
            rs = rs.pipe(createGzip());
        3.依赖库CDN加速
            看到有小伙伴使用CDN的方式引入一些依赖包,觉得非常的 Nice ,然后我也开始使用了。我将 Vue Axios Echarts 等等都分离了出来,
            在正式环境下,通过CDN,确实有了一些明显的提升,所以说大家可以进行尝试。
            // 在html引入script标签后。在vue的配置中,进行声明
            configureWebpack: {
              externals: {
                'echarts': 'echarts' // 配置使用CDN
              }
            }
            
30.项目结构分析
    gshop(脚手架2)
        |-- build : webpack 相关的配置文件夹(基本不需要修改) 
        |-- config: webpack 相关的配置文件夹(基本不需要修改) 
            |-- index.js: 指定后台服务的端口号和静态资源文件夹 
        |-- node_modules 
        |-- src 
            |-- components------------非路由组件文件夹 
                |-- FooterGuide---------------底部组件文件夹 
                    |-- FooterGuide.vue--------底部组件 vue |-- 
            pages-----------------路由组件文件夹 
                |-- Msite---------------首页组件文件夹 
                    |-- Msite.vue--------首页组件 vue 
                |-- Search----------------搜索组件文件夹 
                    |-- Search.vue---------搜索组件 vue 
                |-- Order--------------订单组件文件夹 
                    |-- Order.vue-------订单组件 vue 
                |-- Profile--------------个人组件文件夹 
                    |-- Profile.vue-------个人组件 vue |-- 
            App.vue---------------应用根组件 vue 
            |-- main.js---------------应用入口 js
        |-- static: 静态资源文件夹 
        |-- .babelrc: babel 的配置文件 
        |-- .editorconfig: 通过编辑器的编码/格式进行一定的配置 
        |-- .eslintignore: eslint 检查忽略的配置 
        |-- .eslintrc.js: eslint 检查的配置 
        |-- .gitignore: git 版本管制忽略的配置 
        |-- index.html: 主页面文件 
        |-- package.json: 应用包配置文件 
        |-- README.md: 应用描述说明的 readme 文件
    shop-client(脚手架3)
        |-- node_modules
        |-- public: 任何放置在 public 文件夹的静态资源都会被简单的复制,而不经过 webpack。你需要通过绝对路径来引用它们。
           |-- index.html: 主页面文件
        |-- src
           |-- main.js: 应用入口js
        |-- babel.config.js: babel的配置文件
        |-- vue.config.js: vue的配置文件
        |-- .gitignore: git版本管制忽略的配置
        |-- package.json: 应用包配置文件 
        |-- README.md: 应用描述说明的readme文件

31.vue.config.js文件配置
    const isProduction = process.env.NODE_ENV === 'production';
    const UglifyJSPlugin = require('uglifyjs-webpack-plugin');
    const cdn = {
        css: ["https://cdnjs.cloudflare.com/ajax/libs/element-ui/2.8.2/theme-chalk/index.css"],
        js: [
            'https://cdn.bootcss.com/vue/2.5.17/vue.runtime.min.js',
            'https://cdn.bootcss.com/vue-router/3.0.1/vue-router.min.js',
            'https://cdn.bootcss.com/vuex/3.0.1/vuex.min.js',
            'https://cdn.bootcss.com/axios/0.18.0/axios.min.js',
                    'https://cdn.bootcss.com/element-ui/2.8.2/index.js',
                    'https://cdn.bootcss.com/echarts/3.8.5/echarts.min.js'
        ]
    }
    module.exports = {
      // 基本路径
      // '/'绝对路径,'./'或''相对路径
      publicPath: process.env.NODE_ENV === 'production' ? '/' : './',
        
      // 运行 vue-cli-service build 时生成的生产环境构建文件的目录
      // 默认构建前清除文件夹(构建时传入 --no-clean 可关闭该行为
      outputDir: 'dist',
     
      // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录
      assetsDir: 'static',
     
      // 指定生成的 index.html 的输出路径 (相对于 outputDir),也可以是一个绝对路径
      indexPath: 'index.html',
     
      // 生成的静态资源在它们的文件名中包含了 hash 以便更好的控制缓存
      filenameHashing: true,
     
      // 当在 multi-page 模式下构建时,webpack 配置会包含不一样的插件
      // (这时会存在多个 html-webpack-plugin 和 preload-webpack-plugin 的实例)。
      // 如果你试图修改这些插件的选项,请确认运行 vue inspect
      pages: {
        index: {
          // page 的入口
          entry: 'src/pages/index/index.js',
          // 模板来源
          template: 'src/pages/index/index.html',
          // 在 dist 的输出为 index.html
          filename: 'index.html',
          // 当使用 title 选项时,
          // template 中的 title 标签需要是 <%= htmlWebpackPlugin.options.title %>
          title: '首页',
          // 在这个页面中包含的块,默认情况下会包含
          // 提取出来的通用 chunk 和 vendor chunk。
          chunks: ['chunk-vendors', 'chunk-common', 'index']
        },
        // 当使用只有入口的字符串格式时,
        // 模板会被推导为 `public/subpage.html`
        // 并且如果找不到的话,就回退到 `public/index.html`。
        // 输出文件名会被推导为 `subpage.html`。
        
        // 多入口时,接着写子页面
        //subpage: 'src/subpage/main.js'
      },
      
      // eslint-loader 是否在保存的时候检查
      lintOnSave: true,
     
      // 是否使用包含运行时编译器的Vue核心的构建
      runtimeCompiler: false,
     
      // 默认情况下 babel-loader 忽略其中的所有文件 node_modules,
      // 想要通过 Babel 显式转译一个依赖,可以在这个选项中列出来
      transpileDependencies: [],
     
      // 生产环境 sourceMap
      productionSourceMap: false,
      
      // 跨域设置 
      // 可取值参考: https://developer.mozilla.org/zh-CN/docs/Web/HTML/CORS_settings_attributes
      crossorigin: undefined,
      
      // 构建后的文件是部署在 CDN 上的,启用该选项可以提供额外的安全性, 默认false
      integrity: false,
      
      // webpack 配置,键值对象时会合并配置,为方法时会改写配置
      // https://cli.vuejs.org/guide/webpack.html#simple-configuration
      //configureWebpack: (config) => {},
     
      // webpack 链接 API,用于生成和修改 webapck 配置
      // https://github.com/mozilla-neutrino/webpack-chain
      chainWebpack: (config) => {
          if (isProduction) {
            // 删除预加载
            config.plugins.delete('preload');
            config.plugins.delete('prefetch');
            // 压缩代码
            config.optimization.minimize(true);
            // 分割代码
            config.optimization.splitChunks({
                chunks: 'all'
            })
            // 生产环境注入cdn
            config.plugin('html')
                .tap(args => {
                    args[0].cdn = cdn;
                    return args;
                });
        }
        // 因为是多页面,所以取消 chunks,每个页面只对应一个单独的 JS / CSS
        config.optimization
          .splitChunks({
            cacheGroups: {}
          });
     
        // 'src/lib' 目录下为外部库文件,不参与 eslint 检测
        config.module
          .rule('eslint')
          .exclude
          .add('/Users/maybexia/Downloads/FE/community_built-in/src/lib')
          .end()
      },
     
      configureWebpack: config => {
            if (isProduction) {
                // 用cdn方式引入
                config.externals = {
                    'vue': 'Vue',
                    'vuex': 'Vuex',
                    'vue-router': 'VueRouter',
                    'axios': 'axios',
                                    'element-ui': 'ELEMENT',
                                    'echarts': 'echarts'
                }
                // 为生产环境修改配置...
                config.plugins.push(
                    //生产环境自动删除console
                                     new UglifyJSPlugin({
                        uglifyOptions: {
                            compress: {
                                warnings: true,
                                drop_console: true,
                                drop_debugger: true,
                                pure_funcs: ['console.log']//移除console
                            }
                        },
                        sourceMap: false,
                        parallel: true
                    })
                );
            } else {
                // 为开发环境修改配置...
            }
      },
     
      // 配置高于chainWebpack中关于 css loader 的配置
      css: {
        // false 时只有 *.module.[ext] 结尾的文件才会被视作 CSS Modules 模块
        // true 时可以去掉文件名中的 .module, 并将所有的 *.(css|scss|sass|less|styl(us)?) 文件视为 CSS Modules 模块
        modules: false,
        
         // 是否使用 css 分离插件 ExtractTextPlugin,采用独立样式文件载入,不采用