欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

16、vue之分页组件(含勾选、过滤、ES6写法),Vue框架源码概览,Vue之Observer、Dep和Watcher,Vue.set应用实例,基于ElementUI的vue自定义组件

程序员文章站 2022-04-22 10:51:07
...

16、vue之分页组件(含勾选、过滤、ES6写法),Vue框架源码概览,Vue之Observer、Dep和Watcher,Vue.set应用实例,基于ElementUI的vue自定义组件、el-dialog三层弹窗、各弹窗的区别

一、vue之分页组件(含勾选、过滤,单页面)
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>勾选和分页组件之vue2.6.10版</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <style>
    table{
      border-collapse: collapse;
      border: 1px solid #cbcbcb;
      width:1000px;
    }
    table td,table th {
      padding: 5px;
      border: 1px solid #cbcbcb;
    }
    table thead {
      background-color: #e0e0e0;
      color: #000;
      text-align: left;
    }
    .filter{
      width:998px;
      border:1px solid gray;
      padding:10px 0px;
    }
    .line{
      display:flex
    }
    .group{
      width:330px;
    }
    .label{
      display: inline-block;
      width:120px;
      height: 24px;
      line-height: 24px;
      text-align: right;
    }
    .input{
      display: inline-block;
      width:180px;
      height: 24px;
      line-height: 24px;
      border-radius: 3px;
    }
    .select{
      display: inline-block;
      width:188px;
      height: 26px;
      line-height: 26x;
      border-radius: 3px;
    }
  </style>
</head>
<body>
<div id="app">
  <div style="padding-bottom:5px;color:red"> 
    <button style="color:red" @click="checkDatasOne.getResultOfCheckAndFilter(divideDatasOne.isShowFilter,divideDatasOne.filterOptions)">获取勾选和过滤结果</button> 
    <span>{{checkDatasOne.toServerDatas}}</span>
  </div>
  <div style="padding-bottom:5px">
    <img :src="checkDatasOne.stateAllPages&&checkDatasOne.allExcludedIds.length===0?checkImg.yes:checkImg.no" @click="checkDatasOne.clickAllPages(divideDatasOne.tableDatas)"/>
    <span>{{checkDatasOne.textAllPages}}</span> 
  </div>
  <div style="padding-bottom:5px">
    <button @click="divideDatasOne.toggleShowFilter()">{{divideDatasOne.isShowFilter?'关闭过滤':'使用过滤'}}</button>
    <button @click="divideDatasOne.emptyFilterOptions({value5:10})">清空过滤</button>  
    <button @click="divideDatasOne.request(1)">刷新</button> 
  </div>
  <div style="margin-bottom:5px" class="filter" v-show="divideDatasOne.isShowFilter">
    <div class="line">
      <div class="group">
        <label class="label">标签</label>
        <input class="input" type="text" v-model="divideDatasOne.filterOptions.value1" />
      </div>
      <div class="group">
        <label class="label">这就是长标签</label>
        <input class="input" type="text" v-model="divideDatasOne.filterOptions.value2" />
      </div>
      <div class="group">
        <label class="label">标签</label>
        <input class="input" type="text" v-model="divideDatasOne.filterOptions.value3" />
      </div>
    </div>
    <div class="line" style="padding-top: 10px;">
      <div class="group">
        <label class="label">这就是长标签</label>
        <input class="input" type="text" v-model="divideDatasOne.filterOptions.value4" />
      </div>
      <div class="group">
        <label class="label">下拉框</label>
        <select class="select" v-model="divideDatasOne.filterOptions.value5">
          <option v-for="item in selectOptions" :value="item.back">{{item.front}}</option>
        </select>
      </div>
      <div class="group">
        <label class="label"></label>
        <button style="width:188px;height:28px" @click="divideDatasOne.request(1)">过滤</button>
      </div>
    </div>
  </div>
  <div style="height:353px;width:1000px">
    <table>
      <thead>
      <tr>
        <th><img :src="checkDatasOne.stateThisPage?checkImg.yes:checkImg.no" 
          @click="checkDatasOne.clickThisPage(divideDatasOne.tableDatas,divideDatasOne.allItemsNum)"/></th>
        <th>序号</th>
        <th>数据1</th>
        <th>数据2</th>
        <th>数据3</th>
        <th>数据4</th>
        <th>数据5</th>
        <th>数据6</th>
      </tr>
      </thead>
      <tbody>
      <tr v-for="(data,index) in divideDatasOne.tableDatas">
        <td><img :src="data.state?checkImg.yes:checkImg.no" @click="checkDatasOne.clickSingleItem(data,divideDatasOne.tableDatas,divideDatasOne.allItemsNum)"/></td>
        <td>{{(divideDatasOne.nowPageNum-1)*10 + (index+1)}} </td>
        <td>{{ data.key1 }}</td>
        <td>{{ data.key2 }}</td>
        <td>{{ data.key3 }}</td>
        <td>{{ data.key4 }}</td>
        <td>{{ data.key5 }}</td>
        <td>{{ data.key6 }}</td>
      </tr>
      </tbody>
    </table> 
  </div>
  <divide-page :divide-datas="divideDatasOne" :check-datas="checkDatasOne" :fixed-datas="fixedDatas"></divide-page>
