《JavaScript高级程序设计》(第三版)学习复盘(三)

402 阅读11分钟

一.事件

1)事件流

1.事件冒泡

IE的事件流叫事件冒泡。即事件开始时由最具体的元素(文档中嵌套最深的那个节点)接收,然后逐级向上传播到较为不具体的节点(文档)。

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div class="box">click me</div>
  </body>
</html>

如果你点击了div,那么这个click事件会按照如下顺序传播:

  • 1.div
  • 2.body
  • 3.html
  • 4.document

下图展示了事件冒泡的过程

2.事件捕获

Netscape Communicator团队提出的另一种事件流叫做事件捕获。事件捕获的思想是不太具体的节点应该更早接收到事件,而最具体的节点应该最后接收到事件。用上面的html结构举例,如果你点击了div,那么这个click事件会按照如下顺序传播:

  • 1.document
  • 2.html
  • 3.body
  • 4.div

下图展示了事件捕获过程

3.DOM事件流

“DOM2事件流”规定包括三个阶段:事件捕获、处于目标阶段、事件冒泡。

按照上面DOM结构点击div会是如下图顺序触发事件

2)事件处理程序

1.HTML事件处理程序

每个元素都支持的事件。

  <body>
    <div class="box" onclick="txt()">click me</div>
    <script>
      function txt(){
        console.log("click me!");
      }
    </script>
  </body>

2.DOM0事件处理程序

通过javaScript指定事件处理程序的传统,就是将一个函数赋值给一个事件处理程序属性。

el.onclick = function() {
    console.log("click me!")
}
//  删除事件处理程序
el.onclick = null;

3.DOM2事件处理程序

1.addEventListener(EventName, func, bool)

这个函数接受三个参数,第一个是事件名,第二个参数是事件发生后调用的函数,第三是布尔值,如果为true,代表事件在捕获时触发,为false时是冒泡时触发,如果不传该参数,则默认为false。

onst oBox = document.querySelector(".box");
oBox.addEventListener("click", function() {
    console.log("click me!");
}, false);

2.removeEventListener(EventName, func, bool)

删除使用 EventTarget.addEventListener() 方法添加的事件。使用事件类型,事件侦听器函数本身,以及可能影响匹配过程的各种可选择的选项的组合来标识要删除的事件侦听器

  const oBox = document.querySelector(".box");
  const isFun = false;
  function handler() {
    console.log("click me!");
  }
  oBox.addEventListener("click", handler, isFun);
  oBox.removeEventListener("click", handler, isFun);

使用removeEventListener()方法的时候不能清除addEventListener()方法上的匿名函数事件程序,所以请用函数声明和函数表达式。而且清除的事件线程必须都是在一个事件阶段上。

3.事件对象

我们在触发DOM的某个事件时,会产生一个事件对象event,这个对象包含所有与事件有关的信息。

event事件包含的方法和属性,下表:

1.bubbles。布尔值,用以表明事件是否冒泡。
2.cancelable。布尔值,用以表明是否可以取消事件的默认行为。通常和preventDefault()配合使用。
3.defaultPrevented。布尔值,值为true表明已经调用了preventDefault()。(DOM3新增)
4.trusted。布尔值,值为true表明事件是浏览器生成的,false表明是开发者通过js创建的。(DOM3新增)
5.currentTarget。Element,事件处理程序当前正在处理事件的那个元素,也就是说事件处理程序被添加的那个元素,即事件处理处理程序的this值的指向。
6.target。Element,事件的目标。
7.detail。与事件相关的细节信息。
8.eventPhase。调用事件处理程序的阶段。1表示捕获阶段,2表示处于目标阶段,3表示冒泡阶段。
9.preventDefault()。取消事件的默认行为,前提要求是cancelable值为true10.stopPropagation()。取消事件的进一步捕获或冒泡。前提要求bubbles值为true11.stopImmediatePropagation()。取消事件进一步冒泡,并阻止任何事件处理程序调用。(DOM3新增)
12.type。事件类型。

4.事件类型

UI事件

1.load事件

当页面完全加载后,就会触发window上的load事件

2.unload事件

在文档被完全卸载后触发。只要用户从一个页面切换到另一个页面就会触发。可以利用这个事件清除引用,避免内存泄漏。

3.resize事件

