前端面试题总结以及vue在工作中常见的错误

    <!-- 
     js部分:
     1.JavaScript this 指针、闭包、作用域 
     this:指向调用上下文 闭包:内层作用域可以访问外层作用域的变量
     闭包:内层作用域可以访问外层作用域的变量 
     作用域:定义一个函数就开辟了一个局部作用域,整个 js 执行环境有一个全局作用域
      
     2.闭包是什么,有什么特性,对页面有什么影响 闭包就是能够读取其他函数内部变量的函数。 闭包的缺点:滥用闭包函数会造成内存泄露,因为闭包中引用到的包裹函数中定义的变量都 永远不会被释放,所以我们应该在必要的时候,及时释放这个闭包函数
       闭包的写法:
       function outer()
       { 
           var num = 1;
        function inner()
        { 
           var n = 2; 
           alert(n + num); 
        }
        return inner; 
        }
        outer()();
     3.如何阻止事件冒泡和默认事件
      e. stopPropagation();//标准浏览器
      event.canceBubble=true;//ie9 之前 阻止默认事件: 为了不让 a 点击之后跳转,我们就要给他的点击事件进行阻止 return false e.preventDefault();(注:在写vue按钮点击跳转路由的时候某些按钮也可能会用到这个特性)

     
     4.、添加 删除 替换 插入到某个接点的方法 
     obj.appendChild() 
     obj.insertBefore() //原生的 js 中不提供 insertAfter(); 
     obj.replaceChild()//替换 
     obj.removeChild()//删除

     5.javascript 的本地对象,内置对象和宿主对象
     本地对象为 array obj regexp 等可以 new 实例化 
     内置对象为 gload Math 等不可以实例化的 
     宿主为浏览器自带的 document,window 等

     6.document load 和 document ready 的区别
     Document.onload 是在结构和样式加载完才执行 js window.onload:不仅仅要在结构和样式加载完,还要执行完所有的样式、图片这些资源文 件,全部加载完才会触发 window.onload 事件 
     Document.ready 原生中没有这个方法,jquery 中有 $().ready(function)

     7.js的同源策略
     一段脚本只能读取来自于同一来源的窗口和文档的属性,这里的同一来源指的是主机名、协 议和端口号的组合 http,ftp:协议 主机名;localhost 端口名:8 同源策略带来的麻烦:ajax 在不 0:http 协议的默认端口 https:默认端口是 8083 同域名下的请求无法实现, 如果说想要请求其他来源的 js 文件,或者 json 数据,那么可以通过 jsonp 来解决


     8.undefined 会在以下三种情况下产生: 
     1、 一个变量定义了却没有被赋值 
     2、 想要获取一个对象上不存在的属性或者方法: 
     3、 一个数组中没有被赋值的元素 注意区分 undefined 跟 not defnied(语法错误)是不一样的

     9.Javascript 如何实现继承? 
     原型链继承,借用构造函数继承,组合继承,寄生式继承,寄生组合继承

     10.Javascript 创建对象的几种方式? 
     工厂方式,构造函数方式,原型模式,混合构造函数原型模式,动态原型方式

     11.js 延迟加载的方式有哪些? 
     1. defer 和 async 
     2. 动态创建 DOM 方式(创建 script,插入到 DOM 中,加载完毕后 callBack) 
     3. 按需异步载入 js


     12.那些操作会造成内存泄漏
     内存泄漏指任何对象在您不再拥有或需要它之后仍然存在。 
     垃圾回收器定期扫描对象,并计算引用了每个对象的其他对象的数量。
     如果一个对象的引用 数量为 0(没有其他对象引用过该对象),
     或对该对象的惟一引用是循环的,那么该对象的 内存即可回收。 
      1. setTimeout 的第一个参数使用字符串而非函数的话,会引发内存泄漏。
      2. 闭包 
      3. 控制台日志 
      4. 循环(在两个对象彼此引用且彼此保留时,就会产生一个循环)

     13.
     js数组各类方法:
     concat() 连接两个或更多的数组,并返回结果。 
     join() 把数组的所有元素放入一个字符串。元素通过指定的分隔符进行分隔。 
     pop() 删除并返回数组的最后一个元素 
     push() 向数组的末尾添加一个或更多元素,并返回新的长度。 
     reverse() 颠倒数组中元素的顺序。 
     shift() 删除并返回数组的第一个元素 
     slice() 从某个已有的数组返回选定的元素 
     sort() 对数组的元素进行排序 
     splice() 删除元素,并向数组添加新元素。 
     toSource() 返回该对象的源代码。 
     toString() 把数组转换为字符串,并返回结果。 toLocaleString() 把数组转换为本地数组,并返回结果。 
     unshift() 向数组的开头添加一个或更多元素,并返回新的长度。 
     valueOf() 返回数组对象的原始值

     14.解释 jsonp 的原理,以及为什么不是真正的 ajax 动态创建 script 标签,回调函数 Ajax 是页面无刷新请求数据操作

     15.javascript 的本地对象,内置对象和宿主对象 本地对象为 array obj regexp 等可以 new 实例化 
     内置对象为 gload Math 等不可以实例化的 宿主为浏览器自带的 document,window 等

     16.javascript 中的垃圾回收机制? 答:在 Javascript 中,如果一个对象不再被引用,
     那么这个对象就会被 GC 回收。如果两个对象互相引用,而不再 被第 3 者所引用,
     那么这两个互相引用的 对象也会被回收。
     因为函数 a 被 b 引用,b 又被 a 外的 c 引用,这就是为什么 函数 a 执行后不会被回收的原因

     17.JS 的继承性 
     window.color = 'red'; 
     var o = {color: 'blue'}; 
     function sayColor(){ alert(this.color); }
     考察点:1、this 的指向 2、call 的用法 sayColor(); 
     //red sayColor.call(this); //red this 指向的是 window 对象 sayColor.call(window); //red sayColor.call(o); //blue
     
     18.bind(), live(), delegate()的区别
      bind: 绑定事件,对新添加的事件不起作用,方法用于将一个处理程序附加 到每个匹配元素的事件上并返回 jQuery 对象。 
      live: 方法将一个事件处理程序附加到与当前选择器匹配的所有元素(包含 现有的或将来添加的)的指定事件上并返回 jQuery 对象。 
      delegate: 方法基于一组特定的根元素将处理程序附加到匹配选择器的所有 元素(现有的或将来的)的一个或多个事件上。
      最佳实现:on() off()

    19.link和import的区别:
    区别 1:link 是 XHTML 标签,除了加载 CSS 外,还可以定义 RSS 等其他事务;@import 属于 CSS 范畴,只能加载 CSS。 
    区别 2:link 引用 CSS 时,在页面载入时同时加载;@import 需要页面网页完全载入 以后加载。 
    区别 3:link 是 XHTML 标签,无兼容问题;@import 是在 CSS2.1 提出的,低版本的浏 览器不支持。
    区别 4:link 支持使用 Javascript 控制 DOM 去改变样式;而@import 不支持。

    20.135、window.onload 和 document.ready 的区别?
     load 要等到图片和包含的文件都加在进来之后执行; 
     ready 是不包含图片和非文字文件的文档结构准备好就执行;

    21.解析 URL 成一个对象? 
    String.prototype.urlQueryString = function(){
         var url = this.split('?')[1].split('&'), len = url.length; this.url = {}; for(var i = 0; i < len; i += 1)
         { 
             var cell = url[i].split('='), key = cell[0], val = cell[1]; this.url[''+key+''] = val; 
            }
            return this.url; }var url = '?name=12&age=23'; 
            console.log(url.urlQueryString().age); 
            
            
    22.请用原生 js 实现ajax的跨域请求:
    $.ajax({
    url: ,
    type: '',
    dataType: '',
    data: {
          
    },
    success: function(){
         
    },
    error: function(){
          
    }
    })
    // 第一步: 获得XMLHttpRequest对象
            var ajax = new XMLHttpRequest();
            // 第二步: 设置状态监听函数
            ajax.onreadystatechange = function(){
                console.log(ajax.readyState);
                console.log(ajax.status);
                // 第五步:在监听函数中,判断readyState=4 && status=200表示请求成功
                if(ajax.readyState==4 && ajax.status==200){
                    // 第六步: 使用responseText、responseXML接受响应数据,并使用原生JS操作DOM进行显示
                    console.log(ajax.responseText);
                    console.log(ajax.responseXML);// 返回不是XML,显示null
                    console.log(JSON.parse(ajax.responseText));
                    console.log(eval("("+ajax.responseText+")"));
                }
            }
            // 第三步: open一个链接
            ajax.open("GET","h51701.json",false);//true异步请求,false同步
            
            // 第四步: send一个请求。 可以发送对象和字符串,不需要传递数据发送null
            ajax.send(null);


   注解:
   1. open(method, url, async) 方法需要三个参数:

  method:发送请求所使用的方法(GET或POST);与POST相比,GET更简单也更快,并且在大部分情况下都能用;然而,在以下情况中,请使用POST请求:

    无法使用缓存文件(更新服务器上的文件或数据库)
    向服务器发送大量数据(POST 没有数据量限制)
    发送包含未知字符的用户输入时,POST 比 GET 更稳定也更可靠

    url:规定服务器端脚本的 URL(该文件可以是任何类型的文件,比如 .txt 和 .xml,或者服务器脚本文件,比如 .asp 和 .php (在传回响应之前,能够在服务器上执行任务));

    async:规定应当对请求进行异步(true)或同步(false)处理;true是在等待服务器响应时执行其他脚本,当响应就绪后对响应进行处理;false是等待服务器响应再执行。

     2. send() 方法可将请求送往服务器。

     3. onreadystatechange:存有处理服务器响应的函数,每当 readyState 改变时,onreadystatechange 函数就会被执行。

     4. readyState:存有服务器响应的状态信息。

    0: 请求未初始化
    1: 服务器连接已建立
    2: 请求已接收
    3: 请求处理中
    4: 请求已完成,且响应已就绪
    5. responseText:获得字符串形式的响应数据。
    
    =========================================================================================
    ajax部分:

    23.请尽可能详尽的解释 ajax 的工作原理
    思路:先解释异步,再解释 ajax 如何使用 Ajax 的原理简单来说通过 XmlHttpRequest 对象来向服务器发异步请求,从服务器获得数据, 
    然后用javascript来操作DOM而更新页面。这其中最关键的一步就是从服务器获得请求数据。
     要清楚这个过程和原理,我们必须对 XMLHttpRequest 有所了解。 
     XMLHttpRequest 是 ajax 的核心机制,
     它是在 IE5 中首先引入的,是一种支持异步请求的 技术。
     简单的说,也就是 javascript 可以及时向服务器提出请求和处理响应,而不阻塞用户。 达到无刷新的效果
        

    24.HTTP 协议中,GET 和 POST 有什么区别?分别适用什么场 景 ? 
    get 传送的数据长度有限制,post 没有 get 通过 url 传递,在浏览器地址栏可见,
    post 是在报文中传递 
    适用场景: 
    post 一般用于表单提交 
    get 一般用于简单的数据查询,严格要求不是那么高的场景

    25.HTTP 状态消息 200 302 304 403 404 500 分别表示什么 
    200:请求已成功,请求所希望的响应头或数据体将随此响应返回。
    302:请求的资源临时从不同的 URI 响应请求。由于这样的重定向是临时的,客户端应当 继续向原有地址发送以后的请求。只有在 Cache-Control 或 Expires 中进行了指定的情况下, 这个响应才是可缓存的 
    304:如果客户端发送了一个带条件的 GET 请求且该请求已被允许,而文档的内容(自上 次访问以来或者根据请求的条件)并没有改变,则服务器应当返回这个状态码。
    304 响应禁 止包含消息体,因此始终以消息头后的第一个空行结尾。 
    403:服务器已经理解请求,但是拒绝执行它。 
    404:请求失败,请求所希望得到的资源未被在服务器上发现。 
    500:服务器遇到了一个未曾预料的状况,导致了它无法完成对请求的处理。一般来说,这 个问题都会在服务器端的源代码出现错误时出现。
    
    
    26.HTTP 协 议 中 , header 信 息 里 面 , 怎 么 控 制 页 面 失 效 时 间 
    (last-modified,cache-control,Expires 分别代表什么) 
    Last-Modified 文 档的最后改动时间。
    客户可以通过 If-Modified-Since 请求头提供一个日期,该请求将被视为 一个条件 GET,只有改动时间迟于指定时间的文档 才会返回,否则返回一个 304(Not Modified) 状态。
    Last-Modified 也可用 setDateHeader 方法来设置。 
    Expires 应该在什么时候认为文档已经过期,从而不再缓存它?

    27.ajax的创建:
    使 用 ajax 原 生 方 式 发 送 请 求 主 要 通 过 XMLHttpRequest( 标 准 浏 览 器 ) 、
     ActiveXObject(IE 浏览器)对象实现异步通信效果 基本步骤:
      var xhr =null;//创建对象 
      if(window.XMLHttpRequest){ 
          xhr = new XMLHttpRequest(); 
     }else{ 
        xhr = new ActiveXObject("Microsoft.XMLHTTP"); 
    }
    xhr.open(“方式”,”地址”,”标志位”);//初始化请求 
    xhr.setRequestHeader(“”,””);//设置 http 头信息 
    xhr.onreadystatechange =function(){}//指定回调函数
    xhr.send();//发送请求

    28.同步和异步的区别?
    同步:
    阻塞的 -张三叫李四去吃饭,李四一直忙得不停,
    张三一直等着,直到李四忙完两个人一块去吃饭 =浏览器向服务器请求数据,
    服务器比较忙,浏览器一直等着(页面白屏),直到服务器返 回数据,浏览器才能显示页面 

    异步:
    非阻塞的 -张三叫李四去吃饭,李四在忙,张三说了一声然后自己就去吃饭了,李四忙完后自己去吃 =浏览器向服务器请求数据,服务器比较忙,浏览器可以自如的干原来的事情(显示页面), 
    服务器返回数据的时候通知浏览器一声,浏览器把返回的数据再渲染到页面,局部更新

    29.异步加载
    1. 异步加载的方案: 动态插入 script 标签 2. 通过 ajax 去获取 js 代码,然后通过 eval 执行 3. script 标签上添加 defer 或者 async 属性 4. 创建并插入 iframe,让它异步执行 js

    30.Ajax 的最大的特点是什么。 
    Ajax 可以实现异步通信效果,实现页面局部刷新,带来更好的用户体验;按需获取数 据,节约带宽资源;

    31.ajax 的缺点
    1、ajax 不支持浏览器 back 按钮。
    2、安全问题 AJAX 暴露了与服务器交互的细节。
    3、对搜索引擎的支持比较弱。 
    4、破坏了程序的异常机制

    32.解释 jsonp 的原理,以及为什么不是真正的 ajax
    Jsonp 并不是一种数据格式,而 json 是一种数据格式,jsonp 是用来解决跨域获取数据 的一种解决方案,具体是通过动态创建 script 标签,然后通过标签的 src 属性获取 js 文件 中的 js 脚本,该脚本的内容是一个函数调用,参数就是服务器返回的数据,为了处理这些 返回的数据,需要事先在页面定义好回调函数,本质上使用的并不是 ajax 技术
    
    33.什么是 Ajax 和 JSON,它们的优缺点。
    Ajax 是全称是 asynchronous JavaScript andXML,即异步 JavaScript 和 xml,用于在 Web 页面中实现异步数据交互,实现页面局部刷新。 优点:可以使得页面不重载全部内容的情况下加载局部内容,降低数据传输量,避免用 户不断刷新或者跳转页面,提高用户体验
    缺点:对搜索引擎不友好;要实现 ajax 下的前后退功能成本较大;可能造成请求数的 增加跨域问题限制; JSON 是一种轻量级的数据交换格式,ECMA 的一个子集 优点:轻量级、易于人的阅读和编写,便于机器(JavaScript)解析,支持复合数据类 型(数组、对象、字符串、数字)
   
    34.一个页面从输入 URL 到页面加载显示完成,这个过程中都发生了什么?
    分为 4 个步骤: 
    1. 当发送一个 URL 请求时,不管这个 URL 是 Web 页面的 URL 还是 Web 页面上每个资源 的 URL,浏览器都会开启一个线程来处理这个请求,同时在远程 DNS 服务器上启动一个 DNS 查询。这能使浏览器获得请求对应的 IP 地址。 
    2. 浏览器与远程 Web 服务器通过 TCP 三次握手协商来建立一个 TCP/IP 连接。该握手包 括一个同步报文,一个同步-应答报文和一个应答报文,这三个报文在 浏览器和服务器之间 传递。该握手首先由客户端尝试建立起通信,而后服务器应答并接受客户端的请求,最后由 客户端发出该请求已经被接受的报文。 
    3. 一旦 TCP/IP 连接建立,浏览器会通过该连接向远程服务器发送 HTTP 的 GET 请求。远 程服务器找到资源并使用 HTTP 响应返回该资源,值为 200 的 HTTP 响应状态表示一个正 确的响应。 
    4. 此时,Web 服务器提供资源服务,客户端开始下载资源

    JS高级:
    35.实现一个函数 clone,可以对 JavaScript 中的 5 种主要的数据类型(包括 Number、String、Object、Array、Boolean)进行值复制
    考察点 1:对于基本数据类型和引用数据类型在内存中存放的是值还是指针这一区 别是否清楚
    考察点 2:是否知道如何判断一个变量是什么类型的 
    考察点 3:递归算法的设计 
    // 方法一: Object.prototype.clone = function(){
         var o = this.constructor === Array ? [] : {}; for(var e in this){

              o[e] = typeof this[e] === "object" ? this[e].clone() : this[e]; 
            }return o; }


    //方法二:/*** 克隆一个对象 * @param Obj * @returns */ 
            function clone(Obj) { 
                var buf; if (Obj instanceof Array)
             { buf = [];//创建一个空的数组 var i = Obj.length; while (i--) 
                 { 
                     buf[i] = clone(Obj[i]); 
                }return buf; 

                  }else if (Obj instanceof Object){ 
                      buf = {};//创建一个空对象 for (var k in Obj) { 
                         //为这个对象添加新的属性 buf[k] = clone(Obj[k]); 
                        }
                return buf; }else{ //普通变量直接赋值 return Obj; } }


     36.、如何消除一个数组里面重复的元素?
      var arr=[1,2,3,3,4,4,5,5,6,1,9,3,25,4]; 
      function deRepeat(){ 
          var newArr=[]; var obj={}; 
          var index=0; 
          var l=arr.length; for(var i=0;i<l;i++){ 
         if(obj[arr[i]]==undefined) { 
            obj[arr[i]]=1; newArr[index++]=arr[i]; 
        } else if(obj[arr[i]]==1) continue; 
    }
     return newArr; }var newArr2=deRepeat(arr); alert(newArr2); //输出 1,2,3,4,5,6,9,25


    37.小贤是一条可爱的小狗(Dog),它的叫声很好听(wow),每次看到主人的时候 就会乖乖叫一声(yelp)。从这段描述可以得到以下对象: 
       function Dog() { this.wow = function() { 
        alert(’Wow’); }this.yelp = function() 
        { 
            this.wow(); 
        }
    }

    小芒和小贤一样,原来也是一条可爱的小狗,可是突然有一天疯了(MadDog),一看到人就会 每隔半秒叫一声(wow)地不停叫唤(yelp)。请根据描述,按示例的形式用代码来实。(继承, 原型,setInterval) 
    function MadDog() { this.yelp = function() {
         var self = this; setInterval(function() 
         { 
             self.wow(); 
        }, 500);  
        } 
            }MadDog.prototype = new Dog(); //for test var dog = new Dog(); 
     dog.yelp(); 
    var madDog = new MadDog(); 
    madDog.yelp();

    38.、编写一个 JavaScript 函数,输入指定类型的选择器(仅需支持 id,class,tagName 三种简单 CSS 选择器,无需兼容组合选择器)可以返回匹配的 DOM 节点,需考虑 浏览器兼容性和性能。
    
    原生一部分源码分析可看可不看

    /*** @param selector {String} 传入的 CSS 选择器。* @return {Array}*/ 
    var query = function(selector) { 
        var reg = /^(#)?(\.)?(\w+)$/img; var regResult = reg.exec(selector); 
        var result = []; //如果是 id 选择器 if(regResult[1]) 
        { 
            if(regResult[3]) { if(typeof document.querySelector === "function") 
        { 
            result.push(document.querySelector(regResult[3])); 
        }else { 
            result.push(document.getElementById(regResult[3])); 
        } 
    }}
    //如果是 class 选择器 else if(regResult[2]) 

    { 
        if(regResult[3]) { 
            if(typeof document.getElementsByClassName === 'function') 
            { 
                var doms = document.getElementsByClassName(regResult[3]); 
                if(doms) { result = converToArray(doms); 
                } 
            }
    //如果不支持 getElementsByClassName 函数 
    else {
    var allDoms = document.getElementsByTagName("*") ; 
    for(var i = 0, len = allDoms.length; i < len; i++) 
    {
         if(allDoms[i].className.search(new RegExp(regResult[2])) > -1) 
    { result.push(allDoms[i]); 
    } 
    } 
    } 
    } 
    }
    //如果是标签选择器 
    else if(regResult[3]) { 
        var doms = document.getElementsByTagName(regResult[3].toLowerCase()); if(doms) 
    { 
            result = converToArray(doms); 
    } 
    }return result; 
    }
    function converToArray(nodes){ 
        var array = null; try{array = Array.prototype.slice.call(nodes,0);//针对非 IE 浏览器 
    }catch(ex){ array = new Array(); for( var i = 0 ,len = nodes.length; i < len ; i++ ) {
         array.push(nodes[i]) 
        } 
    }return array; 
     }  if(window.addEventListener){ 
         var addListener = function(el,type,listener,useCapture){ 
             el.addEventListener(type,listener,useCapture);
    }; 
    }else if(document.all){ 
    addListener = function(el,type,listener){ 
        el.attachEvent("on"+type,function(){ listener.apply(el); }); } }
    
    39.定义一个 log 方法,让它可以代理 console.log 的方法。 
    function log(msg) { 
        console.log(msg); 
    }
    log("hello world!") // hello world! 
    如果要传入多个参数呢?显然上面的方法不能满足要求,所以更好的方法是: 
    function log(){ 
    console.log.apply(console, arguments); };到此,追问 apply 和 call 方法的异同。 对于 apply 和 call 两者在作用上是相同的,即是调用一个对象的一个方法,以另一个对象 替换当前对象。将一个函数的对象上下文从初始的上下文改变为由 thisObj 指定的新对象。 但两者在参数上有区别的。对于第一个参数意义都一样,但对第二个参数: apply 传入的 是一个参数数组,也就是将多个参数组合成为一个数组传入,而 call 则作为 call 的参数传 入(从第二个参数开始)。 如 func.call(func1,var1,var2,var3)对应的 apply 写法为: func.apply(func1,[var1,var2,var3])
   
    40.在 Javascript 中什么是伪数组?如何将伪数组转化为标准数组?
    function log(){
         var args = Array.prototype.slice.call(arguments); //为了使用 unshift 数组方法,将 argument 转化为真正的数组 
         args.unshift('(app)'); 
    console.log.apply(console, args); 
     };

    41.模块化开发怎么做? 
    理解模块化开发模式:浏览器端 requirejs,seajs;
    服务器端 nodejs;ES6 模块化;fis、 webpack 等前端整体模块化解决方案;grunt、gulp

  ================================================================================
    ES6

    42. rest 参数用来代替 arguments
    /**
    * 作用与 arguments 类似
    */
    function add(...args){
     console.log(args);
    }
    add(1,2,3,4,5);
    /**
    * rest 参数必须是最后一个形参
    */
    function minus(a,b,...args){
     console.log(a,b,args);
    }
    minus(100,1,2,3,4,5,19);

    43.spread 扩展运算符
    扩展运算符(spread)也是三个点(...)。它好比 rest 参数的逆运算,将一个数组转为用逗号分隔的参数序列,对数组进行解包。
    /**
    * 展开数组
    */ 
    let tfboys = ['小明','小张','小刘'];
    function fn(){
     console.log(arguments);
    }
    fn(...tfboys)
    /**
    * 展开对象
    */
    let skillOne = {
     q: '1',
    };
    let skillTwo = {
     w: '2'
    };
    let skillThree = {
     e: '3'
    };
    let skillFour = {
     r: '4'
    };
    let gailun = {...skillOne, ...skillTwo,...skillThree,...skillFour};


   44.Symbol 基本使用
   Symbol 特点
    (1) Symbol 的值是唯一的,用来解决命名冲突的问题
    (2) Symbol 值不能与其他数据进行运算
    (3) Symbol 定义 的 对象属 性 不能 使 用 for…in 循 环遍 历 ,但 是可 以 使 用
    Reflect.ownKeys 来获取对象的所有键名
   
    //创建 Symbol
     let s1 = Symbol();
     console.log(s1, typeof s1);
     //添加标识的 Symbol
     let s2 = Symbol('小明');
     let s2_2 = Symbol('小李');
     console.log(s2 === s2_2);
     //使用 Symbol for 定义
     let s3 = Symbol.for('小明');
     let s3_2 = Symbol.for('小李');
     console.log(s3 === s3_2);

    45.ES6 还提供了 11 个内置的 Symbol 值
    (1) Symbol.hasInstance:当其他对象使用 instanceof 运算符,判断是否为该对象的实例时,会调用这个方法  
    (2) Symbol.isConcatSpreadable:对象的 Symbol.isConcatSpreadable 属性等于的是一个布尔值,表示该对象用于 Array.prototype.concat()时,是否可以展开。
    (3) Symbol.species:创建衍生对象时,会使用该属性
    (4) Symbol.match:当执行 str.match(myObject) 时,如果该属性存在,会调用它,返回该方法的返回值。
    (5) Symbol.replace:当该对象被 str.replace(myObject)方法调用时,会返回该方法的返回值。
    (6) Symbol.search:当该对象被 str.search (myObject)方法调用时,会返回该方法的返回值。
    (7) Symbol.split 当该对象被 str.split(myObject)方法调用时,会返回该方法的返回值。
    (8) Symbol.iterator:对象进行 for...of 循环时,会调用 Symbol.iterator 方法,返回该对象的默认遍历器
    (9) Symbol.toPrimitive:该对象被转为原始类型的值时,会调用这个方法,返回该对象对应的原始类型值。
    (10) Symbol. toStringTag:在该对象上面调用 toString 方法时,返回该方法的返回值
    (11) Symbol. unscopables:该对象指定了使用 with 关键字时,哪些属性会被 with环境排除

    46.迭代器
    遍历器(Iterator)就是一种机制。它是一种接口,为各种不同的数据结构提
    供统一的访问机制。任何数据结构只要部署 Iterator 接口,就可以完成遍历操作。

    (1) ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费
    (2) 原生具备 iterator 接口的数据(可用 for of 遍历)
    (a) Array
    (b) Arguments
    (c) Set
    (d) Map
    (e) String
    (f) TypedArray
    (g) NodeList
    (3) 工作原理
    (a) 创建一个指针对象,指向当前数据结构的起始位置
    (b) 第一次调用对象的 next 方法,指针自动指向数据结构的第一个成员
    (c) 接下来不断调用 next 方法,指针一直往后移动,直到指向最后一个成员
    (d) 每调用 next 方法返回一个包含 value 和 done 属性的对象

   46.生成器
   function * gen(){
    yield '小红';
    yield '小张';
    return '小刘'; 
    }
   let iterator = gen();
   console.log(iterator.next());
   console.log(iterator.next());
   console.log(iterator.next());
   


   //重点:有经验面试80%会问到
   47.Promise
   Promise 是 ES6 引入的异步编程的新解决方案。语法上 Promise 是一个构造函数,
   用来封装异步操作并可以获取其成功或失败的结果。
   (1) Promise 构造函数: Promise (excutor) {}
   (2) Promise.prototype.then 方法
   (3) Promise.prototype.catch 方法

   Promise解决回调地狱的问题
   //引入fs模块
  const fs = require('fs');
   
  //Promise函数
   
  const p = new Promise((resolve,reject)=>{
      fs.readFile("./resources/aaa.md",(err,data1)=>{
         fs.readFile("./resources/bbb.md",(err,data2)=>{
            fs.readFile("./resources/ccc.md",(err,data3)=>{
              let result = data1 + '\r\n' + data2 + '\r\n' + data3
                resolve(result);
            })
         })
      })
  })
   
  p.then(function(value){
     console.log(value.toString());
  },function(reason){
     console.warn("读取文件失败!!!");
  })
   
   
  //解决以上回调函数的问题
   
  function f1(){
      return new Promise((resolve,reject)=>{
          fs.readFile("./resources/aaa.md",(err,data)=>{
           if(err) return reject(err)
           resolve(data)
          })
      })
  }
   
  function f2(){
      return new Promise((resolve,reject)=>{
          fs.readFile("./resources/bbb.md",(err,data)=>{
              if(err) return reject(err)
              resolve(data);
          })
      })
  }
   
  function f3(){
      return new Promise((resolve,reject)=>{
          fs.readFile("./resources/ccc.md",(err,data)=>{
              if(err) return reject(err)
              resolve(data);
          });
      });
  }
   
   
   
  f1().then((value)=>{
     console.log(value.toString());
  },f2().then((value)=>{
      console.log(value.toString());
  },f3().then((value)=>{
      console.log(value.toString());
  })))

  48.Set
  它类似于数组,但成员的值都是唯一的,集合实现了 iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历,集合的属性和方法:
  (1) size 返回集合的元素个数
  (2) add 增加一个新元素,返回当前集合
  (3) delete 删除元素,返回 boolean 值
  (4) has 检测集合中是否包含某个元素,返回 boolean 值
  (5) clear 清空集合,返回 undefined

  49.Map
  它类似于对象,也是键值对的集合。但是“键”的范围不限于字符串,各种类型的值(包括对象)都可以当作键。Map 也实现了iterator 接口,所以可以使用『扩展运算符』和『for…of…』进行遍历。Map 的属性和方法
  (1) size 返回 Map 的元素个数
  (2) set 增加一个新元素,返回当前 Map
  (3) get 返回键名对象的键值
  (4) has 检测 Map 中是否包含某个元素,返回 boolean 值
  (5) clear 清空集合,返回 undefined

  50.class 类
  (1) class 声明类
  (2) constructor 定义构造函数初始化
  (3) extends 继承父类
  (4) super 调用父级构造方法
  (5) static 定义静态方法和属性
  (6) 父类方法可以重写

  51.Object 对象的方法
  (1) Object.is 比较两个值是否严格相等,与『===』行为基本一致(+0 与 NaN)
  (2) Object.assign 对象的合并,将源对象的所有可枚举属性,复制到目标对象
  (3) __proto__、setPrototypeOf、 setPrototypeOf 可以直接设置对象的原型

  52.async 和 await
  .async 函数
  1. async 函数的返回值为 promise 对象,
  2. promise 对象的结果由 async 函数执行的返回值决定

  .await 表达式
  1. await 必须写在 async 函数中
  2. await 右侧的表达式一般为 promise 对象
  3. await 返回的是 promise 成功的值
  4. await 的 promise 失败了, 就会抛出异常, 需要通过 try...catch 捕获处理

  53.正则表达式 dotAll 模式
  let str = `
  <ul>
  <li>
  <a>肖生克的救赎</a>
  <p>上映日期: 1994-09-10</p>
  </li>
  <li>
  <a>阿甘正传</a>
  <p>上映日期: 1994-07-06</p>
  </li>
  </ul>`;
  //声明正则
  const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs;
  //执行匹配
  const result = reg.exec(str);
  let result;
  let data = [];
  while(result = reg.exec(str)){
   data.push({title: result[1], time: result[2]});
  }
  //输出结果
  console.log(data);

  =============================================================================
   promise

   54.promise 的状态改变
   (1)pending 变为 resolved
   (2)pending 变为 rejected

   55.promise 的基本使用

   <script>
    // 1) 创建 promise 对象(pending 状态), 指定执行器函数
    const p = new Promise((resolve, reject) => {

    // 2) 在执行器函数中启动异步任务
    setTimeout(() => {
    const time = Date.now()

    // 3) 根据结果做不同处理
    // 3.1) 如果成功了, 调用 resolve(), 指定成功的 value, 变为 resolved 状 态
    if (time%2===1) {
    resolve('成功的值 '+ time) } else { // 3.2) 如果失败了, 调用 reject(), 指定失败的 reason, 变为
    rejected 状态
    reject('失败的值' + time) }
    }, 2000)
    })

   // 4) 能 promise 指定成功或失败的回调函数来获取成功的 vlaue 或失败的 reason
    p.then(
    value => { // 成功的回调函数 onResolved, 得到成功的 vlaue
    console.log('成功的 value: ', value)
    },
    reason => { // 失败的回调函数 onRejected, 得到失败的 reason
    console.log('失败的 reason: ', reason) } )

    </script>
    
    使用 2: 使用 promise 封装基于定时器的异步
    <script>
    function doDelay(time) {
    // 1. 创建 promise 对象
    return new Promise((resolve, reject) => {
    // 2. 启动异步任务
    console.log('启动异步任务')
    setTimeout(() => {
    console.log('延迟任务开始执行...')
    const time = Date.now() // 假设: 时间为奇数代表成功, 为偶数代表失败
    if (time %2=== 1) { // 成功了
    // 3. 1. 如果成功了, 调用 resolve()并传入成功的 value
    resolve('成功的数据 ' + time) } else { // 失败了
    // 3.2. 如果失败了, 调用 reject()并传入失败的 reason
    reject('失败的数据 ' + time) }
    }, time)
    })
    }
    const promise = doDelay(2000)
    promise.then(
    value => {
    console.log('成功的 value: ', value)
    },
    reason => {
    console.log('失败的 reason: ', reason)
    },
    )
   </script>

   重点
   56.使用 promise 封装 ajax 异步请求
   <script>
   /*
   可复用的发 ajax 请求的函数: xhr + promise
   */
   function promiseAjax(url) {
   return new Promise((resolve, reject) => {
   const xhr = new XMLHttpRequest()
   xhr.onreadystatechange = () => {
   if (xhr.readyState!==4) return
   const {status, response} = xhr
   // 请求成功, 调用 resolve(value)
   if (status>=200 && status<300) {
   resolve(JSON.parse(response))
   } else { // 请求失败, 调用 reject(reason)
   reject(new Error('请求失败: status: ' + status))
   } }
   xhr.open("GET", url)
   xhr.send()
   })
   }
  promiseAjax('后端接口').then(
  data => {
  console.log('显示成功数据', data)
  },
  error => {
  alert(error.message) } )
  </script>

  4.为什么要用 Promise?
  promise: 启动异步任务 => 返回promie对象 => 给promise对象绑定回调函数(甚至可以在异步任务结束后指定/多个)
  
  57. Promise API
  {
  1.Promise 构造函数: Promise (excutor) {}
  (1) executor 函数: 执行器 (resolve, reject) => {} 
  (2) resolve 函数: 内部定义成功时我们调用的函数 value => {}
  (3) reject 函数: 内部定义失败时我们调用的函数 reason => {}
  说明: executor 会在 Promise 内部立即同步调用,异步操作在执行器中执行

  2.Promise.prototype.then 方法: (onResolved, onRejected) => {}
  (1) onResolved 函数: 成功的回调函数 (value) => {}
  (2) onRejected 函数: 失败的回调函数 (reason) => {}
  说明: 指定用于得到成功 value 的成功回调和用于得到失败 reason 的失败回调
  返回一个新的 promise 对象

  3.Promise.prototype.catch 方法: (onRejected) => {}
  (1) onRejected 函数: 失败的回调函数 (reason) => {}
  说明: then()的语法糖, 相当于: then(undefined, onRejected)

  4. Promise.resolve 方法: (value) => {}
  (1) value: 成功的数据或 promise 对象
  说明: 返回一个成功/失败的 promise 对象

  5. Promise.reject 方法: (reason) => {}
  (1) reason: 失败的原因
  说明: 返回一个失败的 promise 对象

  6. Promise.all 方法: (promises) => {}
  (1) promises: 包含 n 个 promise 的数组
  说明: 返回一个新的 promise, 只有所有的 promise 都成功才成功, 只要有一个失败了就
  直接失败

  7.Promise.race 方法: (promises) => {}
  (1) promises: 包含 n 个 promise 的数组
  说明: 返回一个新的 promise, 第一个完成的 promise 的结果状态就是最终的结果状态

  }


  58. promise 的几个关键问题
  1. 如何改变 promise 的状态?
  (1) resolve(value): 如果当前是 pending 就会变为 resolved
  (2) reject(reason): 如果当前是 pending 就会变为 rejected
  (3) 抛出异常: 如果当前是 pending 就会变为 rejected

  2. 一个 promise 指定多个成功/失败回调函数, 都会调用吗? 当 promise 改变为对应状态时都会调用

  3.改变 promise 状态和指定回调函数谁先谁后?
  (1) 都有可能, 正常情况下是先指定回调再改变状态, 但也可以先改状态再指定回调
  (2) 如何先改状态再指定回调? ① 在执行器中直接调用 resolve()/reject() 延迟更长时间才调用 then()
  (3) 什么时候才能得到数据? ① 如果先指定的回调, 那当状态发生改变时, 回调函数就会调用, 得到数据如果先改变的状态, 那当指定回调时, 回调函数就会调用, 得到数据
  
  4.promise.then()返回的新 promise 的结果状态由什么决定?
  (1) 简单表达: 由 then()指定的回调函数执行的结果决定
  (2) 详细表达: ① 如果抛出异常, 新 promise 变为 rejected, reason 为抛出的异常如果返回的是非 promise 的任意值, 新 promise 变为 resolved, value 为返回的值如果返回的是另一个新 promise, 此 promise 的结果就会成为新 promise 的结果

  5.promise 如何串连多个操作任务?
  (1) promise 的 then()返回一个新的 promise, 可以开成 then()的链式调用
  (2) 通过 then 的链式调用串连多个同步/异步任务

  6. promise 异常传透?
  (1) 当使用 promise 的 then 链式调用时, 可以在最后指定失败的回调, 
  (2) 前面任何操作出了异常, 都会传到最后失败的回调中处理

  7. 中断 promise 链?
  (1) 当使用 promise 的 then 链式调用时, 在中间中断, 不再调用后面的回调函数
  (2) 办法: 在回调函数中返回一个 pendding 状态的 promise 对象
 
  ==========================================================================================
  axios

  59.axios 常用语法
  axios(config): 通用/最本质的发任意类型请求的方式
  axios(url[, config]): 可以只指定 url 发 get 请求
  axios.request(config): 等同于 axios(config)
  axios.get(url[, config]): 发 get 请求
  axios.delete(url[, config]): 发 delete 请求
  axios.post(url[, data, config]): 发 post 请求
  axios.put(url[, data, config]): 发 put 请求
  axios.defaults.xxx: 请求的默认全局配置
  axios.interceptors.request.use(): 添加请求拦截器
  axios.interceptors.response.use(): 添加响应拦截器
  axios.create([config]): 创建一个新的 axios(它没有下面的功能)
  axios.Cancel(): 用于创建取消请求的错误对象
  axios.CancelToken(): 用于创建取消请求的 token 对象
  axios.isCancel(): 是否是一个取消请求的错误
  axios.all(promises): 用于批量执行多个异步请求
  axios.spread(): 用来指定接收所有成功数据的回调函数的方法


  60.拦截器函数/ajax 请求/请求的回调函数的调用顺序
  (1). 说明: 调用 axios()并不是立即发送 ajax 请求, 而是需要经历一个较长的流程
  (2). 流程: 请求拦截器2 => 请求拦截器1 => 发ajax请求 => 响应拦截器1 => 响应拦截器 2 => 请求的回调
  (3). 注意: 此流程是通过 promise 串连起来的, 请求拦截器传递的是 config, 响应拦截器传递的是 response

  61.源码目录分析
    ├── /dist/ # 项目输出目录
  ├── /lib/ # 项目源码目录
  │ ├── /adapters/ # 定义请求的适配器 xhr、http
  │ │ ├── http.js # 实现 http 适配器(包装 http 包) │ │ └── xhr.js # 实现 xhr 适配器(包装 xhr 对象) │ ├── /cancel/ # 定义取消功能
  │ ├── /core/ # 一些核心功能
  │ │ ├── Axios.js # axios 的核心主类
  │ │ ├── dispatchRequest.js # 用来调用 http 请求适配器方法发送请求的函数
  │ │ ├── InterceptorManager.js # 拦截器的管理器
  │ │ └── settle.js # 根据 http 响应状态,改变 Promise 的状态
  │ ├── /helpers/ # 一些辅助方法
  │ ├── axios.js # 对外暴露接口
  │ ├── defaults.js # axios 的默认配置
  │ └── utils.js # 公用工具
  ├── package.json # 项目信息
  ├── index.d.ts # 配置 TypeScript 的声明文件
  └── index.js # 入口文件

  62.instance 与 axios 的区别?
  1. 相同: 
  (1) 都是一个能发任意请求的函数: request(config)
  (2) 都有发特定请求的各种方法: get()/post()/put()/delete()
  (3) 都有默认配置和拦截器的属性: defaults/interceptors
  2. 不同:
  (1) 默认配置很可能不一样
  (2) instance 没有 axios 后面添加的一些方法: create()/CancelToken()/all()

  63.整体运行流程
  (1)整体流程: 
  request(config) ==> dispatchRequest(config) ==> xhrAdapter(config)
  (2)request(config): 
  将请求拦截器 / dispatchRequest() / 响应拦截器 通过 promise 链串连起来, 返回 promise
  (3)dispatchRequest(config):
  转换请求数据 ===> 调用 xhrAdapter()发请求 ===> 请求返回后转换响应数据. 返回 promise
  (4)xhrAdapter(config):
  创建 XHR 对象, 根据 config 进行相应设置, 发送特定请求, 并接收响应数据, 返回 promise

  64.axios 的请求/响应拦截器是什么?
  1. 请求拦截器: 
   在真正发送请求前执行的回调函数
   可以对请求进行检查或配置进行特定处理
   成功的回调函数, 传递的默认是 config(也必须是)
   失败的回调函数, 传递的默认是 error

  2.响应拦截器
   在请求得到响应后执行的回调函数
   可以对响应数据进行特定处理
   成功的回调函数, 传递的默认是 response
   失败的回调函数, 传递的默认是 error

  3. axios 的请求/响应数据转换器是什么?
  1. 请求转换器: 对请求头和请求体数据进行特定处理的函数
   if (utils.isObject(data)) {
    setContentTypeIfUnset(headers, 'application/json;charset=utf-8');
    return JSON.stringify(data);
   }
   2. 响应转换器: 将响应体 json 字符串解析为 js 对象或数组的函数
   response.data = JSON.parse(response.data)
  
   ======================================================================================
   vue 
   64.在调用后端接口接口跨域的问题
   在config文件中index.js文件里第13行配置;
   起初是这样的:
   module.exports = {
   dev: {

    // Paths
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxyTable: {},  //第13行

    // Various Dev Server settings
    host: 'localhost', // can be overwritten by process.env.HOST
    port: 8080, // can be overwritten by process.env.PORT, if port is in use, a free one will be determined
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions-
    ....

    配置之后是这样的:
    proxyTable: {
      '/apis': {
      target: '接口的域名', 
      changeOrigin: true,  
      pathRewrite: {
        '^/apis': ''   
     } 
    }             
   },
   
   //接口的调用
    axios({
        method: 'get',
        url: '后端接口'
      }).then(function (response) {
        console.log(response)
    }
  })

  apis   //就代表了接口的头部信息
  
  65.Vue组件间的参数传递
  1、父组件与子组件传值
  父组件传给子组件:子组件通过props方法接受数据;
  子组件传给父组件:$emit方法传递参数
  

  66.vue路由的钩子函数
  首页可以控制导航跳转,beforeEach,afterEach等,一般用于页面title的修改。一些需要登录才能调整页面的重定向功能。
  beforeEach主要有3个参数to,from,next:
  to:route即将进入的目标路由对象,
  from:route当前导航正要离开的路由
  next:function一定要调用该方法resolve这个钩子。执行效果依赖next方法的调用参数。可以控制网页的跳转。

  67.vuex是什么?怎么使用?哪种功能场景使用它?
  只用来读取的状态集中放在store中;改变状态的方式是提交mutations,这是个同步的事物;异步逻辑应该封装在action中。
  在main.js引入store,注入。新建了一个目录store,….. export 。
  场景有:单页应用中,组件之间的状态、音乐播放、登录状态、加入购物车
  
  1.state
  Vuex 使用单一状态树,即每个应用将仅仅包含一个store 实例,但单一状态树和模块化并不冲突。存放的数据状态,不可以直接修改里面的数据。
  mutations

  mutations定义的方法动态修改Vuex 的 store 中的状态或数据。

  getters
  
  类似vue的计算属性,主要用来过滤一些数据。
  
  action
  
  actions可以理解为通过将mutations里面处里数据的方法变成可异步的处理数据的方法,简单的说就是异步操作数据。view 层通过 store.dispath 来分发 action。

  68.生命周期
  (1) beforeCreate : 实例创建之前,这个时候vue的实例上只有一些生命周期函数 , data和methods还不能使用

  (2) created : 创建结束的时候,vue实例上的data 和 methods就能正常使用了 最早可以在created里使用data和methods
  
  (3) beforeMount : 挂载之前 模板进行编译, 把指令循环插值语句都进行执行,生成一个虚拟的dom节点,在挂载之前,这个虚拟的dom被放在内存中
  
  (4) mounted : 挂载完毕 最早在这个生命周期函数里用dom节点 把上一步生成的虚拟的dom挂载进#app里
  
  (5) beforeUpdate : 数据更新之前之后指的是视图上数据的更新前后,数据更新之前 data里的数据已经发生变化,视图还没有更新
  
  (6) updated : 数据更新之后 视图也跟着变化了
  
  (7) beforeDestroy : 实例销毁之前
  
  (8) destroyed : 实例销毁之后

  69.Vue 如何去除url中的 #
  vue-router 默认使用 hash 模式,所以在路由加载的时候,项目中的 url 会自带 #。如果不想使用 #, 可以使用 vue-router 的另一种模式 history
  new Router({
    mode: 'history',
    routes: [ ]
  })

  70.Vue 组件 data 为什么必须是函数
  因为js本身的特性带来的,如果 data 是一个对象,那么由于对象本身属于引用类型,当我们修改其中的一个属性时,会影响到所有Vue实例的数据。如果将 data 作为一个函数返回一个对象,那么每一个实例的 data 属性都是独立的,不会相互影响了
 
  71.$route 和 $router 的区别
  $route是“路由信息对象”,包括path,params,hash,query,fullPath,matched,name等路由信息参数,而$router是“路由实例”对象,包括了路由的跳转方法,钩子函数等。
  
  72.描述async与await如何使用。
  async函数完全可以看作多个异步操作,包装成的一个 Promise 对象,而await命令就是内部then命令的语法糖
  
  async function timeout(ms) {
  await new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
  }

  async function asyncPrint(value, ms) {
  await timeout(ms);
  console.log(value);
  }

  asyncPrint('hello world', 50);

  73.Vue.js中ajax请求代码应该写在组件的methods中还是vuex的actions中?
  一种是:“请求后端接口”这个ajax代码应该是写在按钮的点击事件处理函数中,然后在这个处理函数里面提交mutations
  另一种是:直接将“请求后端接口”这个ajax代码写在actions中,actions中提交mutations,按钮的点击事件处理函数中只写一个分发actions的按钮


   74.工作某些需求传参数并调动接口
   (1)path中添加/:id来对应 this.$router.push 中path携带的参数获取this.$route.params.id
   (2)通过路由属性中的name来确定匹配的路由,通过params来传递参数
   (3)使用path来匹配路由,然后通过query来传递参数获取this.$route.query.id

   75.Vue 组件间通信有哪几种方式?
  (1)props / $emit 适用 父子组件通信
  (2)ref 与 $parent / $children 适用 父子组件通信
    ref:如果在普通的 DOM 元素上使用,引用指向的就是 DOM 元素;如果用在子组件上,引用就指向组件实例
    $parent / $children:访问父 / 子实例

  (3)EventBus ($emit / $on) 适用于 父子、隔代、兄弟组件通信
   这种方法通过一个空的 Vue 实例作为中央事件总线(事件中心),用它来触发事件和监听事件,从而实现任何组件间的通信,包括父子、隔代、兄弟组件。
   
  (4)$attrs/$listeners 适用于 隔代组件通信
   $attrs:包含了父作用域中不被 prop 所识别 (且获取) 的特性绑定 ( class 和 style 除外 )。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 ( class 和 style 除外 ),并且可以通过 v-bind="$attrs" 传入内部组件。通常配合 inheritAttrs 选项一起使用。
   $listeners:包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件

  (5)provide / inject 适用于 隔代组件通信
   祖先组件中通过 provider 来提供变量,然后在子孙组件中通过 inject 来注入变量。 provide / inject API 主要解决了跨级组件间的通信问题,不过它的使用场景,主要是子组件获取上级组件的状态,跨级组件间建立了一种主动提供与依赖注入的关系。
   
   (6)Vuex 适用于 父子、隔代、兄弟组件通信
   Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。每一个 Vuex 应用的核心就是 store(仓库)。“store” 基本上就是一个容器,它包含着你的应用中大部分的状态 ( state )。

    Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。
    改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化。

   76.computed 和 watch 的区别和运用的场景?
   computed: 是计算属性,依赖其它属性值,并且 computed 的值有缓存,只有它依赖的属性值发生改变,下一次获取 computed 的值时才会重新计算 computed 的值;
   watch: 更多的是「观察」的作用,类似于某些数据的监听回调 ,每当监听的数据变化时都会执行回调进行后续操作;
   运用场景:
    当我们需要进行数值计算,并且依赖于其它数据时,应该使用 computed,因为可以利用 computed 的缓存特性,避免每次获取值时,都要重新计算;
    当我们需要在数据变化时执行异步或开销较大的操作时,应该使用 watch,使用 watch 选项允许我们执行异步操作 ( 访问一个 API ),限制我们执行该操作的频率,并在我们得到最终结果前,设置中间状态。这些都是计算属性无法做到的。

   77.SPA 单页面的理解,它的优缺点分别是什么?
   SPA( single-page application )仅在 Web 页面初始化时加载相应的 HTML、JavaScript 和 CSS。一旦页面加载完成,SPA 不会因为用户的操作而进行页面的重新加载或跳转;取而代之的是利用路由机制实现 HTML 内容的变换,UI 与用户的交互,避免页面的重新加载。
   优点:
 
     用户体验好、快,内容的改变不需要重新加载整个页面,避免了不必要的跳转和重复渲染;
     基于上面一点,SPA 相对对服务器压力小;
     前后端职责分离,架构清晰,前端进行交互逻辑,后端负责数据处理;
 
   缺点:

     初次加载耗时多:为实现单页 Web 应用功能及显示效果,需要在加载页面的时候将 JavaScript、CSS 统一加载,部分页面按需加载;
     前进后退路由管理:由于单页应用在一个页面中显示所有的内容,所以不能使用浏览器的前进后退功能,所有的页面切换需要自己建立堆栈管理;
     SEO 难度较大:由于所有的内容都在一个页面中动态替换显示,所以在 SEO 上其有着天然的弱势。

   78.简述Vue的响应式原理
   当一个Vue实例创建时,vue会遍历data选项的属性,用 Object.defineProperty 将它们转为 getter/setter并且在内部追踪相关依赖,在属性被访问和修改时通知变化。
   每个组件实例都有相应的 watcher 程序实例,它会在组件渲染的过程中把属性记录为依赖,之后当依赖项的 setter 被调用时,会通知 watcher 重新计算,从而致使它关联的组件得以更新。
   
   ===============================================================================================

   vue实战知识在开发中常见的报错的问题:

   79. element UI表格排序sortable最简单的按大小排序,需要添加prop,不然无法使用
   表格后面增加的sortable没有作用
   解决办法:没有给表格添加prop属性
   <el-table-column align="center" label="闸门名称"  prop='gatename' sortable>                                    
   <template slot-scope="scope">
      <span>{{scope.row.gatename}}</span>
  </template>
  </el-table-column>

  80.表单清空没有效果,也不是提示报错的原因
  解决方法:model中绑定的数据字段和Dom里prop的数据字段是不是完全一致, prop属性是否有写

  81.tagview 下拉关闭菜单窗口对不上
  侧边栏 顶部固定定位 偏移距离引起的  
  解决办法:下拉关闭菜单事件x要减去想对应的距离

  82. Dialog 对话框  Vue开发中出现对话框被遮罩层挡住问题解决方案
  解决办法:在el-dialog 标签上增加这个属性  :modal-append-to-body="false"

  83. 表格数据懒加载圈圈位置跑到表格下面去了
  解决办法:v-loading.body='boolean' 改成 v-loading='boolean'

  84.更换主题颜色后 el覆盖样式不起作用了
  解决办法:更改element默认的样式 

  85.找不到未定义的resetFields属性
  vue.esm.js?efeb:591[Vue warn]: Error in event handler for "click": "TypeError: Cannot read property 'validate' of undefined"
  [Vue warn]: Error in event handler for "click": "TypeError: Cannot read property 'resetFields' of undefined"
  解决办法:给表单添加ref属性

  86.我们有时候常碰到vue中明明修改了数据,但是视图无法更新,解决办法:
  1、v-for遍历的数组,当数组内容使用的是arr[0].xx =xx更改数据,vue无法监测到
 
  数组数据变动:我们使用某些方法操作数组,变动数据时,有些方法无法被vue监测,有些可以
  Vue包装了数个数组操作函数,使用这些方法操作的数组去,其数据变动时会被vue监测: 
  push() //向数组的结尾添加一个或多个元素并返回新的长度
  pop() //删除元素并返回数组的最后一个元素
  shift() //向数组的开头添加一个或多个元素并返回新的长度
  unshift() //删除元素并返回数组的第一个元素
  splice() //删除元素并返回被删除元素的数组
  sort() //对数组中元素进行排序
  reverse() //颠倒数组中元素的顺序
  split() //方法用于把一个字符串分割成字符串数组
  vue2.0还增加个方法可以观测Vue.set(items, indexOfItem, newValue)
  filter(), concat(), slice() 。这些不会改变原始数组,但总是返回一个新数组。当使用非变异方法时,可以用新数组替换旧数组
  Vue 不能检测以下变动的数组: 
  当你利用索引直接设置一个项时,vm.items[indexOfItem] = newValue
  当你修改数组的长度时,例如: vm.items.length = newLength
  三种方式都可以解决,使用Vue.set、vm.$set()或者数组的splice方法。

  87.富文本编辑内容返回的数据带标签,后台返回的数据带标签
  解决办法: 使用v-html指令解析标签 
  <el-table-column min-width="500" align="center" label="内容" show-overflow-tooltip>
    <template slot-scope="scope">
      <span v-html='scope.row.content'></span>
    </template>
  </el-table-column>

    88. <router-link :to={path:'',query:{}></router-link>页面跳转后新页面内容不会自动刷新
    //解决办法: 增加以下路由钩子函数,当路由变化时根据id在数据库查找数据,发生create钩子函数之前
   beforeRouteEnter(to, from, next) {
    next(vm => {
      console.log(vm)
      // 这里vm就是当前页面的实例,可以执行methods中方法
      if (vm.$route.params.id < 1) {
        vm.$refs.form.resetFields();
        // vm.form.imagerBase64 = undefined
        vm.dialogStatus = 'create';
      } else if (vm.$route.params.id) {
        vm.getList();
        vm.dialogStatus = 'update';
      }
    })
  }

  89.vue2.0中.vue文件页面跳转传递参数并获取
  //声明式标签写法 
  <router-link :to="{path:'wlWaterWorkSituation',query:{id: this.listData[index].id}}"></router-link>
  <router-link :to="{name:'wlWaterWorkSituation',params:{id: this.listData[index].id}}"></router-link>
   
  //编程式js写法
  this.$router.push(path:'wlWaterWorkSituation',query:{id: this.listData[index].id}})
   
  //页面中获取参数
  this.$route.query.id
  this.$route.params.id
  $route为当前router跳转对象里面可以获取name、path、query、params等
  $router为VueRouter实例,想要导航到不同URL,则使用$router.push方法
  返回上一个history也是使用$router.go方法

  90.拖拽排序不起作用:拖拽元素一定要放在draggble标签内
   <draggable v-model="tags"
                 :move="getdata"
                 @update="datadragEnd">
          <transition-group>
              <div v-for="element in tags"
              :key="element.id">
             {{element.name}}
         </div>
         </transition-group>
  </draggable>

 91.MapBox介绍

  var map = L.map('map').setView([51.505, -0.09], 13);
 
  L.tileLayer('http://{s}.tile.osm.org/{z}/{x}/{y}.png', { 
  attribution: '? <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
  }).addTo(map);
   
  L.marker([51.5, -0.09]).addTo(map) 
  .bindPopup('A pretty CSS3 popup.<br> Easily customizable.')
  .openPopup();
   
  L.map('map')就是捕获一个<div id="map"></div>,把它当作一个地图观察器,将地图数据赋予上面。
   
  setView设置地图中心点的经纬度和比例尺,作为用户的视图区域。
   
  L.tileLayer就是矢量瓦片底图,用对应的URL上找对应的z,y,x。而s是分布式服务器快速选取,一般都是1-4,天地图做到了1-8。
   
  tileLayer图层要addTo(map)加载在地图观察器上。例子当中瓦片底图使用的是openstreetmap,当然也可以使用天地图的瓦片数据作为一个家庭作业留给你们,参考htoooth/Leaflet.ChineseTmsProviders。
   
  L.marker就是图标,首先需设置的是图标的坐标,bindPopup是绑定的冒泡提示,里面可以包装任意的innerHTML元素,openPopup是开启提示的方法。


  92.列表页设置了缓存,详情页面的弹窗关闭后还显示遮罩层,按esc退出又可以了
   原因:详情页关闭后的遮罩层是缓存列表页面弹窗的遮罩层,这个页面的弹窗状态被缓存下来了
   解决方法:在跳转到详情页面之前关闭列表页面的弹窗
  
  93.tag标题动态更改 正则表达式 当字段类型为date或timestamp时才能操作分区字段按钮,不区分大小写

  94.新增页面不经过登录路由跳转不成功,    原因路由被拦截了,router.beforeEach()全局前置守卫

  第一步在router文件里面增加新页面的路由
    第二步在permission文件设置白名单,如果项目没有设置白名单,当路由为新增的url时,执行next(),可参考login写法
  import router from './router'
  import { getCookie } from './utils/auth'
   
  // 通过beforeEach钩子来判断用户是否登陆过 有无token
  const whiteList = ['/login/', '/regist/', '/'] // 不重定向白名单
   
  router.beforeEach((to, from, next) => {
  console.log(to.matched)
  // 判断是否有登录过
  if (getCookie('userId_dev')) {
    // 如果包含userId_dev 从登录页面跳转 直接跳转到首页 /
    if (to.path === '/login') {
      next('/smartHome/decIndex')
    } else {
      if (to.matched.length === 0) {
        next('/404') // 判断此跳转路由的来源路由是否存在,存在的情况跳转到来源路由,否则跳转到404页面
      }
      next() // 如果匹配到正确跳转
    }
  // 没有登录
  } else {
    if (whiteList.indexOf(to.path) !== -1) {
      next()
    } else {
      // 还没有登录过 则跳转到登录界面
      next('/login')
    }
  }
  })


  95.场景:有一个成果列表,每个列表上面右下角显示下拉操作菜单,单击小图标鼠标移到下拉菜单时,下拉菜单消失bug
  //vue 阻止事件冒泡
<ul class="listItemWrapper">
          <draggable v-model="listItems" @chang="change" :move="move" :options="options">
            <transition-group>
              <li v-for="(item, index) in listItems" :key="index" class="listItem" @dblclick="getInfo(index)" @mouseenter="showIcon(index)" @mouseleave="noShowIcon()">
                <div class="itemImgBox">
                  <img :title="'名称: '+item.name" :src="item.src" alt="" />
                </div>
                <p class="itemDescription">
                  <el-input v-model="item.name" v-if="item.isShowInput" size="mini" style="width:90px" @blur="desChange(index)" @keyup.enter.native="desChange(index)" :ref="'input'+index"></el-input>
                  <span v-else @dblclick.stop="showInput(index)">{{ item.name }}</span>
                </p>
                <div class="hoverBox" v-if="listIndex===index?true:false" @click.stop="showHandle(index)"> <img src="../../assets/images/nodemenuico.png" alt="" /></div>
                <ul class="list" v-if="item.isToggle">
                  <li @click="handleOpen(index)" v-if="item.type==='scene'">打开</li>
                  <li @click="handleRename(index)" v-if="item.type!=='scene'">重命名</li>
                  <li @click="handleDesignatedCover(index)">指定封面</li>
                  <li @click="handleRemove(index)">移除</li>
                </ul>
              </li>
            </transition-group>
          </draggable>
        </ul>


  95.vue在没有接口的情况下实现增删改查


  <template>
  <div class="hotel">
    <el-breadcrumb separator-class="el-icon-arrow-right">
      <el-breadcrumb-item :to="{ path: '/storeManage' }">首页</el-breadcrumb-item>
      <el-breadcrumb-item>酒店介绍</el-breadcrumb-item>
      <el-breadcrumb-item>酒店名称</el-breadcrumb-item>
      <el-breadcrumb-item>酒店详情</el-breadcrumb-item>
    </el-breadcrumb>

    <el-row>
      <el-col :span="6">
        <div class="grid-content bg-purple">
          <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="酒店名称">
              <el-input v-model="form.name"></el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
      <el-col :span="6">
        <div class="grid-content bg-purple-light">
          <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="售卖价格">
              <el-input v-model="form.price"></el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
      <el-col :span="6">
        <div class="grid-content bg-purple">
          <el-form ref="form" :model="form" label-width="80px">
            <el-form-item label="酒店分类">
              <el-input v-model="form.sort"></el-input>
            </el-form-item>
          </el-form>
        </div>
      </el-col>
      <el-col :span="6" class="button">
        <div class="grid-content bg-purple-light">
          <el-button type="primary" size="mini" @click="tables">查询</el-button>
          <el-button type="primary" size="mini" @click="addForms()">添加</el-button>
        </div>
      </el-col>
    </el-row>

 //表格 
    <el-table :data="tableData" border>
        <el-table-column label="序号" width="50" type="index"></el-table-column>
        <el-table-column prop="name" label="酒店名称" width="200"></el-table-column>
        <el-table-column prop="style" label="酒店风格" width="200"></el-table-column>
        <el-table-column prop="address" label="地址" width="250"> </el-table-column>
        <el-table-column prop="translate" label="酒店星级" width="180"> </el-table-column>
        <el-table-column prop="servers" label="服务" width="180"> </el-table-column>
        <el-table-column prop="servers" label="操作" width="235">
          <template slot-scope="scope">
            <el-button type="primary" size="mini" @click="edit(scope.$index, scope.row)">编辑</el-button>
            <el-button type="warning" size="mini" @click="Detail(scope.$index)">详情</el-button>
            <el-button type="danger" size="mini" @click="remove(scope.$index, tableData)">删除</el-button>
          </template>
        </el-table-column>
      </el-table>

      //分页
      <el-pagination
        @size-change="handleSizeChange"
        @current-change="handleCurrentChange"
        :current-page="currentPage4"
        :page-sizes="[1, 2, 5, 10]"
        :page-size="1"
        layout="total, sizes, prev, pager, next, jumper"
        :total="5"
      >
      </el-pagination>
       //添加
      <el-dialog title="添加酒店" :visible.sync="addDialogVisible" width="60%" @close="addDialogClosed">
        <el-form :model="addForm" :rules="addRules" ref="addRuleForm" label-width="100px">
          <el-form-item label="酒店名称" prop="name">
            <el-input v-model="addForm.name"></el-input>
          </el-form-item>
          <el-form-item label="酒店风格" prop="style">
            <el-input v-model="addForm.style"></el-input>
          </el-form-item>
          <el-form-item label="酒店地址" prop="address">
            <el-input v-model="addForm.address"></el-input>
          </el-form-item>
          <el-form-item label="酒店星级" prop="translate">
            <el-input v-model="addForm.translate"></el-input>
          </el-form-item>
          <el-form-item label="酒店服务" prop="servers">
            <el-input v-model="addForm.servers"></el-input>
          </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="addDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="addDialog">确 定</el-button>
        </span>
      </el-dialog>
  
      // 编辑
      <el-dialog title="信息编辑" :visible.sync="editDialogVisible" width="60%" @close="editDialogClosed">
        <el-form :model="editForm" :rules="editRules" ref="editRuleForm" label-width="100px" class="demo-ruleForm">
          <el-form-item label="酒店名称" prop="name">
            <el-input v-model="editForm.name"></el-input>
          </el-form-item>
          <el-form-item label="酒店风格" prop="style">
            <el-input v-model="editForm.style"></el-input>
          </el-form-item>
          <el-form-item label="酒店地址" prop="address">
            <el-input v-model="editForm.address"></el-input>
          </el-form-item>
          <el-form-item label="酒店星级" prop="translate">
            <el-input v-model="editForm.translate"></el-input>
          </el-form-item>
          <el-form-item label="酒店服务" prop="servers">
            <el-input v-model="editForm.servers"></el-input>
          </el-form-item>
        </el-form>
        <span slot="footer" class="dialog-footer">
          <el-button @click="editDialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="edits">确 定</el-button>
        </span>
      </el-dialog>
  
      // 详情 
      <el-dialog title="详情" :visible.sync="DetailsdialogVisible" width="50%">
        <div>
          <p>酒店名称:{{ tableData[index].name }}</p>
          <p>酒店风格:{{ tableData[index].style }}</p>
          <p>酒店地址:{{ tableData[index].address }}</p>
          <p>酒店星级:{{ tableData[index].translate }}</p>
          <p>酒店服务:{{ tableData[index].servers }}</p>
        </div>
        <span slot="footer" class="dialog-footer">
          <el-button @click="DetailsdialogVisible = false">取 消</el-button>
          <el-button type="primary" @click="DetailsdialogVisible = false">确 定</el-button>
        </span>
      </el-dialog>
    </div>
  </template>
  
  <script>
  var _index
  import axios from 'axios'
  export default {
    data() {
      return {
        form: {
          name: '',
          translate: '',
          sort: '',
        },
        list: {
          page_number: 1,
          page_count: 10,
        },
        tableData: [
          {
            index: 1,
            name: '希尔顿欢朋酒店',
            style: '高档',
            address: '市政府/万达广场',
            translate: '一星',
            servers: '接机服务',
          },
          {
            index: 2,
            name: '唐宁ONE酒店',
            style: '简约',
            address: '中心区/市政府/万达广场',
            translate: '四星',
            servers: '每日打扫',
          },
          {
            index: 3,
            name: '唯客尚品酒店',
            style: '三星',
            address: '商圈',
            translate: '五星',
            servers: '洗衣服务',
          },
          {
            index: 4,
            name: '旅途新概念公寓',
            style: '复古',
            address: '路69号唐宁街a座6楼',
            translate: '二星',
            servers: '提供发票',
          },
          {
            index: 5,
            name: '汇景国际大酒店',
            style: '豪华',
            address: '朝阳新城/洪城大市场商圈',
            translate: '六星',
            servers: '叫醒服务',
          },
        ],
        addDialogVisible: false,
        // 编辑
        editDialogVisible: false,
        // 详情
        DetailsdialogVisible: false,
        currentPage4: 4,
        // 表单
        addForm: {
          name: '',
          style: '',
          address: '',
          translate: '',
        },
        index: 0,
        // 表单数据编辑
        editForm: {
          name: '',
          style: '',
          address: '',
          translate: '',
        },
        addRules: {
          name: [{ required: true, message: '请输入酒店名称', trigger: 'blur' }],
          style: [{ required: true, message: '请输入酒店风格', trigger: 'blur' }],
          address: [{ required: true, message: '请输入酒店地址', trigger: 'blur' }],
          translate: [{ required: true, message: '请输入酒店星级', trigger: 'blur' }],
          servers: [{ required: true, message: '请输入酒店服务', trigger: 'blur' }],
        },
        // 编辑验证规则
        editRules: {
          name: [{ required: true, message: '请输入酒店名称', trigger: 'blur' }],
          style: [{ required: true, message: '请输入酒店风格', trigger: 'blur' }],
          address: [{ required: true, message: '请输入酒店地址', trigger: 'blur' }],
          translate: [{ required: true, message: '请输入酒店星级', trigger: 'blur' }],
          servers: [{ required: true, message: '请输入酒店服务', trigger: 'blur' }],
        },
      }
    },
    created() {
      this.goodsList()
    },
    methods: {
      async goodsList() {
        const { data: res } = await axios({
          method: 'POST',
          url: 'pcAdmin/hotelType.do',
          headers: {
            'Content-Type': 'application/json;charset=UTF-8',
            type: 'LIST',
            token: 'f4CBg4OEhYeHiIot7G6JuAbdigJv/Xv8MYGP9MYj57Q=',
          },
          data: this.list,
        })
        console.log(res)
      },
      addForms() {
        this.addDialogVisible = true
        this.addForm = {
          name: '',
          style: '',
          address: '',
          translate: '',
          servers: '',
        }
      },
      addDialogClosed() {
        this.$refs.addRuleForm.resetFields()
      },
      // 添加操作
      addDialog() {
        this.tableData = this.tableData || []
        this.tableData.push({
          name: this.addForm.name,
          style: this.addForm.style,
          address: this.addForm.address,
          translate: this.addForm.translate,
          servers: this.addForm.servers,
        })
        this.addDialogVisible = false
        this.$message.success('添加用户成功')
      },
      // 删除操作
      remove(index, row) {
        this.$confirm('此操作将永久删除该数据, 是否继续?', '提示', {
          confirmButtonText: '确定',
          cancelButtonText: '取消',
          type: 'warning',
        })
          .then(() => {
            this.tableData.splice(index, 1)
            this.$message({
              type: 'success',
              message: '删除成功!',
            })
          })
          .catch((err) => {
            this.$message({
              type: 'info',
              message: '取消删除',
            })
          })
      },
      // 编辑
      edit(index, row) {
        this.editForm = Object.assign({}, row)
        _index = index
        this.editDialogVisible = true
      },
      editDialogClosed() {
        this.$refs.editRuleForm.resetFields()
      },
      edits() {
        var editData = _index
        this.tableData[editData].name = this.editForm.name
        this.tableData[editData].style = this.editForm.style
        this.tableData[editData].address = this.editForm.address
        this.tableData[editData].translate = this.editForm.translate
        this.tableData[editData].servers = this.editForm.servers
        this.editDialogVisible = false
        this.$message.success('修改成功')
      },
      // 详情
      Detail(index) {
        this.index = index
        _index = index
        console.log(this.index)
        this.DetailsdialogVisible = true
      },
      // 查询
      tables() {
        const name = this.name
        if (name) {
          return this.tableData.filter((data) => {
            return Object.keys(data).some((key) => {
              return String(data[key]).toLowerCase().indexOf(name) > -1
            })
          })
        }
      },
      //分页
      handleSizeChange(val) {
        console.log(`每页 ${val} 条`)
      },
      handleCurrentChange(val) {
        console.log(`当前页: ${val}`)
      },
    },
  }
  </script>
  
  <style>
  .el-row {
    margin-top: 35px;
  }
  .button {
    margin-top: 6px;
  }
  .bg-purple-light {
    margin-left: 40px;
  }
  </style>


  96.下拉菜单在空白处单击需要隐藏菜单,在mounted钩子中注册一个事件监听器
    mounted () {
    document.addEventListener('click', (e) => {
      if (e.target.className !== 'hoverBox') {
        this.listItems.map(v => {
          if (v.isToggle) v.isToggle = false
        })
      }
    })
  },

  97. zTree树外拖拽,targetNode, moveType只针对树内区域,如果是树外区域返回null
      zTreeOnDrop (event, treeId, treeNodes, targetNode, moveType) {
      let query = {
        'code': treeNodes[0].code,
        'folderId': this.folderId,
        'name': treeNodes[0].name,
        'parentId': treeNodes[0].parentId,
        'type': treeNodes[0].type
      }
      // 如果是把节点拖放到某个区域生成一个元素
      if (treeNodes[0].level === 2) {
        if (this.folderId) {
          // 然后向后端保存数据
          this.$http.post(API.SSAP_moveAchievementFolder, query).then(res => {
            if (res.object.data === '移动成功') {
              this.$parent.getFolderList()
              this.$message({ message: res.object.data, type: 'success', duration: 2000 })
            } else {
              this.$message({ message: res.object.data, type: 'warning', duration: 2000 })
            }
          })
        } else {
          this.$message({ message: '成果只能拖入到文件夹内', type: 'warning', duration: 2000 })
        }
      } else {
        // this.$message({ message: '只有type是scene 的才能移', type: 'warning', duration: 2000 })
      }
    },

    98. promise.all 并发请求

       handleUseStatus (row) {
      this.teamId = row.id
      Promise.all([this.$http.get(API.SSAP_getAll, { params: { keyWord: this.keyWord } }), this.$http.get(API.SSAP_getUseTeams, { params: { id: row.id } })]).then(res => {
        res[0].object.data.forEach(item => {
          item.key = item.id
          item.label = item.name
        })
        this.shuttleBoxData = res[0].object.data
        res[1].object.data.forEach(item => {
          item.key = item.id
          item.label = item.name
        })
        this.defaultData = res[1].object.data.map(v => v.id)
        this.shuttleBoxVisible = true
      })
    },

    99.JSON数据递归生成树结构
    let = [{
    "regionCode": "G0L0",
    "businessLine": null,
    "regionName": "总公司",
    "id": null,
    "regionLevel": 0,
    "parentRegionCode": null
  }]
 
  function getTree(treeData, parentRegionCode) {
    const treeArr = []
    for (var i = 0; i < treeData.length; i++) {
      var node = treeData[i]
      if (node.parentRegionCode === parentRegionCode) {
        var newNode = {
          id: node.id,
          label: node.regionName,
        }
        temp = getTree(treeData, node.regionCode);
        if (temp.length > 0) {
          newNode.children = temp;
        }
        treeArr.push(newNode)
      }
    }
    return treeArr
  }
  console.log(data.object.data, getTree(data.object.data, null));

  100..路由变化页面数据不刷新问题
   解决方案: 监听路由变化

   watch : {
          "$route" (to, from) {
                if(to.path.indexOf('/home') > -1) {
               
                    this.initData()
               }
           }     
   }   
   
  101.setInterval路由跳转继续运行并没有及时进行销毁
  比如一些弹幕,走马灯文字,这类需要定时调用的,路由跳转之后,因为组件已经销毁了,但是setInterval还没有销毁,还在继续后台调用,控制台会不断报错,如果运算量大的话,无法及时清除,会导致严重的页面卡顿。

  解决方案:在组件生命周期beforeDestroy停止setInterval
   beforeDestory() {
       clearInterval(this.timer);
       MessageBox.close()                
   }

   102.vue 滚动行为用法,进入路由需要滚动到浏览器底部、头部等等
   const router = new  VueRouter({
          mode: 'history',
          scrollBehavior (to, from, savedPosition) {
      
               if (savedPosition) { //如果savedPosition存在,滚动条会自动跳到记录的值的地方
          
                    return savedPosition
              } else{
                    return { x: 0, y: 0}//savedPosition也是一个记录x轴和y轴位置的对象
          }
         },
       routes: [...]
     })
     
    103.vue打包上线
    打包vue项目步骤:

    1、对当前vue项目进行打包的命令如下:
    npm run build

    2.打包完成,会输出Build complete并且在vue项目中会生成一个名字为dist的打包文件

    104.表格当选择项发生变化事件selection-change,翻页 搜索事件也会触发它,要改成手动事件
        // 按人员授权选中事件
    handleSelectWaitChoose (selection, row) {
      const index = this.tableData2.findIndex(item => item.id === row.id)
      if (~index) {
        this.tableData2 = this.tableData2.filter(item => item.id !== row.id)
      } else {
        this.tableData2.push(row)
      }
      this.userIds = this.tableData2.map(v => v.id)
    },
    // 按人员授权全部选中事件
    handleSelectWaitChooseAll (selection, flag) {
      if (selection && selection.length) {
        selection.forEach(item => {
          if (this.tableData2.findIndex(ele => ele.id === item.id) === -1) {
            this.tableData2.push(item)
          }
        })
      } else {
        this.tableData.forEach(item => {
          const index = this.tableData2.findIndex(ele => ele.id === item.id)
          if (index !== -1) {
            this.tableData2.splice(index, 1)
          }
        })
      }
      this.userIds = this.tableData2.map(v => v.id)
    },

    105.表格el-table添加数据后自动定位到行前行尾, 注意别忘了 el-table增加ref属性

    106.vue中阻止快速点击两次提交按钮调两次接口(防抖功能)
    通过元素去推vue的写法和思想是一样的
    第一种方法:申明一个变量,点击时置灰提交按钮。等接口调用结束放开按钮,就是你在点击之后,ajax请求完成之前将那个按钮禁止点击
    <template>
    <div>
        <button v-if="canSave" @click="save">提交</button>
        <button v-else disabled>提交</button>
    </div>
    </template>
    <script>
    export default {
     data(){
        return {
            canSave: true,
        }
    },
    methods: {
        save(){
            if(!canSave){
                return;
            }
            this.canSave = false;
            // AJAX 结束后 this.canSave = true;
        },
    }
    }
    </script>

    方法二:利用setTimeout,点击一次时隔两秒后才能再次点击
    <div id="app">
      <button @click="submit"  :disabled="isDisable">点击</button>
    </div>
   data(){
      return{
         isDisable:false
      }
   }
    
   methods:{
       submit(){
          this.isDisable=true
          setTimeout(()=>{
              this.isDisable=false   //点击一次时隔两秒后才能再次点击
          },2000)
       }
   }

   107.vue el-upload组件 批量导入自定义httpRequest方法不会显示上传文件进度条
   //自定义上传
   async httpRequest(params) {
    let fd = new FormData();
    fd.append("file", params.file);
    fd.append("FileName", params.file.name)
    var options = {  // 设置axios的参数
        headers: {
            'Content-Type': 'multipart/form-data'
        }
    }
    const url = this.$api.outbound.uploadTemplate
    try {
        const res = await this.$httpExt().post(url, fd, options)
        if (res.code === 0) {
            this.$emit('closeDialog', 'dialogLogisticStatusVisible')
            this.$parent.$parent.handleSearch()
            this.$message({ type: 'success', message: this.$t('ABGeneral.isco.importSucc') + res.total + this.$t('ABGeneral.isco.theData') })
        } else {
            this.$refs.upload.clearFiles();
            const h = this.$createElement;
            if (res.data) {
                this.$message({
                    type: 'error',
                    message: h('ul', { style: 'padding:0;margin:0' }, res.data.results.map(v => {
                        return h('li', { style: 'color: #f56c6c;fontSize:12px;' }, this.$t('ABGeneral.isco.the') + v.lineNum + this.$t('ABGeneral.isco.row') + ' - ' + v.errMsg)
                    }))
                });
            } else {
                this.$message({ type: 'error', message: res.msg })
            }
        }
    } catch (error) {
        console.log(22222, error);
    }
    }
 
    //上传之前筛选文件类型和大小
    handleBeforeUpload(file) {
        const size = file.size / 1024 / 1024
        const isExcel = file.name.includes('.xlsx') || file.name.includes('.xls')
        if (!isExcel) {
            this.$message({ message: this.$t('ABGeneral.isco.isXls'), type: "error" })
            return false
        } else if (size > 1) {
            this.$message({ message: '文件不能超过1M', type: "error" })
            return false
        }
    }

    108.渲染函数 & JSX Message 消息提示自定义
    const h = this.$createElement;

    this.$message({

    type: 'error',

    message: h('ul', { style: 'padding:0;margin:0' }, res.data.results.map(v => {

        return h('li', { style: 'color: #f56c6c;fontSize:12px;' }, this.$t('ABGeneral.isco.the') + v.lineNum + this.$t('ABGeneral.isco.row') + ' - ' + v.errMsg)

    }))

    }); 

    109.vue中@keyup.enter没有作用 
    注意事项:

    1.首先需要知道的是@click和@keyup是不能在一个元素上同时使用的;
    
    2.当我们在项目中引入了第三方组件库时,发现@keyup.enter没有作用时,这时改成@keyup.enter.native就可以啦
    
    eg: <el-input v-model="password" placeholder="请输入密码" @keyup.enter.native="search()"></el-input>
    
    110.npm 报错: npm ERR Unexpected token in JSON at position 0 while parsing near解决方案
    

    解决方法

    删除根目录下 package-lock.json 文件。
    npm cache clear --force(试过无效)

    111.级联选择器组件输入框回显内容空白
    分析原因

    value id可能重复,参数格式不对

    解决方法

    1 - 排查树形数据结构中的value值是否是唯一的? 比如一级添加all节点与二级添加all节点,新手很容易设置value是重复的值这样就导致显示有问题

    2 - 排查参数格式是否正确?比如要求是数组参数,实际传了字符串

    112.级联选择器下拉菜单滚动时不隐藏,下拉菜单pop会自动从顶部或底部弹出问题
    分析原因
    第三方插件BUG
    
    解决方法
    
    if (this.popElement && this.popElement.offsetHeight) {undefined
    
    const cascaderBrand = this.$refs.cascaderBrand as any
    
    cascaderBrand.dropDownVisible = false
    
    }
    
    .el-popper.el-cascader__dropdown {undefined
    
    position: absolute !important;
    
    transform: translateY(0) !important;
    
    top: 40px !important;
    
    }
    <el-cascader
    v-if="categoryList"
    ref="cascaderBrand"
    v-model="selectedBrand"
    :options="categoryList"
    :placeholder="this.$t('brandDirectory.choose')"
    :props="{
      expandTrigger: 'hover',
      value: 'id',
      label: 'categoryName',
      children: 'childrenCategory'
    }"
    :show-all-levels="false"
    :append-to-body="false"
    :popper-options="{
      boundariesElement: 'viewport'
    }"
    @change="handleChange">
    </el-cascader>

    113.在el-table scopte slot 里面读取不到this.$ref[xxxx] 打印一直显示undefined
    解决办法

    slot 内容需要提取到单独的组件里面ref属性才能打印并且会遍历节点

    抓取省略号状态----- dom节点内容高度<节点滚动高度,代表会显示省略号,反之就不显示
    
    <el-table-column min-width="120" :label="logReportI18.categories">
        <template slot-scope="scope">
          <el-tooltip
            class="item"
            effect="dark"
            :content="scope.row.category_access"
            placement="top-start">
           // slot 里面ref属性要加延时才能取到dom节点, 并且不会遍历节点
            <span class="category" ref="content">
                {{ scope.row.category_access }}
              </span>
            </el-tooltip>
          </template>
        </el-table-column>
        <el-table-column min-width="120" :label="logReportI18.categories">
          <template slot-scope="scope">

        // slot 内容需要提取到单独的组件里面ref属性才能打印并且会遍历节点
            <baseToolTip :data="scope.row.category_access"></baseToolTip>
          </template>
        </el-table-column>

    114.vue2.0就近调用同一组件 数据视图没有更新, 通过vue-devtools调试工具可以看出数据没有变化
    解决办法--- 在动态组件加上Key属性
      <transition name="component">
      <component
      v-if="curEditData.type !== ''"
      :key="curEditData.type"
      class="component"
      :componentData="componentData"
      :moduleErrors="curComponentError && curComponentError.moduleErrors"
      :is="curComponentName"
      @saveAndClose="_handleSaveAndClose"/>
    </transition>


    ==================================================================
    小程序
    小程序的发布流程(开发流程)
    注册微信小程序账号
    获取微信小程序的 AppID
    下载微信小程序开发者工具
    创建小程序项目
    去微信公众平台配置域名
    前后端代码的编写
    手机预览体验
    代码上传
    提交审核
    审核通过后发布


    115.简单描述下微信小程序的相关文件类型
    wxml模板文件:是框架设计的一套标签语言,结合基础组件,事件系统、可以构建出页面的结构;

    wxss样式文件:是一套样式语言,用于描述wxml的组合样式;
    
    js脚本逻辑文件:逻辑处理网络请求;
    
    json配置文件:小程序设置,如页面注册,页面标题及tabBar;
    
    app.json:整个小程序的全局配置,包括:
    
    a.pages:所有路径文件
    
    b.网络设置(网络超时时间)
    
    c.界面表现(网页注册)
    
    d.window:背景色,导航样式,默认标题
    
    e.底部tab等
    
    app.js可以没有内容,监听并处理小程序的生命周期函数,声明全局变量
    
    app. wxss全局配置的样式文件


    116.小程序的生命周期函数
    onLoad():页面加载时触发,只会调用一次,可获得当前页面路径中的参数;

    onShow():页面显示/切入前台时触发,一般用来发送数据请求;
    
    onReady():页面初次渲染完成时触发,只会调用一次,代表页面已可和视图层进行交互;
    
    onHide():页面隐藏/切入后台时触发,如底部tab切换到其他页面或小程序切入后台等

    117.数据请求怎么封装
    将所有的接口放在统一的js文件中并导出(或者将请求地址、头、方法在一个js文件里统一定义为一个常量并导出)
    在app.js创建封装请求数据的方法
    在子页面中调用封装的方法请求数据

    118.参数传递

    给HTML元素中添加data-*属性来传递需要的值,之后通过e.currentTarget.dataset或onload的param参数获取。注意不能有大写字母,不可以存放对象
    跳转页面时通过navigator传递需要的参数值
    设置id的方法标识,通过e.currentTarget.id获取设置的id值,然后通过设置全局变量的方法来传递数值
    
    119.如何自定义组件(弹窗)
    (1)先创建一个components文件夹,用来存放所有自定义组件的,目录结构依然是js,wxml,json,wxss
    (2)基本配置json——进行自定义组件声明
     {
       "component": true
     }

    3.使用组件
    假如在index.wxml中使用这个自定义的组件,首先在index.json中进行声明。
    {  
    "usingComponents": {  
      "toastdemo": "/components/toastdemo/toastdemo"  
    }  
    }

    (4)接着在index.wxml中引用

    (5)然后在index.js进行配置

    (6)使用时直接执行this.toastdemo.showToast(‘弹框组件调用成功’,2000)就可以了

    120.小程序内的页面跳转
    wx.navigateTo——保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面(参数必须为字符串)
    wx.redirectTo——关闭当前页面,跳转到应用内的某个页面。但是不允许跳转到 tabbar 页面
    wx.switchTab——跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面,路径后不能带参数
    wx.navigateBack——关闭当前页面,返回上一页面或多级页面。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层
    wx.reLaunch——关闭所有页面,打开到应用内的某个页面

    121.小程序的优点和缺点
    优点

    无需下载
    打开速度快
    开发成本低
    为用户提供良好的安全保障。发布有一套严格的审查流程,不能通过审查的程序无法发布上线
    服务请求快

    缺点

    依托微信,不能开发后台管理功能
    大小限制不能超过2M,不能打开超过5个层级的页面
    
    ==================================================================================
    小程序项目中常见的问题的需求

    122.如何实现下拉刷新
    先在app.json或page.json中配置enablePullDownRefresh:true
    page里用onPullDownRefresh函数,在下拉刷新时执行
    在下拉函数执行时发起数据请求,请求返回后,调用wx.stopPullDownRefresh停止下拉刷新的状态

    123.bindtap和catchtap的区别是什么
    bindtap不会阻止冒泡事件,catchtap阻止冒泡

    124.webview中的页面怎么跳回小程序中
      先在管理后台配置域名白名单,
     然后引入jweixin-1.3.2.js(接口头部)

     wx.miniProgram.navigateTo({url: '接口字段'})
     wx.miniProgram.navigateTo({url: '接口字段'})

    125.webview的页面怎么跳转到小程序导航的页面?
    小程序导航的页面可以通过switchTab,但默认情况是不会重新加载数据的。若需加载新数据,则在success属性中加入以下代码即可:
    success: function (e) {
    var page = getCurrentPages().pop();
    if (page == undefined || page == null) return;
    page.onLoad();
    }
    //webview的页面,则通过
    wx.miniProgram.switchTab({
    url: '/pages/index/index'  
    })

    126.如何实现微信支付
    <view class="tx-18" bindtap="postMoney">确认充值</view>
    postMoney() { // 点击确认充值
    wx.showLoading({
      title: '加载中',
    })
    let that = this
    app.http.getData({ //第一步请求后台接口,获取发起支付所需要的数据
      amount: that.data.orderId // 我这里发起请求传过去的是订单编号
    }).then((res1) => {
            if (res1.code == 200) {
                wx.requestPayment({ // 这一步是调起微信支付
                  "appId": res1.appId,
                  "timeStamp": res1.timeStamp,
                  "nonceStr": res1.nonceStr,
                  "package": res1.package,
                  "signType": res1.signType,
                  "paySign": res1.paySign,
                  "success": function (res) {
                    wx.hideLoading({})
                    app.ShowToast('充值成功')
                  },
                  "fail": function (res) {
                    app.ShowToast('支付失败');
                  },
                  "complete": function (res) {
                    app.ShowToast('取消支付');
                  }
                })
            } else {
              wx.hideLoading({})
              app.ShowToast('支付失败')
            }
    })
  },


  127.预览文件时,downloadFile下载文件返回的临时地址是(jpeg ,jpg,msword等格式时),预览文件失败的处理办法
  showfile: function(e) {
    var that = this;
   var url = that.data.fileurl[e.currentTarget.dataset.index]
    wx.downloadFile({
      url: url,
      success: function(res) {
        var filePath = res.tempFilePath
        console.log(res)
        var type = url.lastIndexOf(".")
        var filetype = url.substring(type + 1, url.length)
        console.log(type)
        console.log(filetype)
          wx.openDocument({
            filePath: filePath ,
            fileType: filetype,
            success: function (res) {
              console.log('打开文档成功')
            }
          })
     }
    })
  },

  128.微信小程序实现毛玻璃弹窗效果
  使用方法:
使用 backdrop-filter : blur()配合background : rgba()来实现弹窗的毛玻璃效果。

WXML 页面:

<view class='bg_contain'>
	<view class='blur_demo'></view>
</view>


WXSS 样式:

.bg_contain {
	background-image: url('./image_demo.jpg');
}

.blur_demo {
	background: rgba(250,250,250,0.6);
	backdrop-filter: blur(10px);
}









  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值