</div>
</body>
<script>
  new Vue({
    el: '#app',
    data(){
      return {
        divideDatasOne:{
          nowPageNum:0,
          allPagesNum:0,
          allItemsNum:0,
          tableDatas:[],
          filterOptions:{value5:10},
          isShowFilter:false,
          otherDatas:{}
        },
        checkDatasOne:{
          idKey: 'id',//每条数据的唯一标志
          stateThisPage: false,//当前页所有项是否全选
          allIncludedIds: [],//所有被选中数据的ID构成的数组
          allExcludedIds: [],//所有没被选中数据的ID构成的数组
          textAllPages: '全选未启用,没有选择任何项!',//复选框被点击后的提示文字。
          stateAllPages: false,//复选框被点击后的提示文字。
          toServerDatas: null,
        },
      }
    },
    methods: {

    },
    created(){
      this.fixedDatas = {
        
      };
      this.selectOptions = [
        { back: 10, front: '来' },
        { back: 20, front: '来自于' },
        { back: 30, front: '来自于国内' },
        { back: 40, front: '来自于国内攻击' },
        { back: 50, front: '来自于国内攻击-2' }
      ];
      this.checkImg = {
        yes: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAA+UlEQVQoFZWSMU4DMRBF/584G7QSRcIxuAZKEykNEiUVHVTQRaKh4AIcgAvQpkukVDlBOAYNSGSlXXuwpViyYYFdS9aMZ/6bsezh5HZ3T2KhqkfosEhWqnjkyd1u3xWKdQMsfaEAB0Zilf8swfdU0w0klmpGpz1BvpbHcklbPf8Okts0CfJtWBTz/Yc++Jc8S3PZVQfKGwiuvMD6XYsMzm1dT/1jXKdQ8E0asHRrAzOzbC6UGINWHPQp1UQ/6wjF2LpmJSKfhti4Bi8+lhWP4I+gAqV1uqSi8j9WRuF3m3eMWVUJBeKxzUoYn7bEX7HDyPmB7QEHbRjyL+/+VnuXDUFOAAAAAElFTkSuQmCC',
        no: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAADqADAAQAAAABAAAADgAAAAC98Dn6AAAAbklEQVQoFWM8c+ZMLQMDQxUQcwAxMeAHUFEbC5CoYmNj02ZmZn5FjK6/f/+K/fr16ypIIwdIk7a29hdiNF69ehWkjIOJGMXY1IxqxBYqULEhFDiglPMDlIygKQKPryBSILUgPSCNbaC0B6RJSuQAbowizhJuOsAAAAAASUVORK5CYII=',
      }
    },
    components: {
      dividePage: {
        props: {
          divideDatas: {
            type: Object,
            default: {}
          },
          checkDatas: {
            type: Object,
            default: {}
          },
          fixedDatas: {
            type: Object,
            default: {}
          }
        },
        template: `
          <div v-show="divideDatas.allPagesNum>=1" style="display:flex;width:1000px;margin-top:20px;">
            <div>
              <button 
                v-show="divideDatas.allPagesNum>10" 
                @click="clickDividePage('front') " 
                :disabled="divideDatas.nowPageNum===1"
              >上一页</button>
              <button  
                :disabled="number==='...'"
                v-for="number in divideArray" 
                @click="clickDividePage(number)" 
                :style="{marginRight:'5px',color:number===divideDatas.nowPageNum?'red':'gray'}"
              >{{ number }}</button>
              <button 
                v-show="divideDatas.allPagesNum>10" 
                @click="clickDividePage('back')"  
                :disabled="divideDatas.nowPageNum===divideDatas.allPagesNum"
              >下一页</button>
            </div>
            <div style="display:flex; flex:1; justify-content:flex-end;">
              <div style="margin-right:20px;">
                <span>转到第</span>
                <input type="text" v-model="customString" @keydown="clickDividePage('leap',$event)" style="width:30px;">
                <span>页</span>
                <button @click="clickDividePage('leap',{which:13})">Go</button>
              </div>
              <div>
                <span>每页显示</span>
                <select v-model="divideDatas.eachPageItemsNum">
                  <option v-for="item in numOptions" :value="item.back">{{item.front}}</option>
                </select>
                <span>条,</span>
              </div>
              <div>
                <span>{{frontMoreText}}</span>
                <span>{{totalText}}</span>
                <span>{{divideDatas.allItemsNum}}</span>
                <span>{{totalUnit}}</span>
                <span>{{backMoreText}}</span>
              </div>
            </div>   
          </div
        `,
        data() {
          return {
            customString:''
          }
        },
        created(){
          var that = this;
          //1、请求配置
          this.url = this.fixedDatas.url || '';
          this.method = this.fixedDatas.method || 'post';
          this.isShowParams = this.fixedDatas.isShowParams || false;//显式还是隐式传参。有时需要在请求发出前手动改变。
          //2、响应配置(前端通过这个配置,获取后台的数据)
          this.nowPageNumKey = this.fixedDatas.nowPageNumKey || 'nowPageNumKey';//来自服务器的当前页码key
          this.allPagesNumKey = this.fixedDatas.allPagesNumKey || 'allPagesNumKey';//来自服务器的所有页页数key
          this.allItemsNumKey = this.fixedDatas.allItemsNumKey || 'allItemsNumKey';//来自服务器的所有页数据数key
          this.eachPageItemsNumKey = this.fixedDatas.eachPageItemsNumKey || 'eachPageItemsNumKey';//来自服务器的每页最多数据数key
          this.tableDatasKey = this.fixedDatas.tableDatasKey || 'tableDatasKey';//来自服务器的表格数据key
          //3、以下配置使用哪种转圈方式(前端根据需要决定,不受后台影响)
          this.partCircle = this.fixedDatas.partCircle;//局部是否转圈。this.fixedDatas.partCircle=$scope.partCircle={isShow =false}。
          this.isUsePartCircle = this.fixedDatas.isUsePartCircle;//局部是否转圈,由当前页的一个变量控制
          this.isUseWholeCircle = this.fixedDatas.isUseWholeCircle;//全局是否转圈,由本项目的一个服务控制
          //4、初始化以下数据,供页面使用(前端根据需要决定,不受后台影响)
          this.frontMoreText = this.fixedDatas.frontMoreText || "";//('文字 ')或者("文字 "+result.numOne+" 文字 ")
          this.totalText = this.fixedDatas.totalText || "";//'共'
          this.totalUnit = this.fixedDatas.totalUnit || '条';//总数据的单位
          this.backMoreText = this.fixedDatas.backMoreText || "";//(' 文字')或者("文字 "+result.numThree+" 文字")
          this.numOptions = [
            { back: 10, front: 10 },
            { back: 20, front: 20 },
            { back: 30, front: 30 },
            { back: 40, front: 40 },
            { back: 50, front: 50 }
          ];
          var data=[];
          for(var i=1;i<=144;i++){
            var obj={
              id:'id'+i,
              key1:'数据'+(i+0),
              key2:'数据'+(i+1),
              key3:'数据'+(i+2),
              key4:'数据'+(i+3),
              key5:'数据'+(i+4),
              key6:'数据'+(i+5),
              key7:'数据'+(i+6),
            };
            data.push(obj)
          }
          var result={
            tableDatasKey:[],
            nowPageNumKey:5,
            allPagesNumKey:15,
            allItemsNumKey:144,
            eachPageItemsNum:10,
          };
          this.request = this.divideDatas.request = function (pageNum) {
            //此处向后台发送请求,
            //1、返回正确结果result
            if(that.divideDatas.trueCb){
              that.divideDatas.trueCb(result)
            }else{
              result.tableDatasKey = data.slice((pageNum-1)*10,(pageNum)*10);
              that.customString = pageNum||result.nowPageNumKey;
              that.divideDatas.tableDatas = result.tableDatasKey;
              that.divideDatas.nowPageNum = pageNum||result.nowPageNumKey;
              that.divideDatas.allPagesNum = result.allPagesNumKey;
              that.divideDatas.allItemsNum = result.allItemsNumKey;
              that.divideDatas.eachPageItemsNum = result.eachPageItemsNum;
              if(that.checkDatas && that.checkDatas.signCheckbox){
                that.checkDatas.signCheckbox(that.divideDatas.tableDatas)
              }
             
            }
            that.createDividePage();
            //2、返回错误结果result
            if(that.divideDatas.errorCb){
              that.divideDatas.errorCb(result)
            }
          };
          if (!this.divideDatas.isNoInit) {
            this.request(1);
          };
          this.divideDatas.toggleShowFilter = function () {
            this.isShowFilter = !this.isShowFilter;
            if (!this.isShowFilter) {
              this.request(1);
            }
          };
          this.divideDatas.emptyFilterOptions = function (extraObject) {
            //清空选项时,所有值恢复成默认
            for(var key in this.filterOptions){
              this.filterOptions[key] = undefined;
            };
            if (extraObject) {
              //小部分选项的默认值不是undefined
              for(var key in extraObject){
                this.filterOptions[key] = extraObject[key];
              };
            };
            this.request(1);
          }; 
          this.checkDatas.init=function(){//点击“刷新”、“过滤”、“清除过滤”时执行
            this.idKey = idKey ? idKey : 'id';
            this.allIncludedIds = [];
            this.allExcludedIds = [];
            this.textAllPages = '全选未启用,没有选择任何项!';
            this.stateAllPages = false;
            this.stateThisPage = false;
          };
          this.checkDatas.clickAllPages = function (itemArray) {//所有页所有条目全选复选框被点击时执行的函数
            if(this.stateAllPages){
              if(this.allExcludedIds.length>0){
                this.stateAllPages = true;
                this.stateThisPage = true;
                this.textAllPages= '全选已启用,没有排除任何项!';
                itemArray.forEach(function (item) {
                  item.state = true;
                });
              }else if(this.allExcludedIds.length==0){
                this.stateAllPages = false;
                this.stateThisPage = false;
                this.textAllPages= '全选未启用,没有选择任何项!';
                itemArray.forEach(function (item) {
                  item.state = false;
                });
              }
            }else{
              this.stateAllPages = true;
              this.stateThisPage = true;
              this.textAllPages= '全选已启用,没有排除任何项!';
              itemArray.forEach(function (item) {
                item.state = true;
              });
            }
            this.allExcludedIds = [];
            this.allIncludedIds = [];
          };
          this.checkDatas.clickThisPage = function (itemsArray,allItemsNum) {//当前页所有条目全选复选框被点击时执行的函数
            var that = this;
            this.stateThisPage = !this.stateThisPage
            itemsArray.forEach(function (item) {
              item.state = that.stateThisPage;
              if (item.state) {
                that.delID(item[that.idKey], that.allExcludedIds);
                that.addID(item[that.idKey], that.allIncludedIds);
              } else {
                that.delID(item[that.idKey], that.allIncludedIds);
                that.addID(item[that.idKey], that.allExcludedIds);
              }
            });
            if(this.stateAllPages){
              if(this.stateThisPage && this.allExcludedIds.length === 0){
                this.textAllPages = '全选已启用,没有排除任何项!';
              }else{
                this.textAllPages = '全选已启用,已排除'+ this.allExcludedIds.length + '项!排除项的ID为:' + this.allExcludedIds;
              }
            }else{
              if(!this.stateThisPage && this.allIncludedIds.length === 0){
                this.textAllPages='全选未启用,没有选择任何项!';
              }else{
                this.textAllPages = '全选未启用,已选择' + this.allIncludedIds.length + '项!选择项的ID为:' + this.allIncludedIds;
              }
            }
          };
          this.checkDatas.clickSingleItem = function (item, itemsArray, allItemsNum) {//当前页单个条目复选框被点击时执行的函数
            var that = this;
            item.state = !item.state;
            if (item.state) {
              this.stateThisPage = true;
              this.addID(item[this.idKey], this.allIncludedIds);
              this.delID(item[this.idKey], this.allExcludedIds);
              itemsArray.forEach( function (item) {
                if (!item.state) {
                  that.stateThisPage = false;
                }
              });
            } else {
              this.stateThisPage = false;
              this.addID(item[this.idKey], this.allExcludedIds);
              this.delID(item[this.idKey], this.allIncludedIds);
            }
            if(this.stateAllPages){
              if(this.stateThisPage && this.allExcludedIds.length === 0){
                this.textAllPages = '全选已启用,没有排除任何项!';
              }else{
                this.textAllPages = '全选已启用,已排除'+ this.allExcludedIds.length + '项!排除项的ID为:' + this.allExcludedIds;
              }
            }else{
              if(!this.stateThisPage && this.allIncludedIds.length === 0){
                this.textAllPages='全选未启用,没有选择任何项!';
              }else{
                this.textAllPages = '全选未启用,已选择' + this.allIncludedIds.length + '项!选择项的ID为:' + this.allIncludedIds;
              }
            }
          };
          this.checkDatas.signCheckbox = function (itemsArray) {//标注当前页被选中的条目,在翻页成功后执行。
            var that = this;
            if(this.stateAllPages){
              this.stateThisPage = true;
              itemsArray.forEach(function (item) {
                var thisID = item[that.idKey];
                var index = that.allExcludedIds.indexOf(thisID);
                if (index > -1) {
                  item.state = false;
                  that.stateThisPage = false;
                } else {
                  item.state = true;
                }
              });
            }else{
              this.stateThisPage = true;
              itemsArray.forEach( function (item) {
                var thisID = item[that.idKey];
                var index = that.allIncludedIds.indexOf(thisID);
                if (index === -1) {
                  item.state = false;
                  that.stateThisPage = false;
                }
              });
            }
          };
          this.checkDatas.addID = function (id, idArray) {
            var index = idArray.indexOf(id);
            if (index === -1) {
              idArray.push(id);//如果当前页的单项既有勾选又有非勾选,这时勾选当前页全选,需要这个判断,以免重复添加
            }
          };
          this.checkDatas.delID = function (id, idArray) {
            var index = idArray.indexOf(id);
            if (index > -1) {
              idArray.splice(index, 1)
            }
          };
          this.checkDatas.getResultOfCheckAndFilter = function (isShowFilter,filterOptions) {//获取发送给后台的所有参数。
            var toServerDatas;
            var allIncludedIds = that.deepClone(this.allIncludedIds);
            var allExcludedIds = that.deepClone(this.allExcludedIds);
            if (!this.stateAllPages) {
              if (allIncludedIds.length === 0) {
                //return 弹窗告知:没有勾选项
              }
              toServerDatas = {
                isSelectAll: false,
                allIncludedIds: allIncludedIds,
              }
            }else {
              toServerDatas = { //exclude
                isSelectAll: true,
                allExcludedIds: allExcludedIds,
              };
            }
            if (isShowFilter) {
              for(var key in filterOptions){
                toServerDatas[key]=filterOptions[key]
              }
            }
            this.toServerDatas=toServerDatas;//这行代码在实际项目中不需要
            return toServerDatas;
          }
        },
        methods: {
          deepClone : function (arrayOrObject) {
            function isArray(value) { return {}.toString.call(value) === "[object Array]"; }
            function isObject(value) { return {}.toString.call(value) === "[object Object]"; }
            var target = null;
            if (isArray(arrayOrObject)) target = [];
            if (isObject(arrayOrObject)) target = {};
            for (var key in arrayOrObject) {
              var value = arrayOrObject[key];
              if (isArray(value) || isObject(value)) {
                target[key] = deepClone(value);
              } else {
                target[key] = value;
              }
            }
            return target;
          },
          createDividePage : function () {
            var divideArray = [];
            var allPagesNum = this.divideDatas.allPagesNum;
            var nowPageNum = this.divideDatas.nowPageNum;
            if (allPagesNum >= 1 && allPagesNum <= 10) {
              for (var i = 1; i <= allPagesNum; i++) {
                divideArray.push(i);
              }
            } else if (allPagesNum >= 11) {
              if (nowPageNum > 6) {
                divideArray.push(1);
                divideArray.push(2);
                divideArray.push(3);
                divideArray.push('...');
                divideArray.push(nowPageNum - 1);
                divideArray.push(nowPageNum);
              } else {
                for (i = 1; i <= nowPageNum; i++) {
                  divideArray.push(i);
                }
              }
              // 以上当前页的左边,以下当前页的右边
              if (allPagesNum - nowPageNum >= 6) {
                divideArray.push(nowPageNum + 1);
                divideArray.push(nowPageNum + 2);
                divideArray.push('...');
                divideArray.push(allPagesNum - 2);
                divideArray.push(allPagesNum - 1);
                divideArray.push(allPagesNum);
              } else {
                for (var i = nowPageNum + 1; i <= allPagesNum; i++) {
                  divideArray.push(i);
                }
              }
            }
            this.divideArray = divideArray;
          },
          clickDividePage : function (stringOfNum, event) {
            var allPagesNum = this.divideDatas.allPagesNum;
            var nowPageNum = this.divideDatas.nowPageNum;
            if (stringOfNum === 'front' && nowPageNum != 1) {
              nowPageNum--;
            } else if (stringOfNum === 'back' && nowPageNum != allPagesNum) {
              nowPageNum++;
            } else if (stringOfNum === 'leap') {
              if (event.which != 13) return;//不拦截情形:(1)聚焦输入框、按“Enter”键时;(2)点击“GO”时
              var customNum = Math.ceil(parseFloat(this.customString));
              if (customNum < 1 || customNum == 'NaN') {
                nowPageNum = 1;//不给提示
              } else if(customNum > allPagesNum) {
                nowPageNum = allPagesNum;//不给提示
              } else {
                nowPageNum = customNum;
              }
            } else {
              nowPageNum = Math.ceil(parseFloat(stringOfNum));
            }
            this.request(nowPageNum);
          },
        }
      }
    },
  })