当浏览器被调整到一个新的高度或宽度时,就会触发。

4.scroll事件

scroll事件一般是在windos对象上触发的,但是在混杂模式下,body元素的scroll能监听到变化。

焦点事件

1.biur事件

在元素失去焦点的时候触发。这个事件不会冒泡。

2.focusout事件

在元素失去焦点的时候触发。和biur事件等价,但是会冒泡。

3.focus事件

在元素获得焦点的时候触发。这个事件不会冒泡。

4.focusin事件

在元素获得焦点的时候触发,和focus事件等价,但是会冒泡。

鼠标和滚轮事件

1.click

用户单击鼠标左键时触发。

2.dblclick

用户双击鼠标左键时触发。

3.mousedown

用户按下鼠标任意键触发。

4.mouseenter

鼠标从元素外部首次移入元素范围之内触发(鼠标移入事件)。不冒泡。

5.mouseleave

鼠标从元素上方移动到元素范围之外时触发(鼠标移出事件)。不冒泡

6.mousemove

鼠标指针在元素内部移动的时重复的触发(会被“mouseout”事件打断)。

7.mouseout

鼠标位于一个元素上方,然后移入另一个元素时触发。移入的可以是这个元素的子元素。

8.mouseover

鼠标位于一个元素外部,然后鼠标首次移入另一个元素边界之内时触发。移入的可以是这个元素的子元素。

9. mouseup

用户释放鼠标时触发。ps:当你点击鼠标你移动到目标元素之外释放就不会被触发。

10.mousewheel

用户滚动鼠标时或者电脑触控板时触发。

键盘事件

1.keydown

用户按下键盘上任意的按键时触发。

2.keypress

当用户按下键盘上的字符会触发。

3.keyup

用户释放键盘上的按键时触发。

文本事件

1.textInput

用户在克编辑的区域中输入字符时会触发。

变动事件

1.DOMSubtreeModified

在DOM结构发生人后变化的时触发。

2.DOMNodeInserted

在一个节点作为子节点被插入到另一个节点中时;

3.DOMNodeRemoved

在节点从其父节点中被移除时;

4.DOMNodeInsertedIntoDocument

在一个节点被直接插入文档,或通过子树间接插入文档之后触发。这个事件在DOMNodeInserted之后触发;

5.DOMNodeRemovedFromDocument

在一个节点被直接从文档中移除,或通过子树间接从文档中移除之前触发。这个事件在DOMNodeRemoved之后触发;

6.DOMAttrModified

在特性被修改之后触发;

7.DOMCharacterDataModified

在文本节点的值发声变化时触发;

HTML5事件

1.contextmenu事件

点击鼠标右键调出上下文菜单。

2.beforeunload事件

在浏览器卸载页面前触发。

3.DOMContenLoaded事件

在页面浏览器形成完整的DOM树之后触发。

4.readystatechange事件

IE为 DOM文档中的某些部分提供了 readystatechange 事件。这个事件的目的是提供与文档或 元素的加载状态有关的信息,但这个事件的行为有时候也很难预料。支持 readystatechange 事件的 每个对象都有一个 readyState 属性,可能包含下列 5个值中的一个。

   uninitialized(未初始化):对象存在但尚未初始化。 
   loading(正在加载):对象正在加载数据。
   loaded(加载完毕):对象加载数据完成。
   interactive(交互):可以操作对象了,但还没有完全加载。
   complete(完成):对象已经加载完毕

5.pageshow和pagehide事件

pageshow事件在页面显示的时候触发 pagehide事件在页面卸载的时候触发

6.hashchange事件

当URL的参数发生变化的时候通知开发人员

5.内存和性能

1.事件委托

对“事件处理程序过多”问题的解决方法就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。

  <ul id="test">
        <li>
            <p>11111111111</p>
        </li>
        <li>
            <div>
                22222222
            </div>
        </li>
        <li>
            <span>3333333333</span>
        </li>
        <li>4444444</li>
    </ul>
    
    <scrpit>
        var oUl = document.getElementById('test');
        oUl.addEventListener('click',function(ev){
            var target = ev.target;
            while(target !== oUl ){
                if(target.tagName.toLowerCase() == 'li'){
                    console.log('li click~');
                    break;
                }
                target = target.parentNode;
            }
        })
    </scrpit>

2.移除事件处理程序

在内存中过时不用的“空事件处理程序”,也是造成web应用程序内存与性能问题的主要原因。 处理办法有两种:

  • 从文档中移除带有事件处理程序的元素。
  • 在页面卸载之前,先通过onunload事件处理移除所有的事件踔厉程序。

二.HTML5脚本编程

1)原生拖放

1.拖放事件

拖动元素时,依次触发下列事件:

(1)dragstart

按下鼠标左键开始移送鼠标左键时,触发。

(2)drag

dragstart事件触发后,元素拖拽期间一直会持续触发该事件。

(3)dragend

当拖拽停止时(不管目标放在有效位置或无效位置)会触发。 当元素被拖拽到i一个有效的目标上时,会依次触发下列事件:

(1)dragenter

拖拽元素放置在目标上时触发。

(2)dragover

dragenter事件触发后,元素拖拽期间在放置目标元素范围内移动时触发。

(3)dragleave或drop

dragleave:拖拽的元素被拖出目标元素就会触发dragleave。

drop:拖拽目标放置在目标中时触发。

在html5中,遇到"drop"事件无效,可能原因是浏览器的默认操作,需执行事件阻止系统的默认操作,如下代码:

      el.addEventListener("dragover", function (e) {
        e.preventDefault();
      });
      el.addEventListener("drop", function () {
        console.log("drop");
      });

2)dataTransfer 对象

dataTransfer对象是事件对象属性,用于从拖拽元素向放置目标传递字符串格式的事件。

dataTransfer对象主要有两个方法 getData() 和 setData(),方法是存在于dataTransfer对象的原型之上。

getData:获取setData方法保存的值。

setData:传入两个值,一个参数是key,第二个是值(字符串格式)。

具体实际运用请看如下代码:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
    <style>
      .box {
        width: 200px;
        height: 200px;
        background-color: blanchedalmond;
        line-height: 200px;
      }
      .lists {
        display: flex;
        list-style: none;
        padding: 0;
        margin: 0;
      }
      .lists li {
        flex: 1;
        padding: 3rem;
        background-color: chocolate;
        text-align: center;
      }
      .lists li + li {
        margin-left: 10em;
      }
    </style>
  </head>
  <body>
    <div class="box">
      拖拽至此删除
    </div>
    <ul class="lists">
      <li class="item1">1</li>
      <li class="item2">2</li>
      <li class="item3">3</li>
      <li class="item4">4</li>
      <li class="item5">5</li>
    </ul>
    <script>
      const oBox = document.querySelector(".box");
      let aLis = document.querySelectorAll("li");
      aLis.forEach((item) => {
        item.setAttribute("draggable", true);
        item.addEventListener("dragstart", function (e) {
          e.dataTransfer.setData("class",this.className)
        });
      });
      oBox.addEventListener("dragover", function (e) {
        e.preventDefault();
      });
      oBox.addEventListener("drop", function (e) {
        let element = `.${e.dataTransfer.getData("class")}`;
        let removeEl = document.querySelector(element);
        removeEl.remove();
      });
    </script>
  </body>
</html>

3)可拖动

默认情况下,图片(在选择的情况下可以拖动)、链接文本是可以拖动的。HTMPL5给每个元素添加了“draggable”属性,表示元素是否可以拖动。图像和链接自动设置为true,其他元素则是false。例如下列代码:

    <div draggable="true">可以拖拽</div>
    <div>不可以拖拽</div>

2)媒体元素

HTML5主要增加了video和audio两个多媒体标签分别是视频和音频。 插入方式:

<!-- 视频标签 -->
<video src="meng.ogg" id="myVideo">视频不支持</video>
 
<!-- 音频标签 -->
<audio src="long.mp3" id="myAudio">音频不支持</audio

可以指定多个不同的媒体来源适配多个浏览器:

<!-- 音频标签 -->
<audio id="audio">
	<source id="s1" src="meng.mp3"></source>
	<source id="s2" src="meng.ogg"></source>
	音频不支持
</audio>
 
<!-- 视频标签 -->
<video id="video">
	<source id="s1" src="meng.mp3"></source>
	<source id="s2" src="meng.ogg"></source>
	视频不支持
</video>

1.属性