</script>
</html>
附:vue之分页组件(含勾选、过滤、ES6写法)
<template>
  <div v-show="divideDatas.allPagesNum>=1" style="display:flex;width:1000px;margin-top:20px;">
    
  </div>
</template>
<script>
import comTab from '@/components/ComTab'
export default {
  name: 'dividePage',
  components: {
    comTab
  },
  props: {
    
  },
  data() {
    return {

    }
  },
  created(){

  },
  methods: {
    
  }
}
</script>
<style rel="stylesheet/scss" lang="scss" scoped>
</style>

二、Vue框架源码概览
1、自动执行
(1)initMixin(Vue) 
=> Vue.prototype._init 
2、手动执行new Vue(options)   
new Vue(options) 
=> this._init(options) 
=> initState(vm) 
=> initData();initProps();initComputed();initMethods();initWatch();vm.$mount(vm.$options.el)
(1)initData(vm) 
=> data=getData(data, vm);observe(data, true); 
(2)initProps(vm, opts.props) 
=> defineReactive$$1(props, key, value); proxy(vm, "_props", key)
(3)initComputed(vm, opts.computed) 
=> vm._computedWatchers[key] = new Watcher(vm,getter) 
=> defineComputed(vm, key, userDef) 
=> Object.defineProperty(target, key, sharedPropertyDefinition)
(4)initMethods(vm, opts.methods) 
=> vm[key] = Function.prototype.bind(methods[key], vm);
(5)initWatch(vm, opts.watch) 
=> createWatcher(vm, key, handler) 
=> vm.$watch(expOrFn, handler, options) 
=> new Watcher(vm, expOrFn, cb, options) 
=> vm._watchers.push(this);popTarget() 
(6)vm.$mount(vm.$options.el)
=> mountComponent(this, el, hydrating)
=> updateComponent
=> new Watcher(vm,updateComponent);
=> Watcher 2个作用,(1)初始化的时候会执行回调函数,(2)实例中,监测的数据发生变化的时候执行回调函数。
 