HTML5提供了完善的接口来查看当前多媒体元素的状态:

    autoplay:数据类型是布尔值,取得或者设置autoplay标志,媒体是否自动播放。
    buffered:数据类型是时间范围,表示已下载的缓冲的时间范围对象
    bufferedBytes:数据类型是字节范围,表示已下载的缓冲的字节范围对象
    bufferingRate:数据类型是整数,下载过程中每秒钟平均接收到的位数
    bufferingThrottled:数据类型是布尔值,表示浏览器是否对缓冲进行了节流
    controls:数据类型是布尔值,取得或者设置controls的属性,显示或隐藏用户控制界面。
    currentLoop:数据类型是整数,媒体文件已经循环的次数。
    currentSrc:数据类型是字符串,表示当前播放的媒体文件的URL(只读)。
    currentTime:数据类型是浮点数,表示已经播放的秒数。
    defaultPlaybackRate:数据类型是浮点数,表示取得或者设置默认的播放速度。默认值为1.0秒
    duration:数据类型是浮点数,表示媒体播放的总时间(秒数)。
    ended:数据类型是布尔值,表示媒体文件是否播放完成。
    loop:数据类型是布尔值,表示媒体是否循环播放。
    muted:数据类型是布尔值,表示是否静音。
    networkState:数据类型是整数。表示当前媒体的网络连接状态。0表示空;1表示正在加载;2表示正在加载数据;3表示已经加载了第一帧;4表示加载完成。
    paused:数据类型是布尔值,表示媒体是否暂停。
    playbackRate:数据类型浮点数。表示取得或者设置当前的播放速度。用户可以改变这个值,让媒体播放速度变快或者变慢。这个与defaultPlaybackRate只能有开发人员修改的defaultPlaybackRate不同。
    played:数据类型时间范围,表示到目前为止已经播放的时间范围。
    readyState:数据类型是整数。表示媒体是否已经就绪(可以播放了)。0表示数据不可用;1表示可以显示当前帧;2表示可以开始播放;3表示媒体可以从头到尾播放。
    seekable:数据类型是时间范围。表示可以搜索的时间范围。
    seeking:数据类型是布尔值。表示媒体是否正移动到媒体文件中的新位置。
    src:数据类型是字符串。表示媒体来源。任何时候都可以重写这个属性。
    start:数据类型是浮点数。表示取得或者设置媒体文件中开始播放的位置,以秒表示。
    totalBytes:数据类型是整数,表示当前资源所需的总字节数。
    videoHeight:数据类型是整数。返回视频(不一定是元素)的高度。适用于视频。
    videoWidth:数据类型是整数。返回视频(不一定是元素)的宽度。适用于视频。
    volume:数据类型是浮点数。表示取得或者设置当前的音量。值为0.0-1.0

2.事件

下列表是媒体元素相关的事件

    abort:触发时机是下载中断。
    canplay:在可以播放的时候,readyState的值为2的时候触发。
    canplaythrough:readyState的值为3的时候,触发。播放可以继续,而应该不会中断的时候触发。
    canshowcurrentframe:readyState的值为1的时候,触发。当前帧已经下载完成的时候触发。
    dataunavailable:因为没有数据而不能播放的时候,readyState的值为0。
    durationchange:duration属性值改变触发的事件。
    emptied:网络连接关闭。
    empty:发生错误阻止了媒体下载。
    ended:媒体播放到末尾,播放停止(只读)
    error:下载期间发生网络错误。
    load:触发时间所有媒体已经加载完成。这个事件可能会被废弃,建议使用canplaythrough。
    loadeddata:触发时间媒体的第一帧已经加载完成。
    loadedmetadata:触发时机媒体的元素数据已经加载完成。
    loadstart:下载已经开始。
    pause:方法是媒体开始暂停。
    play:方法是媒体开始播放。
    playing:媒体已经实际开始播放。
    progress:正在下载。
    ratechange:播放媒体的速度改变。
    seeked:搜索结束。
    seeking:正移动到新位置。
    stalled:浏览器尝试下载,但未接收到数据。
    volumechange:触发时间是volume属性或muted属性值已经改变。
    waiting:触发时间是播放暂停,等待下载更多数据。

三.错误处理于调试

1)try-catch语句

      try{
        if(5 === "5"){
          
        }else {
            throw "";
        }
      }catch(error){
        console.log("错误");
        // console.log(error);
      } finally {
        console.log("2");
      }

try:我们吧可能抛出错误的代码都放在这里。

catch:把错误处理的代码放在这里。

finally: 在try-catch语句中是可选的,一经使用不管代码如何都会执行。甚至包含return都会执行。

throw: throw操作符,用于随时抛出自定义错误。抛出错误的时候必须要给throw操作符一个值。遇到throw操作符时,代码块会立即停止执行,仅在try-catch语句捕获到被抛出的值时,代码才会继续执行。

2)错误事件

js中任何没有通过 try-catch 处理的错误都会触发window对象的error事件。

onerror事件处理程序她可以接收三个参数:错误消息,错误所在的url,错误所在的行号。要指定onerror事件处理,必须使用DOM0级技术,它没有遵循DOM2级技术

      window.onerror = function(msg, url, line){
        console.log(msg, url, line);
        //  返回false,可以阻止浏览器报错的默认行为。
        return false;
      }

四.Ajax 与 Comet

Ajax技术的核心是XMLHttpRequest对象(简称XMR)。

1)XMLHttpRequest对象

对IE7以下兼容方法

  function createXHR(){
    if(typeof XMLHttpRequest !== "undefined"){
      //  检查有无 XMLHttpRequest 对象
      return new XMLHttpRequest();
    }else if(typeof ActiveXObject !== "undefined"){
      //  检查有无 ActiveXObject 对象
      if (typeof arguments.callee.activeXString !== "string"){
          var versions = ["MSXML2.XMLHttp.6.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp"];
          var i = 0;
          var len = versions.length;
          for( i<len; i+=1;){
            try{
              new ActiveXObject(versions[i]);
              arguments.callee.activeXString = versions[i];
              break;
            }catch(error){
              //  跳过
            }
          }
      }
      return new ActiveXObject(arguments.callee.activeXString);
    }else {
      throw new Error("你使用的环境没法使用XHR");
    }
  };
  var xhr = createXHR();

1.XHR 用法

  //  open(),接受三个参数:发送的请求类型,请求的url和是否发送异步请求。
  xhr.open("get", "https://www.baidu.com", false);

  //  设置超时时间(适用于IE8及以上)
  xhr.timeout = 1000;
  xhr.ontimeout = function(){
    console.log("连接超时!");
  }

  /*
   * 必须在调用open()方法之后且send()方法之前调用setRequestHeader
   * 设置请求头部
   */

  xhr.setRequestHeader("Accept", "application/json");
  /*
   * readyState的值发生改变,都会触发onreadystatechange事件
   * 这里使用的DOM0事件级,为了兼容浏览器,而且onreadystatechange事件没有传递event对象。
   */

  xhr.onreadystatechange = function () {
    /*
     * 发送异步请求的时候,检测XHR对象的readyState的属性。
     * 0: 为初始化。尚未调用open()方法。
     * 1: 启动。一经调用open()方法,尚未调用send()方法。
     * 2: 发送。已经调用send()方法。为接收到响应。
     * 3: 接收到部分响应数据。
     * 4: 接收全部数据,可以在客户端使用。
     */

    if (xhr.readyState === 4) {
      //  检查status属性,一般为200为成功
      if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) {
        /*
         * 收到响应的数据会自动填充XHR对象,并且返回如下属性:
         * responseText:响应主体返回的文本。
         * responseXML:响应主体返回的文本。
         * status:响应的HTTP状态。
         * statusText:HTTP状态的说明。
         */

        console.log(xhr.responseText);
      } else {
        console.error(xhr.status);
      }
    }
  };

  //  send(),接收一个参数,要作为请求主体发送的数据。如果不需要通过请求主体发送数据,则必须传入null。
  xhr.send(null);

  /*
  * 在接收到响应之前还可以取消异步请求
  * 在停止请求之后还应该进行解引用操作,防止内存堆积
  */
  xhr.abort();

2.跨域资源请求

1.CORS

CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。

整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

更为详细的可以移步到 阮一峰老师的文章-跨域资源共享 CORS 详解 ,他有更详细的解析。本文CORS的介绍也是来自于阮一峰老师爹文章。

2.图像Ping(img标签跨域请求)

图像Ping是使用 img 标签与服务器单向、简单的跨域通信方式。

图像Ping常用于跟踪用户点击页面或动态广告曝光次数。