三、Vue之Observer、Dep和Watcher
1、Observer实例化时,给data和props绑定响应式
(1)vue数据赋值给Observer实例的value属性:this.value = value;
(2)Observer实例赋值给vue数据的__ob__属性:def(value, '__ob__', this);
(3)Dep实例赋值给Observer实例的dep属性:this.dep = new Dep();
(4)给数据绑定getter和setter
2、Dep实例化时,
(1)获取id属性
(2)初始化订阅属性
(3)给监听函数提供操作方法
3、Watcher实例化时,给vue实例绑定Watcher实例
(1)vue实例赋值给Watcher实例的vm属性:this.vm = vm;
(2)Watcher实例赋值到vue实例的_watchers属性里:vm._watchers.push(this);
(3)Watcher实例赋值给Dep.target:Dep.target = this;
(4)计算旧值:this.value = this.get();
(5)给依赖提供操作方法
4、实例化过程
(1)实现响应式:初始化过程中initData(vm)、initProps(vm,opts.props)执行时,Observer将数据定义为响应式并建立依赖池。
(2)产生依赖:初始化过程中,initComputed(vm,opts.computed)除了将opts.computed定义为响应式,还和initWatch(vm,opts.watch)一样,执行new Watcher,产生并存放依赖项,用this.get()计算旧值。
(3)注入依赖:初始化过程最后,除了执行new Watcher,还通过render函数对“用到的变量”进行getter操作、通过dep.depend向watcher注入依赖。https://www.cnblogs.com/Qooo/p/13711967.html  
(4)使用依赖:数据变化进行setter操作、通过dep.notify执行watcher里的依赖,执行update方法,更新页面。
总之,(1)页面初次渲染时,用初始值和由初始值获取的计算属性值渲染页面,当某个初始值改变时,执行watch监听函数,改变别的初始值,最后再次获取计算属性值并渲染页面。(2)["push","pop","shift","unshift","splice","sort","reverse"];
var arrayProto = Array.prototype; 
var arrayMethods = Object.create(arrayProto);//{}.__proto__ = arrayProto;
[].__proto__ = arrayMethods;