缺点:

  • 只能发送GET请求。
  • 无法访问服务器的响应文本。

3.JSONP(script标签跨域请求)

JSONP是通过动态 script标签元素来使用的,使用的时候可以为 src 属性指定一个跨域url。

优点:

  • 简单、易用。
  • 能直接访问响应文本。

缺点:

  • 确定JSONP请求是否失败不容易。
  • 跨域不安全,会被注入恶意代码

4.Comet

Comet是一种高级的Ajax技术,实现了服务器向页面实时推送数据的技术,应用场景有体育比赛比分和股票报价等。

实现Comet有两种方式:长轮询与流

长轮询是短轮询的翻版,短轮询的方式是:页面定时向服务器发送请求,看有没有更新的数据。

而长轮询的方式是,页面向服务器发起一个请求,服务器一直保持tcp连接打开,知道有数据可发送。发送完数据后,页面关闭该连接,随即又发起一个新的服务器请求,在这一过程中循环。

短轮询和长轮询的区别是:短轮询中服务器对请求立即响应,而长轮询中服务器等待新的数据到来才响应,因此实现了服务器向页面推送实时,并减少了页面的请求次数。

http流不同于上述两种轮询,因为它在页面整个生命周期内只使用一个HTTP连接,具体使用方法即页面向浏览器发送一个请求,而服务器保持tcp连接打开,然后不断向浏览器发送数据。

以下为客户端实现长轮询的方法:

	var xhr = new XMLHttpRequest();
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 4){
			result = xhr.responseText
			console.log(result);
			xhr.open('get', 'test2.php');	//在获得数据后重新向服务器发起请求
			xhr.send(null);
		}
	}
	xhr.open('get', 'test.php');
	xhr.send(null);

以下为客户端实现http流的方法:

var xhr = new XMLHttpRequest();
var	received = 0;	//最新消息在响应消息的位置
	xhr.onreadystatechange = function(){
		if(xhr.readyState == 3){
			result = xhr.responseText.substring(received);
			console.log(result);
			received += result.length;
		}else if(xhr.readyState == 4){
			console.log("完成消息推送");
		}
	}
	xhr.open('get', 'test.php');
	xhr.send(null);

5.SSE

SSE(Server-Sent Events,服务器发送事件),是服务器向客户端声明,发送的是一个数据流。

更加详细的可以参照阮一峰老师的Server-Sent Events 教程

  //url可以与当前网址同域,也可以跨域。跨域时,可以指定第二个参数,打开withCredentials属性,表示是否一起发送 Cookie。
  var source = new EventSource(url, { withCredentials: true });

  /*
  * EventSource的实例有一个readyState属性
  * 0:正连接到服务器
  * 1:打开了连接
  * 2:关闭了连接
  */
  
  /*
  * EventSource的实例还有三个事件
  * open: 在建立连接时发生。
  * message: 在从服务器接收到新事件时候触发。
  * error: 在无法建立连接时触发。
  */
  
  source.addEventListener("open",function(e){
    console.log(e);
  },false);
  
  source.addEventListener("message",function(e){
    console.log(e);
    var data = e.data;
  },false);
  
  source.addEventListener("error",function(e){
    console.log(e);
  },false);
  
  //  强制断开不带连接
  source.clone();

6.Web Socket

Web Socket的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。

Web Socket使用的是自定义的协议,使用的url模式也不同。连接使用的是 "ws://",加密的是"wss://"。

想了解更多可以参考阮一峰老师的WebSocket 教程

  var ws = new WebSocket("wss://echo.websocket.org");


  /*
   * WebSocket有一个类似于readystatechange的事件 readyState的初始值为0
   * readyState的状态有4种
   * CONNECTING:值为0,表示正在连接。
   * OPEN:值为1,表示连接成功,可以通信了。
   * CLOSING:值为2,表示连接正在关闭。
   * CLOSED:值为3,表示连接已经关闭,或者打开连接失败。
   */

  // 连接成功后的回调函数
  ws.addEventListener("open",function () {
      ws.send("Hello WebSockets!");
      /*
       * 要向服务器传送数据,可以使用send()方法传输任意字符串
       * WebSocket只能通过连接发送纯文本数据,
       * 所以对于复杂的数据结构可以通过 JSON.stringify()方法转换
       */
    },
    false
  );
  
 // 收到服务器数据后的回调函数
  ws.addEventListener("message", function (event) {
    console.log("Received Message: " + event.data);
    ws.close();
    /*
    * 要关闭WebSocket连接,可以在任何时候调用close()方法。
    * close()调用之后,readyState会变为2
    */
  },
  false);
  
  // 连接关闭后的回调函数
  ws.addEventListener("close", function (event) {
    const {code,reason,wasClean} = event;
    /*
    * code:服务器返回的数值状态码
    * reason: 字符串,返回服务器返回信息
    * wasClean:布尔值,表示连接是否关闭
    */
  },
  false);
  ws.addEventListener("error", function (event) {
    // 报错时的回调函数
  },
  false);

五.高级技巧

1)高阶函数

1.安全类型检测

javeScript中的内置的类型检测并非完全可靠,发生错误的不再少数。 用Object原生的toSrting()方法,都会返回一个[object NativeConstructorName]格式的字符串,而每个数据类型在内部都有一个[[class]]的属性,这个属性就能指定字符串中的构造函数名。所以我们可以写出如下函数方法:

// 判断是否是数组数据
function isArry(val) {
    return Object.prototype.toSrting.call(val) === "[object Array]";
}

// 同理我们可以来判断是否是函数
//  判断是否是函数
function isFunction(val){
    return Object.prototype.toSrting.call(val) === "[object Function]";
}

2.作用域安全的构造函数

构造函数是使用new操作符调用的函数,如果未使用new操作符来调用构造函数,那this会映射到全剧对象(window)上,例如:

  function Pro(txt){
    this.txt = txt;
  }
  var pro = Pro("111");
  console.log(pro.txt); // undefined
  console.log(window.txt); // 111

解决办法:

  function Pro(txt) {
    if (this instanceof Pro) {
    //  判断是否是Pro的实例
      this.txt = txt;
    } else {
      return new Pro(txt);
    }
  }
  var pro = Pro("111");
  console.log(pro.txt); // 111
  console.log(window.txt); // undefined

3.惰性载入函数

如果javaScript中有大量的if语句,每次执行代码都会在函数内部多次判断。但是有if语句肯定比没有if语句的性能慢。例如:

  function doSomeing(num){
    if(num === 1){
      console.log(1);
    }else{
      console.log(2)
    }
  };
  doSomeing(1); //  1

这类的问题,我们可以用惰性载入来完成。

惰性载入表示函数执行的分支只会发生一次。

第一种就是在函数被调用时在处理函数,函数在第一次调用时候就覆盖函数。例如:

  function doSomeing(num){
    if(num === 1){
      console.log(1);
    }else{
      console.log("num")
      doSomeing = function () {
        console.log(2)
      }
    }
  };
  doSomeing(2); //  num
  doSomeing(1);  //  2
  doSomeing(1);  //  2

第二种实现懒加载的方式是在声明函数时就指定适当的函数;这样第一次调用函数就不会损失性能,只是在首次加载会损失一点。

const doSomeing = (num => {
  if(num === 1){
    console.log(1);
    return () => {
      console.log("进入了第一行");
    }
  }else {
    console.log(2);
    return () => {
      console.log("进入了第二行");
    }
  }
})();
doSomeing();
doSomeing();

4.函数绑定

个人认为函数是解决this丢失问题

  const handler = {
    message: "Event handled",
    handleClick (event) {
      console.log(this.message);
    },
  };
  const btn = document.querySelector(".btn");
  
  // 因为this指针指向了window,但是在window环境下我们并没有声明message这个变量
  btn.addEventListener("click",handler.handleClick);
  
  //  方法1
  btn.addEventListener("click", function(){
    handler.handleClick()
  }); 
  
  // 方法2
  const msg = handler.handleClick.bind(handler);
  btn.addEventListener("click", msg);

解决思路:

其实就是在调用的时候,调用方法的时候在window环境下,所以this指向了window,只要我们从新纠正this指向就好了。

方法1 闭包

这个click事件处理的里面增加一个闭包,从闭包内部向外查找handler。

方法2 bind()

利用bind()函数创建一个保持执行环境的函数,在执行下去。

5.函数柯里化

柯里化官方的解释是:在数学和计算机科学中,柯里化是一种将使用多个参数的一个函数转换成一系列使用一个参数的函数的技术。