四、Vue.set应用实例 
Vue框架只对数组方法中的'push','pop','shift','unshift','splice','sort','reverse'实现了响应式。通过索引改变数组,没有执行发布函数,没法执行订阅函数,需要通过Vue.set来执行发布函数,实现响应式。 
<!DOCTYPE html>
<html>
<head lang="en">
  <meta charset="UTF-8">
  <title></title>
  <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>
  <div id="app2">
    <p v-for="item in items" :key="item.id">
      {{item.message}}
    </p>
    <button class="btn" @click="btn2Click()">动态赋值</button><br />
    <button class="btn" @click="btn3Click()">为data新增属性</button>
  </div>
</body>
</html>
<script>
  var vm2 = new Vue({
    el: "#app2",
    data: {
      items: [
        { message: "Test one", id: "1" },
        { message: "Test two", id: "2" },
        { message: "Test three", id: "3" }
      ]
    },
    methods: {
      btn2Click: function () {
        Vue.set(this.items, 0, { message: "Change Test", id: '10' })
      },
      btn3Click: function () {
        var itemLen = this.items.length;
        Vue.set(this.items, itemLen, { message: "Test add attr", id: itemLen });
      }
    }
  });
</script>
 
<!DOCTYPE html>
<html>
<head>
  <title></title>
  <meta charset="utf-8">
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
</head>
<body> 
  <div id="box">{{msg}}||{{reMsg}}</div>
  <script type="text/javascript">
    var vm = new Vue({
      el:'#box',
      data:{
        msg:'12345'
      },
      computed:{
        reMsg:function(instance){
          console.log(instance===this);//true
          return this.msg.split('').reverse().join('')
        }
      }
    });
  </script>
</body>
</html>

五、基于ElementUI的vue自定义组件子改父
1、通过属性传函数参数来实现
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>子改父:通过属性传参</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script>
  <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet">
  <style>
    #app{
      display: flex;
      justify-content: space-between;
    }
    .parent, .child{
      width: 45%;
    }
    .el-card{
      height: 100%;
    }
  </style>
</head>
<body>
<div style="margin: 30px 0;">本案例改编自https://www.bbsmax.com/A/kjdwmRaOJN/</div>
<div style="margin-bottom: 30px;">
  <div>总逻辑 </div>
  <div>父组件通过属性传参,给子组件传值 </div>
  <div>父组件通过属性传参,给子组件传属性函数 </div>
  <div>触发子组件的某个事件,执行属性函数,改变父组件的值 </div>
</div>
<div id="app">
  <div class="parent">
    <el-card>
      <div slot="header">
        <span>父组件</span>
      </div>
      <el-input v-model="ParentMsg"></el-input>
      <el-button type="primary" @click="changeChild" style="margin-top: 44px">父组件改变子组件</el-button>
    </el-card>
  </div>
  <div class="child">
    <el-card>
      <div slot="header">
        <span>子组件</span>
      </div>
      <child :self-msg="childMsg" :fn="changeParent"></child>
    </el-card>
  </div>