个人认为,柯里化是:函数作为参数对象作为处理,并且可以复用参数的函数。举个例子:

  function personalInfo(name,job,age){
    return console.log(`我叫${name},是一名${job},今年${age}岁`);
  }
  
  personalInfo("阿伟","学生","18");

在上述的例子中我们每次调用这个函数都要传入三个参数,但是我们下次调用这个函数,前几项要是一样的。那么我们可以复用么?我们可以改成如下的样子:

  function personalInfo(name){
    return function(job){
      return function(age) {
        return console.log(`我叫${name},是一名${job},今年${age}岁`);
      }
    }
  };
    
  //  参数复用
  const per1 = personalInfo("阿伟");
  const per2 = per1("学生");
  
  // 调用的输入第三个参数直接得到结果
  const per3 = per2("18");
  const per4 = per2("19");
  
  //  也可以一次调用
  personalInfo("阿伟")("学生")("18");

柯里化本身运用了闭包,在闭包本身在return回去一个结果或者函数。 虽然实现了函数柯里化,但是在面对不同的场景和不同的业务需求,这种柯里化函数不能复用所以我们改造如下:

  /** 
   * @param {fucntion} fn:要被柯里化的函数
   */
  function curry(fn){
    const len = fn.length;
    return function temp() {
      //  收集传入的参数
      const args = [...arguments];
      if(args.length >= len) {
        //  如果接受参数和传入的函数个数相等,就可以直接执行
        return fn(...args)
      }else {
        //  递归调用
        return function () {
          return temp(...args, ...arguments);
        }
      }
    }
  }
  const personalFun = curry(personalInfo);
  personalFun("阿伟")("学生")("18");
  
  //  参数复用
  const per1 = personalFun("阿伟");
  const per2 = per1("老师");
  const per3 = per2("28");

2)防篡改对象

// 不可扩展对象,使用该方法可以让传入的对象禁止添加属性和方法
Object.preventExtensions(obj);
// 使用Object.isExtensible(obj)可以判断对象是否可扩展
Object.isExtensible(obj);

// 密封的对象,不可扩展,不能删除,但可以修改
object.seal(obj);
// 使用Object.isSealed()可以确定对象是否密封
Object.isSealed(obj);

// 冻结的对象,不可扩展,密封,不能修改,访问器属性可写
Object.freeze(obj);

3)高级定时器

//  延迟执行
setTimeout(function, number);
//  清除定时器
clearTimeout(function);

//  延迟重复执行
setInterval(function, number);
//  清除定时器
clearInterval(function)

4)函数节流

概念:   规定一个单位时间,在这个单位时间内,只能有一次触发事件的回调函数执行,如果在同一个单位时间内某事件被触发多次,只有一次能生效。

在浏览器执行任务的时候,高频率的操作会让浏览器挂起或者崩溃。为了绕开这个问题,可以使用定时器对函数节流。

/**
* @description:  函数节流;
* @param {function}: fn:需要进行防抖的函数
* @param {number}: time: 需要等待的事件
*/
function throttle(fn, time) {
    let timeOut = null;
    return function () {
        if (canRun) return;
        const arg = arguments;
        const that = this;        
        timeOut = setTimeout(function () {
            fn.apply(that, arg);
          }, time);
    }
}

函数节流的基本思想是某些代码不可以在没有间断的情况下重复执行。

5) 函数防抖

防抖的概念: 在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时

防抖的原理: 触发高频函数事件后,n秒内函数只能执行一次,如果在n秒内这个事件再次被触发的话,那么会重新计算时间,要等你触发完事件 n 秒内不再触发事件,才执行

知道原理我们实现一个简单的版本:

      /**
       * @description:  防抖函数;
       * @param {function}: fn:需要进行防抖的函数
       * @param {number}: time: 需要等待的事件
       */
      function debouce(fn, time) {
        let timeOut = null;
        return function () {
          const arg = arguments;
          const that = this;

          timeOut = setTimeout(function () {
            fn.apply(that, arg);
          }, time);
        };
      }

具体业务使用方法:

 <input type="text" placeholder="请输入" class="input" />
 
 const oInput = document.querySelector(".input");
 
function getValue() {
	console.log("打印");
}

oInput.addEventListener("keyup", debouce(getValue, 300), false);