</div>
</body>
<script>
  new Vue({
    el: '#app',
    data(){
      return {
        ParentMsg:'父组件的内容',
        childMsg:'父组件传给子组件的内容'
      }
    },
    methods: {
      changeParent(data){
        this.ParentMsg = data,
        this.childMsg = '子-组件传给子组件的内容'
      },
      changeChild(){
        this.ParentMsg = '父-组件传给父组件的内容',
        this.childMsg = '父-组件传给子组件的内容'
      }
    },
    components: {
      child:{
        props: { 
          selfMsg: {
            type: String,
            default: ''
          },
          fn: {
            type: Function,
            default: function(){}
          }
        },
        template: `
          <div>
            <p>{{selfMsg}}</p>
            <el-button type='primary' @click='fromChild' style='margin-top: 30px'>子组件改变父组件</el-button>
          </div>
        `,
        data () {
          return {
            
          }
        },
        methods:{
          fromChild () {
            this.fn('子-组件传给父组件的内容')
          }
        }
      }
    },
  })
</script>
</html>
2、通过属性传自定义事件来实现
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>Title</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script>
  <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet">
  <style>
    #app{
      display: flex;
      justify-content: space-between;
    }
    .parent, .child{
      width: 45%;
    }
    .el-card{
      height: 100%;
    }
  </style>
</head>
<body>
<div style="margin: 30px 0;">本案例改编自https://www.bbsmax.com/A/kjdwmRaOJN/</div>
<div style="margin-bottom: 30px;">
  <div>总逻辑 </div>
  <div>父组件通过属性传参,给子组件传值 </div>
  <div>父组件通过属性传参,给子组件传自定义事件及执行函数 </div>
  <div>触发子组件的某个事件,发射自定义事件,并给执行函数传参,改变父组件的值 </div>
</div>
<div id="app">
  <div class="parent">
    <el-card>
      <div slot="header">
        <span>父组件</span>
      </div>
      <el-input v-model="ParentMsg"></el-input>
      <el-button type="primary" @click="changeChild" style="margin-top: 44px">父组件改变子组件</el-button>
    </el-card>
  </div>
  <div class="child">
    <el-card>
      <div slot="header">
        <span>子组件</span>
      </div>
      <child :self-msg="childMsg" @from-child="changeParent"></child>
    </el-card>
  </div>
</div>
</body>
<script>
  new Vue({
    el: '#app',
    data(){
      return {
        ParentMsg:'父组件的内容',
        childMsg:'父组件传给子组件的内容'
      }
    },
    methods: {
      changeParent(data){
        this.ParentMsg = data,
        this.childMsg = '子-组件传给子组件的内容'
      },
      changeChild(){
        this.ParentMsg = '父-组件传给父组件的内容',
        this.childMsg = '父-组件传给子组件的内容'
      }
    },
    components: {
      child:{
        props: { 
          selfMsg: {
            type: String,
            default: ''
          }
        },
        template: `
          <div>
            <p>{{selfMsg}}</p>
            <el-button type='primary' @click='fromChild' style='margin-top: 30px'>子组件改变父组件</el-button>
          </div>
        `,
        data () {
          return {
            
          }
        },
        methods:{
          fromChild () {
            this.$emit('from-child', '子-组件传给父组件的内容')
          }
        }
      }
    },
  })
</script>
</html>

四、el-dialog三层弹窗
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <title>vue2.6.10组件el-dialog之三层弹窗</title>
  <script src="https://cdn.bootcss.com/vue/2.6.10/vue.min.js"></script>
  <script src="https://cdn.bootcss.com/element-ui/2.10.1/index.js"></script>
  <link href="https://cdn.bootcss.com/element-ui/2.10.1/theme-chalk/index.css" rel="stylesheet">
  <style>
    #app{
      display: flex;
      justify-content: space-between;
    }
    .parent, .child{
      width: 45%;
    }
    .el-card{
      height: 100%;
    }
  </style>
</head>
<body>
<div id="app">
    <el-button type="text" @click="outerVisible = true">点击打开外层弹窗</el-button>
    <!-- 以下是外层 -->
    <el-dialog 
      width="70%" 
      title="外层" 
      :visible.sync="outerVisible">
      这是外层
      <!-- 以下是中层 -->
      <el-dialog
        width="50%"
        title="中层"
        :visible.sync="middleVisible"
        append-to-body>
        这是中层
        <!-- 以下是内层 -->
        <el-dialog
        width="30%"
        title="内层"
        :visible.sync="innerVisible"
        append-to-body>
        这是内层
          <div slot="footer" class="dialog-footer">
            <el-button @click="innerVisible = false">关闭内层</el-button>
            <el-button type="primary" @click="innerVisible = false">关闭内层</el-button>
          </div>
        </el-dialog>
        <!-- 以上是内层 -->
        <div slot="footer" class="dialog-footer">
          <el-button @click="middleVisible = false">关闭中层</el-button>
          <el-button type="primary" @click="innerVisible = true">打开内层</el-button>
        </div>
      </el-dialog>
      <!-- 以上是中层 -->
      <div slot="footer" class="dialog-footer">
        <el-button @click="outerVisible = false">关闭外层</el-button>
        <el-button type="primary" @click="middleVisible = true">打开中层</el-button>
      </div>
    </el-dialog>
    <!-- 以上是外层 -->
</div>
</body>
<script>
  new Vue({
    el: '#app',
    data() {
      return {
        outerVisible: false,
        middleVisible: false,
        innerVisible: false
      };
    },
    methods: {
      
    },
    components: {
      
    },
  })
</script>
</html>

五、elementUI各弹窗的区别
1、第1组(3秒钟后自动消失)
(1)Message 消息提示,常用于主动操作后的反馈提示。
(2)Notification 通知,常用于系统级通知的被动提醒。
2、第2组(点击确认后消失)
(1)MessageBox 弹窗,模拟系统的消息提示框alert、confirm和prompt而实现的一套模态对话框组件,用于消息提示、确认消息和提交内容。
(2)Dialog 对话框,弹出较为复杂的内容.
3、第3组(非悬停时消失)
(1)Tooltip 文字提示,常用于展示鼠标hover时的提示信息。
(2)Popover 弹出框,与Tooltip类似。