@click=“confirm”@confirm=“confirm()”的区别

@confirm=“confirm” 会有默认传参,没有event事件

@confirm=“confirm()” 加了() 就会导致默认的参数传递不过去,在某些组件例如uView ,和ant desgin of vue 的那个组件库有默认的参数使用@click=“confirm()”会有默认的event事件

函数声明与函数表达式

​ 解析器会率先读取函数声明,并使其在执行任何代码前可用(可以访问);至于函数 表达式则必须等到解析器执行到它所在的代码行,才会真正被解释执行。

​ 例如:

     alert(sum(1,1))​        function sum(num1,num2){​           renturn num1 + num2​        }

​ 以上代码可以正常执行,alert 能够拿到它之后的函数返回值;解析器会通过函数声明提升的过程,读取并将函数声明添加到执行环境中。JavaScript引擎会在第一次声明函数并将他们放到源代码树的顶部。如果换成等价的函数表达式,就会导致执行错误。

package.json 包管理器,需要哪些依赖,统一管理

webpack.config.js 配置文件 是webpack的配置文件特定名称

//引入一个包
// node 核心模块中的路径模块
const path = require('path');
// webpack 中所有的配置信息都应该写在module.exports中
module.exports = {}

let const 与var

var 可以声明相同的变量 var num = 1; var num =2 ;最新的值会覆盖

而let 和const 不行,因为他们俩具有块级作用域。

所有对象的键底层都是字符串

重名的键

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-10zgamDF-1618561342884)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201222145458364.png)]

apply和call 扩充函数作用域

apply和call接收的第一个参数,都是this的指向,apply接收的第二个参数以数组的形式接收,call则是逐个列举出来

function a(){a = 1}var c = {a:5}function b(){console.log(this.a);}b.apply(c)   //5b.call(a()) //1

encodeURI() 和 encodeURIComponent()

​ encodeURI() 和 encodeURIComponent()方法可以对URI 进行编码,以便发送给浏览器。有效的url中不能包含某些字符。例如空格;这两个编码方法就可以对URI进行编码,他们用特殊的utf-8编码替换所有无效的字符;从而可以让浏览器能够接受和理解。

​ 其中encodeURI()主要用于对整个urI(例如:http://www.xxx.com/xxxx.html)进行编码。他们的主要区别就在于:encodeURI()不会对本身属于URI的特殊字符进行编码,例如冒号,斜杠和#字符;而encodeURIComponent()则会对它发现的任何非标准字符进行编码。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nxxO93Qa-1618561342886)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201209160143983.png)]

​ 使用encodeURI()编码后的结果是除了空格之外其他的特殊字符都原封不动,只有空格被替换成了%20.而encodeURIComponent()方法则会使对应的编码替换所有非字母数字字符。这也正可以对整个uri使用encode URI(),而只能对附加在现有URI后面的字符使用encodeURIComponent()的原因所在。

​ 一般来说我们使用encodeURIComponent()方法的时候要比encodeURI()更多,因为在实践中更常见的是对查询字符串参数而不是对基础uri进行编码。

​ 与encodeURIComponent()和encodeURI()方法对应的是decodeURI()和decodeURIComponent().其中decodeURI()只能对使用encodeURI()替换的字符进行解码。例如它可以将%20替换成一个空格。同样的decodeURIComponent()编码能够解码使用encodeURIComponent()编码的所有字符,即它可以解码任何特殊字符的编码。

delete操作符

delete操作符返回一个布尔值: true指删除成功,否则返回false. 但是通过 var, constlet 关键字声明的变量无法用 delete 操作符来删除。

Set对象

const set = new Set([1, 1, 2, 3, 4]);console.log(set);
//set对象是独一无二的值的集合:也就是说同一个值在其中仅出现一次。
//我们传入了数组[1, 1, 2, 3, 4],他有一个重复值1.以为一个集合里不能有两个重复的值,其中一个就被移除了。所以结果是 {1, 2, 3, 4}

eval()方法

​ eval()方法就像一个完整的ECMAscript解析器,它只接受一个参数,即要执行的ECMAscript(或JavaScript)字符串。

​ 可以函数可以用来执行一段字符串形式的js代码,并将执行结果返回。

​ 如果使用eval()执行的字符串中,含有{} ,会将{}当成代码块,如果不想被当成代码块需要在字符串后加一个()

​ 但在开发中尽量不要使用,性能慢,还具有安全隐患。

var str = "alert('hello')"
eval(str)  //会弹出警示框

在函数中,可以常规函数添加属性,但是不能给构造函数添加属性,要想一次性给所有实例添加属性应该使用原型

URI(标识、定位任何资源的字符串)

URL是Uniform Resource Locator的缩写,译为"统一资源定位符"。URL是一种URI,它标识一个互联网资源,并指定对其进行操作或获取该资源的方法。可能通过对主要访问手段的描述,也可能通过网络“位置”进行标识。表示指定的 URI,要使用涵盖全部必要信息的绝对 URI、绝对 URL 以及相对 URL。相对 URL,是指从浏览器中基本 URI 处指定的 URL

浏览器缓存

​ 1.浏览器缓存过程:浏览器第一次加载资源服务器返回200,浏览器将资源文件从服务器上请求下来,并把response header及该请求的返回时间一并缓存。

​ 2.下一次加载资源时,先比较当前时间和上一次返回200时的时间差,如果没有超过cache-control设置的max-age,则没有过期,命中缓存,不发请求直接从本地缓存读取该文件(如果浏览器不支持http1.1,则用expires判断是否过期);如果时间过期,则向服务器发送header带有if-none-match和if-modified-since的请求。

​ 3.服务器收到请求后,优先根据Etag的值判断被请求的文件有没有做过修改,Etag值一致则没有修改,命中协商缓存,返回304;如果不一致则有改动,直接返回新的资源文件并带上新的Etag值并返回200

​ 4.如果服务器收到的请求没有Etag值,则将if-modified-since和被请求文件的最后修改时间做对比,一致则命中协商缓存,返回304,不一致则返回新的last-modified和文件并返回200.

  • ​ http协议提供了非常强大的缓存机制,了解这些缓存机制,对提高网站性能非常有帮助。
  • ​ 浏览器缓存是对浏览器在本地磁盘对用户最近请求过的文档进行存储,当访问者再次访问同一页面时。浏览器就可以直接从本地磁盘加载文档。
  • 优点有:
    • 减少冗余的数据传输,节省网费
    • 减少服务器的负担,大大提升网站性能
    • 加快了客户端加载网页速度

浏览器缓存主要分两类:协商缓存强缓存

  • 强缓存:不会向服务器发送请求,直接从缓存中读取资源,在chrome控制台的network选择中可以看到200的状态码(不会做任何询问,默认从缓存中拿取数据)

  • 协商缓存:向服务器发送请求,服务器会根据这个请求request header 的一些参数来判断是否命中协商缓存,如果命中,则返回304状态码并带上新的request header 来通知浏览器从缓存中读取资源;(会询问服务器)

  • 两则的共同点是,都是从客服端中读取资源;区别就是强缓存不会发送请求,协商缓存会发送请求。

  • 缓存中的header参数

    • 强缓存

      • Expires:response header里的过期时间,浏览器再次加载资源时,如果在这个过期时间内,则命中缓存。

      • Cache-Control: 当值设为max-age = 300 时,则代表在这个请求正确返回时间(浏览器会记录下来)的五分钟内再次加载资源,就会命中强缓存。

        设置**-no-cache**:不使用本地缓存。需要使用缓存协商,先与服务器确认返回的响应是否被更改,如果在之前的响应中存在ETag,那么请求的时候会与服务端验证,如果资源未被更改,则可以避免重新下载。

        设置**-no-store**: 直接禁止浏览器缓存数据,每次用户请求该资源,都会向服务器发送一个请求,每次都会下载完整的资源。

        设置-public:可以被所有的用户缓存,包括终端用户和CDN(内容分发网络)等中间代理服务器。

        设置private:只能被终端用户的浏览器缓存,不允许CDN等中间继缓存服务器对其缓存。

    • 协商缓存

      • Last-Modify/if-Modift-Since: 浏览器第一次请求一个资源得时候,服务器返回得header中会加上Last-modify,Last-modify是一个时间标识标识该资源得最后修改时间;当浏览器再次请求该资源时,request-header的请求该资源时,该值为缓存之间返回的Last-Modify。服务器收到if-Modify-Since后,根据资源的最后修改时间判断是否名中缓存。

      • Etag:web服务器响应请求时,告诉浏览器当前资源在服务器的唯一标识(生成规程由服务器决定)。

      • if-None-Match: 当资源过期时(使用Cache-Control标识的max-age),发现资源具有Etage声明,则再次向web服务器请求时带上头if-None-Match(Etag的值)。web服务器收到请求后发现有头if-None-Match则于被请求的资源的相应检验串进行对比,决定是否名中协商缓存。

    • 在性能上Etag要逊于last-modified,毕竟last-modified只需要记录时间,而Etag需要服务器通过算法来计算出一个hash值

    • 在优先级上,服务器校验优先考虑Etag

**协商缓存原理:**客户端向服务器端发出请求,服务端会检测是否有对应的标识,如果没有对应的标识,服务器端会返回一个对应的标识给客户端,客户端下次再次请求的时候,把该标识带过去,然后服务器端会验证该标识,如果验证通过了,则会响应304,告诉浏览器读取缓存。如果标识没有通过,则返回请求的资源。

浏览器第一次发出请求一个资源的时候,服务器会返回一个last-Modify到hearer中. Last-Modify 含义是最后的修改时间。
当浏览器再次请求的时候,request的请求头会加上 if-Modify-Since,该值为缓存之前返回的 Last-Modify. 服务器收到if-Modify-Since后,根据资源的最后修改时间(last-Modify)和该值(if-Modify-Since)进行比较,如果相等的话,则命中缓存,返回304,否则, 如果 Last-Modify > if-Modify-Since, 则会给出200响应,并且更新Last-Modify为新的值。

理解强制缓存

**基本原理:**浏览器在加载资源的时候,会先根据本地缓存资源的header中的信息(Expires 和 Cache-Control)来判断是否需要强制缓存。如果命中的话,则会直接使用缓存中的资源。否则的话,会继续向服务器发送请求。

setIntervald  // 返回一个唯一的 id。此 id 可被用于 clearInterval 函数来取消定时。

appendChild 无法添加字符串

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5DGnZvks-1618561342887)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201223105827501.png)]

remove 可以直接删除元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4vjK8Y7T-1618561342888)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201223112233448.png)]

math 方法

var values = [1,9,8,6,9,5]var max = Math.max.apply(Math,values)console.log('max',max);

​ 这里将math作为apply的第一个参数,设置正确的this指向,然后可以将任何数组作为第二个参数传递求最大值。

math.ceil() 向上取整

math.floor () 向下取整

math.round()标准舍入,四舍五入

math.random() 方法,返回一个0-1 之间的随机数,不包括0和1.对于某些站点来说这个方法非常实用,因为可以利用他来随机显示一些名人名言和新闻事件。套用下面的公司,就可以利用math.random()从某个范围内随机选择一个值。

值 = math.floor(math.random()*可能值的总数+第一个可能的值)

例如

var num = Math.floor(Math.random()*9 + 1)console.log('num',num);

typeof与instanceof

typeofinstanceof都是判断数据类型的方法,区别如下:

  • typeof会返回一个变量的基本类型,instanceof返回的是一个布尔值
  • instanceof 可以准确地判断复杂引用数据类型,但是不能正确判断基础数据类型
  • typeof 也存在弊端,它虽然可以判断基础数据类型(null 除外),但是引用数据类型中,除了function 类型以外,其他的也无法判断

可以看到,上述两种方法都有弊端,并不能满足所有场景的需求

如果需要通用检测数据类型,可以采用Object.prototype.toString,调用该方法,统一返回格式“[object Xxx]”的字符串

如下

cancelBubble 阻止冒泡事件

设置为true即可阻止冒泡事件

大部分情况下冒泡是有用的

比如事件事件委派

将事件统一绑定给共同的祖先元素,这样后代上的事件触发时,会一直冒泡到祖先元素,从而通过祖先元素的响应来处理响应函数事件。

事件委派利用了冒泡,通过委派可以减少绑定事件的次数,提高性能。

还需要注意一点需要判断是否是你需要执行的对象,不然祖先元素任何地方都可以触发响应事件。通过浏览器事件对象来判断以下

event.target 触发事件的对象

if(event.target)…然后触发事件

bom对象:

  1. window -代表这个浏览器窗口,同时window也是网页中的全局对象
  2. navigator -代表的当前浏览器的信息,通过该对象可以识别不同的浏览器
  3. location -代表当前浏览器的地址栏信息,通过location可以获取地址栏信息,或者操作浏览器跳转页面。
  4. history - 代表浏览器的历史记录,可以通过该对象来操作浏览器的历史记录,由于隐私原因,该对象不能获取到具体的历史记录,只能操作浏览器向前或向后翻页,而且该操作只能在当次访问时有效。
  5. screen -代表用户的屏幕的信息,通过该对象可以获取到用户的显示器的相关信息。

原型与in操作符

判断你某个属性存在与否,存在在实例中还是原型中:

hasOwnproperty()只在属性存在与实例中才返回true,而in 可以判断存在于原型中和实例中的属性。

只要当in操作符返回true 而hasOwnproperty()返回false,就可以确定是原型中的属性。

hasPrototypeProperty() 当只在原型中有某个属性,而实例中没有,就返回true,而当实例中存在了相同的这个属性,那么原型中的属性就用不到了,就会返回false。

javascript:; 阻止a标签默认跳转

快捷操作方法

按住ctrl 不要丢,用鼠标点击可以实现向下选中。再按住ctrl+shift+→ 横向选择(单词选中),遇到空格停止

JavaScript事件

事件冒泡

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

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>bubbling example</title></head><body><div id="myDive">click me</div></body>
</html>

​ 如果你单击了页面中的

元素,那么这个click事件会按照如下顺序传播

div->body->html->document

​ 也就是说,click事件首先在div元素上发生,而这个元素就是我们单击的元素,然后click事件沿着DOM数向上传播,子每一级节点上都会发生,直到传播到document。

​ 所有的现代浏览器都支持事件冒泡,但具体实现上还是有差别。IE5.5及更早的版本的事件冒泡会跳过元素从body直接到document。而ie9,Firefox,Chrome和Safari则将事件一直冒泡到window对象。

事件捕获

​ 另一种事件流叫做事件捕获(event capturing)。事件捕获的思想是不太具体的节点应该更早的接收到事件,而具体的节点应该最后接收到事件。事件捕获的用意在于在事件到达预定目标之前捕获它。如果任然已上面的html为例那么事件的触发顺序为:

​ document->html-> body -> div

​ 在事件捕获过程中,document对象首先接收到click,然后沿着DOM树依次向下传递。

​ 建议放心使用事件冒泡,有特殊需要时再使用事件捕获。

DOM事件流

​ 事件流包括三个阶段:事件捕获阶段,处于目标阶段和事件冒泡阶段。首先发生是事件捕获,为截获事件提供了机会。然后就是实际的目标接收到事件。最后就是冒泡阶段,可以在这个阶段对事件作出响应。

​ 在dom事件流中,实际的目标在捕获阶段不会接收到事件。这意味着在捕获阶段,事件从document到html再到body就停止了。下一个阶段就是‘处于目标阶段’,于是事件在div等元素上发生,并处理这个事件,事件处理中被看成冒泡阶段的一部分,事件冒泡发生事件又传播回文档。

事件处理程序

​ 事件就是用户或者浏览器执行的某种动作。如click,load等。而响应某个事件的函数就叫做事件处理程序(或者事件侦听器)。

HTML事件处理程序

​ 某个元素支持的每种事件,都可以使用一个相应的事件处理程序同名的html特性来指定。这个特性的值应该是能够执行的JavaScript代码。如,要在按钮被单击时执行一些JavaScript

<script>function showMessage() {alert('hello world')
}
</script>
<input type="button" value="click me" onclick="showMessage()">

​ 单击这个按钮,就会调用showMessage()函数。这个函数是一个独立的script元素定义的,当然也可以包含在一个外部文件中。事件处理程序中的代码执行时,有权访问全局作用域中的任何代码。

​ 这样指定事件处理程序具有一些独到之处。首先,这样创建一个封装着元素属性值的函数。这个函数中有一个局部变量event,也就是事件对象。

​ 通过event变量。可以直接访问事件对象,你不用自己定义它,也不用从函数的参数列表中读取。在这个函数内部,this值等于事件的目标元素。

​ 关于这个动态创建的函数,另一个有意思的地方就是它的扩展作用域的方式。在这个函数内部,可以像访问局部变量一样访问document及该元素本身的成员。这个函数使用with像下面这样扩展作用域:

function () {with(document){with(this) {// 元素属性值}}
}

实际上,这样扩展作用域的方式,无非就是想让事件处理程序无需引用表单元素就能访问其他表单字段:

<form method="post"><input type="text" name="username" value=""><input type="button" value="echo username" οnclick="alert(username.value)"</form>

在这个例子中,单击按钮会显示文本框中的文本。值得注意的是,这里直接引用了username元素。不过在HTML中指定处理程序有两个缺点。首先,存在时差问题:因为用户可能会在HTML元素一出现在页面上就触发相应事件,但当时的事件处理程序有可能尚不具备执行条件。以前面的例子来说,假设showMessage()函数是在按钮下方,页面最底部定义的。如果用户在页面解析showMessage() 函数前就点击了按钮,就会引发错误。为此很多HTML事件处理程序都会被封装在一个try-catch中,以便错误不会浮出水面。如:

<input type="button" value="click me" οnclick="try{showMessage()}catch(ex){}"

这样如果在showMessage函数有定义之前点击了按钮,用户就不会看到JavaScript错误,因为浏览器有机会处理错误之前,错误就被捕获了。

另一个缺点就是,这样扩展事件处理的作用域链在不同浏览器会导致不同结果。不同JavaScript引擎遵循的标识符解析规则略有差异,很有可能在访问非限定成员时出错。

通过HTML指定事件处理程序的最后一个缺点就是HTML与JavaScript代码紧密耦合。如果要更换事件处理程序,就要改动两个地方:HTML代码和JavaScript代码。而这正是许多开发人员摒弃HTML事件处理程序,转而使用JavaScript指定事件处理程序的原因所在。

HTML事件处理程序 html与js紧密耦合

<input id="btn1" value="按钮" type="button" onclick="showmsg();"><script>function showmsg(){alert("HTML添加事件处理");}</script>

JavaScript事件处理程序 html与js耦合度大大降低

<input id="btn2" value="按钮" type="button"><script>var btn2= document.getElementById("btn2");btn2.onclick=function(){
      alert("DOM0级添加事件处理");} btn.onclick=null;//如果想要删除btn2的点击事件,将其置为null即可</script>

DOM0级事件

​ 通过JavaScript指定事件处理程序的传统方式,就是将一个函数赋值给一个事件处理程序属性。这种为事件处理程序赋值的方法是在第四代web浏览器中出现的,而且至今任然为现代浏览器所支持。一是因为简单,而是因为具有跨浏览器的优势。要使JavaScript指定事件处理程序,首先必须取得一个要操作的对象的引用

​ 每个元素(包括window和从document)都有自己的事件处理程序属性,这些属性通常全部小写,例如onclick。这种属性的值设置为一个函数,就可以指定事件处理程序。如:

var btn = document.getElementById("myBtn")
btn.onclick = function () {alert('btn 被点击了')
}

​ 这里我们通过文档对象取得了一个按钮的引用,然后为它指定了onclick事件处理程序。但要注意,在这些代码运行以前不会指定事件处理程序,因此如果这些代码在页面中位于按钮后面,就有可能在一段时间内怎么单击都没有反应。

​ 使用DOM0级事件方法指定的事件处理程序被认为是元素的方法。因此,这个时候的事件处理程序是在元素的作用域中运行:换句话说,程序中的this引用当前元素。

var btn = document.getElementById("myBtn")
btn.onclick = function () {alert(this.id)  //myBtn
}

​ 单击按钮显示的元素的ID,这个ID是通过this.id取得的。不仅仅是ID,实际上可以在事件处理程序中通过this访问元素的人格属性和方法。以这种方式添加的事件处理程序会在事件流的冒泡阶段被处理。

​ 也可以删除DOM0级事件方法指定的事件处理程序,只要在下面这样将事件处理程序属性的值设为null即可

btn.onclick = null // 删除事件处理程序

DOM2级事件

​ DOM2级事件定义了两个方法,用于处理指定和删除事件处理程序:addEventListener()和removeEventListener()。所有的DOM节点中都包含这两个方法,并且它们都接收三个参数:要处理的事件名,作为事件处理程序的函数和一个布尔值。最后这个布尔值参数如果是true,表示在捕获阶段调用事件处理程序,如果是false,表示在冒泡阶段调用事件处理程序。

var btn = document.getElementById("myBtn")
btn.addEventListener('click',function(){alert(this.id)  //myBtn
},false)
btn.addEventListener('click',function(){alert('myBtn')  //myBtn
},false)

​ 这里为按钮添加了两个事件处理程序。这两个事件处理程序为按照添加它们的顺序触发,因此首先会显示id,然后显示myBtn。

​ 通过addEventListener添加的事件处理程序只能使用removeEventListener来移除,一出事传入的参数与添加处理程序时的参数相同。这就意味着通过addEventListener添加的匿名函数无法移除。如:

var btn = document.getElementById("myBtn")
btn.addEventListener('click',function(){alert(this.id)  //myBtn
},false)// 这样的移除方法是无效的
btn.removeEventListener('click',function(){alert(this.id)
},false)

想要通过dom二级事件移除需要传入具名事件函数

var btn = document.getElementById("myBtn")
var handler = function() {alert(this.id)
}
btn.addEventListener('click',handler,false)
btn.removeEventListener('click',handler,false) //这样才能移除

跨浏览器的事件处理程序

事件对象

​ 在触发dom上的某个事件时,会产生事件对象event,这个对象包含着所有与事件有关的信息。包括导致事件的元素,事件的类型以及其他事件与特定事件的相关的信息。如:鼠标操作导致的事件对象中,会包含鼠标的位置信息,而键盘操作导致的事件对象中,会包含与按下键有关的信息。所有的浏览器都支持event对象,但支持的方式不同。

DOM中的事件对象

​ 兼容dom对的浏览器会将一个event对象传入到事件处理程序中。无论指定事件处理程序使用什么方法(dom0级还是dom2级),都会传入event对象。

var btn = document.getElementById('myBtn')
btn.onclick = function(event) {console.log(event.type)  // click
}
btn.addEventListener('click',function(event){console.log(event.type)  //click
},false)

​ 这里两个事件处理程序都会打印event.type属性表示的事件类型。这个属性始终都会包含被触发的事件类型。

​ 所有事件都会有下列成员:

属性/方法 类型 读/写 说明
bubbles Boolean 只读 表明事件是否冒泡
cancelable Boolean 只读 表明是否可以取消事件的默认行为
currentTarget() element(元素) 只读 其事件处理程序当前正在处理事件的那个元素
defaultPrevented Boolean 只读 为true表示已经调用了preventDefault
detail Integer 只读 与事件相关信息
eventPhase Integer 只读 调用事件处理程序的阶段:1表示捕获阶段,2表示“处于目标阶段”,3表示冒泡阶段。
preventDefault() function 只读 取消事件的默认行为。如果cancelable是true,则可以使用这个方法。
stopImmediatePropagation() function 只读 取消事件的进一步捕获或冒泡,同时阻止任何事件处理程序被调用(dom三级事件新增)
stopPropagation() function 只读 取消事件的进一步捕获或冒泡。如果bubbles为true,则可以使用这个方法。
target element 只读 事件的目标
trusted Boolean 只读 为true表示事件时浏览器生成的。为false表示事件是由开发人员通过JavaScript创建的
type String 只读 被触发的事件的类型
view AbstractView 只读 与事件的抽象视图。等同于事件的window对象

​ 在事件处理程序内部,对象this始终等于currentTarget的值,而target则只包含事件的实际目标。如果直接将事件处理程序指定给了目标元素,则this,currentTarget和target包含相同的值。

var btn = docment.getElementById("myBtn")
btn.onClick = function(event) {console.log(event.currentTarget === this)  // trueconsole.log(event.target === this)  // true}

​ 这个例子检测了currentTarget和target与this的值。由于click事件的目标是按钮。因此这三个值是相等的。如果这个事件处理程序存在于按钮的父节点中,如document.body,那么这些纸是不相等的。

document.body.onclick = function(event) {console.log(event.currentTarget === document.body)  // trueconsole.log(this === document.body) // trueconsole.log(event.target === document.getElementById("myBtn")) // true
}

​ 单击按钮时,this和currentTarget都等于document.body,因为事件处理程序是注册在body上面的,而target元素却等于这个按钮,因为他是click事件的真正的目标。由于按钮上并没有注册事件,结果click事件就冒泡到了document.body。在那里事件才得到了处理。

​ 在需要通过一个函数处理多个事件时,可以使用type属性。

var btn = docment.getElementById("myBtn")
var handler = function(event) {switch(event.type){case "click":console.log("点击事件")breakcase "mouseover":console.log("鼠标事件")breakcase "mouseout":console.log("鼠标离开")break}
}
btn.click = handler
btn.onmouseover = handler
btn.onmouseout = handler

​ 这个例子定义了一个名为handler的函数,用于处理三种事件:click,mouseover,mouseout,执行相应操作时会通过event.type来判断是哪种事件,然后执行相应操作。

​ 要阻止特定事件的默认行为,可以使用preventDefault()方法。例如,链接的默认行为就是在被单击时会导航到指定的URL。如果你想阻止链接导航这一默认行为,那么通过链接的onclick事件处理程序可以取消它。

var link = document.getElementById("myLink")
link.onclick = function(event) {event.preventDefault()
}

​ 只要cancelable属性设置为true的事件,才可以使用preventDefault来取消其默认行为。

​ 另外,stopPropagation() 方法用于立即停止事件在DOM层次中的传播,即取消进一步事件捕获或冒泡。例如直接添加一个按钮的事件处理程序可以调用stopPropagation(),从而避免触发注册在document.body上面的事件。

var btn = document.getELementById('myBtn')
btn.onclick = function(event){console.log("按钮点击事件")event.stopPropagation()
}
document.body.onclick = function(event) {console.log("窗口点击")
}

​ 对于这个例子而言,如果不调用stopPropagation()事件,就会在点击按钮时,会打印两个日志。可是由于click 事件根本不会触发注册宰割元素上的onclick事件处理程序。

​ 事件对象的eventPhase属性,可以用来确定当前事件正位于事件流的那个阶段。如果事件在捕获阶段调用的事件处理程序,那么eventPhase 等于1;如果事件处理程序处于目标对象上,则eventPhase等于2,如果是在冒泡阶段调用事件处理程序,eventPhase等于3,这里需要注意的是,经管“处于目标”,发生在冒泡阶段,但eventPhase 任然等于2。

var btn = document.getELementById("myBtn")
btn.onclick = function(event){console.log(event.eventPhase) //2
}
document.body.addEventListener('click',function(event){console.log(event.eventPhase)  //1
},true)
document.body.onclick = function(event){console.log(event.document)    //3
}

​ 当点击这个按钮时,首先执行的事件处理程序时在捕获阶段触发的添加到document.body中的那一个,结果会打印1,接着会触发按钮上注册的事件处理程序,此时的evnetPhase值为2.最后一个触发的事件处理程序,是在冒泡阶段执行的添加到document.body上的那一个,显示eventPhase的值为3,而当eventPhase等于2时,this,target和currentTarget始终都是相等的。

​ 只有在事件处理程序执行期间,event对象才会存在;一旦事件处理程序执行完成,event对象就会被销毁。

跨浏览器事件对象

​ 虽然DOM和IE中的event对象不同,但基于它们之间的相似性依旧可以拿出夸浏览器的方案来。IE中的event对象的全部信息和方法DOM中都有,只不过实现方式不一样。不过这种对应关系让实现两种事件模型之间的映射非常容易。

var EventUtil = {addHander: function(element,type,handler){};getEvent: function(event) {return event?event: window.event};getTarget: function(event) {return event?target: window.srcElement};preventDefault: function(event){if(event.preventDefault) {event.preventDefault()} else {event.returnValue = false}};removeHandler: function(event){};stopPropagation: function(event){if(event.stopPropagation){event.stopPropagation()} else {event.cancelBubble = true}}
}

JavaScript事件类型

  • UI(user interface,用户界面)事件,当用户与界面上的元素交互时触发

    UI事件指的是那些不一定与用户操作有关的事件

    • load:当页面完全加载后在window上面触发,当所有框架都加载完毕时在框架集上面触发,当图像加载完毕时在元素上触发,或当嵌入的内容加载完毕时在元素上面触发。

      一般来说,在window上面触发的事件都可以做body元素通过相应的特性来指定,因在HTML中无法访问window元素。实际上,这是为了保证向后兼容的一种权宜之计,但所有的浏览器都能很好的支持这种方式。

      就像在图像也可以也可以触发load事件,无论是在DOM元素图像还是HTML中的图像元素。因此,可以住HTML中为任何图像指定onload事件处理程序

      <img id="img" src='1.jpg' οnlοad="alert('图片加载完毕')">
      

      同样可以使用JavaScript来实现

      var image = document.getElementById('img')
    • unload:当页面完全卸载后在window上面触发,当所有框架都卸载后在框架集上面触发,或者当嵌入的内容卸完毕后在元素上面触发。

      与load事件对应的是unload事件,这个事件在文档被完全卸载后触发。只要用户从一个页面切到另一个页面,就会发生unload事件。而利用这个事件多情况是清除引用,以避免内存泄漏。与load事件类似,也有两种指定onUnload事件处理程序的方式。第一种方式是JavaScript方式。

      EventUtil.addHandler(window,'unload',function(event){console.log('onUnload')
      })
      

      此时生成的event对象在兼容DOM的浏览器只包含target属性(值为document)

      第二种就是写在元素标签上

      <body onunload="alert('unload!!')"></body>
      
    • ebort:在用户停止下载过程是,如果嵌入的内容还没有加载完,则在元素上面触发

    • error: 当发生JavaScript错误时在window上面触发,当无法加载图像时在元素上面触发,当无法加载嵌入内容是在元素上触发,或者当有一个或多个框架无法加载时在框架集上面触发。

    • select :当用户选择文本框(input或)中的一个或多个字符时触发。

    • resize:当窗口或框架的大小发生变化是在window或框架上面触发。

      当浏览器窗口被调整到一个新的高度或宽度时,就会触发resize事件。这个事件在window(窗口)上面触发,因此可以调用JavaScript或者元素中onresize特性来指定事件处理程序。

      EventUtil.addHandler(window,'resize',function(event){console.log('resize')
      })
      
    • scroll: 当用户滚动带滚动条的元素中的内容时,在该元素上面触发。元素包含所加载页面的滚动条

      虽然scroll事件是在window对象上发生的,但是它实际表示的则是页面中相应元素的变化。在混杂模式下,可以通过元素的scrollLeft和scrollTop来监控到这一变化;而在标准模式下,除Safari之外的所有浏览器都会通过HTML元素来反应这一变化。

      EventUitl.addHandlder(window,"scroll",function(event){if(document.compatMode == 'csslCompat'){alert(document.documentElement.scroolTop)} else {alert(document.body.scrollTOP)}
      })
      

      以上代码的事件处理的事件处理程序会输出页面的垂直滚动位置-根据呈现模式不同使用不同的元素。

      与resize事件类似,scroll事件也会在文档被滚动期间重复被触发,所以必要尽量保持事件处理程序的代码简单。

  • 焦点事件,当元素获得或失去焦点时触发。

    ​ 焦点事件会在页面获得或失去焦点时触发。利用这些事件与document.hasFocus()方法及document.activeELement属性配合,可以知晓用户在页面上的行踪。主要焦点事件

    • blur:在元素失去焦点时触发。这个事件不会冒泡;所有浏览器都支持它。

    • DOMFocusIn:在元素获得焦点时触发。这个事件与HTML事件focus等价。但是它冒泡。只有Opera支持这个事件。DOM3级事件废弃了它,选择了focusin。

    • DOMFocusOut:在元素失去焦点时触发。这个事件与HTML的blur的通用版本。只有Opera支持这个事件。DOM3级事件废弃了它,选择了focusout。

    • focus:在元素获得焦点时触发。这个事件不会冒泡;所有浏览器都支持它。

    • focusin:在元素获得焦点时触发,与HTML事件focus等价,但是它冒泡。

    • focusout: 在元素失去焦点时触发。这个事件是HTML blur的通用版本。

      这一类事件中最主要的是focus和blur,它们都是JavaScript早期就得到所有浏览器支持的事件。这些事件的最大问题是它们不冒泡。因此IE的focusin和focusout的方式被DOM3级事件采纳为标准方式。

  • 鼠标事件,当用户通过鼠标在页面上执行操作时触发

  • 滚轮事件,当用户使用鼠标滚轮(或类似设备)时触发

    鼠标事件是web开发中最常用的一类事件,毕竟鼠标是最主要的定位设备。DOM3级事件中定义了9个鼠标事件:

    • click:在用户单击鼠标(一般是左边的按钮)或者按下回车键时触发。这一点对确保易访问性很重要,这就意味着onclick事件处理事件既可以通过鼠标也可以通过键盘执行。

    • dbclick: 在用户双击主鼠标按钮(一般是左边的按钮)时触发。从技术上说,这个事件并不是DOM2级事件中规范规定的,但是鉴于得到广泛支持,所以DOM3级事件把它纳入标准。

    • mousedown:用户按下了任意鼠标按钮时触发。不能通过键盘触发这个事件。

    • mouseenter:在鼠标光标从元素外部移动到元素范围之内时触发。这个事件不冒泡,而且光标移动到该元素的后代元素上时不会触发。DOM3级事件把它纳入了规范。

    • mouseleave:在位于元素上方的鼠标光标移动到元素范围之外时触发。这个事件不冒泡,而且在光标移动到后代元素不会触发。DOM3级事件纳入规范。

    • mousemove:当鼠标指针在元素内部移动时重复的触发。不能通过键盘触发。

    • mouseout:当鼠标指针位于一个元素上方,然后用户将其移入到另一个元,移入的另一个元素可能位于前一个元素外部,也可能是这个元素的子元素。不能通过鼠标触发。

    • mouseover:在鼠标指针位于一个元素外部,然后将其首次移入到另一个元素边界之内是出发。不能通过键盘触发。

    • mouseup:在鼠标释放鼠标是触发,不同通过键盘触发。

      页面上所有元素都支持鼠标事件。除了mouseenter和mouseleave,所有的鼠标事件都会冒泡。

    • 客户区坐标位置

      鼠标事件都是在浏览器可视口中的特定位置上发生的。这个位置保存在事件对象的clientX和clientY属性中。所有浏览器都支持这两个属性,它们的值表示事件发生时鼠标指针在可视口中水平和垂直坐标。

      获取鼠标事件的客户端坐标信息:

      var div = document.getElementById("myDiv")
      Event.addHandler(div,"click",function(event){alert(event.clientX+","+event.clientY)
      })
      

      当用户单击div这个元素是,就会看到事件的客户端坐标信息。注意:这些值不包括页面滚动的距离,因此这个位置并不表示鼠标在页面上的位置。

    • 页面坐标位置

      ​ 通过客户端坐标能够知道鼠标在可视窗口中什么位置发生的,而页面坐标(比如:有滚动条的情况下)通过事件对象pageX和pageY属性,能够得到事件是在页面中什么位置发生的。位置是从页面左边和顶边开始计算,而非从可视窗口开始计算。

      var div = document.getElementById("myDiv")
      Event.addHandler(div,"click",function(event){alert(event.pageX+","+event.pageY)
      })
      

      在页面没有滚动条的情况下,pageX和pageY的值与clientX和clientY的值相等。

      • 屏幕坐标位置
        鼠标事件发生时,还有相对整个电脑屏幕的位置,而通过screenX和screenY属性就可以确定鼠标发生时鼠标指针相对整个屏幕的坐标信息。
    • 修改键

      ​ 虽然鼠标事件主要是使用鼠标来触发,但是在按下鼠标键盘上的某些键的状态也可以影响到所要才去的操作。这些修改键就是shift、ctrl、alt、和meta(在window键盘中就是windows键,在苹果机中就是cmd键)、它们经常没用来修改鼠标事件行为。DOM为此规定了四个属性,表示这些修改键的状态:shiftKey,ctrlKey、altKey和metaKey。这些属性包含的都是布尔值,如果相应的键被按下了,则值true,否则为false。当某个鼠标事件发生时,通过检测这几个属性就可以确定用户是否按下了其中的键。例子:

      var div = document.getElementById("myDiv")
      EventUtil.addHandler(div,"click",function(event){event = EventUtil.getEvent(event)var keys = new Array()if(event.shiftKey){keys.push("shift")}if(event.ctrlKey){keys.push("ctrlKey")}if(event.altKey){keys.push("altKey")}if(event.metaKey){keys.push("metaKey")}alert("ksys" + keys.join(","))
      })
      

      ​ 在这里,我们通过一个onclick事件处理程序检测不同修改键的状态。数组keys中包含着被按下的修改键名称,如果值为true就被添加到kyes

    • 相关元素

      ​ 在发生mouseover和mouseout 事件时,还会涉及更多的元素。这两个事件都会涉及把鼠标指针从一个元素移动到另一个元素。对mouseover事件而言,事件的主目标是获得光标元素,而相关元素就是失去光标光标的元素。类似的对mouseout事件而言,事件的主目标是失去光标的元素,而相关元素则是获得光标的元素。

      <div id="myDiv"style="height:100px;width:100px;backgroundColor:red"></div>
      

      ​ 这里页面上有一个div元素。如果指针一开始就位于这个div元素上,然后再移出这个元素,那么就会触发mouseout事件。与此同时body元素上触发mouseover事件。

      ​ DOM通过event对象的relatedTarget属性提供了相关了相关元素的信息。这个属性只对mouseover和mouseout事件才包含值;对于其他事件这个值是null。

      var EventUtil = {getRelatedTarget: function(event) {if(event.relatedTarget) {return event.relatedTarget} }
      }
      
    • 鼠标按钮

      ​ 只有在主鼠标按钮被单击(或键盘回车键被按下)时才会触发click事件,因此检测按钮的信息并不是必要的。

      ​ 但是对于mousedown和mouseup事件来说,则在其event对象上存在一个button属性,表示按下或释放的按钮。DOM的button属性可能如下三个值:0表示主鼠标按钮,1表示中间的鼠标按钮(鼠标滑轮按钮),2表示次鼠标按钮。在常规的设置中,主鼠标按钮就是鼠标左键,而次鼠标按钮就是鼠标右键。

    • 更多的事件信息

      ​ “dom2级事件” 规范在event对象中还提供了detail属性,用于给出有关事件的更多信息。对于鼠标事件来说,detail中包含了一个数值,表示给定位置上发生了多少次点击。在同一一个像素上相继地发生一次mousedown和mouseup事件上算作一次单击。detail属性从1开始计数,每次发生后都会递增。如果鼠标在mousedown和mouseup之间移动了位置,则detail就会被重置为0.

      IE也提供了一些属性

      • altLeft:布尔值,表示是否按下了alt键。如果altLrft的值为true,则AltKey的值也为true。
      • CtrlLeft:布尔值。表示是否按下了ctrl键。如果CtrlLeft的值为true,则CtrlKey的值为true。
      • offsetX:光标相对于目标元素边界的X坐标
      • offsetY: 光标相对于目标元素边界的y坐标
      • shiftLeft:布尔值,表示是否按下了shift键。如果shiftLeft的值为true,则shiftKey的值也为true。
    • 滚轮事件

      ​ 当用户通过鼠标滚轮与页面交互、在垂直方向上滚动页面是(无论是向上还是向下),就会触发mousewheel事件。这个事件可以在任何元素页面上触发,最终会冒泡到document或window对象。与mousewheel事件对应的event对象包括鼠标事件所有的标准信息外,还包含一个特殊的wheelDelta属性。当用户向前滚动鼠标滚轮是,wheelDelta是120的倍数,当用户向后鼠标滚轮时,wheelDelta时-120的倍数

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BUbe7ZCy-1618561342890)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210127171921078.png)]

    • 触摸设备

      iOS和Android设备的实现非常特别,因为这些设备没有鼠标。在面向iPhone和iPod中的Safari开发时,要记住以下几点:

      • 不支持dblclick事件。双击浏览器窗口页面会放大画面,而且没办法改变该行为。
      • 轻击可点击元素会触发mousemove事件。如果此操作会导致内容变化,将不再有其他事件发生。如果屏幕没有因此变化,那么会依次发生,mousedown、mouseup和click事件。轻击不可点击元素不会触发任何事件。可点击元素是指那些点击可产生默认操作的元素(如链接),或者那些已经被指定了onclick事件处理程序的元素。
      • mousemove事件也会触发mouseover和mouseout事件。
      • 两个手指放在屏幕上且页面随瘦子移动而滚动时会触发mousewheel和scroll事件。
  • 文本事件,当在文档中输入文本时触发

  • 键盘事件,当用户通过键盘在页面上执行操作时触发

    键盘与文本事件

    ​ 用户在使用键盘会触发键盘事件。“DOM2级事件”最初规定了键盘事件,但是在最终定稿之前又删除了相应的内容。结果,对键盘事件的主要遵循的是DOM0级。

    "DOM3级事件"为键盘事件制定了规范。

    有三个键盘事件

    • keydown:当用户按下键盘上的任意键时触发,而且按住不放的话,就会重复触发此事件。

    • keypress:当用户按下键盘上的字符键(例如:a,b,c键)时触发,而且按住不放的话,会重复触发此事件。按下ESC键也会触发这个事件。

    • keyup:当用户释放键盘上的键时触发。

      ​ 虽然所有的元素都支持这个是那个事件,但是在用户通过文本框输入文本时最常用到。

      ​ 只有一个文本事件:textInput。这个事件是对keypress的补充,用意是将文本显示给用户之前更容易拦截文本。在文本插入文本框之前会触发textInput事件。

      ​ 在用户按下了一下键盘上的字符键时首先会触发keydown事件,紧接着就是keypress事件,最后触发keyup事件。其中,keypress都是在文本框发生变化之前被触发的;而keyup事件则是在文本框已经发生变化之后被处罚的。如果用户按下一个字符不放,就会重复触发keydown和keypress事件,知道用户松开该键为止。

      ​ 如果用户按下的是一个非字符键,那么首先会触发keydown事件,然后就是keyup事件。如果按住这个非字符键不放,那么就会一直重复触发keydown事件,直到用户松开这个键,此时触发keyup事件。

    • 键码

      ​ 在发生keydown和keyup事件,event对象的keycode属性会包含一个代码,在键盘上有一个特定的键对应。对数字字母字符键,keyCode属性的值与ASCII码对应小写字母或数字的编码相同。因此,数字键7的keyCode值为55,而字母A键的keyCode值为65–与shift键的状态无关。DOM和IE的event对象都支持keyCode属性。

      var textbox = document.getElementById("myText")
      EventUtil.addHandler(textbox,"keyup",function(event){event = EventUtil.getEvent(event)alert(event.keyCode)
      })
      
  • textInput 事件

    ​ “DOM3级事件” 规范中引入了一个新事件,名叫textInput。根据规范,当前用户在可编辑区域中输入字符时,就会触发这个事件。这个用于替代keypress的textInput事件的行为稍有不同。区别之一就是任何可以获得焦点的元素都可以触发keypress事件,但是只有可编辑区域才能触发textInput事件。区别二就是textInput事件只会在用户按下能够输入实际字符的键时才会触发,而keypress则是在按下能够影响文本显示的键时才会触发(如退格键)

    ​ 由于textInput 事件主要考虑的是字符,因此它的event对象中还包含一个data属性,这个属性的值就是用户输入的字符(而非字符编码)。换句话谁,用户在没有按下档键(shift键)的的情况下按下了s键,data的值就是小写“s”

    var textbox = document.getElementById("myText")
    EventUtil.addHandler(textbox,"textInput",function(event){event = EventUtil.getEvent(event)alert(event.data)
    })
    

    在这里,插入文本框中的字符会通过一个警告框显示出来。

    另外,event对象上还有一个属性,叫inputMethod,表示把文本输入到文本框中的方式。

    • 0,表示浏览器不确定是怎么输入的。

    • 1,表示是用键盘输入的。

    • 2,表示文本是粘贴进来的。

    • 3,表示文本是拖放进来的。

    • 4,表示文本是使用IME输入的。

    • 5,表示文本是通过表单中选择某一项输入的。

    • 6,表示文本是通过手写输入的(比如手写笔)。

    • 7,表示文本是通过语音输入的。

    • 8,便是文本是通过几种方法组合的。

    • 9,表示文本是通过脚本输入的。

      使用这个属性可以确定文本是如何输入到控件中的,从而可以验证其有效性。

  • 变动事件。

    DOM2级的变动(mutation)事件能在DOM中的某一部分发生变化是给出提示。变动事件是为XML或HTML DOM设计的,并不特定于某种语言。DOM2级级定义了如下变动事件。

    • DOMSubtreeModified:在DOM结果中发生人格变化时触发。这个事件在其他任何事件触发后都会触发。

    • DOMNodeInserted:在一个节点作为子节点插入到另一个节点时触发。

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

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

    • DOMAttrModified:在特性被修改之后触发。

    • DOMCharacterDataModified:在文本节点的值发生变化时触发。

  • 删除节点

    ​ 在使用removChild() 或replaceChild() 从DOM删除节点时,首先会触发DOMNodeRemoved事件。这个事件的目标(event.target)是被删除的节点,而event.relatedNode属性中包含着对目标节点父节点的引用。在这个事件触发时,节点尚未从父节点删除,因此其parentNode属性任然指向父节点(event.relatedNode相同)。这个事件会冒泡,因而可以在DOM的任何层次上处理它。

    ​ 如果被移除的节点包含子节点,那么在其所有子节点以及这个被移除的节点上会相继触发DOMNodeRemovedFromDocument事件。但这个事件不会冒泡,所以只有直接指定给其中一个子节点的事件处理程序才会被调用。这个事件的目标是相应的子节点或者那个被移除的节点,除此之外event对象中不包含其他信息。

    ​ 紧随其后的触发的是DOMSubtreeModified事件。这个事件。这个事件的目标是被移除的父节点;此时的event对象也不会提供与事件相关的其他信息。

    为了理解上述事件的触发过程:

    <body><ul id="myList"><li>item 1</li><li>item 2</li><li>item 3</li></ul>
    </body>
    

    在这个例子中假设我们要移除ul 元素,此时就会依次触发以下事件。

    1.在ul元素上会触发DOMNodeRemoved事件。relatedNode属性等于document.body

    1. ul元素上触发DOMNodeRemovedFromDocument
    2. ul元素上子节点的每个li会上触发DOMNodeRemovedFromDocument事件。
    3. 在document.body上触发DOMSubtreeModified事件,因为ul是document.body的直接子元素。
  • 插入节点

    ​ 在使用appendChild()、replaceChild()或insertBefore()向DOM中插入节点时,首先会触发DOMNodeInserted事件。这个事件的目标是被插入的节点,而event.relatedNode属性中包含一个队父节点的引用。在这个事件触发时,这个节点以及被插入到了新的父节点中。这个事件时冒泡的,因此可以在DOM的各个层次上处理它。

    ​ 紧接着,会插入的节点上面触发DOMNodeInsertedIntoDocument事件。事件不冒泡,因此必须在插入节点之前为它添加这个事件处理程序。这个事件的目标是被插入的节点,除此之外event对象不包括其他信息。

    最后一个触发的事件时DOMSubtreeModified,触发新插入节点的父节点。

  • 合成事件,当为IME(Input Method Editor,输入法编辑器)输入字符时触发,而如果在按住上档键时,data就是大写“S”

  • HTML5事件

    ​ DOM规范没有涵盖所有浏览器支持的所有事件。很多浏览器处于不同的目的—满足用户需求或解决特殊问题,还是现实一些自定义的事件。HTML5详尽列出了浏览器应该支持的所有事件。

    • contextmenu事件

      ​ windows95在PC中引入了上下文菜单的概念,即通过单击鼠标右键可以调出上下文菜单。不久这个概念也被引入了web领域。为了实现上下文菜单,开发人员面临的主要问题是如何确定应该显示上下文菜单(在windows中,是右键单击;在Mac中,是ctrl+单击),以及如何屏蔽与该操作关联的默认上下文菜单。为解决这个问题,出现了contextmenu这个事件,用以表示何时应该显示上下文菜单,以便开发人员取消默认的上下文菜单而提供自定义的菜单。

      ​ 由于contextmenu事件是冒泡的,因此可以为document指定一个事件处理程序,用以处理页面中发生的所有此类事件。这个事件的目标是发生用户操作的元素。在所有浏览器中都可以取消这个事件;在兼容的DOM浏览器中,使用event.preventDefalut();在IE中,将event.retuenValue的值设置为false。因为contextmenu事件属于鼠标事件,所以其事件对象包含于光标位置有关的所有属性。通常使用contextmenu事件来显示自定义的上下文菜单。而使用onclick事件处理程序来隐藏该菜单。

      [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bSFkSAOJ-1618561342891)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210129171611381.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AZAJB8XY-1618561342891)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210129171633276.png)]

​ 在这里,为div元素添加了oncontextmenu事件的处理程序。这个事件处理程序首先会取消默认行为,以保证不显示浏览器默认的上下文菜单。然后,再根据event对象的clientX和clientY属性的值,来确定放置ul元素的位置。最后一步就是通过将visibility属性设置为visible来显示自定义上下文菜单。另外,还未document添加了一个onclick事件处理程序,以便用户能够通过鼠标单击来控制显示还是隐藏菜单(单击也是隐藏系统上下文菜单的默认操作)。

  • beforeunload 事件

    ​ 之所以有发生在window对象上的beforeunload 事件,是为了让开发人员有可能在页面卸载前阻止这一操作。这个事件会在浏览器卸载之前触发。可以通过它来取消卸载并继续使用原有页面。但是,不能彻底取消这个事件,因为那就相当于让用户无法离开当前页面了。为此,这个事件的意图是将控制权交给用户。显示的消息会告知用户页面将被卸载(正因为如此才会显示弹框,询问用户是否真的要关闭页面,还是希望继续留下来)

    EventUtil.addHandler(window,'beforeunload',function(event){event = EventUtil.getEvent(event)var message = 'I`m really going to miss you if you go'event.returnValue = messagereturn message
    })
    
  • DOMContentLoad 事件

    ​ window的load事件会在页面中的一切都加载完毕时触发,但是这个过程可能会因为要加载外部资源过多而花费一段时间。而DOMContentLoad 事件则在形成完整的DOM树之后就会触发,不理会图像,JavaScript文件,css文件或其他资源十分已经下载。与load事件不同,DOMContentLoad 支持在页面下载的早期添加事件处理程序,也就意味着用户能够尽早地与页面进行交互。

    ​ 要处理DOMContentLoad 事件,可以为document或window添加相应的事件处理程序(尽管这个事件会冒泡到window,但是它的目标实际上是document)

    Event.addHandler(document,'DOMContentLoaded', function(event){alert('content load')
    })
    

    ​ DOMContentLoaded 事件对象不会提供任何额外的信息(其target属性时document)

    ​ 这段代码的意思就是:在“当前JavaScript处理完成后立即运行这个函数。”在页面下载和构建期间,只有一个JavaScript处理过程,因此超时调用会在该过程结束时立即触发。至于这个事件与DOMContentLoad 被触发时间能否同步,主要还是取决于用户使用的浏览器和页面中的其他代码。为了确保这个方法有效,必须将其作为页面中的第一个超时调用;即便如此,也还是无法保证在所有环境中该超时调用一定会早于load被触发。

  • pageshow和pagehide事件

    ​ 名叫“往返缓存”:可以在用户使用浏览器的“后腿”和“前进” 按钮时加快页面的转换速度。这个缓存中不仅保存着页面数据,还保存了DOM和JavaScript的状态;实际上是将整个页面都保存在了内存里。如果页面位于bfcache中,那么在此打开该页面就不会触发load事件。尽管由于内存中保存了整个页面的状态,不触发load事件也不应该导致什么问题,但是为了更形象说明bfcache,Firefox还是提供了一些新事件。

    ​ 第一个事件就是pageshow,这个事件在页面显示时触发,无论该页面是否来自bfcache。在重新加载的页面中,pageshow会在load事件触发后触发;而对于bfcache中的页面,pageshow会在页面状态完全恢复的那一刻触发。另外需要注意的是,虽然这个事件的目标是document,但是必须将其事件处理程序添加到window

    (function(){var showCount = 0EventUtil.addHandler(window,"load",function(){alert("load fired")})EventUtil.addHandler(window,"pageshow",function(){showCount++alert("show has been fired" + showCount +"tiems.persisted?" +event.persisted)})
    })
    

    ​ 通过检测persisted属性,就可以根据页面在bfcache中的状态来确定是否需要采取其他操作。

    ​ 与pageshow事件对应的是pagehide事件,该事件会在浏览器卸载页面的时候触发,而且是在unload事件之前触发。与pageshow事件一样,pagehide在document上面触发,但其事件处理程序必须要添加到window对象。这个事件的event对象包括persisted属性。

    ​ 有时候,可能需要在pagehide事件触发时根据persisted的值采取不同的操作。对于pageshow事件,如果页面是从bfcache中加载,那么persisted的值就是true,对于pagehide事件,如果页面在卸载之后被保存在bfcache中,那么persisted的值也会被谁知为true。因此,当第一次触发pageshow时,persisted的值一定是false,而在第一次触发pagehide时,persisted就会变成true(除非页面不被保存在bfcache中)

    ​ 注意:指定了onunload事件处理程序的页面会被自动排除在bfcache之外,即使事件处理程序是空的。原因在于,onunload最常用于撤销在onload中所执行的操作,而跳过onload后再次显示页面很可能会导致页面不正常。

haschange事件

​ HTML新增了haschange事件,以便在URL的参数列表(及URL中#号后面的所有字符串)发生变化时通知开发人员。之所以新增这个事件,是因为在ajax应用中,开发人员经常要利用URL参数列来保存状态或导航栏信息。

​ 必须把haschange事件处理程序添加给window对象,然后URL参数列表只要变化就会调用它。此时的event对象应该包含两个属性:oldURL和newURL。这两个属性分别保存着参数列表变化前后的完整URL。例如:

EventUtil.addHandler(window,"haschange", function(event){alert("oldURL:"+ event.oldURL + "\nNEW URL" + event.newURL)
})

但是最好使用location对象来确定当前的参数列表:、

EventUtil.addHandler(window,"haschange", function(event){alert("current hash:" + loaction.hash)
})

设备事件

​ 智能手机和平板电脑的普及,为用户与浏览器交互引入了一种新的方式,而一类新的事件也应运而生。设备事件(device event)可以让开发人员确定在怎样使用设备。w3c从2011年开始制定一份关于设备事件的新草案,以涵盖不断增长的设备类型并为它们定义相关事件。

  • orientationchange事件

    ​ 苹果公司为移动Safari中添加了orientationchange事件,以便开发人员能够确定用户何时将设备由横向查看模式切换为纵向查看模式。移动Safari的window.orientation属性中可能包含3个值:0表示肖像模式,90表示向左旋转的横向模式(“主屏幕”按钮在右侧),-90表示向右旋转的横向模式(“主屏幕在按钮左侧”)。相关文档中还提到一个值。即180表示iPhone头朝下;但这种模式至今尚未得到支持。

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBU0lkIG-1618561342892)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210202160924218.png)]

    只要用户改变了设备的查看模式,就会触发orientationchange事件。此时的event对象不包含任何有价值的信息。因为唯一的相关信息就可以通过window.orientation访问到。下面是使用这个事件的典型案例:

    EventUtil.addHandler(window,"load",function(event){var div = document.getElementById('myDiv')div.innerHTML = "Current orientation is" + window.orientationEventUtil.addHandler(window,"orientationchange", function(event){div.innerHTML =  div.innerHTML = "Current orientation is" + window.orientation})
    })
    
  • MozOrientation 事件

    Firefox 3.6位检测设备方向MozOrientation 的新事件。(前缀Moz表示这是特定于浏览器开发商的事件,不是标准事件)

  • deviceorientation 事件

    本质上,deviceorientation Event规范定义的deviceorientation 事件与MozOrientation 事件类似。它也是在加速计检测到设备方向变化时在window对象触发,而且具有与MozOrientation 事件类似的支持限制。

触摸与手势事件

  • 触摸事件

    • touchstart:当手指触摸屏幕时触发;即使已经有一个手指放在了屏幕上也会触发。

    • touchmove:当手指在屏幕上滑动时连续的触发。在这个事件发生期间,调用preventDefault() 可以阻止滚动。

    • touchend:当手指从屏幕上移开是触发。

    • touchcancel:当系统停止跟踪触摸时触发。关于此事件的的确切触发时间,文档中没有明确说明。

      ​ 上面这几个事件都会冒泡,也都可以取消。虽然这些触摸事件没有在dom规范中定义,但是他们却是以兼容dom的方式实现的。因此,每个触摸事件的event对象都提供了在鼠标事件中常见的属性:bubbles,cancelable,view,clientX,clientY,screenX,screenY,derail,altKey,shiftKey,CtrlKey,metaKey。

      除了常见的dom属性外,触摸事件还包含下列三个用于跟踪触摸的属性。

      • touches:表示当前跟踪的触摸操作的touch对象的数组

      • targetTouchs:特定于事件目标的rouch对象的数组

      • changeTouches: 表示自上次触摸以来发生了什么改变的touch对象的数组

    • 每个touch对象包含下列属性。

      • clientX:触摸目标在可视窗口中的x坐标
      • clientY:触摸目标在可视窗口中的Y坐标
      • identifier:标识触摸的唯一ID
      • pageX: 触摸目标在页面中的x坐标
      • pageY:触摸目标在页面中的y坐标
      • screenX:触摸目标在屏幕中的x坐标
      • screenY:触摸目标在屏幕中的y坐标
      • target:触摸的DOM节点目标。

      使用这些属性可以跟踪用户对屏幕的触摸操作。

      function handleTouchEvent(event) {// 只跟踪一次触摸if(event.touches.length === 1) {switch(event.type) {case "touchstart":output.innerHTML = "touchstart(" +event.touches[0].clientX + "," +event.touches[0].clientY + ")"breakcase "touchend":output.innerHTML = "<br>touch end(" +event.changedTouches[0].clientX + "," +event.changedTouches[0].clientY + ")"breakcase "touchmove":event.preventDefault() //阻止滚动output.innerHTML = "<br>touchmove(" +event.changedTouches[0].clientX + "," +event.changedTouches[0].clientY + ")"}}
      }
      

      以上代码会跟踪屏幕上发生的一次触摸操作,为简单起见,只会在有一次活动触摸操作的情况下输出信息。

内存和性能

​ 由于事件处理程序可以为现代web应用程序提供交互能力,因此许多开发人员会不分青红皂白的向页面中添加大量的处理程序。在创建GUI的语言(如c#)中,为GUI,为GUI(图形交互界面)中的每个按钮添加一个onclick事件处理程序是司空见惯的事,而且这样做也不会导致什么问题。可是在JavaScript中,添加到页面上的事件处理程序将直接关系到页面的整体运行性能。导致这一问题的原因是多方面的。首先每个函数都是对象,都会占用内存;内存中的对象越多,性能就越差。其次,必须事件指定所有事件处理程序而导致的DOM访问次数,会延迟整个页面的交互就绪时间。事实上,从如何利用好事件处理程序的角度出发,还是有一些方法能够提升性能的。

事件委托

​ 对“事件处理程序过多”问题的解决方案就是事件委托。事件委托利用了事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。例如click事件会一直冒泡到document层次。也就是说,我们可以为整个页面指定一个onclick事件处理程序,而不必给每个可单击的元素分别添加事件处理程序。

<ul id="myLinks"><li id="goSomeWhere">go SomeWhere</li><li id="doSomething">do Something</li><li id="sayHi">Say Hi </li>
</ul>

​ 其中包含三个被单击后会执行操作的列表项。按照传统的做法,需要像每一个添加事件处理程序。

var item1 = document.getElementById('goSomeWhere')
var item2 = document.getElementById('doSomething')
var item3 = document.getElementById('sayHi')
EventUtil.addHandler(item1,"click", function(event){loaction.href = "http://www.wrox.com"
})
EventUtil.addHandler(item2,"click", function(event){loaction.href = "http://www.wrox.com"
})
EventUtil.addHandler(item3,"click", function(event){loaction.href = "http://www.wrox.com"
})

​ 如果在一个复杂的web应用程序中,对所有可单击的元素都采用这种方式,那么结果就会有数不清的代码用于添加事件处理程序。此时,可以利用事件委托技术解决这个问题。使用事件委托,只需在DOM树种尽量最高层次上添加一个事件处理程序。

var list = document.getElementById('myLinks')
EventUtil.addHandler(list,"click",function(event){event = EventUtil.getEvent(event)var target = EventUtil.getTarget(event)switch(target.id) {case "goSomeWhere"loaction.href = "http://www.wrox.com"breakcase "doSomething"loaction.href = "http://www.wrox.com"break case "sayHi"console.log('hi')break      }
})

​ 在这段代码里,我们使用事件委托只为

  • 元素添加了一个onclick事件处理程序。由于所有列表都是这个元素的子节点,而且他们的事件会冒泡,所以单击事件最终会被这个函数处理。我们知道,事件目标是被单击的列表项,故而可以通过检测id属性决定采取适当的操作。与前面未使用事件委托的代码比一比,会发现这段代码的事前消耗更低,因为只取得一个DOM元素,只添加了一个事件处理程序。虽然对用户来说最终的结果相同,但是这种技术需要占用的内存更少。所以用到按钮的事件(多数鼠标事件和键盘事件)都适合采用事件委托技术。

​ 如果可行的话,也可以考虑为document对象添加一个事件处理程序,用以处理页面上发生的某种特殊类型的事件。这样做与采取传统做法相比具有以下优点:

document对象很快就可以访问,而且可以在页面生命周期的任何节点时为它添加事件处理程序(无需等待DOMContentLoaded或load事件)。换句话说,只要可单击的元素呈现在页面上,就可以立即具备适当的功能。

在页面中设置事件处理程序所需的事件更少。只添加一个事件处理程序所需的DOM引用更少,所花的事件也更少。

整个页面占用的内存空间更少,能够提升整体性能。最适合采用事件委托的事件包括click,mousedown,mouseup,keydown,keyup,和keypress。虽然mouseover,和mouseout,事件也冒泡,但要处理它们并不容易,而且经常需要计算元素位置。(因为当鼠标从一个元素移动到其子节点时,或者该鼠标移出该元素时都会触发mouseout事件。)

移出事件处理程序

​ 每当将事件处理程序指定给元素是,运行中的浏览器代码与支持页面交互的JavaScript代码之间就会建立一个连接。这种连接越多运行起来就越慢。如前所诉,可以采用事件委托技术,限制建立的连接数量。另外,在不需要的时候移除事件处理程序,也是解决这个问题的一种方案。内存中留有那些过时不用的“空事件处理程序”(dangling event handler),也是造成web应用程序内存与性能问题的主要原因。

​ 在这两种情况下,可能会造成上诉问题。第一种情况就是从文档中移除带有事件处理程序的元素是。这可能是通过纯粹的DOM操作,例如使用removeChild()和replaceChild()方法,但是更多的是发生在使用innerHTML替换页面中某一部分的时候。如果带有事件处理程序的元素被innerHTML删除了,那么原来添加到处理程序中的事件处理程序极有可能无法被当做来及回收。

<div id="myDiv"><input type="button" value="click me" id="myBtn">
</div>
<script>var btn = document.getElementById("myBtn")...执行某些操作document.getElementById("myBtn").innerHTML = "processing"   //就麻烦了。</script>

​ 这里有一个按钮被包含在div元素中。为了避免双击,单击这个按钮时就将按钮移除并替换成一条消息;这是网站设计中非常流行的一种做法。但问题在于,当按钮被页面移除是,他还带着一个事件处理程序呢。在

元素上设置innerHTML可以把按钮移走,但是事件处理程序仍然与按钮保存着引用关系。有的浏览器(尤其是IE)在这种情况下不会作出恰当的处理,它们很可能会将对元素和对事件处理程序的引用都保存在内存中。如果你知道某个元素被移除,那么最好的手工移除事件处理程序:
<div id="myDiv"><input type="button" value="click me" id="myBtn">
</div>
<script>var btn = document.getElementById("myBtn")...执行某些操作btn.onclick = nulldocument.getElementById('myDiv').innerHtml = 'processing'</script>

​ 注意在事件处理程序中删除按钮也能阻止事件冒泡。目标元素在文档中事件冒泡的前提。

​ 导致“空事件处理程序”的另一种情况就是卸载页面的时候。毫不奇怪,ie8及更早版本在这种情况下依然是问题最多的浏览器,尽管其他浏览器或多或少也有类似的情况。如果页面在被卸载之前没有清理干净事件处理程序,那么他们就会滞留在内存中。每次加载完页面再卸载时(可能是两个页面来回切换,也有可能是单击了刷新按钮),内存中滞留的对象数目就会增加,因为事件处理程序占用的内存并没有释放。

​ 一般来说,做好的做法就是在页面卸载之前,先通过onlunload事件处理程序移除所有事件处理程序。在此事件委托技术再次表现出他的优势–需要跟踪的事件处理程序越少,移除它们就越容易。对这种类似的撤销操作,我们可以把它想象成:只要通过onload事件处理添加的东西,最后都要通过onunload事件处理程序将它们移除。

​ 不要忘了,使用onunload事件处理程序意味着页面不会被缓存在bfcache中。如果你在以这个问题,那么就只能在IE中通过onunload来移除事件处理程序了。

模拟事件

​ 事件,就是网页中某个特别值得关注的瞬间。事件经常有用户操作或通过其他浏览器功能来触发。但很少有人知道,也可以使用JavaScript在任意时刻来触发特定的事件,而此时的事件就如同浏览器创建的事件一样。也就是说,这些事件该冒泡还是会冒泡,而且照样能够导致浏览器执行已经指定的处理他们的事件处理程序。在测试web应用程序,模拟事件触发事件是一种极其有用的技术。DOM2级规范为此规定了模拟特定事件的方式。

DOM中的事件模拟

​ 可以在document对象上使用createEvent()方法创建event对象。这个方法接收一个参数,即表示要创建的事件类型的字符串。在DOM2级中,所有这些字符串使用英文复数形式,而在DOM3级中都变成了单数。这个字符串可以是下列几字符串之一。

  • UIEvents:一般化的UI事件。鼠标事件和键盘事件都继承自UI事件。DOM3级中是UIEvents。

  • MouseEvents:一般化的鼠标事件。DOM3级中是MouseEvent。

  • MutationEvents:一般化的DOM变动事件。DOM3级中是MutationEvent。

  • HTMLEvents:一般化的HTNL事件。没有对应的DOM3级事件。(HTML事件被分散到其他的类别中)。

    模拟事件的最后一步就是触发事件。这一步需要使用dispatchEvent()方法。所有支持事件的DOM节点都支持这个方法。调用dispatchEvent()方法时,需要传入一个参数,即表示要触发事件的event对象。触发这个事件之后,该事件就跻身“官方事件”之列了。因而能够照样冒泡并引发相应事件的处理程序的执行。

表单脚本

​ JavaScript最初的一个应用,就是分担服务器处理表单的责任,打破处处依赖服务器的局面。尽管没有为许多常见任务提供现场的解决手段,很多开发人员不仅会在验证表单时使用JavaScript,而且还增强了一些标准表单控件的默认行为。

表单的基础知识

​ 在HTML中,表单是由元素来表示的,而在JavaScript中,表单对应的则是HTMLForm-Element类型。HTMLFormElement,因而与其他元素具有相同的默认属性。不过HTMLFormElement也有自己独有的属性和方法。

  • acceptCharset:服务器能够处理的字符集:等价于HTML中的accept-charset。

  • action:接收请求的URL;等价于HTML中的action特性。

  • elements:表单中所有控件的集合(HTMLCollection)。

  • enctype:请求的编码类型;等价于HTML中的enctype特性。

  • length:表单控件的数量。

  • method:要发送的HTTP请求类型,通常是“get”或“post”;等价于HTML的method特性。

  • name:表单的名称:等价于HTML的name特性。

  • reset():将所有表单域重置为默认值。

  • submit(): 提交表单。

  • target:用于发送请求和接收响应的窗口名称;等价于HTML的target特性。

    取得元素引用的方式有好几种。其中最常见的方式就是将它看成与其他元素一样,并为其添加id特性,然后再用getElementById()方法找到它。

    var form = document.getElementById('form')
    

    其次,就通过document.forms 就可以取得页面中所有的表单。在这个集合中,可以通过数值索引或name值来取得特定的表单:

    var firstForm = document.forms[0]   //取得页面中的第一个表单
    var myForm = document.forms['form2'] //取得页面中名称为“form2”的表单。
    

    另外,在较早的浏览器或者那些支持向后兼容的浏览器中,也会把每个设置了name特性的表单作为属性保存在document对象中。例如,通过document.form2可以访问到名为“form2”的表单。不过,我们不推荐使用这种方式: 一是容易出错,二是将来的浏览器可能会不支持。

    注意:可以同时为表单指定id和name属性,但它们的值不一定相同。

表单提交

​ 用户单击提交按钮或图像按钮时,就会提交表单。使用或都可以定义提交按钮,只要将其type特性的值设置为submit即可,而图像按钮则是通过将的type特性设置为“image”来定义的。因此,只要我们单击以下代码生成的按钮,就可以提交表单。

<!--通用提交按钮-->
<input type="submit" value="submit form">
<!--自定义提交按钮-->
<button type="submit">submit form</button>
<!--图像按钮-->
<input type="image" src="aa.gif">

​ 只要表单中存在上面列表中的任何一个按钮,那么在相应表单控件拥有焦点的情况下,就可以按回车键就可以提交该表单。(textarea是一个例外,在文本区中回车就会换行。)如果表单里没有提交按钮,按回车键就不会提交表单。

​ 这种方式提交表单是,浏览器会将请求发生给服务器之前触发submit事件。这样,我们就有机会验证表单数据,并以此决定是否允许提交表单。阻止这个事件的默认行为就可以取消表单提交。以下代码就可以阻止表单提交:

var form = document.getElementById("myForm")
EventUtil.addHander(form,"submit", function(event){event = EventUtil.getEvent(event)EventUtil.preventDefault(event)
})

​ 这里使用了13章定义的EventUtil对象。一般来说,在表单数据无效而不能发送给服务器时,就可以使用这一技术。

​ 在JavaScript中,以编程方式调用submit() 方法也可以提交表单。而且,这种方式无需表单包含提交按钮,任何时候都可以正常提交表单。

var form = document.getElementById("myForm")
form.submit()

​ 在调用submit() 方法的形式提交时不会触发submit事件,因此要记得在调用此方法时,首先要验证表单数据。

​ 提交表单是可能出现的最大问题,就是重复提交表单。在第一次提交表单后,如果长时间没有反应,用户可能变得不耐烦。这时候,他们也许会反复点击提交按钮。结果往往会很麻烦(因为服务器要处理重复的请求),或者会造成错误(如果用户是下订单,那么可能会多订好几份)。解决这类问题的办法有两个:第一次提交表单后就禁用提交按钮,或者利用onsubmit事件处理程序取消后续的表单提交操作

表单重置

​ 在用户点击重置按钮时,表单会被重置。使用type特性值为“reset”的input或button都可以创建重置按钮:

<!--通用提交按钮-->
<input type="reset" value="reset form">
<!--自定义提交按钮-->
<button type="reset">reset form</button>

​ 这两个按钮都可以用来重置按钮。在重置表单时,所有表单字段都会被恢复到页面刚加载完毕时的初始值。如果某个字段的初始值为空,就会恢复为空;而带有某人值的字段,也会恢复为默认值。

​ 用户单击重置按钮时,就会触发reset事件。利用这个机会,我们可以在在必要时取消重置按钮。

表单字段

​ 可以像访问页面中的其他元素一样,使用原生DOM方法访问表单元素。此外,每个表单都有elements属性,该属性是表单中所有元素的集合。这个elements集合是一个有序数列,其中包含着表单中的所有字段,例如input,textare,button,fieldset。每个表单字段在elements集合中的顺序,与它们出现在标记中的顺序相同,可以按照位置和name特效来访问他们

var form = document.getElementById("form1")
//取得表单中的第一个字段
var field = form.elements[0]
// 取得名为“textbox”字段
var field1 = form.elements["textbox"]
// 取得表单中包含的字段的数量
var fieldCount = form.elements.length

如果有多个表单控件都在使用一个同一个name,name就会返回name命名的一个NodeList。

---- 共有的表单字段属性

​ 除了元素外,所有表单都拥有相同的一组属性。由于input类型可以表示很多种表单字段,因此有些属性只适用于某些字段,但还有一些属性是所有字段共有的属性和方法:

  • disable:布尔值,表示当前字段是否被禁用。

  • form:指向当前字段所属表单的指针;只读。

  • name:当前字段的名称。

  • readOnly:布尔值,表示当前字段是否只读。

  • tabIndex:表示当前字段的切换(tab)序号

  • type:当前字段的类型,如checkbox,radio,等等。

  • value:当前字段被提交给服务器的值。对文化字段来说,这个属性是只读的,包含着文件在计算机中的路径。

    除了form属性之外,可以通过JavaScript动态修改其他任何属性。

    ​ 能够动态修改表单字段属性,意味着我们可以在任何时候,以任何方式来动态操作表单。例如,很多用户可能会重复单击表单的提交按钮。在涉及信用卡消费时,这就是个问题;因为会导致费用翻番。为此,最常见的解决方案,就是在第一次单击后就禁用提交按钮。只要侦听submit事件,并且在该事件发生时禁用提交按钮即可。

共有的表单字段方法

​ 每个表单字段都有两种方法:focus()和blur()。其中,focus()方法用于将浏览器的焦点设置到表单字段,使其可以响应键盘事件。例如,接收到焦点的文本框会显示插入符号,随时可以接收输入。使用focus()方法,可以将用户的注意力吸引到页面的某个部位,例如在页面加载完毕后,将焦点转移到表单中的第一个字段。为此,可以侦听页面的load事件,并且在该事件发生时在表单的第一个字段上调用focus()方法。

​ 要注意的是,如果第一个表单字段是一个input元素,且type的特性设置为了“hidden”,那么以上代码会导致错误。另外,如果使用css的display和visibility属性隐藏拿过来该字段,同样会导致错误。

​ 在HTML5为表单字段新增了一个autofocus属性。在支持这个属性的浏览器中,只要设置了这个属性,不用JavaScript就能自动把焦点移动到相应字段。例如

<input type="text" autofocus>

​ 为了保证前面的代码设置autofocus的浏览器中正常运行,必须检测是否设置了该属性,如果设置了,就不再调用focus()了。

​ 因为autofocus是一个布尔值属性,所以在支持的浏览器中的值是true(在不支持的浏览器中,它的值是空字符串)。为此,上面的代码只要在autofocus不等于true的情况下才会调用focus(),从而保证向前兼容。

​ 与focus()方法对应的就是blur()方法,它的作用是从元素移走焦点。在调用blur()方法时,并不会把焦点转移到某个特定的元素上;仅仅是将焦点从调用这个方法的元素上移走而已。在早期的web开发中,那个时候表单字段还没有readonly特性,因此就可以使用blur()方法来创建只读字段。现在虽然使用blur的场合不多了,但是必要时还是可以使用的。

document.forms[0].element[0].blur()

共有的表单字段事件

​ 除了支持鼠标,键盘,更改和HTML事件之外,所有表单字段都支持3个事件:

  • blur: 当前字段失去焦点时触发。

  • change:对于input和textare元素,它们失去焦点且value值改变时触发;对于select元素,在其选项改变时触发。

  • focus:当前字段获得焦点时触发。

    ​ 当用户改变了当前字段的焦点,或者我们调用了blur或focus方法时,都可以触发blur和focus事件。这两个事件在所有表单字段中都是相同的。但是,change事件在不同表单控件中触发的次数会有所不同。对于input和textarea元素,当他们从获得焦点到失去焦点且value值改变时,才会触发change事件。对于select元素,只要用户选择了不用的选项就会触发change事件;换句话说,不是去焦点也会触发change事件。

    ​ 通常,可以使用focus和blur事件来以某种方式改变用户界面,那么是向用户给出某种视觉提示,要么是向页面中添加额外的功能(例如,为文本框显示一个下拉选项菜单)。而change事件则经常用于验证用户在字段中输入的数据。例如,假设有一个文本框,我们只允许用户输入数值。此时,可以利用focus事件修改文本的背景颜色,以便更清楚地表明这个字段获得了焦点。可以利用blur事件恢复文本框背景颜色,利用change事件在用户输入了非数值字符串时再次修改背景颜色。

文本框脚本

​ 在HTML中,有两种方式来表现文本框:一种是使用input元素的单行文本框,另一种是使用textarea的多行文本框。这狂歌控件非常相似,而且多数行为也差不多。不过他们之间任然存在着一些重要的区别

​ 要表现文本框,必须将input元素type特性设置为“text”。而且通过设置size特性,可以知道文本框中能够显示的字符数。用过value特性,可以设置文本框的初始值,而maxlength特性则用于指定文本框可以接受的最大字符数。如果要创建一个文本框,让它能够显示25个字符,但输入不能超过50个字符,可以使用以下代码:

<input type="text" size="25" maxlength="50" value="aa">

​ 相对而言,textarea元素则始终会呈现一个多行文本框。要指定文档框的大小,可以使用rows和cols特性。其中,rows特性指定的是文本框的字符行数,而cols指定的是文本框的字符列数。与input元素不同的是,textarea的初始值必须要放在和之间

<textarea rows="25" cols="5">aaa</textarea>

​ 另一个与input的区别在于,不能再HTML中给textarea指定最大字符数。

​ 无论这两种文本框在标记中有什么区别,但它们都会将用户输入的内存保存在value属性中。可以通过这个属性读取和设置文本框的值。

var textbox = document.forms[0].elements["textbox"]
console.log(textbox.value)
textbox.value = "change textbox value"

​ 我们建议读者像上面这样使用value属性读取或设置文本框的值,不建议使用标准的DOM方法。换句话说,不要使用setAttribute()设置value特性,也不要去修改textarea元素的第一个子节点。原因很简单:对于value属性所作的修改,不一定会反应在DOM中,因此,在处理文本框时,最好不要使用DOM方法。

选择文本

​ 上诉两种文本框都支持select()方法,这是方法用于选择文本框的所有文本。在调用select()方法时,大多数浏览器都会将焦点设置到文本框中。这个方法不接受参数,可以在任何时候被调用。

var textbox = document.forms[0].elements["textbox"]
textbox.select()

​ 在文本框获得焦点时选择其所有文本,这是一种非常常见的做法,特别是在文本框包含默认值的时候。因为这样做可以让用户不必一个一个的删除文本。

取得选择的文本

​ 虽然通过select事件我们可以知道用户选择了文本,但仍然不知道用户选择了什么文本。HTML5通过一些扩展方法解决了这个问题,以便顺利取得选择的文本。该规范采取的办法是添加两个属性:selectionStart和selectionEnd。这两个属性中保存的值是基于0的数值,表示所选择文本的范围(即文本选区开头可结尾的偏移量)。因此,要取得用户在文本框中选择的文本,可以使用如下代码:

function getSelectedText(textbox) {return textbox.value.substring(textbox.selectionSatrt,textbox.selectionEnd)
}

​ 因为substring()方法基于字符串的偏移量执行操作,所以将selectionStart和selectionEnd直接传给他就可以取得选中的文本。

选择部分文本

​ HTML5也为选择文本框中的部分文本提供了解决方案,即最早由Firefox引入的setSelectionRange()方法。除了select方法之外,所有文本框都有一个setSelectionRange()方法,这个方法接收两个参数:要选择的第一个字符的索引和要选择的最后一个字符之后的字符的索引(类似于substring()方法的两个参数。

textbox.value = "hello world"
// 选择所有文本
textbox.setSelectionRange(0,textbox.value.length)
//选择3个字符
textbox.setSelectionRange(0,3)
// 选择第4到6个字符
textbox.setSelectionRange(4,7)

​ 要看到选择的文本,必须在调用setSelectionRange()之前或之后立即将焦点设置到文本框。

过滤输入

​ 我们经常会要求用户在文本框中输入特定的数据,或者输入特定格式的数据。例如:必须包含某些,或者匹配某种模式。由于文本框在默认情况下没有提供多少验证数据的手段,因此必须使用JavaScript来完成此类过滤输入的操作。而综合运用事件的和DOM手段,就可以将普通文本框转换成能够用户输入数据的功能型控件。

屏蔽字符

​ 有时候,我们需要用户输入的文本中包含或不包含某些字符。例如,电话号码中不能包含非数值字符。如前所诉,响应向文本框中插入字符操作的是keypress事件。因此,可以通过阻止这个事件的默认行为来屏蔽此类字符。在极端情况下,可以

EventUtil.addHandler(textbox,"keypress",function(event){event = EventUtil.getEvent(event)var target = EventUtil.getTarget(event)var charCode = EventUtil.getCharcode(event)if(!/\d/.test(String.formCharCode(charCode))){EventUtil.preventDefault(event)}
})

​ 在这个例子中,我们使用的 EventUtil.getCharcode()实现了跨浏览器取得字符编码。然后,使用String.formCharCode()将字符编码转换成字符串,再使用正则表达式来测试该字符串,从而确定用户输入的是不是数值。

​ 虽然理论上只应该在用户按下字符键时才会触发keypress事件,但是有些浏览器也会对其他键触发此类事件。例如Firefox和Safari以前会对向上向下键,退格键等触发keypress事件;

​ 除此之外还有一个问题需要处理:复制粘贴及其他操作还要用到ctrl键。确保用户没有按下ctrl键

if(!/\d/.test(String.formCharCode(charCode))&&!event.ctrlKey){EventUtil.preventDefault(event)}

剪切板操作

  • beforecopy:在发生复制操作时触发。

  • copy:在发生复制操作时触发。

  • beforecut:在发生剪切操作前触发。

  • cut:在发生剪切操作时触发。

  • beforepaste:在发生粘贴操作之前触发。

  • paste:在发生粘贴操作时触发。

    ​ 由于没有针对剪切板操作的标准,这些事件及相关对象浏览器而异。在Safari,Chrome和Firefox中,beforecopy,beforecut,beforepaste事件只会在显示针对文本框的上下文菜单(预期将发生剪切板事件)的情况下触发。但是,ie则会在触发copy,cut,paste事件之前现行触发这些事件。至于copy,cut,paste事件,只要是在上下文菜单选择了相应选项,或者使用了相应的键盘组合键,所有浏览器都会触发它们。

    ​ 在实际的事件发生之前通过beforecopy,beforecut和beforepaste事件可以在向剪切板发送数据,或者从剪切板取得数据之前修改数据。不过,取消这些事件并不会取消对剪切板的操作—只有取消copy,cut,paste事件,才能阻止相应操作的发生。

    ​ 要访问剪切板中的数据,可以使用clipboardData对象:在IE中这个对象是window对象的属性,其他浏览器对应的是event对象的属性。但是只有在处理剪切板事件期间clipboardData对象才有效,这是为了防止剪切板的未授权访问。

    ​ 在clipboardData对象中有三个方法;getData(),setData(),clearData()。其中getData是用于从剪切板中取得数据,它接受一个参数,即要取得的数据的格式。在ie中有两种数据格式:‘text’,和‘URL’ 在Firefox,Safari和Chrome,这个参数是一种mime类型:不过可以用text代表‘text/plain’

    ​ 类似的,setData()方法的第一个参数也是数据类型,第二个参数是要放在剪切板的文本。对于第一个参数ie照样支持‘text’,和‘URL’。而Safari和Chrome仍然只支持MIME类型。这两个浏览器在成功将文本放到剪切板后,都会返回true,否则返回false。

    var EevntUtil = {getClipboardText: function(event){var clipboardData = (event.clipboardData ||window.clipboardData)return clipboardData.getData('text')}setClipboardText: function(event,value){if(event.getClipboardText){return event.clipboardData.setData("text/plain",value)} else if(window.clipboardData) {return window.clipboardData.setData("text", value)}}
    }
    

    ​ 在需要确保粘贴到文本框中的文本包含某些字符,或者符合某种格式要求时,能够访问剪切板是非常有用的。例如一个文本框只接受数值,那么必须检测粘贴过来的值,以确保有效。在paste事件中,可以确定剪切板中的值是否有效,如果无效,就可以取消默认行为:

    EventUtil.addHandler(textbox,"paste", function(event){event = EventUtil.getEvent(event)var text = EventUtil.getClipboardText(event)if(!/^\d*$/.test(text)){EventUtil.preventDefault(event)}
    })
    

    ​ 在这里onpaste事件处理程序可以确保只有数值才会被粘贴到文本框中。如果剪切板的值与正则表达式不匹配,则会取消粘贴事件操作。

自动切换焦点

​ 使用JavaScript可以从多个方面增强表单的易用性。其中,最常见的一种方式就是在用户填写完当前字段时,自动将焦点切换到下一个字段。通常,在自动将焦点切换到下一个字段之前,必须知道用户已经输入了既定长度的数据(例如电话号码)。

​ 为了增强易用性,同时加快数据输入,可以在前一个文本框中的字符达到最大数量后,自动将焦点切换到下一个文本框。

HTML5约束验证API

​ 为了在表单提交到服务器之前验证数据,HTML5新增了一些功能。有了这些功能,即便JavaScript被禁用或者由于种种原因未能加载,也可以确保基本验证。换句话说,浏览器自己会根据标记中的规则执行验证,然后自己显示适当的错误信息(完全不用JavaScript插手)。当然,这个功能只有在支持HTML5这部分浏览器中才有效。

​ 只有在某些情况下表单字段才能自动验证。具体来说木九十在HTML标记中为特定的字段指定一些约束,然后浏览器才会自动执行表单验证。

  • 必填字段

    <input type="text" name="username" required>
    

    ​ 任何标注有required的字段,在提交表单是都不能空着。这个属性适用于,input,textarea和select字段,在JavaScript中,通过required属性,可以检测某个表单字段是否为必填字段。

    var inUsernameRequired = document.forms[0].elements["username"].required
    
  • 其他输入类型

    ​ HTML为input的type元素的type属性又增加了几个值。这些新的类型不仅能反应数据类型的信息,而且还能提供一些默认的验证功能。

    <input type="email" name="email">
    <input type="url" name="url">
    

    ​ 顾名思义,email要求用户输入电子邮箱格式,url要求输入的文本必须符合URL格式。

    ​ 要注意的是如果不给input元素设置required,那么空文本也能通过验证。

  • 数字范围

    ​ 除了email和url,HTML5还定义了另外几个输入元素。这几个输入元素都要求填写某种基于数字的值:number ,range ,datetime-local,date,month,week还有time。浏览器对这几个类型支持情况并不好,因此如果真的想选用的话,要特别小心。

    ​ 对所有这些数值类型的输入元素,可以指定min属性(最小的可能值),max(最大的可能值)和step属性(从min到max的两个刻度键的差值)。例如,如果想让用户输入只能输入0-100的值,而且这个值必须是5的倍数:

    <input type="number" min="0" max="100" step="5" name="count">
    

    ​ 在不同的浏览器可能会也可能不会看到能够自动递增和递减的数值调节按钮(向上和向下按钮)。

    ​ 以上这些属性在JavaScript中都能通过对元素的访问(或修改)。此外,还有两个方法:stepup和stepDown(),接收一个可选参数:要在当前基础上加上或减去的值。(默认是加或减1)。

    input.stepUp()  //加1
    input.stepUp //加5
    input.stepDown() //减1
    input.stepDown(10) //减10
    
  • 输入模式

    HTML5为文本字段新增了pattern属性。这个属性的值是一个正则表达式,用于匹配文本框中的值。例如如果只想允许在文本字段中输入数值:

    <input type="text" pattern="\d+" name="count">
    

    ​ 注意,模式的开头和末尾不用加^和$符号(假定已经有了)这两个符号表示输入的值必须从头到尾都与模式匹配。

    ​ 与其他类型相似,指定pattern也不能阻止用户输入无效的文本。这个模式应用给值,浏览器来判断值是否有效。在JavaScript中可以通过pattern属性访问模式。

    ​ var pattern = document.forms[0].elements[‘count’].pattern

    使用以下代码可以检测浏览器是否支持pattern属性

    ​ var isPatternSupported = “pattern” in document.createElement(“input”)

  • 检测有效性

    ​ 使用checkValidity() 方法可以检测表单中的某个字段是否有效。所有表单字段都有这个方法,如果字段的值有效,这个方法返回true,否则返回false。字段的值是否有效的判断依据是本节前面介绍过的那些约束。换句话说,必填字段如果没有值就是无效的,而字段中的值与pattren属性不匹配也是无效的。

    例如:

    if(document.forms[0].elements[0].checkValidity()){

    ​ // 字段有效,继续

    } else {

    ​ // 字段无效

    }

    要检测整个表单是否有效,可以在表单自身调用checkValidity()方法。如果所有字段有效,这个方法返回true;即便一个字段无效这个方法也会返回true。

    if(document.forms[0].checkValidity()){// 表单有效,继续
    } else{// 表单无效
    }
    

    与checkValidity()方法简单地告诉你字段是否有效相比,validity属性则会告诉你字段为什么有效或无效。这个对象包含一系列属性,每个属性会返回一个布尔值。

    • customError:如果设置了setCustomValidity(),则为true,否则返回false。

    • patternMismatch:如果值与指定的pattern属性不匹配,则返回false。

    • rangeOverflow:如果值比max大,返回true。

    • rangeUnderflow:如果值比min小,返回true

    • stepMismatch:如果min和max之间的长值不合理,返回true。

    • tooLong:如果值的长度超过maxlength属性指定的长度,返回true。

    • typeMissing:如果值不是mail 或url 要求的格式返回true

    • valid: 如果这两的其他属性都是false,返回true。

    • valueMissing:如果标注为required的字段没有值,返回true。

      因此要想得到更具体的信息,就应该使用validity属性来检测表单的有效性:

      if(input.validity&&!input.validity.valid){if(input.validity.valueMissing) {alert("please specify a value") //请指定一个值} else if (input.validity.typeMismatch) {alert("please enter an email address") // 请输入电子邮箱地址} {alert("value is invalid") // 值无效}
      }
      
  • 禁用验证

    通过设置novalidate属性,可以告诉表单不进行验证。

    <form method="post" action="sigup.php" novalidate><!--这里输入表单元素-->
    </form>
    

    在JavaScript中使用novalidate 属性取得或设置这个值,如果这个属性存在,值为true,如果不存在,值为false。

    document.form[0].noValidate = true // 禁用验证
    

    如果一个表单中有多个提交按钮,为了点击某个提交按钮不必验证表单,可以在相应按钮上添加formnovalidate属性。

    <form method="post" action="foo.php"><!--这里输入表单元素--><input type="submit" value="regular submit"><input type="submit" formnovalidate name="btnNo">
    </form>
    

    这里点击第一个提交按钮会向往常一样验证表单,而点击第二个按钮则不会经过验证表单而直接提交表单。也可以使用JavaScript来设置这个属性。

    document.forms[0].elements["btnNo"].formnovalidate = true
    

选择框脚本

​ 选择框是通过和 元素创建的,为了方便与这个空间交互,除了表单字段共有的属性和方法外,HTMLSelectElement类型还提供了下列属性和方法。

  • add(newOption,reloption):向控件中插入新的元素,位置在相关项(reloption)之前。

  • multiple:布尔值,表示是否允许多项选择;等价于HTML中multiple特性。

  • options:控件中所有的元素的HTMLCollection

  • remove(index):移除给定位置的选项。

  • selectedIndex:基于0的选中项的索引,如果没有选中项,则值为-1.对于支持多选的控件,值保存选项中第一项的索引。

  • size:选择框中可见的行数;等价于HTML中size特性。

    选择框的type属性不是“select-one”就是“select-multiple”,这取决于HTML代码中有没有multiple特性。选择框的value属性由当前选中项决定。相应规则:

    • 如果没有选中项,则选中框的value属性保存空字符串。

    • 如果有一个选中项,而且该项的value特性已经在html中指定,则选择框的value属性等于选中的value特性。即使该项value特性值是空字符串,也同样遵循此条规则。

    • 如果有一个选中项,但该选中项的value特性在HTML中未指定,则选择框的value属性等于该项的文本。

    • 如果有多个选中项,则选择框的value属性依据前两条规则取得第一个选中项的值。
      例子:

      <select name="loaction" id="selLocation"><option value="sunnyvale,ca">sunnyvale</option><option value="Los angles,ca">los angeles</option><option value="Mountain view,ca">Mountain view</option><option value="">china</option><option>australia</option>
      </select>
      

      ​ 如果用户选择了其中的第一项,则选择框的值就是“sunnyvale,ca”。如果文本为china的选项被选中,则选择框的值就是一个空字符串,因为其value特性是空的。如果选择了最后一个选项,那么中没有指定value特性,则选择框的值就是“australia”

      ​ 在DOM中,每一个元素都有一个HTMLOptionElement对象表示。为了便于访问数据,

      HTMLOptionElement对象添加了下列属性:

      • index :当前选项在options集合中的索引。

      • label:当前选项的标签;等价于html中label特性。

      • selected:布尔值,表示当前选项是否被选中。将这个属性设置为true可以选中当前选项。

      • text:选项的文本。

      • value;选项的值(等价于html中value特性。

        其中大部分属性的目的,都是为了方便对选项数据的访问。虽然也可以使用常规的DOM功能来访问这些信息,但是效率是比较低的。

        var selectbox = document.forms[0].elements["location"]//不推荐
        var text = selectbox.options[0].firstChild.nodeValue //选项的文本
        var value = selectbox.options[0].getAttributr("value") //选项的值
        

        以上是使用标准的DOM方法,取得了选择框中第一项的文本值。可以与下面使用选项属性的代码作比较:

        var selectbox = document.forms[0].elements["location"]//推荐
        var text = selectbox.options[0].text //选项的文本
        var value = selectbox.options[0].value //选项的值
        

        ​ 在操作选项时,我们建议是使用特定于选项的属性,因为所有浏览器都支持这些属性。在将表单控件作为DOM节点的情况下,实际的交互方式会因浏览器而异。我们不锐减使用标准DOM技术修改option元素的文本或值。

        ​ 最后我们还要注意:选择框的change事件与其他表单字段的change事件触发的条件不一样。其他表单字段的change事件时在值被修改且焦点离开当前字段时触发,而选择框的change事件只要选中了就会触发。

选择选项

​ 对于只允许选择一项的选择框,访问选中项的最简单方式,就是使用选择框的selectedIndex属性。

var selectedOption = selectbox.options[selectbox.selectedIndex]
// 取得选中项之后
var selectedIndex = selectbox.selectedIndex
var selectedOption = selectbox.options[selectedIndex]
alert("selected index:"+selectedIndex+"\n selected text:"+ selectedOption.text+"\n selected value:"+selectedOption.value)

​ 这里通过一个警告框显示了选中项的索引,文本和值。

​ 对于可以选择多项的选择框,selectedIndex属性好像就是只允许选择一项一样。设置selectedIndex会导致取消以前所有选项并指定的哪一项,而读取selectedIndex则只会返回选项中第一项的索引值。

​ 另一种选择选项的方式,就是取得某一项的引用,然后将其selected属性设置为true。

selectbox.options[0].selected = true

​ 与selectedIndex不同,在允许多选的选择框中设置选项的selected属性,不会取消对其他选项的选择。因而可以动态选中任意多个项。但是如果在单选选择框中,修改某个选项的selected属性则会取消对其他选项的选择。需要注意的是,将selected属性设置为false时对单选框没有影响。

​ 实际上,selected属性的作用主要是用户选择选择框中的哪一项。要取得所有选中的向可以循环遍历选项集合,然后测试每个选项的selected属性:

function getSelectedOptions(selectbox){var result = new Array()var option = nullfor (var i = 0, len = selectbox.options.length;i<len;i++) {option = selectbox.options[i]if(option.selected){result.push(option)}}return result
}

​ 这个函数可以返回给定选择框中选中项的一个数组。首先创建一个包含选中项的数组,然后使用for循环迭代所有选项,同时坚持每一项的selected属性。如果有选项被选中,则将其添加到result数组中。最后,返回包含选中项的数组。

添加选项

​ 可以使用JavaScript动态创建选项,并将他们添加到选中框中。添加选项的方式有很多,第一种方式就是使用DOM方法。

var newOption = document.createElement("option")
newOption.appendChild.createTextNode("option text")
newOption.setAttribute("value","option value")
selectbox.appendChild(newOption)

​ 以上代码创建了一个新的option元素,然后为它添加了一个文本节点,并设置其value特性,最后将它添加到选择框中。添加到选择框之后,用户立即就可以看到新选项。

第二种方式是使用option构造函数来创建新选项,这个构造函数是DOM出现之前就有的,一直遗留到现在。option构造函数接受两个参数:文本(text)和值(value);第二个参数可选。虽然这个构造函数会创建一个object的实例,但兼容dom的浏览器都会返回一个元素。换句话说,在各种情况下,我们任然可以使用appendChild()将新的选项添加到选择框中:

var newOption = new Option("Option text", "option value")
selectbox.appendChild(newOption) //在ie8之前的版本有问题。

​ 这种方式在除IE之外的浏览器中都可以使用。由于存在bug,IE在这种方式下不能正确设置新选项的文本。

第三种方式添加新选项的方式是使用选择框的add()方法。DOM规定这个方法接受两个参数;要添加的新选项和将位于新选项之后的选项。如果想在列表的最后添加一个选项,应该将第二个参数设置为null。在IE对add()方法的实现中,第二个参数是可选的,而且如果指定,该参数必须是新选项之后的索引。兼容DOM的浏览器要求必须指定第二个参数,而且如果指定,该参数必须是新选项之后选项的索引。兼容DOM的浏览器要求必须指定第二个参数,因此要想编写浏览器的代码,不能只传入一个参数。这时候,为第二个参数传入undefined,就可以在所有浏览器中都将新选项插入到列表最后了。

var newOption = new Option('Option text', 'Option value')
selectbox.add(newOption, undefined)  //最佳方案

移除选项

​ 与添加选项类似,移除选项的方式也有很多种。首先,可以使用DOM的removeChild(),为其传入要移除的选项:

selectbox.removeChild(selectbox.options[0]) //移除第一个选项

​ 其次,可以使用选择框的remove()方法。这个方法接受一个参数,即要移除选项的索引:

selectbox.remove(0) //移除第一个选项

​ 最后一种方式,就是想要选项设置为null。这种方式也是DOM出现之前的遗留机制。

selectbox.options[0] = null  //移除第一个选项

​ 要清除选择框中所有的项,需要迭代所有选项并逐个移除它们:

function clearSelectbox(selectbox) {for(var i = 0; i<selectbox.options.length; i++ ){selectbox.remove(i)}
}

​ 这个函数每次只移除选择框中的第一个选项。由于移除第一个选项后,所有后续选项都会自动向上移动一个位置,因此重复移除第一个选项就可以移除所有选项了。

移动和重排选项

​ 在dom标准出现之前,将一个选择框中的选项移动到另一个选择框中是非常麻烦的。整个过程要涉及从第一个选择框中移除选项,然后意向图的文本和值创建新的选项,最后再将新的选项添加到第二个选择框中。而使用DOM的appendChild()方法,就可以将第一个选择框中的选项直接移动到选项移动到第二个选择框中。我们知道如果为appendChild()方法传入一个文档已有的元素,那么就会从该元素的父节点中移除它,然后再将它添加到指定的位置。

var selectbox1 = document.getElementById("selLocations1")
var selectbox2 = document.getElementById("selLocations2")
selectbox2.appendChild(selectbox1.options[0])

​ 移动选项与移除选项有一个共同之处,即会重置每一个选项的index属性。

重排选项次序的过程也十分类似,最好的方式仍然是使用DOM方法。要将选择框中的某一项移动到特定的位置,最适合的DOM方法就是insertBefore();appendChild()方法只适用于将选项添加到选择框的最后。要在选择框中向前移动一个选项的位置:

var optionToMove = selectbox.options[1]
selectbox.insertBefore(optionToMove,selectbox.options[optionToMove.index-1])

​ 以上代码选项选中了要移动的选项,然后将其插入到了排在它前面的选项之前。实际上。第二行代码对除第一个选项之外时通用的。类似的如果想向后移动一个位置;

var optionToMove = selectbox.options[1]
selectbox.insertBefore(optionToMove,selectbox.options[optionToMove.index+2])

​ 以上代码使用选择框中的所有选项,包括最后一个选项。

表单序列化

​ 随着ajax的出现,表单序列化已经成为一种常见的需求。在JavaScript中,可以利用表单字段的type属性,连同name和value属性一起实现对表单的序列化。在编写代码之前,有必须先搞清楚在表单体检期间,浏览器是怎样讲述库发送给服务器的。

  • 对表单的名称和值进行URL编码,使用(&)分隔

  • 不发送禁用的表单字段。

  • 只发送勾选的复选框按钮和单选按钮。

  • 不发送type为“reset” 和 “button”的按钮。

  • 多选选择框中的每一个选择的值单独一个条目。

  • 在单击提交按钮提交表单的情况下,也会发送提交按钮;否则不发送提交按钮。也包括type为image的input元素

  • select 元素的值,就是选择的option元素的value特性的值。如何option元素没有value特性,则是option元素的文本值。

    在表单序列化过程中,一般不包含人格按钮字段,因为结果字符串很可能是通过其他方式提交的。除此之外其他上诉规则都应该遵循。:、

    function serialize(form) {var parts = [],field = null,i,len,j,optLen,option,optValue;for(i=0;i<form.elements.length;i++){field = form.elements[i]switch(field.type){case "select-one":case "select-multiple":if(field.name.length) {for(j=0;j<field.options.length; j++){option = field.options[j]if(option.selected) {optValue = ""if(option.hasAttribute) {optValue = (option.hasAttribute("value")?option.value: option.text)} else {optValue = (option.attributes["value"].specified)?option.value:option.text)}parts.push(encodeURIComponent(field.name)+"="+encodeURIComponent(optValue))}}}break;case undefined:  //字段集case "file":  //文件输入case "submit": //提交按钮case "button": //自定义按钮break;case "radio":  //单选按钮case "check":  //复选框if(!field.checked){break}default://不包含没有名字的表单字段if(field.name.length){             parts.push(encodeURIComponent(field.name)+"="+encodeURIComponent(field.value))}}}return parts.join("&")
    }
    

    ​ 上面的serialize()函数首先自定义了一个名为parts的数组,用于保存将要创建的字符串的各个部分。然后,通过for循环迭代每个表单字段,并将其保存在field变量中。在获得了一个字段的引用之后,使用switch语句检测其type属性。序列化过程中最麻烦的就是select元素,它可能是单选框也可能是多选框。为此,需要变量控件中的每一个选项,并在相应选项被选中的情况下向数组添加一个值。对于单选框,只可能有一个选中项,而多选框则可能有0或多个选中项。这里的代码适用于这两种选择框,至于可选看的数量则是由浏览器控制的。在找到一个选中项之后,需要确定使用什么值。如果不存在value特性,或者虽然存在value特性,但是值为空,都要使用选项文本来代替。为检测这个特性,在DOM兼容的浏览器中使用hasAttribute()方法,而在ie中需要使用specified属性。

    ​ 如果表单中包含fieldset元素,则该元素会出现在元素几个中,但没有type属性。因此如果type属性未定义,则不需要对其进行序列化。同样对于各种按钮以及文件输入字段也是如此(文件输入字段在表单提交过程中包含文件的内容;但是这个字段无法模仿的,序列化是一般都要忽略)。对于单选按钮和复习框,首先要检查其checked属性是否被设置为false,如果是则退出switch语句。如果checked属性为true,则继续执行default语句,即将当前字段的名称和值进行编码,然后在添加到parts数组中。函数的最后一步,就是使用join()格式化整个字符串,也就是用和号来风格每一个表单字段。

    ​ 最后serialize()函数会以查询字符串的格式输出序列化之后的字符串。当然,要序列化成其他格式也不是什么困难的事。

富文本编辑

​ 富文本编辑,又被称为WYSIWYG(what you see is what you get 所见即所得)。在网页中编辑富文本内容,是人们对web应用程序最大的期待之一。虽然也没有规范,胆子ie最早引入的这一功能基础上,已经出现了事实标准。这一技术本质,就是在页面中嵌入一个包含HTML页面的iframe。通过设置designMode属性,这个空白的HTML页面可以被编辑,而编辑对象则是该页面的bidy元素的html代码。designMode属性由两个可能的值:“off”(默认值)和“on”。在设置为on时整个文档都会变得可编辑(显示插入符号),然后就可以像是要字处理软件一样,通过键盘将文本内容加粗,变成斜体等等。

​ 可以给iframe指定一个非常简单的html页面作为其内容来源

<!DOCTYPE HTML>
<html><head><title>blank page for rich text editing</title></head><body></body>
</html>

​ 这个页面在iframe中可以像其他页面一样被加载。要让它可以编辑,必须要将designMode设置为“on”,但是在页面完全加载之后才能设置这个属性。因此,在包含页面中需要使用onload事件处理程序来在恰当的时刻设置desginMode:

<iframe name="richedit"style="height:100px;width:100px" src="blank.htm"></iframe>
<script>EventUtil.addHandler(window,"load", function(){frames("richedit").document.designMode = "on"})</script>

使用contenteditable属性

​ 另一种编辑富文本内容方式就是使用名为contenteditable的特殊属性,这个属性也是ie最早实现的。可以吧contenteditable属性易用给页面中的任何元素,饭后用户就可以立即编辑该元素。这种方法之所以受到欢迎,是因为他不需要iframe,空白页和JavaScript,只要为元素设置contenteditable属性即可。

<div class="editable" id="richedit" contenteditable></div>

​ 这样元素中包含的任何文本内容都可以编辑了,就好像这个元素变成了textarea元素一样。通过这个元素上设置contenteditable属性,也能打开或关闭编辑模式。

var div = document.getElementById("richedit")
richedit.contentEditable = "true"

​ contenteditable属性有三个可能的值:true便是打开,false表示关闭

inherit表示从父元素那里继承(因为可以在contenteditable元素中创建或删除元素)。

使用canvas绘图

​ 不用说,HTML5添加的最受欢迎的就是元素。这个元素负责在页面中设定一个区域,然后就可以通过JavaScript动态的在这个区域中绘制图形。元素最早由苹果公司推出。很快html5加入了这个元素,主流浏览器也迅速支持。

​ 与浏览器中的其他组件类似由几组api构成,但并非所有的浏览器都支持这些API。除了具备基本绘图的2D上下文,还建议了一个名为webgl的3D上下文。目前,支持该元素的浏览器都支持2D上下文及文本api,但是对于WebGl支持还不够好。

基本用法

​ 要使用元素,必须先设置其width和height属性,指定可以绘图区域的大小。出现在开始和结束标签中的内容时后背信息,如果浏览器不支持元素,就会显示这些信息。

<canvas id="drawing" width="200" height="200">A drawing of something
</canvas>

​ 与其他元素一样,元素对应的DOM元素对象也有width和height属性,可以鼠疫修改,也能通过css为该元素添加样式,如果不添加人格样式或者不绘制任何图形,在页面中是看不到该元素的。

​ 要在这块画布上绘制,就需要取得绘图上下文。而取得绘图上下文对象的引用,需要调用getContext()方法并传入上下文的名字。传入“2d”,就可以取得2d上下文对象。

var drawing = document.getElementById('drawing')
// 确定浏览器支持<canvas>元素
if(drawing.getContext) {var context = drawing.getContext("2d")
}

​ 在使用元素之前,首先要检测getContext()方法是否存在,这一步非常重要。有些浏览器会为HTML规范之外的元素创建默认的HTML元素对象。这种情况下,即使drawing变量中保存着一个有效的元素引用,也检测不到getContent方法。

​ 使用toDataURL()方法,可以导出在元素上绘制的图形。这个方法接受一个参数,即图像的mime类型格式,而且适合于创建图像的任何上下文。比如,要取得一个画布中的一幅png格式的图形:

var drawing = document.getElementById('drawing')
if(drawing.getContext) {// 获取图形的数据URIvar imgURI = drawing.toDataURL("image/png")// 显示图形var image = document.createElement('img')image.src = imgURIdocument.body.appendChild(image)
}

2D上下文

​ 使用2D绘图上下文提供的方法,可以绘制简单的2D图形,比如矩形,弧线和路径2D上下文的坐标开始于元素的左上角,原点坐标是(0,0)。所有的坐标值都是基于这个原点计算,x值越大表示越靠右,y值越大表示越靠下。默认情况下width和height表示水平和垂直两个方向上可用的像素数目。

填充和描边

2d上下文的两种基本绘制图操作时填充和描边。填充就是知道的样式(颜色,渐变或图像)填充图像;描边,就是只在图形的边缘画线。大多数2D上下文操作都会细分为填充和描边两个操作,而操作的结果取决于fillStyle 和strokeStyle

​ 这两个属性的值可以是字符串,渐变对象或模式对象,而且他们的默认值都是“#000000”。如果为他们指定表示颜色的字符串值,可以使用css中指定颜色值的任何格式,包括颜色名,十六进制,rgb,rgba,hsl或hsla:

var drawing = document.getElementById('drawing')
if(drawing.getContext) {var context = drawing.getContext('2d')context.strokeStyle = "red"context.fillStyle = "#0000ff"
}

绘制矩形

​ 矩形是唯一一种可以直接在2d上下文中绘制的形状。与矩形有关的方法包括fillRect(),strokeRect()和clearRect(),这三个方法都接收4个参数,矩形的x坐标,矩形的y坐标,矩形的宽度和矩形的高度。这些参数的单位都是像素。

​ 首先,fillRect()方法在画布上绘制的矩形会填充指定的颜色。填充的颜色通过fillStyle属性指定。

var drawing = document.getElementById('drawing')
if(drawing.getContext) {var context = drawing.getContext('2d')// 绘制红色矩形context.fillStyle = "#ff0000"context.fillRect(10,10,50,50)// 绘制半透明蓝色矩形context.fillStyle = "rgba(0,0,255,0.5)"context.fillRect(30,300,50,50)
}

strokeRect()方法在画布上绘制的矩形会使用指定的颜色描边。描边颜色通过strokeStyle属性指定

var drawing = document.getElementById('drawing')
if(drawing.getContext) {var context = drawing.getContext('2d')// 绘制红色矩形context.fillStyle = "#ff0000"context.fillRect(10,10,50,50)//描边颜色context.strokeStyle = 'yellow'context.strokeRect(10,10,50,50)// 绘制半透明蓝色矩形context.fillStyle = "rgba(0,0,255,0.5)"context.fillRect(30,30,50,50)context.strokeStyle = 'pink'context.strokeRect(30,30,50,50)
}

​ 描边线条的宽度由lineWith属性控制,该属性的值可以是任意整数。另外通过lineCap 属性可以控制线条末端的形状是平头,圆头还是方头(“butt”、“round”、或“square”),通过lineJoin属性可以控制线条相交的方式是圆头,斜交还是斜接(“round”、“bevel”、“miter”)

​ 最后、**clearRect()**方法用于清除画布上的矩形区域。本质上,这个方法可以把绘制上下文中的某一矩形区域变透明。通过绘制形状后再清除指定区域,就可以生成有意思的效果,例如把某个形状切掉一块。:

var drawing = document.getElementById('drawing')
if(drawing.getContext) {var context = drawing.getContext('2d')// 绘制红色矩形context.fillStyle = "#ff0000"context.fillRect(10,10,50,50)//描边颜色context.strokeStyle = 'yellow'context.strokeRect(10,10,50,50)// 绘制半透明蓝色矩形context.fillStyle = "rgba(0,0,255,0.5)"context.fillRect(30,30,50,50)context.strokeStyle = 'pink'context.strokeRect(30,30,50,50)// 在两个矩形重叠地方清除一个小矩形context.clearRect(40,40,10,10)
}

绘制路径

​ 2D绘制上下文支持很多在画布上绘制路径的方法。可以通过创造出复杂的形状和线条。要绘制路径,首先要调用beginPath()方法,表示开始绘制新路径。然后在通过调用以下方法来实际地绘制路径。

  • arc(x,y ,radius,startAngle, endAngle, counterclockwise): 以(x,y)为圆心绘制一条弧线,弧线半径为radius,起始和结束角度(用弧度表示)分别为startAngle和endAngle。最后一个参数表示startAngle和endAngle是否按逆时针方向计算,值为false表示按顺时针方向计算。

  • arcTo(x1,y1,x2,y2,radius):从上一点开始绘制一条弧线,到(x2,y2)为止,并且以给定的半径radius穿过(x1,y1)

  • bezierCurveTo(c1x, c1y, c2x, c2y, x, y) : 从上一点开始绘制一条曲线,到(x,y)为止,并且以(c1x,c1y)和(c2x,c2y)为控制点。

  • lineTo(x,y): 从上一点开始绘制一条直线,到(x,y) 为止。

  • moveTo(x,y):将绘图游标移动到(x,y),不画线。

  • quadraticCurveTo(cx,cy,x,y): 从上一点开始绘制一条二次曲线,到(x,y)为止,并且以(cx,cy)作为控制点。

  • rect(x,y width,height): 从点(x,y)开始绘制一个矩形,宽度和高度分别由width和height指定。这个方法绘制的是矩形路径,而不是strokeRect()和fillRect()所绘制的独立形状。

    ​ 创建路径后,接下来有几种可能选择。如果想绘制一条连接到路径起点的线条,可以调用closePath().如果路径已经完成,你想使用fillStyle填充它,可以调用fill()方法。另外,还可以调用stroke()方法对路径描边。描边使用的是strokeStyle。最后还可以调用clip(),这个方法可以在路径上创建一个剪切区域。

    var drawing = document.getElementById('drawing')//确定浏览器支持canvas元素
    if(drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()// 绘制外圆context.arc(100,100,99,0,2*Math.PI, false)// 绘制内圆context.moveTo(194,100)context.arc(100,100,94,0,2*Math.PI,false)//绘制分针context.moveTo(100,100)context.lineTo(100,15)// 绘制时针context.moveTo(100,100)context.lineTo(35,100)// 描边路径context.stroke()
    }
    

    ​ 这里使用arc方法绘制了两个圆:一个内圆,一个外圆。构成看表盘,外圆的表盘半径是99像素,圆心位于(100,100),也是画布的中心点。为了绘制一个完整的圆形,我们从0弧度开始,绘制2π弧度(通过Math.PI来计算)。在绘制内圆之前,必须把路径移动到外圆上的某一点,以避免绘制出多余的线条。第二次调用调用arc使用了小一点的半径。然后使用moveTo和lineTo()方法来绘制时针和分针。最后一步调用stroke()方法,这样才能把图形绘制到画布上。

    ​ 在2D绘制上下文中,路径是一种主要的绘图方式,因为路径能为要绘制的图形提供更多控制。由于路径的使用很频繁。所以就有了一个名为isPointInPath()的方法。这个方法接收x和y坐标作为参数,用于在路径被关闭之前确定画布上的某一点是否位于路径上:

    if(context.isPointInPath(100,100)){alert("point (100,100) is in the path")
    }
    

    ​ 2D上下文的路径API已经非常稳定,可以利用他们介个不同的填充和描边样式,绘制出非常复杂的图型来。

绘制文本

​ 文本与图形如影随形。为此,2D绘图上下文也提供了绘制文本的方法。绘制文本主要有两个方法:fillText()和strokeText()。这两个方法都可以接收四个参数:要绘制的文本字符串,x坐标,y坐标和可选的最大像素宽度。以下列三个属性为基础:

  • font :表示文本样式,大小及字体,用css中指定字体的格式来指定。例如“10px Arial”

  • textAlign:表示文本对齐方式。可能的值有start,end,left,right和center。建议使用start和end,不用使用left和right,因为前两者的意思更稳妥,同时适合从左到右和从右到左显示(阅读)的语言。

  • textBaseline:表示文本的基线。可能的值有top,hanging,middle,alphabetic,ideographic和bottom

    这几个属性都有默认值,因此没有必要每次都重新设置一遍值。fillText()方法使用fillStyle属性绘制文本,而strokeText()方法使用strokeStyle属性为文本描边。相对来说还是使用fillText()的时候更多。

    例如在上面的表盘中添加一个12数字

    context.font = "bold 14px Arial"
    context.textAlign = "center"
    context.textBaseline = "middle"
    context.fillText("12",100,20)
    

    ​ 因为这里把textAlign设置为center,把textBaseline设置为middle,所以坐标(100,20)表示的是把文本水平和垂直居中点的坐标,如果将textAlign设置为start则x坐标表示的是文本左端的位置,(从左往有阅读的语言);设置为end,则x坐标表示的是文本右端的位置(从从左往有阅读的语言)

变换

​ 通过上下文的变换,可以把处理后的图像绘制到画布上。2d绘制上下文支持各种基本的绘制变换。创建绘制上下文时,会以默认值初始化变换矩阵,在默认的变换矩阵下,所有处理描述直接绘制。为绘制上下文应用变换,会导致使用不同的变换矩阵处理应用,从而产生不同的结果。

​ 可以通过以下方法来变换矩阵。

  • rotate(angle): 围绕原点旋转图像angle弧度。

  • scale(scaleX,scaleY);缩放图像,在x方向乘以scaleX,在y方向乘以scaleY。scaleX,和scaleY默认值都是1.0。

  • translate(x,y):将坐标原点移动到(x,y)。执行这个变换后,坐标(0,0)会变成之前又(x,y)表示的点。

    拿前面绘制表针来说,如果把原点变换到表盘中心,然后在绘制表针就容易多了:

    var drawing = document.getElementById('drawing')//确定浏览器支持canvas元素
    if(drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()// 绘制外圆context.arc(100,100,99,0,2*Math.PI, false)// 绘制内圆context.moveTo(194,100)context.arc(100,100,94,0,2*Math.PI,false)//变换原点content.translate(100,100)//绘制分针context.moveTo(0,0)context.lineTo(0,-85)// 绘制时针context.moveTo(100,100)context.lineTo(-65,0)// 描边路径context.stroke()
    }
    

    ​ 把原点变换到时钟表盘中心点(100,100)后,在同一方向上绘制线条变成了简单的数学问题了。所有数学计算都基于(0,0),而不是(100,100)。还可以更近一步,像是要rotate()方法旋转时钟的表针。

    var drawing = document.getElementById('drawing')//确定浏览器支持canvas元素
    if(drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()// 绘制外圆context.arc(100,100,99,0,2*Math.PI, false)// 绘制内圆context.moveTo(194,100)context.arc(100,100,94,0,2*Math.PI,false)//变换原点context.translate(100,100)// 旋转表针context.rotate(1)//绘制分针context.moveTo(0,0)context.lineTo(0,-85)// 绘制时针context.moveTo(100,100)context.lineTo(-65,0)// 描边路径context.stroke()
    }
    

    ​ 因为原点已经变换到了时钟表盘的中心点,所以旋转也是以该点为圆心的。结果就像是表针真的被固定在表盘中心一样,然后向右旋转了一定的角度。

    无论是刚才执行的变换,还是fillStyle,strokeStyle等属性,都会在当前上下文中一直有效,除非再对上下文进行修改。虽然没有什么办法把上下文中的一切都重置回默认值。但有两个值可以跟踪上下的状态变化。如果你知道将来还要返回某组属性与变换的组合,可以调用save()方法。调用这个方法后,当时所有设置都会进入一个栈结构,妥善保管。然后对上下文进行修改。等到想要回到之前保存的设置时,可以调用restore()方法,在保存设置的栈结构中向前返回一级,恢复之前的状态。连续调用save()可以把更多的设置保存到栈结构,之后连续调用restore()则可一级一级返回。

    context.fillStyle = "#ff0000"
    context.save()context.fillStyle = "#00ff00"
    context.translate(100,100)
    context.save()context.fillStyle = "#0000ff"
    context.fillRect(0,0,100,200) //从点(100,100)开始绘制蓝色矩形context.restore()
    context.fillRect(10,10,100,200) //从(110,110)开始绘制绿色矩形context.restore()
    context.fillRect(0,0,100,200) //从(110,110)开始绘制红色矩形
    

    ​ 首先将fillStyle设置为红色,并调用save()保存上下文状态。接下来,把fillStyle修改为绿色,把原点坐标变换到(100,100),再调用save()保存上下文状态。然后,把fillStyle修改为蓝色,并绘制蓝色矩形。因为此时坐标原点已经变了,所以矩形的左上角坐标实际上是(100,100)。然后调用restore(),之后fillStyle变回了绿色,因而第二个矩形是绿色。之所以第二个矩形的起点坐标是(110,110)是因为坐标位置的变换任然起作用。在调用一次restore(),变换就被取消掉了,而fillStyle返回了红色,且绘制起点是(0,0)

    ​ 需要注意的是,save() 方法保存的只是绘图上下问的设置个变换,不会保存绘图上下文的内容。

绘制图像

​ 2D绘图上下文内置了对图像的支持。如果你想把一幅图像绘制到画布上,可以使用drawImage()方法。根据期望的最终结果不同,调用这个方法时,可以使用三种不同的参数组合。最简单的调用方式是传入一个元素,以及绘制该图像的起点的x和y坐标。:

var image = document.images[0]
image.onload = function () {context.drawImage(image, 10, 10);}

​ 这两行代码取得了文档找那个的第一幅图,然后将它绘制到上下文中,起点为(10,10)。绘制到画布上的图形大小与原始大小一样。如果你想改变绘制后图形的大小,可以再多传入两个参数,分别表示目标的宽度和高度。通过这种方式来缩放图像,并不影响上下文的变换矩形:

var image = document.images[0]
image.onload = function () {context.drawImage(image, 50, 10,20,30);}

​ 执行这段代码,会绘制一个20x30像素大小的图形。

​ 除了上诉两种方式,还可以选择吧图像中的某个部位位置到上下文中。drawImage()方法的调用方式需要传入9个参数:要绘制的图像,源图像x坐标,原图像y坐标,原图像的宽度,原图像高度,目标图像的x坐标,目标图像的y坐标,目标图像的宽度,目标图像的高度。

image.onload = function () {context.drawImage(image, 0,10, 50,50,0,100,40,60);}

​ 这里会把原始图像的一部分绘制到画布上。原始图像的这一部分的起点为(0,10),宽和高都是50像素。最终绘制到上下文中的图形起点是(0,100),大小40x60像素。

​ 除了给drawImage()方法传入html的img元素,还可以传入另一个canvas元素作为第一个参数。这样就可以把另一个画布绘制到当前画布上。

​ 结合使用drawImage()和其他方法,可以对图像进行各种基本操作。而操作的结果可以通过toDataURL()方法获得。不过有一个例外,及图像不能来自其他域。如果图像来自其他域,调用toDataURL()会抛出一个错误。例如位于www.example.com上绘制的图像来自于www.baidu.com,那么当前上下文就会被认为“不干净”,因而跑出错误。

阴影

​ 2D上下文会根据以下几个属性的值,自动为形状或路径绘制出阴影。

  • shadowColor:用css颜色格式表示阴影颜色,默认是黑色。

  • shadowOffsetX:形状或路径x轴方向偏移量,默认是0

  • shadowOffsetY:形状或路径y轴方向偏移量,默认为0

  • shadowBlur:模糊的像素数,默认为0,即不模糊

    这些属性都可以通过context对象来修改。只要在绘制前为他们设置适当的值,就能自动产生阴影。

    <body><canvas id="drawing" width="200" height="200"></canvas><script>var drawing = document.getElementById('drawing')//确定浏览器支持canvas元素if (drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()// 设置阴影context.shadowOffsetX = 5context.shadowOffsetY = 5context.shadowBlur = 4context.shadowColor = "rgba(0,0,0,.5)"//绘制矩形context.fillStyle = "#f00f00"context.fillRect(10,10,50,50)// 绘制矩形context.fillStyle = "rgba(0,0,255,1)"context.fillRect(30,30,50,50)// 描边路径context.stroke()}</script>
    </body>
    

渐变

​ 渐变有canvasGradient 实例表示,很容易通过2D上下文来创建和修改。要创建一个新的线性渐变,可以调用createLinearGradient()方法。这个方法接收四个参数;起点的x坐标,起点的y坐标,终点的x坐标,终点的y坐标。调用这个方法后,它就会创建一个指定大小的渐变,并返回canvasGradient 对象的实例。

​ 创建渐变对象后,下一步就是使用addColorStop()方法来指定色标。这个方法接收两个参数:色标位置和css颜色值。色标位置是一个0(开始的颜色)到1(结束的颜色)之间的数字:

<body><canvas id="drawing" width="200" height="200"></canvas><script>var drawing = document.getElementById('drawing')//确定浏览器支持canvas元素if (drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()var gradient = context.createLinearGradient(30, 30, 70, 70)gradient.addColorStop(0, 'black');gradient.addColorStop(1, 'orange');// 绘制矩形context.fillStyle = "#ff0000"context.fillRect(10,10,100,100)// 绘制渐变矩形context.fillStyle = gradientcontext.fillRect(30,30,100,100)// 描边路径context.stroke()}</script>
</body>

​ 这里绘制绘制的渐变只有左上角稍微有一点渐变色。主要是因为距的的起点位于渐变的中间位置,而此时渐变差不多已经结束了。由于渐变不重复,所以只有左上角有一点渐变色。确保渐变与形状对齐非常重要,有时候可以考虑使用函数来确保坐标合适。

function createLinearGradient(context,x,y,width,height) {return context.createLinearGradient(x,y,x+width,y+height)
}

​ 这个函数基于起点的x和y坐标以及宽度和高度来创建渐变对象,从而让我们可以在fillRect()中使用相同的值。

var gradient = createLinearGradient(context,30,30,100,100)gradient.addColorStop(0, 'black')gradient.addColorStop(1, 'orange')context.fillStyle = gradientcontext.fillRect(30,30,100,100)

完整代码

<body><canvas id="drawing" width="200" height="200"></canvas><script>var drawing = document.getElementById('drawing')function createLinearGradient(context, x, y, width, height) {return context.createLinearGradient(x, y, x + width, y + height)}//确定浏览器支持canvas元素if (drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()var gradient = createLinearGradient(context, 30, 30, 100, 100)gradient.addColorStop(0, 'blue')gradient.addColorStop(1, 'orange')// 绘制渐变矩形context.fillStyle = gradientcontext.fillRect(30, 30, 100, 100)// 描边路径context.stroke()}</script>
</body>

​ 要创建镜像渐变(或放射渐变)可以使用createRadialGradient()方法。这个方法接收6个参数,对于着两个圆的圆心和半径,前三个参数指定的是起点圆的圆心(x和y)及半径,,后三个参数指定的是终点圆的原心(x,y)及半径。**可以把径向渐变想象成一个长圆通,而这6个参数定义的正是同的两个圆形开口的位置。**如果把一个圆形开口定义得比另一个小一些,那么圆桶就变成了圆锥,而通过移动圆形开口的位置,就可以达到像旋转这个圆锥体一样的效果。

​ 如果想从某个形状的中心点开始创建一个向外扩散的渐变效果,就将两个圆定义为同心圆。比如前面创建的矩形来说,径向渐变的两个圆圆心都应该在55,因为矩形区域是从30,30到80,80

   if (drawing.getContext) {var context = drawing.getContext("2d")//开始绘制路径context.beginPath()var gradient = context.createRadialGradient(55,55,10,55,55,30)gradient.addColorStop(0, 'blue')gradient.addColorStop(1, 'orange')// 绘制渐变矩形context.fillStyle = gradientcontext.fillRect(30, 30, 100, 100)// 描边路径context.stroke()}

​ 因为创建比较麻烦,所以径向渐变并不那么容易控制。不过一般来说,让起点圆和终点圆保持为同心圆的情况比较多,这个时候只要考虑给两个圆设置不同半径就好了。

模式

​ 模式其实就是重复的图像,可以用来填充或描边图像。要创建一个新模式,可以调用createPattern()方法并传入两个参数:一个HTML元素和一个表示如歌重复图像的字符串。其中,第二个参数的值与css的background-repeat属性值相同,包括repeat,repeat-x,repeat-y和no-repeat

var image = document.image[0],pattern = context.createPattern(image,"repeat")
// 绘制矩形
context.fillStyle = pattern
context.fillRect(10,,10,150,150)

​ 需要注意的是,模式与渐变一样,都是从画布的原点(0,0)开始的。将填充样式(fillStyle)设置为模式对象,只是在某个特定区域内重复图像,而不是要从某个位置开始绘制重复的图像。

使用图像数据

​ 2D上下文的一个明显长处就是,可以通过getImageData()取得原始图像数据,这个方法接收四个参数:要取得其数据的画面区域的x和y坐标以及该区域的像素宽度和高度。例如,要取得左上角坐标为(10,5),大小为50x50像素区域的图像数据:

var imageDate = context.getImageData(10,5,50,50)

​ 这里返回的对象是ImageData的实例。每个ImageData对象都有三个属性:width,height和data。其中data属性是一个数组,保存着图像中每一个像素的数据。在data数组中,每一个像素用4个元素来保存,分别表示红,绿,蓝,透明度值。因此,第一个的数据就保存在数组的地0到第三份元素中。:

var data = imageData.data
red = data[0],
greed = data[1]
blue = data[2]
alpha = data[3]

​ 数组中每个元素的值都介于0到255之间包括0和255。能够直接访问到原始图像数据,就能够以各种方式来操作这些数据,例如通过修改图像数据,创建一个简单的灰阶过滤器。

json

json

  • js中的对象只有js自己认识,其他语言他都不认识。

json 就是一个特殊格式的字符串。这个字符串可以被任意语言识别。并且转换成任意语言的对象。json和js对象的格式一样,只不过json字符串中,属性名必须加 --双引号-- 其他的和js语法一致

​ JSON 分类

1.对象{}

2.数组[]

json 允许的值:

1.字符串

2.数值

3.布尔值

4.null

5.对象

6.数组

将json字符串转换为js中的对象

所以在js中,为我们提供另一个工具类,就叫JSON

这个对象可以帮我们将一个JSON转换为js对象,也可以将JSON转换为js对象

JSON.parse()

多用于数据交换格式

可以将JSON字符串转换为js对象

-它需要一个JSON字符串作为参数,会将该字符串转换为js对象。

JSON.Stringify()

可以将js对象转换为JSON

需要一个js对象作为参数

JSON.stringify的第二个参数是 替代者(replacer). 替代者(replacer)可以是个函数或数组,用以控制哪些值如何被转换为字符串。

继承

原型链继承

​ 原型链继承是比较常见的继承方式之一,其中涉及的构造函数、原型和实例,三者之间存在着一定的关系,即每一个构造函数都有一个原型对象,原型对象又包含一个指向构造函数的指针,而实例则包含一个原型对象的指针。

function Parent() {this.name = 'parent1';this.play = [1, 2, 3]}function Child() {this.type = 'child2';}Child.prototype = new Parent();console.log(new Child())

存在潜在问题

var s1 = new Child();
var s2 = new Child();
s1.play.push(4);
console.log(s1.play, s2.play); // [1,2,3,4]

​ 改变s1play属性,会发现s2也跟着发生变化了,这是因为两个实例使用的是同一个原型对象,内存空间是共享的。

构造函数继承

借助 call调用Parent函数

function Parent(){this.name = 'parent1';
}Parent.prototype.getName = function () {return this.name;
}function Child(){Parent.call(this);this.type = 'child'
}let child = new Child();
console.log(child);  // 没问题
console.log(child.getName());  // 会报错

​ 可以看到,父类原型对象中一旦存在父类之前自己定义的方法,那么子类将无法继承这些方法。

​ 相比第一种原型链继承方式,父类的引用属性不会被共享,优化了第一种继承方式的弊端,但是只能继承父类的实例属性和方法,不能继承原型属性或者方法。

组合继承

​ 前面两种继承方式,各有优缺点。组合继承则将前两种方式继承起来

function Parent3 () {this.name = 'parent3';this.play = [1, 2, 3];
}Parent3.prototype.getName = function () {return this.name;
}
function Child3() {// 第二次调用 Parent3()Parent3.call(this);this.type = 'child3';
}// 第一次调用 Parent3()
Child3.prototype = new Parent3();
// 手动挂上构造器,指向自己的构造函数
Child3.prototype.constructor = Child3;
var s3 = new Child3();
var s4 = new Child3();
s3.play.push(4);
console.log(s3.play, s4.play);  // 不互相影响
console.log(s3.getName()); // 正常输出'parent3'
console.log(s4.getName()); // 正常输出'parent3'

​ 这种方式看起来就没什么问题,方式一和方式二的问题都解决了,但是从上面代码我们也可以看到Parent3 执行了两次,造成了多构造一次的性能开销。

原型式继承

​ 这里主要借助Object.create方法实现普通对象的继承。

let parent4 = {name: "parent4",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}};let person4 = Object.create(parent4);person4.name = "tom";person4.friends.push("jerry");let person5 = Object.create(parent4);person5.friends.push("lucy");console.log(person4.name); // tomconsole.log(person4.name === person4.getName()); // trueconsole.log(person5.name); // parent4console.log(person4.friends); // ["p1", "p2", "p3","jerry","lucy"]console.log(person5.friends); // ["p1", "p2", "p3","jerry","lucy"]

​ 这种继承方式的缺点也很明显,因为Object.create方法实现的是浅拷贝,多个实例的引用类型属性指向相同的内存,存在篡改的可能。

寄生式继承

​ 寄生式继承在上面继承基础上进行优化,利用这个浅拷贝的能力再进行增强,添加一些方法。

let parent5 = {name: "parent5",friends: ["p1", "p2", "p3"],getName: function() {return this.name;}
};function clone(original) {let clone = Object.create(original);clone.getFriends = function() {return this.friends;};return clone;
}let person5 = clone(parent5);console.log(person5.getName()); // parent5
console.log(person5.getFriends()); // ["p1", "p2", "p3"]

寄生组合式继承

​ 寄生组合式继承,借助解决普通对象的继承问题的Object.create 方法,在亲全面几种继承方式的优缺点基础上进行改造,这也是所有继承方式里面相对最优的继承方式。

function clone (parent, child) {// 这里改用 Object.create 就可以减少组合继承中多进行一次构造的过程child.prototype = Object.create(parent.prototype);child.prototype.constructor = child;
}function Parent6() {this.name = 'parent6';this.play = [1, 2, 3];
}
Parent6.prototype.getName = function () {return this.name;
}
function Child6() {Parent6.call(this);this.friends = 'child5';
}clone(Parent6, Child6);Child6.prototype.getFriends = function () {return this.friends;
}let person6 = new Child6();
console.log(person6); //{friends:"child5",name:"child5",play:[1,2,3],__proto__:Parent6}
console.log(person6.getName()); // parent6
console.log(person6.getFriends()); // child5

ES6 中extends继承

class Person {constructor(name) {this.name = name}// 原型方法// 即 Person.prototype.getName = function() { }// 下面可以简写为 getName() {...}getName = function () {console.log('Person:', this.name)}
}
class Gamer extends Person {constructor(name, age) {// 子类中存在构造函数,则需要在使用“this”之前首先调用 super()。super(name)this.age = age}
}
const asuna = new Gamer('Asuna', 20)
asuna.getName() // 成功访问到父类的方法

​ 利用babel工具进行转换,我们会发现extends实际采用的也是寄生组合继承方式,因此也证明了这种方式是较优的解决继承的方式。

JavaScript进阶

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p6j8VdEn-1618561342893)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201214154830171.png)]

API 和web API

API 是对程序员提供的一种工具,以便轻松实现想要的功能;一般是是一些函数。

web api 是浏览器提供的一套操作浏览器功能和页面元素的API(BOM和DOM)

ID获取 getElementById,返回的是对象;

console.dir 打印我们返回的元素对象,可以更好的查看属性和方法。

标签名获取:getElementByTagName 获取带有指定标签名的对象集合。以伪数组形式存储。

根据类名获得 getElementByClassName

h5新增querySelect 返回指定选择器的第一个内容

querySelectAll 返回指定选择起的所有选择器对象集合

事件由三部分组成: 事件源(事件被触发的对象,谁)

事件类型(如何触发,点击,键盘)

事件处理程序(通过函数,要做什么事)

执行事件步骤:1 获取事件源

2 绑定事件,注册事件

3 添加事件处理程序

innerText (不识别html标签,去除空格和换行)和innerHTML(识别html标签 w3c schoole 推荐使用,保留空格和换行)的区别

JavaScript 修改className 会直接覆盖原先的类名;如果想要保留原型的ClassName 在修改className 时 要把原先的类名。

排他思想:

如果有同一组元素,想要实现某种样式,需要用到循环排他思想:

1.所有元素全部清除样式(干掉所有人)

2.给当前元素设置样式(留下自己)

注意顺序不能变

自定义属性:

目的:是为了保存数据,有些数据可以保持在页面,,不需要保存到数据模库,h5 中 以data- 开头的 为自定义属性。

dataset 也可以获取自定义元素,但是只能获取

//getAttribute 可以获取自定义属性

<div id="demo" index="oo"></div>  //index 是自定义属性
<script>var div =  document.querySelector('div') getAttribute('index')  //attribute
</script>

setAttribute(‘属性’,值) //主要针对自定义属性

例; div.setAttribute(‘index’, ‘xx’)

移除属性: removeAttribute

节点操作

一般的节点至少用于,nodeType(节点类型),nodeName(节点名称)和nodeValue(节点值)三个基本属性

元素节点nodeType 为 1

属性节点nodeType 为 2

文本节点nodeTpye 为 3 (包含空格换行)

节点操作主要操作元素节点

节点层级,dom树把节点划分为不同的层级,常见的是父子兄关系

父节点

parentNode // 得到是离元素的最近的父节点,找不到父节点返回空

子节点

childNodes 获取所有子节点(包含元素,文本节点)

如果指向获得元素节点 ,可以通过指定nodeType 来处理

还可以通过 children 获取所有元素节点;实际开发中常用。

还提供了 firstChild 和lastChild 但是他们获取的是子节点 ,及包括文本,元素节点。

可以通过firstElementChild 和lastElementChild 和获取子元素节点(ie9以上才支持)

兄弟节点

nextSibling 得到下一个兄弟节点, 包含元素节点 和文本节点。

previousSibling 得到上一个兄弟节点,包含元素文本节点。

nextElementSibling 得到下一个兄弟元素节点

previousElementSibling 得到上一个兄弟元素节点

创建节点

createElement 动态创建元素节点

//创建节点

var li = document.createElement (‘li’)

// 添加节点 appendChild(),也称为追加元素 ,在最后面加

// 如果想在前面添加 insertBdfore(child , 指定元素) //两个参数,第二个是指定元素如:ul.children[0]

var ul = document.querySelect(‘ul’)

ul.appendChild(li)

删除节点

removeChild(child)

复制节点

cloneNode() //参数为空(默认是false) 是浅拷贝 ,只复制标签,参数为true 就为深拷贝

复制过后还需要添加,才会显示

for in 可以循环变量对象

for (var k in obj)

k得到属性名

obj[k] 得到属性值

三种动态创建元素区别

document.write() 直接全部写入页面内容流,但是文档执行完毕,则它会导致页面全部重绘。

innerHTML 和createElement 不会导致页面全部重绘。

innerHTML : 在创建多个元素时 ,通过拼接字符串时,耗时比较长,采用数组形式拼接会更快,只是采用数组形式,结构比较复杂;

createElement :创建元素效率稍微比数组形式低一点,但是结构清晰。

注册事件

onclick 具有事件唯一性 (只能添加一个) ,绑定多个onClick会被覆盖

button.onclick = function() {

}

addEventListener 事件监听,可以添加多个,w3c 推荐使用方式 事件不会被覆盖

传递三个参数,第一个事件类型,是点击还时鼠标移入移出之类

第二个,处理事件函数,

第三个参数:如果第三个参数时true ,表示事件处于捕获阶段(从上往下,从外往里,传递事件)

第三个参数时false,或者不传,表示冒泡阶段(从下到上。从里到外传递事件)。 实际开发中更关注冒泡

button.addEventListener (‘click’,function(){

})

button.addEventListener (‘click’,function(){

})

解绑(删除)事件

button.onclick = null (传统方式)

addEventListener 注册的只能通过removeEventListener

但是不能解绑匿名函数,所以addEventListener 必须以具名函数形式注册,才能解绑。

事件对象

button.onclick = function(event){

}

event 就是一个事件对象包含了用户点击了鼠标,按下了键盘,移动了鼠标等等,写到监听函数的参数里

事件对象时一系列相关数据的相关集合,

e.target 和this的区别

target返回的时触发事件的对象(点击哪个元素就返回哪个元素)

this返回的时绑定事件的对象

例如:在ul绑定了一个事件 ,但是它下面有几个li

现在点击li ,this返回的是ul ,

e.target 返回的是被点击的li

跟this相似的属性currentTarget指向当前元素

**阻止默认行为 **

让链接不跳转让按钮不提交

e.preventDefault() // dom推荐标准写法

return false 也能阻止默认行为 但是见了return return后面的代码就不会执行了

阻止冒泡

e.stopPropagation

事件冒泡的好处

事件委托,委派

委托原理:不是给每个子节点设置监听事件,而是将监听器设置在其父节点,然后利用冒泡原理影响设置每个子节点。

作用:只操作一次dom,提高了性能

<ul>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
<script>var ul = document.querySelect('ul')ul.addEventListener('click',function(){alert('aaaa')//而e.target 又能拿到当前被点击的元素,就容易操作dom了e.target.style.backgroundColor = 'red'
})
</script>

禁止某些事件

例如:禁止复制文字

document.addEventListener(‘selectstart’,function(e){

e.preventDefault()   //这种禁止对于开发人员几乎是无效的

})

键盘事件

keyup 键盘弹起时触发

keydown 键盘按下时触发(不松开会一直触发)

keypress 键盘按下时触发,不识别功能键 ,ctrl shift 等

执行顺序,keydown keypress keyup

键盘事件对象:其中keyCode 判断用户按了哪个键 ,得到相应键的ASCLL码值。

// 注意,keyup ,keydown 不区分大小写

keypress 区分大小写

keypress 和 keydown 在文本框中的特点:他们两个事件触发时时候,文字还没有落入文本框中。先触发,才输入。所以用keyup

BOM 事件(浏览器对象模型)

核心对象时window,bom缺乏标准,兼容性比较差

定时器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ap23uXrT-1618561342894)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201217092031090.png)]

注意:定时器使用全局变量(‘timer’)来赋值,这样,关闭时,关闭事件才能访问全局对象,局部变量是无法访问的。全局变量也要赋值为空,不然会undefind

原生js 发送短验证码案例

   <body><input type="number" /><button>发送</button><script>var btn = document.querySelector('button')time = 3;btn.addEventListener('click', function(){this.disabled = truevar timer = setInterval(function(){if(time === 0){// 这里不能用this ,this指向已发生改变btn.disabled = falseclearInterval(timer)time = 3btn.innerHTML = '发送'} else {btn.innerHTML = time + '秒后重新发送'time--}},1000)})</script></body>

js执行机制

JavaScript 语言是单线程,同一时间只能做一件事,所有的任务需要排队。现在js允许多个任务,异步进程。

同步任务:都在主线程,形成一个主线程执行栈。

异步任务:通过回调函数实现的。放到了任务队列。

js执行机制: 先执行执行栈的同步任务,所有同步任务完成后,在去执行异步任务。把异步任务,放到执行栈去执行。

不断的到任务队列获取异步任务,这种机制成为事件循环。

location对象

location.href : 获取或者设置整个url

location.host: 返回主机(域名)www.baidu.com

location.port : 返回端口号,如果未写返回空字符串

location.pathname 返回路径

location.search 返回参数

location.hash 返回片段,# 后面的内容,常见于链接,锚点

获取URL参数

login.html

<body><form action="index.html"><input type='text' name='uname'><input type='submit' value='登录'> </form>
</body>

index.html

<body><div></div><script>console.log(location.search); // ?uname=xx  打印出来带问号// 去掉问号// substr 带两个参数,起始位置,和结束位置,只带一个参数,表示从第一个参数作为起始位置取到最后var params = location.search.substr(1) // 利用=把字符串分割为数组;因为得到的数据 uname = xx 是以=分割的。var arr = params.split('=') // [uname,xx]// 把数据写入divvar div = document.querySelector('div')div.innerHTML = arr[1]</script>
</body>

location 对象常见方法

location.assign() 跟href一样,可以跳转页面(也称为重定向页面)记录浏览历史,可以后退。

location.replace() 替换当前页面,因为不记录历史,所以不能后退页面

location.reload() 重新加载页面,相当于刷新按钮或非,如果参数设置为true,强制刷新ctrl+f5

navigation

其中包含了浏览器信息,通过userAgent可以拿到浏览器信息

navigation.userAgent 可以判断是pc端还是移动端

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0KI2Kku6-1618561342894)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201217112137177.png)]

元素偏移量offset系列

可以动态获取元素的位置,大小

  • 获得元素距离, 父元素 必须带有定位 ,如果父亲没有定位,就以body为准
  • 获取元素自身大小(宽度高度)
  • 注意:返回的数据都不带单位

offsetTop 距离顶部

offsetLeft 距离左边距离

offsetParent 返回带有定位的父元素,没有则返回body

offsetWidth 返回padding,边框,内容区的宽度,返回数值不带单位

offsetHeight 返回包括padding,边框,内容区的高度,返回数值不带单位

offset与style的区别

  • offset :可以得到任意样式表中的样式值;

    ​ offset系列获得的数值不带单位

    ​ 获取的offset包含padding+border + width

    ​ 内容只能读取属性,不能赋值

    所以想要获取元素大小位置offset更合适

  • style: 只能得到行内样式表中的样式值

    ​ style.width 获得的是带有参数的字符串

    ​ style.width 获取的的不包含padding+border

    ​ 内容可读可写

    所以想要改变元素的大小的值,需要style

拖动模态框案例

     <style>.login{width: 400px;height: 210px;background-color: #00FFFF;position: fixed;left: 50%;top: 50%;transform: translate(-50%,-50%);box-shadow: 0  0 20px #ddd;display: none;z-index: 100;}#link{display: block;text-decoration: none;text-align: center;margin: 50px auto;}.login-title{text-align: center;/* margin-left: 20%; */padding-top: 10px;cursor: move;}.close-login{display: block;width: 40px;text-align: center;margin-top: -20px;color: #000000;font-size: 14px;border: 1px solid #000000;margin-right: -10px;border-radius: 50%;line-height: 35px;float: right;text-decoration: none;height: 35px;background-color: white;}.login-input{margin-top: 20px;margin-left: 30px;height: 40px;}.login-input label{float: left;height: 25px;line-height: 25px;width: 90px;text-align: right;}#username{width: 200px;height: 25px;}#password{width: 200px;height: 25px;}#loginBtn a{text-decoration: none;display: block;width: 150px;margin: 15px auto;border-radius: 5px;text-align: center;height: 30px;line-height: 30px;background-color: palegoldenrod;}#bg{width: 100%;display: none;height: 100%;position: fixed;top: 0;left: 0;background: rgba(0,0,0,0.3);}</style></head><body><div class="login-header"><a id="link" href="javascript:;">点击登录</a></div><div id="login" class="login"><div id="title" class="login-title">会员登录<span><a href="javasript:;" id="closeBtn" class="close-login">关闭 </a></span></div><div class="login-input-content"><div class="login-input"><label>用户名:</label><input type="text" placeholder="请输入用户名" name="info[username]" id="username"></div><div class="login-input"><label>登录密码:</label><input type="text" placeholder="请输入登录密码" name="info[password]" id="password"></div></div><div id="loginBtn"><a href="javascipt:;">登录</a></div></div><div id="bg" class="login-bg"></div><script type="text/javascript">var login = document.querySelector('.login')var mask = document.querySelector('.login-bg')var link = document.querySelector('#link')var closeBtn = document.querySelector('#closeBtn')var title = document.querySelector("#title")link.addEventListener('click',function(){login.style.display = 'block'mask.style.display = 'block'})closeBtn.addEventListener('click',function(){login.style.display = 'none'mask.style.display = 'none'})// 拖拽效果   鼠标按下mousedown 鼠标移动mousemove  鼠标松开mouseup// 拖拽过程 获得最新的值给模态框的left和top值,就可以移动了// 鼠标在特定区域按下title.addEventListener('mousedown',function(e){// 鼠标在盒子内的坐标var x = e.pageX - login.offsetLeftvar y = e.pageY - login.offsetTop// 移动事件 只有在特定区域 且在鼠标按下事件的前提下// 在任何地方都可以拖拽 所以是documentdocument.addEventListener('mousemove',move)function move(e){// 鼠标所在页面坐标减去在合子内的坐标,就是模态框所在页面的坐标,不停的赋值给它,就可以拖动了login.style.left = e.pageX - x + 'px'login.style.top = e.pageY -y +'px'}// 鼠标弹起 解除移动事件document.addEventListener('mouseup',function(){// 解除事件必须要有确定的函数名 ,所以把上面 的移动事件抽离成一个函数document.removeEventListener('mousemove',move)})})</script></body>

eventphase属性

如果事件流处于捕获阶段调用事件处理程序

event.eventphase = 1

如果事件处理程序处于目标对象上,则

eventphase = 2

如果在冒泡阶段调用的事件处理程序,

eventphase = 3 注意:尽管处于目标,发生在冒泡阶段eventphase仍然等于2

类与构造方法的创建

   <script type="text/javascript">// 创建类class Star {// constructor() 是类的构造函数默认方法,用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用。constructor(uname,age) {this.uname = uname;this.age = age;}sing(song){console.log(this.uname + song);}}//  利用类创建对象 newlet ldh = new Star('刘德华',18)console.log(ldh.uname);ldh.sing('冰雨')</script>

类的继承

<script type="text/javascript">// 类的继承class Father {// constructor() 是类的构造函数默认方法,用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用。// 如果在子类中有一样的方法,子类的方法会覆盖掉父类的方法,称为方法的重写constructor() {}money(){console.log('100亿');}}class Son extends Father{}let son = new Son()son.money()</script>

super关键字

super关键字用于访问和调用父类的函数。也可以掉用符父类的构造方法。

<script type="text/javascript">// super关键字class Father {// constructor() 是类的构造函数默认方法,用于传递参数,返回实例对象,通过new命令生成对象实例时,自动调用。constructor(x,y) {this.x = xthis.y = y}sum(){console.log(this.x + this.y);}}class Son extends Father{constructor(x,y) {super(x,y)   //调用符类的构造函数}}let son = new Son(22,22)son.sum()</script>

构造函数和原型

类是对象的模板,通过模板创建对各实例。

es6没有类。而用构造函数来创建对象

构造函数的方法

  • 利用new object() 创建对象

var obj = new object();

  • 利用对象字面量

    var obj = {}

  • 利用构造函数创建对象

    function Star (name,age){

    this.name = name

    this.age = age

    this.sing= function() {

    ​ }

    }

var ldh = new Star(‘ll’,12) // 构造函数创建对象 配合new使用。

在构造方法中比如name,age,sing等成员

成员又分为静态成员实例成员

实例成员: 在函数内部通过this添加成员都称为实例成员,实例成员只能通过实例化的实例去访问。如 ldh .sing() // 不可以通过构造函数来访问 如 Star.age()

静态成员: 在函数本身上添加的成员sex 是静态成员

如:Star.sex = ‘男’

静态成员只能通过构造函数来访问如Star.sex

不能通过实例对象来访问 如 ldh.sex

构造函数存在内存浪费的问题,创建一个实例,就会开辟一个空间

构造函数原型prototype

构造函数通过原型分配的函数是所有对象共享的。

JavaScript规定,每一个构造函数都有一个prototype属性,指向另一个对象。注意这个prototype就是一个对象,这个对象的所有属性和方法,都会被构造函数所拥有。

原型的作用: 共享方法(公共的方法)

在对象身上都会有一个______proto__的存在,

指向构造函数的prototype原型对象,之所以我们对象可以使用构造函数的prototype原型对象的属性和方法,都是因为有______proto__原型的存在。

自动添加______proto__

console.log(ldh.____proto === Star.prototype) // true

先判断ldh身上有没有这个方法,如果有就执行它本身的sing方法,如果没有这个方法,因为有______proto__的存在,就回去构造函数原型对象prototype身上去查找sing方法。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B44nGqzh-1618561342895)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201223141020262.png)]

原型constructor构造函数

对象原型(____proto)和构造函数(prototype)原型对象里面都有一个属性constructor属性,constructor我们称为构造函数,因为它指回构造函数本身。

主要用于记录了该对象引用了哪个构造函数,它可以让原型对象重新指向原来的构造函数。

很多情况下,需要我们手动指回原来的函数。

因为我们可以将公共的方法放到原型对象里面

如: Star.prototype.sing = function(){

​ console.log()

​ // 这里的指向是没有问题的,因为他们是单独添加的

}

Star.prototype.movie = function(){

​ console.log()

​ // 这里的指向也是没有问题的,因为他们是单独添加的

}

但是如果我们的公共方法不只有一个

Star.prototype = {

​ constructor: Star // 手动只会原来的构造函数

​ sing: function() {},

​ movie: function{},

}

这里将prototype这个对象赋值给了另一个对象{} ,所以现在它就不指回原来的构造函数了,而是指向这个对象了。后面这个对象覆盖了Star.prototype 对象、Star.prototype 就没有constructor这个属性了 ,所以现在需要手动指回原来的构造函数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQzY6j3p-1618561342896)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201224110754607.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6hxxc3Ae-1618561342896)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201224110854693.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VfmvxIlU-1618561342896)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201224111147731.png)]

原型链

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PBGUSonm-1618561342897)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201224111247867.png)]

外层Object.prototype

原型对象this指向

原型对象里的函数this。指向对象实例(调用者)。

原型用于扩展内置对象

可以通过原型对象,对原来的内置对象进行扩展自定义的方法。比如给数组添加自定义的偶数求和功能。

先看看数组的原型对象有哪些东西

console.log(Array.portotype);

在数组的原型对象里添加一个扩展方法

Array.prototype.sum = function() {

​ for(var i = 0; i< this.length; i++) {

​ sum + = this[i]

​ }

​ return sum

}

var arr = [1,2,3]

console.log(arr.sum())

//再看看Array的原型对象

console.log(Array.portotype); // 会有一个高亮的sum方法被添加进去

var arr1 = new Array() // 通过new创建的新数组,可是可以直接调用这个方法的。

//注意:数组和字符串不能通过Array.prototype = {} 的方式来追加方法,否则原来的方法会被覆盖 ,只能通过 "."的方式来追加。

call()方法的作用

  1. 可以调用函数

    fn.call(); // 呼叫某个函数

    2.改变this指向

function fn(x,y) {
name = ‘emil’;
console.log(this.name); // andy
console.log(this.x+this.y); //3
console.log(x+y); //4
}
var o = {
name: ‘andy’,
x: 1,
y: 2
}
fn.call(o,2,2) // andy

利用父构造函数继承属性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ieHAXu1C-1618561342897)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20201224134840561.png)]

缺点,修改了子原型对象,父原型对象对象也会跟着一起变化。

解决办法:

 把子类的原型指向父类

​ Son.prototype = new Father()

​ 但是此时子类的原型就指向父类了

​ // 可以利用constructor 值回原来的构造函数

​ Son.prototype.constructor = Son

类的本质

es6之前通过构造函数+原型实现面向对象编程

  • 构造函数有原型对象prototype
  • 构造函数原型对象prototype里面有constructor 指向构造函数本身
  • 构造函数可以通过原型对象添加方法
  • 构造函数创建的实例对象有______proto______ 原型指向构造函数的原型对象。

es6 通过实现面向对象编程

类就是构造函数的另外一种写法

  • 类也有原型对象,指向类的本身
  • 也可以通过原型对象添加方法
  • 通过类创建的实例也有原型对象,指向类的原型对象。
class Star {}
console.log(typeof Star) //function

数组方法

迭代方法

  • forEach,map

    array.forEach(function(currentValue,index,arr){})
    // 返回一个回调函数,有三个参数
    // current:数组当前项的值,每一个数组元素
    // index:数组当前项的索引
    // arr:数组对象本身
    // 可以利用这个方法实现对数组求和
    
  • filter

    也可以迭代数组;

    map也返回一个新数组

    但是这个方法会返回一个新数组,新数组中的元素是通过检查指定数组中符合条件的所有元素,主要用于筛选数组。

    注意:它直接返回一个新数组,所以需要一个新的数组去接收它。

    var newarr = array.filter(function(currentValue,index,arr){})
    // 返回一个回调函数,有三个参数
    // current:数组当前项的值,每一个数组元素
    // index:数组当前项的索引
    // arr:数组对象本身
    
  • some,every

    some方法用于检测数组中的元素是否满足指定查找条件,通俗点就是查找数组中是否有满足条件的元素

    **注意:**它返回的是布尔值,就返回true,如果查找不到就返回false

    如果找到第一个满足条件的元素,则终止循环,不再继续查找。

    every() 是必须每个元素满足才会返回true

    var flag = array.some(function(currentValue,index,arr){})
    // 返回一个回调函数,有三个参数
    // current:数组当前项的值,每一个数组元素
    // index:数组当前项的索引
    // arr:数组对象本身
    

字符串方法

trim() 去掉字符串两边的空格,不会去掉中间的。

str.trim()

对象方法

给对象添加或修改属性

以前的做法

var obj = {id:1,name:'xx'
}
obj.num = 100
obj.name = 'yy'

新增方法object.defineProperty() 定义新属性或修改原有属性,

没有就添加,有就修改

注意: 第三个参数以对象的形式书写

value :设置属性的值,默认为undefined

writable:值是否可以重写,true|false默认为false

enumerable:目标是否可以被枚举。(是否可以被遍历出来,可以用于设置隐私信息等)true|false 默认为false

configurable:目标属性是否可以被删除或是否可以再次修改特性true|false,默认为false

object.defineProperty(obj,'age',{value: 18
})

函数进阶

函数的定义方式

  • 自定义函数(命名函数)

    function fn() {}

  • 函数表达式(匿名函数)

    var fn = function() {}

  • 利用new Function(‘参数1’,’参数2’, ‘参数3’, ‘函数体’) // 很少使用

var f = new Function('') // 里面必须以字符串的形式书写

改变函数内部this指向

js提供了三种方法 call() ,apply(),bind().

  • call() 经常用作继承

    var o = {name: 'emil'
    }
    function fn(a,b){console.log(this)console.log(a+b)
    }
    fn.call(o,1,2)
    // call第一个作用是调用函数,第二个作用是改变函数内部this的指向
    // call 主要作用可以实现继承
    function Father(uname, age, sex) {this.uname = unamethis.age = agethis.sex = sex
    }
    function Son (uname, age, sex) {Father.call(this, uname, age, sex)
    }
    var son = new Son('llh',16,'男')
    console.log(son)
    
  • apply() 经常跟数组有关

    var o = {name:'emil'
    }
    function fn(){console.log(this)
    }
    fn.apply(o)
    //第一个作用是调用函数,第二个作用也可以改变函数内部this指向
    // 但是它的第二个参数必须是数组
    fn.apply(o,[1,2])
    // apply的主要应用,比如我们可以apply借助于数学内置对象求最大值
    var arr = [1,2,5,44,6]
    var max = Math.max.apply(Math,arr)
    
  • bind方法,不调用函数,能改变this指向

    //bind() 方法不会调用函数。但是能改变函数内部this指向
    // 返回由指定的this值和初始化参数改造的原函数拷贝
    var o = {name:'emil'
    }
    function fn(a,b){console.log(this)console.log(a+b)
    }
    fn.bind(o,1,2) // 这是不调用的,可以改变原函数内部this的指向
    // 返回的是原函数改变this之后产生的新函数
    var f = fn.bind(o,1,2) // 这样才会调用

bind() 函数的应用

​ 在实际开发中经常用到的是bind,因为有的函数我们不需要立即调用,但又想改变这个函数内部的this指向此时用bind

例如:

// 现在有一个按钮,当我们点击之后,就禁用这个按钮,3秒之后再开启这个按钮
var btn = document.querySelector('button')
btn.onclick = function() {this.disabled = true; //这里的this指向btn按钮// var that = thissetTimeout(function(){// that.disabled = false  定时器函数里面的this指向windowthis.disabled = false // 定时器函数里的this 指向window}.bind(this),3000)
}

严格模式

  • 消除了JavaScript语法的一些不合理,不严谨之处,减少了一些怪异行为
  • 消除代码运行的一些不安全之处,保证代码运行的安全
  • 提高编译效率,增加运行速度
  • 禁用了未来版本可能会定义的语法。很多未来的关键字不能做变量名

严格模式分为:为整个脚本开启严格模式 和为函数开启严格模式

在最开始写上 ‘use strict’ 为整个脚本开启严格模式

function fn(){

‘use strict’ 为fn函数开启严格模式 ,下面的代码将按着严格模式执行

}

高阶函数

函数可以作为参数传递

返回的也可以是函数

闭包

闭包(closure)指有权访问另一个函数函数作用域中变量的函数。

作用:可以延伸变量的作用范围。

例子:输出当前li的索引号。

<li></li>
<li></li>
<li></li>
<li></li>
<script>var lis = document.querySelector('li')for(var i = 0; i < lis.length; i++) {(function(i){lis[i].onclick = function() {console.log(i)}})(i)}</script>

递归

​ 如果一个函数在内部可以调用其本身,那么这个函数就是递归函数。简单来说就是函数内部自己调用自己。

​ 递归函数的作用和循环效果一样,由于递归很容易发生栈溢出错误,所以必须要加退出条件return

递归斐波那契数列

利用递归函数求斐波那契数列(兔子数列)1,1,2,3,5,8,13,21…

用户输入一个数字n就可以求出这个数字对应的兔子序列值。

我们只需要知道用户输入的n前面两项(n-1)-(n-2)就可以计算出对应的序列值

function fb(n) {if(n === 1 || n === 2) {return 1}return fb(n-1) + fb(n-2)// 如当n=4时 ,返回fb(3)+fb(2),fb(3) 又去调用fb依次循环下去,知道n=1
}

浅拷贝

1.浅拷贝只是拷贝一层,更深层次对象级别的只拷贝引用

2.深拷贝拷贝多层,每一级别的都会拷贝

  • 浅拷贝
var obj = {id = 1,name: 'emil',msg: {age: 18}
}
var o = {}for (var k in obj) {// k 是属性名 obj[k]是属性值o[k] = obj[k]}
// 这样就通过循环实现了一次浅拷贝
//浅拷贝是通过将地址赋值给了另一个对象,所以修改任何一个都会影响另一个//在es6中提供一个语法糖的浅拷贝方法、。
object.assign(o,obj)

深拷贝

深拷贝会把要被拷贝的复制一份,重新开辟一个空间进行复制

//(1)采用递归去拷贝所有层级属性
function deepClone(obj){let objClone = Array.isArray(obj)?[]:{};if(obj && typeof obj==="object"){for(key in obj){if(obj.hasOwnProperty(key)){//判断ojb子元素是否为对象,如果是,递归复制if(obj[key]&&typeof obj[key] ==="object"){objClone[key] = deepClone(obj[key]);}else{//如果不是,简单复制objClone[key] = obj[key];}}}}return objClone;
}
let a=[1,2,3,4],b=deepClone(a);
a[0]=2;
console.log(a,b);
// (2) 通过JSON对象来实现深拷贝
//缺点: 无法实现对对象中方法的深拷贝,会显示为undefined
function deepClone2(obj) {var _obj = JSON.stringify(obj),objClone = JSON.parse(_obj);return objClone;
}

正则表达式

​ 正则表达式通常用来检索,替换那些符合某个模式(规则)的文本,例如:表单验证:只允许输入英文,数字等等。此外正则表达式还通常用于过滤掉页面内容的一些敏感词(替换),或从字符串中获取我们想要的特定部分(提取)等。

特定: 灵活性,逻辑性和功能性非常强

​ 可以驯熟地用简单的方式达到字符串的复杂控制。

  • 创建正则表达式
// 利用RegExp来创建正则表达式
var regExp = new RegExp()
// 利用字面量创建正则表达式
var rg = /123/
  • 测试正则表达式 test

test() 正则表达式对象方法,用于检测字符串是否符合该规则,该对象会返回true 或false,其参数就是测试字符串。

var rg = /123/
console.log(rg.test(123)) // true
console.log(rg.test('abc')) // false
  • 正则表达式中的特殊字符

    • 边界符

      边界符 说明
      ^ 表示匹配行首的文本(以谁开始)
      $ 表示匹配行尾的文本(以谁结束)
      var rg = /123/  // 只要包含123 都返回true
      
    • 字符类: [ ] 表示有一系列字符可供选择,要匹配其中一个就可以了。

    • ”-“ 短横线表示范围

    • “!” 叹号表示去反

    • var rg = /[abc]/ // 只要包含有a 或包含有b 或包含有c 都会返回true,包含其中一个
      console.log(rg.test('baby')) // true
      console.log(rg.test('emil')) // falsevar rg2 = /^[abc]$/ // 三选一var rg3 = /^[a-z]$/  // 26选一
      var rg3 = /^[a-zA-Z0-9]$/  //不区分大小写,数字也可以,其中的一个
      
    • 量词符

      量词 说明
      * 重复0次或更多次
      + 重复一次或更对次
      重复0次或一次
      {n} 重复n次
      {n,} 重复n次或更多次
      {n,m} 重复n到m次
      // *号
      var reg = /^a*$/
      console.log(reg.test('')) // true
      console.log(reg.test('a')) // true
      console.log(reg.test('aa')) // true// +号 相当于>=1
      var reg1 = /^a+$/
      console.log(reg1.test('')) // false
      console.log(reg1.test('a')) // true
      console.log(reg1.test('aa')) // true// ?号 允许重复一次或0次
      var reg2 = /^a?$/
      console.log(reg1.test('')) // true
      console.log(reg1.test('a')) // true
      console.log(reg1.test('aa')) // false// {3},就是只允许重复三次
      var reg3 = /^a{3}$/
      console.log(reg3.test('')) // false
      console.log(reg3.test('a')) // false
      console.log(reg3.test('aa')) // false
      console.log(reg3.test('aaa')) // true
      // {3,}  >=3次
      // {3,5}  3到5次,不能超出这个范围

ES6 新特性

let

  • let 声明 变量不能重名。
  • let具有块及作用域,只在作用域内有效。
  • let不存在变量提升。
  • 不影响作用域链

const

  • 一定要初始值
  • 一般常量使用大写(潜规则)
  • 常量的值不能修改
  • 具有块级作用域
  • 对于数组和数组和对象的元素修改,不算做对常量的修改。因为他指向的地址未发生改变。

扩展运算符

​ ES6扩展运算符 ‘…’,好比rest参数的逆运算,将一个数组用逗号分隔的参数序列。

console.log(...[1, 2, 3])
// 1 2 3console.log(1, ...[2, 3, 4], 5)
// 1 2 3 4 5[...document.querySelectorAll('div')]
// [<div>, <div>, <div>]

主要用于函数调用的时候,将一个数组变为参数序列

function push(array, ...items) {array.push(...items);
}function add(x, y) {return x + y;
}const numbers = [4, 38];
add(...numbers) // 42

合并数组

const arr1 = ['a', 'b'];
const arr2 = ['c'];
const arr3 = ['d', 'e'];
[...arr1, ...arr2, ...arr3]
// [ 'a', 'b', 'c', 'd', 'e' ]

如果将扩展运算符用于数组赋值,只能放在参数的最后一位,否则会报错

const [...butLast, last] = [1, 2, 3, 4, 5];
// 报错const [first, ...middle, last] = [1, 2, 3, 4, 5];
// 报错

可以将字符串转化为数组

[...'hello']
// [ "h", "e", "l", "l", "o" ]

构造函数新增的方法

  • array.from()

    将对象类型转化为真正的数组:类似数组的对象和可遍历(iterable)对象(包括es6新增的数据结构set和map)

    let arrayLike = {'0': 'a','1': 'b','2': 'c',length: 3
    };
    let arr2 = Array.from(arrayLike); // ['a', 'b', 'c']
    

    还可以接受第二个参数,用来对每个元素进行处理,将处理后的值放入返回的数组

    Array.from([1, 2, 3], (x) => x * x)
    // [1, 4, 9]
    

array.of()

​ 用于将一组值,转换为数组

Array.of(3, 11, 8) // [3,11,8]

没有参数的时候,返回一个空数组

当参数只有一个的时候,实际上是指定数组的长度

参数个数不少于 2 个时,Array()才会返回由参数组成的新数组

Array() // []
Array(3) // [, , ,]
Array(3, 11, 8) // [3, 11, 8]

实例对象新增方法

  • copyWithin()

    将指定位置的成员复制到其他位置(会覆盖原有成员),然后返回当前数组

    参数如下:

    • target(必需):从该位置开始替换数据。如果为负值,表示倒数。

    • start(可选):从该位置开始读取数据,默认为 0。如果为负值,表示从末尾开始计算。

    • end(可选):到该位置前停止读取数据,默认等于数组长度。如果为负值,表示从末尾开始计算。

      [1, 2, 3, 4, 5].copyWithin(0, 3) // 将从 3 号位直到数组结束的成员(4 和 5),复制到从 0 号位开始的位置,结果覆盖了原来的 1 和 2
      // [4, 5, 3, 4, 5]
      
  • find()、findIndex()

    find()用于找出第一个符合条件的数组成员

    参数是一个回调函数,接收三个参数依次为当前的值,当前的位置和原数组

    [1, 5, 10, 15].find(function(value, index, arr) {return value > 9;
    }) // 10
    

    findIndex()返回第一个符合条件的数组成员的位置,如果所有成员都不符合条件,则返回-1

    [1, 5, 10, 15].findIndex(function(value, index, arr) {return value > 9;
    }) // 2
    

    这两个方法都可以接受第二个参数,用来绑定回调函数的this对象。

    function f(v){return v > this.age;
    }
    let person = {name: 'John', age: 20};
    [10, 12, 26, 15].find(f, person);    // 26
    
  • fill()

    使用给定值,填充一个数组

    ['a', 'b', 'c'].fill(7)
    // [7, 7, 7]new Array(3).fill(7)
    // [7, 7, 7]
    

    还可以接受第二个和第三个参数,用于指定填充的起始位置和结束位置

    ['a', 'b', 'c'].fill(7, 1, 2)
    // ['a', 7, 'c']
    

    注意:如果填充的类型为对象,则是浅拷贝

  • entries(),keys(),values()

    keys()是对键名的遍历、values()是对键值的遍历,entries()是对键值对的遍历

    or (let index of ['a', 'b'].keys()) {console.log(index);
    }
    // 0
    // 1for (let elem of ['a', 'b'].values()) {console.log(elem);
    }
    // 'a'
    // 'b'for (let [index, elem] of ['a', 'b'].entries()) {console.log(index, elem);
    }
    // 0 "a"
    
  • includes()

    用于判断数组是否包含给定的值

    [1, 2, 3].includes(2)     // true
    [1, 2, 3].includes(4)     // false
    [1, 2, NaN].includes(NaN) // true
    

    方法的第二个参数是表示搜索的起始位置,默认为0

    参数为负数则表示倒数的位置

    [1, 2, 3].includes(3, 3);  // false
    [1, 2, 3].includes(3, -1); // true
    
  • flat(),flatMap

    将数组扁平化处理,返回一个新数组,对原数据没有影响

    [1, 2, [3, 4]].flat()
    // [1, 2, 3, 4]
    

    flat()默认只会“拉平”一层,如果想要“拉平”多层的嵌套数组,可以将flat()方法的参数写成一个整数,表示想要拉平的层数,默认为1

    [1, 2, [3, [4, 5]]].flat()
    // [1, 2, 3, [4, 5]][1, 2, [3, [4, 5]]].flat(2)
    // [1, 2, 3, 4, 5]
    

    flatMap()方法对原数组的每个成员执行一个函数相当于执行Array.prototype.map(),然后对返回值组成的数组执行flat()方法。该方法返回一个新数组,不改变原数组

    // 相当于 [[2, 4], [3, 6], [4, 8]].flat()
    [2, 3, 4].flatMap((x) => [x, x * 2])
    // [2, 4, 3, 6, 4, 8]
    flatMap()`方法还可以有第二个参数,用来绑定遍历函数里面的`this
  • 排序稳定性

    将sort()默认设置为稳定的排序算法

    const arr = ['peach','straw','apple','spork'
    ];const stableSorting = (s1, s2) => {if (s1[0] < s2[0]) return -1;return 1;
    };arr.sort(stableSorting)
    // ["apple", "peach", "straw", "spork"]
    

    排序结果中,straw在spork的前面顺序一致

**新引入模板字符串声明方式 —> ``(反引号) **

  • 内容可以直接出现换行符
let str =`<ul><li>1</li><li>2</li></ul>`
  • 直接进行变量拼接

    let lovest = 'emil'
    let  out = `${lovest}is so smart`
    
  • es6 允许在大括号里面,写入变量和函数,作为对象的属性和方法。

    let name = 'emil'
    let change = function(){console.log('change')
    }
    const school = {name,change,study(){console.log('好好学习')}
    }
    

ES6允许使用箭头函数

  • this 是静态的,始终指向函数声明时所在作用域下的this的值

  • 不能作为构造函数实例化对象

  • 不能使用arguments变量

  • 当代码体只有一句时可以省略 ’{}‘

  • 当参数只有一个时可以省略 ’()‘

ES6允许给函数参数赋初始值,具有默认值得参数要靠后(潜规则)

ES6,引入rest参数,用来获取函数的实参,用来代替arguments

// es5 获取函数参数
function date(){console.log(arguments)  // 得到一个对象
}
// date(1,2,3)// rest 参数,  ...args参数必须放到最后
function date(...args) {console.log(args)
}
date(1,2,3)    // 得到一个数组

【…】扩展运算符能将【数组】转换为逗号分隔的【参数序列】

Symbol 新的原始数据类型

  • 表示独一无二的值,它是JavaScript语言的第七种数据类型,类似于字符串。
  • 值是唯一的,用来解决命名冲突的问题
  • 值不能与其他类型进行运算
  • 定义的属性不能使用for…in循环遍历,但是可以使用reflect.ownKeys来获取对象的所有键名
let s1 = Symbol('小明')
let s2 = Symbol('小明')
console.log(s1 === s2) //false
//虽然s1和s2 一模一样,但是通过symbol声明,他们的编号不一样// Symbol.for 创建
let s3 = Symbol.for('小红')
let s4 = Symbol.for('小红')
console.log(s3 === s4) //true  ,通过for创建的一样// 不能与其他类型运算
  • 向对象中添加方法。

    当我们不知道对象的结构是否复杂,新添加的方法是否会对象对来的结构造成影响,是否会覆盖原有的方法,就可以使用symbol添加唯一的方法

    let game = {......
    }
    // 我们现在不知道game有哪些方法,新增方法是否会对原有的方法有影响,所以通过symbol,// 声明一个新的对象let methods = {up: Symbol(),down:Symbol()
    }
    game[methods.up] = function() {console.log('up')
    }
    game[methods.down] = function() {console.log('down')
    }
    // 这样可以安全往里添加方法
    

    第二种添加symbol方法的方式

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mDw55MjK-1618561342898)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210113112503856.png)]

symbol的内置值

ES6生成器

  • 生成器就是一个特殊的函数

  • 异步编程, 纯回调函数

// yield 分隔生成器,分成三个模块
function * fun(){//console.log('1')yield '第一部分'// console.log('2')yield '第二部分'// console.log('3')yield '第三部分'// console.log('4')
}
let ter = fun()   //函数执行需要ter.next()才会执行
ter.next() //1    一步步执行 ,第一次调用返回第一个yield的后面的值,依次执行
ter.next()  //2
ter.next()  //3
ter.next()  //4
for(let v of fun(){console.log(v)  //
})function * gen(){yield 111yield 222yield 333
}
// 执行获取迭代器对象
let tor = gen()
console.log(tor.next()) //执行第一个yield并将第一个yield后的值,返回。// next() 调用可以传递实参,作为yield的返回值

生成器解决异步回调

例如,一秒后执行打印11,2秒后打印22,3秒后打印33

  • 定时器做法
// 必须要上一个定时器执行成功过后才能执行下一个,后面还要加新的时容易造成回调地狱
setTimeout(()=>{console.log(11)setTimeout(()=>{console.log(22)serTimeout(()=>{console.log(33)},3000)},2000)
},1000)
  • 函数生成器做法
function one() {setTimeout(()={console.log(11)iter.next()},1000)
}
function two() {setTimeout(()={console.log(22)iter.next()},2000)
}
function three() {setTimeout(()={console.log(33)iter.next()},3000)
}
function * gen(){yield one()yield two()yield three()
}
let iter = gen()

proxy

关于Proxy

Proxy在计算机领域是一个很普遍的概念,中文通常翻译为代理,“代理”一般用于描述某人或某事代表他人行事。常见的概念有Proxy Server(代理服务器)、Reverse Proxy(反向代理)、Proxy Pattern(代理模式)等。

  1. 什么是Proxy?

    代理就是某人或某事代表他人行事。

  2. 为什么需要Proxy?

    有几种可能,1. 被代理对象不想直接被访问,就像找明星拍戏需要先联系他的经纪人;2. 被代理对象某些能力不足需要找个人帮他做,比如打官司需要找律师。所以Proxy至少可以起到两方面的作用:进行访问控制和增加功能。

ES6的Proxy

  1. Proxy代理什么?

    代理Object(javascript里面所有的东西都是Object)

  2. Proxy代理Object做什么?
    控制和修改Object的基本行为

  3. 哪些是Object的基本行为?
    比如属性调用、属性赋值、删除属性、方法调用等

  4. 为什么要控制和修改Object的基本行为?
    进行访问控制和增加功能

proxy 基础语法

​ 创建一个proxy

const p = new Proxy(target, handler);

target:是被代理的对象,可以是对象、数组、方法、构造函数class甚至是另外一个proxy,总之可以是任何JavaScript对象;

handler:一个对象,属性是各种控制或修改target基本行为的方法;

let obj = {name: '最光阴',age: 3000}let objProxy = new Proxy(obj,{get:function(target,key) {// 判断被代理的对象有没有该属性if(key in obj){console.log(target);//taeget被proxy使用的目标console.log(key); //key是属性名return obj[key]  //必须要有返回值,不然外界访问不到,//这里返回代理对象的各个属性,外界就可以通过属性名,去访问值} else {throw new ReferenceError('该对象没有您想访问的属性')}},set:function(target,key,value) {console.log(key,value);// 可以做一些拦截// 比如去除字符串的空格if(typeof value === "string") {value = value.trim()}target[key] = value}})console.log(objProxy.age); //30objProxy.name = '   藻雪   'console.log(objProxy);console.log(obj.name);   //藻雪

示例:

比如用户未设置头像则返回默认头像可以这么写:

const user = { name: 'bruce' };
const userProxy = new Proxy(user, {get: (obj, prop) => {if (prop === 'avatar') {if (!obj.avatar) {return 'https://avatar-static.segmentfault.com/100/035/1000355095-5b3c339ebdbe1_big64';}}return obj[prop];}
});console.log(userProxy.avatar); // https://avatar-static···

或者我们可以实现alert换行显示多条信息:

const myAlert = new Proxy(alert, {apply: (target, thisArg, argumentsList) => {const msg = argumentsList.join('\n');target(msg);}
});myAlert('haha', 'lala');

promise基本使用

语法上promise是一个构造函数

// 实例化promise 对象
const p = new Promise(function (resolve,reject){setTimeout(function(){if(err) {// rejectreject(err)} else{let data = '数据库中的用户数据'//resolveresolve(data)}},1000)
})
// 调用promise对象的then方法
p
.then(function(data){console.log(data)
},function(err){console.log('出错了')
})

Promise,译为承诺,是异步编程的一种解决方案,比传统的解决方案(回调函数)更加合理和更加强大

在以往我们如果处理多层异步操作,我们往往会像下面那样编写我们的代码

doSomething(function(result) {doSomethingElse(result, function(newResult) {doThirdThing(newResult, function(finalResult) {console.log('得到最终结果: ' + finalResult);}, failureCallback);}, failureCallback);
}, failureCallback);

现在通过Promise的改写上面的代码

doSomething().then(function(result) {return doSomethingElse(result);
})
.then(function(newResult) {return doThirdThing(newResult);
})
.then(function(finalResult) {console.log('得到最终结果: ' + finalResult);
})
.catch(failureCallback);

瞬间感受到promise解决异步操作的优点:

  • 链式操作减低了编码难度
  • 代码可读性明显增强

下面我们正式来认识promise

promise对象仅有三种状态

  • pending(进行中)

  • fulfilled(已成功)

  • rejected(已失败)

    特点

    • 对象的状态不受外界影响,只有异步操作的结果,可以决定当前是哪一种状态
    • 一旦状态改变(从pending变为fulfilled和从pending变为rejected),就不会再变,任何时候都可以得到这个结果

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-E5T1YRwU-1618561342899)(C:\Users\baiji_pc\AppData\Roaming\Typora\typora-user-images\image-20210310101828454.png)]

用法

promise对象是一个构造函数,用来生成Promise实例

const promise = new Promise(function(resolve, reject) {});
Promise`构造函数接受一个函数作为参数,该函数的两个参数分别是`resolve`和`reject
  • resolve函数的作用是,将Promise对象的状态从“未完成”变为“成功”
  • reject函数的作用是,将Promise对象的状态从“未完成”变为“失败”

实例方法

Promise构建出来的实例存在以下方法:

  • then()
  • then()
  • catch()
  • finally()

then()

then是实例状态发生改变时的回调函数,第一个参数是resolved状态的回调函数,第二个参数是rejected状态的回调函数

then方法返回的是一个新的Promise实例,也就是promise能链式书写的原因

getJSON("/posts.json").then(function(json) {return json.post;
}).then(function(post) {// ...
});

catch

catch()方法是.then(null, rejection).then(undefined, rejection)的别名,用于指定发生错误时的回调函数

getJSON('/posts.json').then(function(posts) {// ...
}).catch(function(error) {// 处理 getJSON 和 前一个回调函数运行时发生的错误console.log('发生错误!', error);
});

Promise对象的错误具有“冒泡”性质,会一直向后传递,直到被捕获为止

getJSON('/post/1.json').then(function(post) {return getJSON(post.commentURL);
}).then(function(comments) {// some code
}).catch(function(error) {// 处理前面三个Promise产生的错误
});

一般来说,使用catch方法代替then()第二个参数

Promise对象抛出的错误不会传递到外层代码,即不会有任何反应

const someAsyncThing = function() {return new Promise(function(resolve, reject) {// 下面一行会报错,因为x没有声明resolve(x + 2);});
};

浏览器运行到这一行,会打印出错误提示ReferenceError: x is not defined,但是不会退出进程

catch()方法之中,还能再抛出错误,通过后面catch方法捕获到

finally()

finally()方法用于指定不管 Promise 对象最后状态如何,都会执行的操作

promise
.then(result => {···})
.catch(error => {···})
.finally(() => {···});

构造函数方法

Promise构造函数存在以下方法:

  • all()
  • race()
  • allSettled()
  • resolve()
  • reject()
  • try()

all()

Promise.all()方法用于将多个 Promise实例,包装成一个新的 Promise实例

const p = Promise.all([p1, p2, p3]);

接受一个数组(迭代对象)作为参数,数组成员都应为Promise实例

实例p的状态由p1p2p3决定,分为两种:

  • 只有p1p2p3的状态都变成fulfilledp的状态才会变成fulfilled,此时p1p2p3的返回值组成一个数组,传递给p的回调函数
  • 只要p1p2p3之中有一个被rejectedp的状态就变成rejected,此时第一个被reject的实例的返回值,会传递给p的回调函数

注意,如果作为参数的 Promise 实例,自己定义了catch方法,那么它一旦被rejected,并不会触发Promise.all()catch方法

const p1 = new Promise((resolve, reject) => {resolve('hello');
})
.then(result => result)
.catch(e => e);const p2 = new Promise((resolve, reject) => {throw new Error('报错了');
})
.then(result => result)
.catch(e => e);Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// ["hello", Error: 报错了]

如果p2没有自己的catch方法,就会调用Promise.all()catch方法

const p1 = new Promise((resolve, reject) => {resolve('hello');
})
.then(result => result);const p2 = new Promise((resolve, reject) => {throw new Error('报错了');
})
.then(result => result);Promise.all([p1, p2])
.then(result => console.log(result))
.catch(e => console.log(e));
// Error: 报错了

race()

race竞赛的意思,谁先成功,就执行成功的回调。

Promise.race()方法同样是将多个 Promise 实例,包装成一个新的 Promise 实例

const p = Promise.race([p1, p2, p3]);

只要p1p2p3之中有一个实例率先改变状态,p的状态就跟着改变

率先改变的 Promise 实例的返回值则传递给p的回调函数

const p = Promise.race([fetch('/resource-that-may-take-a-while'),new Promise(function (resolve, reject) {setTimeout(() => reject(new Error('request timeout')), 5000)})
]);p
.then(console.log)
.catch(console.error);

allSettled()

Promise.allSettled()方法接受一组 Promise 实例作为参数,包装成一个新的 Promise 实例

只有等到所有这些参数实例都返回结果,不管是fulfilled还是rejected,包装实例才会结束

const promises = [fetch('/api-1'),fetch('/api-2'),fetch('/api-3'),
];await Promise.allSettled(promises);
removeLoadingIndicator();

resolve()

将现有对象转为 Promise对象

Promise.resolve('foo')
// 等价于
new Promise(resolve => resolve('foo'))

参数可以分成四种情况,分别如下:

  • 参数是一个 Promise 实例,promise.resolve将不做任何修改、原封不动地返回这个实例

  • 参数是一个thenable对象,promise.resolve会将这个对象转为 Promise对象,然后就立即执行thenable对象的then()方法。

    thenable是一个对象或者函数,thenable对象指的是具有then方法的对象。

  • 参数不是具有then()方法的对象,或根本就不是对象,Promise.resolve()会返回一个新的 Promise 对象,状态为resolved

  • 没有参数时,直接返回一个resolved状态的 Promise 对象

reject()

Promise.reject(reason)`方法也会返回一个新的 Promise 实例,该实例的状态为`rejected
const p = Promise.reject('出错了');
// 等同于
const p = new Promise((resolve, reject) => reject('出错了'))p.then(null, function (s) {console.log(s)
});
// 出错了

Promise.reject()方法的参数,会原封不动地变成后续方法的参数

Promise.reject('出错了')
.catch(e => {console.log(e === '出错了')
})
// true

await与async

async 是 ES7 才有的与异步操作有关的关键字,和 Promise , Generator 有很大关联的。

语法

async function name([param[, param[, ... param]]]) { statements }

​ 返回值:async 函数返回一个 Promise 对象,可以使用 then 方法添加回调函数。

async function helloAsync(){return "helloAsync";}console.log(helloAsync())  // Promise {<resolved>: "helloAsync"}helloAsync().then(v=>{console.log(v);         // helloAsync
})

​ async 函数中可能会有 await 表达式,async 函数执行时,如果遇到 await 就会先暂停执行 ,等到触发的异步操作完成后,恢复 async 函数的执行并返回解析值。

​ await 关键字仅在 async function 中有效。如果在 async function 函数体外使用 await ,你只会得到一个语法错误。

function testAwait(){return new Promise((resolve) => {setTimeout(function(){console.log("testAwait");resolve();}, 1000);});
}async function helloAsync(){await testAwait();console.log("helloAsync");}
helloAsync();
// testAwait
// helloAsync

await

​ await 操作符用于等待一个 Promise 对象, 它只能在异步函数 async function 内部使用。

​ await 只能得到成功后的结果,想要的到失败的结果需要使用try…catch

async function f(){try {var res = await asyncFn()console.log(res)} catch(err) {console.log(err)}}f()

返回值

​ 返回 Promise 对象的处理结果。如果等待的不是 Promise 对象,则返回该值本身。

​ 如果一个 Promise 被传递给一个 await 操作符,await 将等待 Promise 正常处理完成并返回其处理结果。

function testAwait (x) {return new Promise(resolve => {setTimeout(() => {resolve(x);}, 2000);});
}async function helloAsync() {var x = await testAwait ("hello world");console.log(x);
}
helloAsync ();
// hello world

​ 正常情况下,await 命令后面是一个 Promise 对象,它也可以跟其他值,如字符串,布尔值,数值以及普通函数。

function testAwait(){console.log("testAwait");
}
async function helloAsync(){await testAwait();console.log("helloAsync");
}
helloAsync();
// testAwait
// helloAsync

await针对所跟不同表达式的处理方式:

  • Promise 对象:await 会暂停执行,等待 Promise 对象 resolve,然后恢复 async 函数的执行并返回解析值。
  • 非 Promise 对象:直接返回对应的值。

使用Map实现promise队列

function queue(num){let promise = promise.resolve()num.map(v =>{promise = promise.then(_=>{return new promise(resolve =>{setTimeout(()=>{console.log(v)resolve()},1000)})})})
}
queue([1,2,3,4,5])

reduce封装promise队列

function queue(num){num.reduce((promise,n)=>{return promise.then(_=>{return new promise(resolve=>{setTimeout(()=>{console.log(n)resolve()},1000)})})})
}

微队列与宏队列

​ 微队列与宏队列函数,微队列的回调函数优先级比较高,微队列的函数会优先执行。js是单线程的,同一时间只能干一件事情。先执行完所有的同步代码,在执行队列里的代码。相同队列的按放入的时间最早的开始执行。

​ promise回调和mutation属于微队列mutation,其他的异步函数属于宏队列。

setTimeout(()=>{console.log('宏队列1')
},0)
setTimeout(()=>{console.log('宏队列2')
},0)
promise.resolve(1).then(value => {console.log('微队列1', value)}
)
promise.resolve(2).then(value => {console.log('微队列2', value)}
)
// 会先打印   微队列1 1 然后打印  微队列2  2 最后打印定时器中的宏队列1 宏队列2

​ 假如在宏任务中加入一个微任务

setTimeout(()=>{console.log('宏队列1') promise.resolve(3).then(value => {console.log('微队列3', value)}
)
},0)
setTimeout(()=>{console.log('宏队列2')
},0)
promise.resolve(1).then(value => {console.log('微队列1', value)}
)
promise.resolve(2).then(value => {console.log('微队列2', value)}
)
// 执行顺序// 会先打印   微队列1 1 然后打印  微队列2  2 再打印定时器中的宏队列1 微队列3 宏队列2

​ 因为每当开始执行一个宏任务,都会去微队列把所有的微任务拿出来执行,我们在执行第一个定时器时,往微队列中添加了一个微任务。在开始第二个定时器时,就是开启一个新的宏任务,所以会先去微队列中拿出微任务先去执行。

使用场景

​ 将图片的加载写成一个Promise,一旦加载完成,Promise的状态就发生变化

const preloadImage = function (path) {return new Promise(function (resolve, reject) {const image = new Image();image.onload  = resolve;image.onerror = reject;image.src = path;});
};

​ 通过链式操作,将多个渲染数据分别给个then,让其各司其职。或当下个异步请求依赖上个请求结果的时候,我们也能够通过链式操作友好解决问题。

// 各司其职
getInfo().then(res=>{let { bannerList } = res//渲染轮播图console.log(bannerList)return res
}).then(res=>{let { storeList } = res//渲染店铺列表console.log(storeList)return res
}).then(res=>{let { categoryList } = resconsole.log(categoryList)//渲染分类列表return res
})

​ 通过all() 方法实现多个请求合并在一起,汇总所有结果,只需设置一个loading即可。

function initLoad(){// loading.show() //加载loadingPromise.all([getBannerList(),getStoreList(),getCategoryList()]).then(res=>{console.log(res)loading.hide() //关闭loading}).catch(err=>{console.log(err)loading.hide()//关闭loading})
}
//数据初始化
initLoad()

​ 通过race()可以设置图片请求超时

//请求某个图片资源
function requestImg(){var p = new Promise(function(resolve, reject){var img = new Image();img.onload = function(){resolve(img);}//img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg"; 正确的img.src = "https://b-gold-cdn.xitu.io/v3/static/img/logo.a7995ad.svg1";});return p;
}//延时函数,用于给请求计时
function timeout(){var p = new Promise(function(resolve, reject){setTimeout(function(){reject('图片请求超时');}, 5000);});return p;
}Promise
.race([requestImg(), timeout()])
.then(function(results){console.log(results);
})
.catch(function(reason){console.log(reason);
});

set

​ 新的数据结构set(集合)。它类似于数组,但成员的值都是唯一的。集合实现了iterator(迭代器)接口,所以可以使用扩展运算符和【for-of】进行遍历。

​ 集合的属性和方法:

  • size 返回集合的元素个数
  • add 增加一个新的元素,返回当前集合
  • delete 删除元素,返回Boolean值
  • has 检测集合中是否包含某个元素,返回boolean
let s = new Set()
console.log(s,typeof s)
// 会自动去掉重复的
let s2 = new Set([1,2,3,4,5,1,2])
console.log(s2) // 1,2,3,4,5

集合实践

let arr = [1,2,3,4,3,2,1]
// 数组去重
let result = [...new Set(arr)]  // set 是一个集合,不是一个数组,解构,让它变成一个数组
console.log(result)// 交集  交集就是这个数组,是否有相同的部分
let arr2 = [3,4,3,5]
let result2 = [...new Set(arr)].filter(item =>{let s2 = new Set(arr2) //3  4  5if(s2.has(item)){return true } else {return false}
})
//可以简化
let result3 = [...new Set(arr)].filter(item =>new Set(arr2).has(item)
)
//并集 两个相加
let arr3 = new Set([...arr,...arr2])// 差集

map

​ ES6提供了map数据结构,它类似于对象,也是键值对的集合。但是“键”的范围不局限与字符串,各种类型的值(包括对象)都可以当做键。map也实现iterator 接口,所以也可以用扩展运算符【for -of】进行遍历。

​ map的属性和方法:

  • size 返回map的元素个数
  • set 增加一个新元素,返回当前map
  • get 返回键名对象的键值
  • has 检测map中是否包含某个元素,返回bolean值
  • clear 清空集合,返回undefined
// 创建一个空map
let m = new Map()
m.set('name','emil')  // 接收两个值,第一个键名,第二个键值m.set('change', function(){console.log('test')
})
let key = {name: 'emil'
}
// 对象作为键
m.set(key,[e,m,i,l])

class 类

​ ES6提供了传统语言(Java,c)的写法,引入了calss(类)这个概念,作为对象的模板。通过class关键字,可以定义类。

  • class 声明类
  • constructor 定义构造函数初始化
  • extends 继承父类
  • super 调用父级构造方法
  • static 定义静态方法和属性
  • 父类方法可以重写
calss Phone{// 静态成员static name = '手机'static change() {console.log('emil')}
}
let redmi = new Phone()
console.log(redmi.name) // undefind
console.log(Phone.name) //手机
//static 标注的方法属于类,而不属于实例对象

ES6数值扩展

  • Number.EPSILON 是JavaScript表示的最小精度

EPSILON 属性的值接近于2.2204460492503130808472633361816E-16

可以用于解决 console.log(0.1+0.2 === 0.3) //false 精度问题

function equal(a,b) {if(Math.abs(a-b) < Number.EPSILON) {return true} else {return false}
}
console.log(equal(0.1+ 0.2,0.3)) // true
  • 二进制和八进制
let b = 0b 1010;  // 二进制表示方式
let o = 0o 777  // 八进制
let d = 100  // 十进制
let x = 0xff  //十六进制
  • Number.isFinite 检测一个数值是否为一个有限数还是无限数

  • Number.isNaN 检测一个数组是否为NaN

  • Number.parseInt Number.parseFloat 字符串转换为数字

  • Number.isInteger 判断一个数是否是整数

  • Math.trunc 将数字小数部分抹掉

  • Math.sign 判断一个数到底是正数,负数,还是0,是正数返回1,负数返回-1 ,0返回0

静态成员

function Phone() {}
//下面两个方法和属性是属于函数对象的,它并不属于实例对象,这样的属性就是静态成员
Phone.name = '手机'
Phone.change = function () {console.log('emil Phone')
}
let emil = new Phone()
console.log(emil.name) // undefined 实例对象是没有构造函数的方法和对象的。class P {// static 声明的属性和方法也是静态成员static name = 'emil'static change () {console.log('my name is emil')}
}

ES5继承方法

// 父类方法
function Phone(brand,price) {this.brand = brandthis.price = price
}
Phone.prototype.call = function() {console.log('I can call')
}
// 子类方法
function SmartPhone(brand,price, color size) {//使用call 调用父类Phone方法,把this指向自己Phone.call(this, brand,prcie)// 添加子类自己的属性this.color = colorthis.size = size
}//设置子级构造函数的原型,继承父类
SmartPhone = new Phone
// 把原型指回原对象
SmartPhone.prototype.constructor = SmartPhone//声明子类自己的方法
SmartPhone.prototype.photo = function() {console.log('I can take pictures')
}
const nowa = new SmartPhone('诺娃',1222,'白色','4.7inch')
console.log(nowa)

ES6继承方法

class Phone {constructor(brand, price) {this.brand = brandthis.price = price}call(){console.log('can call')}
}
calss SmartPhone extends Phone {constructor(brand,price,color,size) {super(brand,prcie)this.color = colorthis.size = size},photo() {console.log('I can take pictures')}
}
const nowa = new SmartPhone('诺娃',1222,'白色','4.7inch')
console.log(nowa)

子类方法对父类方法的重写

class Phone {constructor(brand, price) {this.brand = brandthis.price = price}call(){console.log('can call')}
}
calss SmartPhone extends Phone {constructor(brand,price,color,size) {super(brand,prcie)this.color = colorthis.size = size},photo() {console.log('I can take pictures')}  // 普通成员里不同出现super去调用父类的同名方法,只能重写call(){console.log('I can call by weChat')}
}
const nowa = new SmartPhone('诺娃',1222,'白色','4.7inch')

ES6 中的get和set

class Phone {// get  读取price这个属性,返回值就是属性的值get price(){console.log('The price has been read')}// set 修改price 属性时,调用这个方法set price() {console.log('The price has been revised')}
}
const redmi = new Phone()
console.log(redmi.price)  //The price has been readredmi.price = 'free' //The price has been revised

ES6对象方法的扩展

  • Object.is 判断两个值是否完全相等
console.log(Object.is(121,122)) // false
cOnsole.log(Object.is(121,121)) // true
//跟全等号有点类似//不同的地方
cnsole.log(Object.is(NaN,NaN)) // true
cnsole.log(NaN === NaN) //false
  • object.assign 对象的合并

    bject.assign()`方法用于对象的合并,将源对象`source`的所有可枚举属性,复制到目标对象`target
    

    Object.assign()方法的第一个参数是目标对象,后面的参数都是源对象

    const target = { a: 1, b: 1 };const source1 = { b: 2, c: 2 };
    const source2 = { c: 3 };Object.assign(target, source1, source2);
    target // {a:1, b:2, c:3}
    

    注意:Object.assign()方法是浅拷贝,遇到同名属性会进行替换

const p1 = {host: 'localhost',port: 3306,name: 'root',paw:  'root'phone: 110
}
const p2 = {host: 'http://www.baidu.com',port: 3307,name: 'emil',paw:  'emil'
}
Object.assign(p1,p2) // 接收两个参数,第一个参数是要被覆盖的,第二参数是要覆盖前面的,没有的值会被保存下来。
  • Object.setPrototypeOf 设置原型对象
const school = {name = '北大'
}
const city = {address: ['北京','上海']
}
Object.setPrototypeOf(school,city)
//school, city 会被添加到原型链
  • Object.getPrototypeOf 获取原型
 Object.getPrototypeOf(shool)
  • Object.setPrototypeOf()

Object.setPrototypeOf方法用来设置一个对象的原型对象

Object.setPrototypeOf(object, prototype)// 用法
const o = Object.setPrototypeOf({}, null);
  • Object.getPrototypeOf()

用于读取一个对象的原型对象

Object.getPrototypeOf(obj);
  • Object.keys()

返回自身的(不含继承的)所有可遍历(enumerable)属性的键名的数组

var obj = { foo: 'bar', baz: 42 };
Object.keys(obj)
// ["foo", "baz"]
  • Object.values()

返回自身的(不含继承的)所有可遍历(enumerable)属性的键对应值的数组

const obj = { foo: 'bar', baz: 42 };
Object.values(obj)
// ["bar", 42]
  • Object.entries()

返回一个对象自身的(不含继承的)所有可遍历(enumerable)属性的键值对的数组

const obj = { foo: 'bar', baz: 42 };
Object.entries(obj)
// [ ["foo", "bar"], ["baz", 42] ]
  • Object.fromEntries()

用于将一个键值对数组转为对象

Object.fromEntries([['foo', 'bar'],['baz', 42]
])
// { foo: "bar", baz: 42 }

ES6函数变化

  • 一、参数

ES6允许为函数的参数设置默认值

function log(x, y = 'World') {console.log(x, y);
}console.log('Hello') // Hello World
console.log('Hello', 'China') // Hello China
console.log('Hello', '') // Hello

函数的形参是默认声明的,不能使用letconst再次声明

function foo(x = 5) {let x = 1; // errorconst x = 2; // error
}

参数默认值可以与解构赋值的默认值结合起来使用

function foo({x, y = 5}) {console.log(x, y);
}foo({}) // undefined 5
foo({x: 1}) // 1 5
foo({x: 1, y: 2}) // 1 2
foo() // TypeError: Cannot read property 'x' of undefined

上面的foo函数,当参数为对象的时候才能进行解构,如果没有提供参数的时候,变量xy就不会生成,从而报错,这里设置默认值避免

function foo({x, y = 5} = {}) {console.log(x, y);
}foo() // undefined 5

参数默认值应该是函数的尾参数,如果不是非尾部的参数设置默认值,实际上这个参数是没发省略的

function f(x = 1, y) {return [x, y];
}f() // [1, undefined]
f(2) // [2, undefined]
f(, 1) // 报错
f(undefined, 1) // [1, 1]
  • 二、属性

函数的length属性

length将返回没有指定默认值的参数个数

(function (a) {}).length // 1
(function (a = 5) {}).length // 0
(function (a, b, c = 5) {}).length // 2

rest 参数也不会计入length属性

(function(...args) {}).length // 0

name属性

返回该函数的函数名

var f = function () {};// ES5
f.name // ""// ES6
f.name // "f"

如果将一个具名函数赋值给一个变量,则 name属性都返回这个具名函数原本的名字

const bar = function baz() {};
bar.name // "baz"
Function`构造函数返回的函数实例,`name`属性的值为`anonymous
(new Function).name // "anonymous"
  • 三、作用域

一旦设置了参数的默认值,函数进行声明初始化时,参数会形成一个单独的作用域

等到初始化结束,这个作用域就会消失。这种语法行为,在不设置参数默认值时,是不会出现的

下面例子中,y=x会形成一个单独作用域,x没有被定义,所以指向全局变量x

let x = 1;function f(y = x) { // 等同于 let y = xlet x = 2; console.log(y);
}f() // 1
  • 四、严格模式

只要函数参数使用了默认值、解构赋值、或者扩展运算符,那么函数内部就不能显式设定为严格模式,否则会报错

// 报错
function doSomething(a, b = a) {'use strict';// code
}// 报错
const doSomething = function ({a, b}) {'use strict';// code
};// 报错
const doSomething = (...a) => {'use strict';// code
};const obj = {// 报错doSomething({a, b}) {'use strict';// code}
};
  • 五、箭头函数

使用“箭头”(=>)定义函数

var f = v => v;// 等同于
var f = function (v) {return v;
};

如果箭头函数不需要参数或需要多个参数,就使用一个圆括号代表参数部分

var f = () => 5;
// 等同于
var f = function () { return 5 };var sum = (num1, num2) => num1 + num2;
// 等同于
var sum = function(num1, num2) {return num1 + num2;
};

如果箭头函数的代码块部分多于一条语句,就要使用大括号将它们括起来,并且使用return语句返回

var sum = (num1, num2) => { return num1 + num2; }

如果返回对象,需要加括号将对象包裹

let getTempItem = id => ({ id: id, name: "Temp" });

set、map两种数据结构怎么理解

如果要用一句来描述,我们可以说

Set是一种叫做集合的数据结构,Map是一种叫做字典的数据结构

什么是集合?什么又是字典?

  • 集合
    是由一堆无序的、相关联的,且不重复的内存结构【数学中称为元素】组成的组合
  • 字典
    是一些元素的集合。每个元素有一个称作key 的域,不同元素的key 各不相同

区别?

  • 共同点:集合、字典都可以存储不重复的值

  • 不同点:集合是以[值,值]的形式存储元素,字典是以[键,值]的形式存储

一、Set

Setes6新增的数据结构,类似于数组,但是成员的值都是唯一的,没有重复的值,我们一般称为集合

Set本身是一个构造函数,用来生成 Set 数据结构

const s = new Set();

增删查改

set的实例关于增删查改的方法

  • add()

    添加某个值,返回set结构本身,当添加实例中已经存在的元素,set不会进行处理添加

    s.add(1).add(2).add(2); // 2只被添加了一次
    
  • delete()

    删除某个值,返回一个布尔值,表示时拿出是否成功

    s.delete(1) //true
    
  • has()

    返回一个布尔值,表示判断该值是否为set的成员

    s.has(2) //true
    
  • clear()

    清除所有成员,没有返回值

    s.clear()
    

遍历

​ set实例的遍历方法

​ set的遍历顺序就是插入顺序

keys方法,values方法,entries方法返回的都是遍历器对象

let set = new Set(['red', 'green', 'blue']);for (let item of set.keys()) {console.log(item);
}
// red
// green
// bluefor (let item of set.values()) {console.log(item);
}
// red
// green
// bluefor (let item of set.entries()) {console.log(item);
}
// ["red", "red"]
// ["green", "green"]
// ["blue", "blue"]

​ **forEach()**用于对每个成员执行某种操作,没有返回值,键值和键名都相等,同样的forEach方法有第二个参数,用于处理绑定函数的this

let set = new Set([1, 4, 9]);
set.forEach((value, key) => console.log(key + ' : ' + value))
// 1 : 1
// 4 : 4
// 9 : 9

扩展运算符和set 结构相结合实现数组或字符串去重

// 数组
let arr = [3, 5, 2, 2, 5, 5];
let unique = [...new Set(arr)]; // [3, 5, 2]// 字符串
let str = "352255";
let unique = [...new Set(str)].join(""); // ""

实现并集,交集,和差集

let a = new Set([1, 2, 3]);
let b = new Set([4, 3, 2]);// 并集
let union = new Set([...a, ...b]);
// Set {1, 2, 3, 4}// 交集
//filter() 方法创建一个新的数组,新数组中的元素是通过检查指定数组中符合条件的所有元素。
let intersect = new Set([...a].filter(x => b.has(x)));
// set {2, 3}// (a 相对于 b 的)差集
let difference = new Set([...a].filter(x => !b.has(x)));
// Set {1}

二、Map

​ Map 类型是对键值对的有序列表,而键和值都是任意类型

​ Map 本身是一个构造函数,用来生成Map数据结构

const m = new Map()

增删查改

  • size 属性返回map结构的成员总数

    const map = new Map();
    map.set('foo', true);
    map.set('bar', false);map.size // 2
    
  • set ()

    设置键名key对应的键值为value,然后返回整个Map结构

    如果key值已经有值,则键值会被更新,否则生成新的键值

    同时返回的是当前的Map对象,可采用链式写法

    const m = new Map();m.set('edition', 6)        // 键是字符串
    m.set(262, 'standard')     // 键是数值
    m.set(undefined, 'nah')    // 键是 undefined
    m.set(1, 'a').set(2, 'b').set(3, 'c') // 链式操作
    
  • get()

    get方法读取key对应的键值,如果找不到key,则返回undefined

    const m = new Map();const hello = function() {console.log('hello');};
    m.set(hello, 'Hello ES6!') // 键是函数m.get(hello)  // Hello ES6!
    
  • has()

    has方法返回一个布尔值,表示某个键值是否在当前map对象之中

    const m = new Map();m.set('edition', 6);
    m.set(262, 'standard');
    m.set(undefined, 'nah');m.has('edition')     // true
    m.has('years')       // false
    m.has(262)           // true
    m.has(undefined)     // true
    
  • delete()

    delete方法删除某个键,返回true。如果删除失败,返回false

    const m = new Map();
    m.set(undefined, 'nah');
    m.has(undefined)     // truem.delete(undefined)
    m.has(undefined)       // false
    
  • clear()

    clear方法清除所有成员,没有返回值

    let map = new Map();
    map.set('foo', true);
    map.set('bar', false);map.size // 2
    map.clear()
    map.size // 0
    

    遍历

    Map结构原生提供三个遍历器生成函数和一个遍历方法:

    • keys():返回键名的遍历器
    • values():返回键值的遍历器
    • entries():返回所有成员的遍历器
    • forEach():遍历 Map 的所有成员

    遍历顺序就是插入顺序

    const map = new Map([['F', 'no'],['T',  'yes'],
    ]);for (let key of map.keys()) {console.log(key);
    }
    // "F"
    // "T"for (let value of map.values()) {console.log(value);
    }
    // "no"
    // "yes"for (let item of map.entries()) {console.log(item[0], item[1]);
    }
    // "F" "no"
    // "T" "yes"// 或者
    for (let [key, value] of map.entries()) {console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"// 等同于使用map.entries()
    for (let [key, value] of map) {console.log(key, value);
    }
    // "F" "no"
    // "T" "yes"map.forEach(function(value, key, map) {console.log("Key: %s, Value: %s", key, value);
    });
    

WeakSet和WeakMap

​ 创建 WeakSet 实例

const ws = new WeakSet();

WeakSet可以接受一个具有 Iterable接口的对象作为参数

const a = [[1, 2], [3, 4]];
const ws = new WeakSet(a);
// WeakSet {[1, 2], [3, 4]}

APIWeakSetSet有两个区别:

  • 没有遍历操作的API
  • 没有size属性

WeackSet只能成员只能是引用类型,而不能是其他类型的值

let ws=new WeakSet();// 成员不是引用类型
let weakSet=new WeakSet([2,3]);
console.log(weakSet) // 报错// 成员为引用类型
let obj1={name:1}
let obj2={name:1}
let ws=new WeakSet([obj1,obj2]);
console.log(ws) //WeakSet {{…}, {…}}

WeakMap只接受对象作为键名(null除外),不接受其他类型的值作为键名

const map = new WeakMap();
map.set(1, 2)
// TypeError: 1 is not an object!
map.set(Symbol(), 2)
// TypeError: Invalid value used as weak map key
map.set(null, 2)
// TypeError: Invalid value used as weak map key

WeakMap的键名所指向的对象,一旦不再需要,里面的键名对象和所对应的键值对会自动消失,不用手动删除引用

举个场景例子:

在网页的 DOM 元素上添加数据,就可以使用WeakMap结构,当该 DOM 元素被清除,其所对应的WeakMap记录就会自动被移除

const wm = new WeakMap();const element = document.getElementById('example');wm.set(element, 'some information');
wm.get(element) // "some information"

注意:WeakMap 弱引用的只是键名,而不是键值。键值依然是正常引用

下面代码中,键值obj会在WeakMap产生新的引用,当你修改obj不会影响到内部

const wm = new WeakMap();
let key = {};
let obj = {foo: 1};wm.set(key, obj);
obj = null;
wm.get(key)
// Object {foo: 1}

ES6模块化介绍

优势:防止命名冲突,代码复用,高维护性

ES6模块化主要有两个命令构成:export(暴露)和import(导入)

默认暴露

export default {

​ ·········

}

导入import {default as m} form ‘./src/a.js’ 等于import m form './src/a.js(简便形式,只针对默认暴露才能使用这种方法) 最好给default 起一个别名

调用就是需要加一层.defailt

当暴露的变量名字相同冲突时就可以使用别名as来解决冲突

import {school,teach} form ‘./src/a.js’

import {school as sch,teach as tea} form ‘./src/b.js’

import {default as m} form ‘./src/a.js’

ES7新特性

includes 方法用来检测数组是否包含某个元素,返回布尔值

//includes
const m = [1,2,3,4]
m.includess(1) // true//幂运算 ** 跟Math.pow(2,10)
console.log(2**10) //

ES8 新特性

async 和await

async 和await 两种语法结合可以让异步代码像同步代码一样

async函数

  • async 函数的返回值为promise对象
  • promise 对象的结果由async函数执行的返回值决定
// async 函数
async function fn() {// 可以返回字符串,数字,只要返回的不是一个promise对象,那么这个函数的返回结果就是成功的promise// return 'emil'// 或者跑出错误 throw new Error('出错了'),返回的是一个失败的promise//如果返回的是一个promise对象return new promise((resolve,reject) = >{resolve('成功')})
}const  result = fn()console.log(result)// result 也是一个promiseresult.then(value = >{console.log('成功')},reason=>{console.log('失败')})

await表达式

  • await必须写在async 函数中
  • await右侧的表达式一般为promise对象
  • await返回的是promise成功的值
  • await的promise失败了,就会抛出异常,需要通过try…catch捕获处理
//创建promise好对象
const p = new promise((resolv,reject)=>{// 成功resolve('成功')// 如果失败了//reject('失败了')
})
async function fn() {// await 返回的是promise对象成功的值let result = await p;// 失败的情况try{} catch(e){console.log(e)}
}
fn() // 成功

async 和await结合读取文件

// 引入fs模块
const fs = require('fs')
// 读取a.txt
function readA(){return new promise((resolve,reject) =>{// 如果失败if(err) reject(err)//如果成功resolve(data)})
}
//读取b.txt
function readB(){return new promise((resolve,reject) =>{// 如果失败if(err) reject(err)//如果成功resolve(data)})
}
//读取c.txt
function readC(){return new promise((resolve,reject) =>{// 如果失败if(err) reject(err)//如果成功resolve(data)})
}// 声明一个async函数
async function fn(){let A = await readA()let B = await readB()let C = await readC()console.log(A.toString())onsole.log(B.toString())onsole.log(C.toString())
}

async 和await结合发送请求

发送请求返回的是一个promise对象

function sendRequest(url) {return new promise((resolve,reject)=>{cosnt x = new XMLHttpRequset()//初始化x.open('GET',url)//发送x.send()//事件绑定x.onreadystatechange = function(){if(x.readyState === 4){if(x.status >= 200 && x.status <= 300){// 这里不能返回promise 因为现在if内部,不能作为sendGet的返回值,所以直接整个包装包一个promise中,成功就调用resolve,失败就调用rejectresolve(x.response)} esle {reject(x.status)}}}  })
}
// promise then 方法
sendsendRequest("这里写入请求的接口地址").then(value=>{console.log(value)
},reson=>{})// async 与await方法
async function fn(){let result = await sendRequest("这里写入请求的接口地址")console.log(result)
}
fn()

对象扩展方法

object.values和object.entries

object.values() 方法返回一个给定对象的所有可枚举属性值得数组

object.entries() 方法返回一个给定对象自身可遍历属性【key,value】的数组

object.getOwnPropertyDescriptors:该方法返回指定对象所有自身属性的描述对象

const school = {name: '清华大学',city: '北京',subject: ['计算机','历史','物理', '化学']
}
// 获取所有的键
console.log(Object.keys(school))  // name,city,subject
// 获取所有的值
console.log(Object.values(school)) // ['清华大学',北京,['计算机','历史','物理', '化学']]// 获取键和值
console.log(Object.entries(school)) //每一个成员又是一个数组
// 创建map
const m = new Map(Object.entries(school))
console.log(m.get('name')) // 清华大学// 返回对象属性的描述对象
console.log(Object.getOwnPropertyDescriptors(school))

ES9新特性

在es9 为对象提供了像数组一样的rest参数和扩展运算符

// 参数是一个对象,相当于前两个参数还是照样存,后面所有的参数存到user里面
function connect({host,port,...user}){console.log('host')console.log('port')console.log('username')console.log('pwd')
}
connect({host:'192.168.1.1',port: 8080,username: 'emil',pwd: 1124
})// 展开,扩展运算符
const skillOne = {Q: '天音波'
}
const skillTwo = {W: '金钟罩'
}
const skillThree = {E: '天雷破'
}
const skillFour = {R: '猛龙摆尾'
}
const monk = {...skillOne,...skillTwo,..skillThree,...skillFour}

正则扩展

正则扩展-命名捕获分组

 // 声明一个变量
let str = '<a href="www.baidu.com">百度一下</a>'// 提取url 与标签文本
// 第一个()是获取的第一个内容,第二个小括号() 是获取的第二个内容
const reg = /<a href="(.*)">(.*)<\/a>/
// 执行
const result = reg.exec(str)
console.log(result[1])
console.log(result[2])// 有了捕获分组过后
let str2 = '<a href="www.baidu.com">百度一下</a>'
// ?url 和 ?text 表示给提取的内容命名,方便操作
const reg2 = /<a href="(?<url>.*)">(?<text>.*)<\/a>/
const result2 = reg2.exec(str2)
// 会生成自动生成一个groups的分组
console.log(result2.groups.url)
console.log(result.groups.text)

正则扩展-反向截断(判断)

let str = 'add111ssasda1234aaa'// 截取数字,且提取后面的数字,在这个字符串是动态传入的情况下提取//正向断言(?=a) 根据当前匹配后面的内容判断前面的内容是否合法
const reg = /\d+(?=a)///反向断言 判断前面是否是a
const reg = /(?<=a)\d+/
const result = reg.exec(str)
console.log(result) // 1234

正则扩展-dotAll模式

作用针对html文件的提取数据特别有用

. 匹配除换行符以外的所有字符

// dot . 元字符,除换行符以外的任意单个字符
let str = `<ul><li><a>肖生克的救赎</a><a>上映日期:1994-09-10</a></li><li><a>阿甘正传</a><a>上映日期:1994-07-06</a></li></ul>
`// 单个匹配   /s 开启dotAll 模式const reg2 = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/s//全局匹配const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gslet resultlet data = []//遍历出数据while(result = reg.exec(str)){data.push({title:result[1],time: reslut[2]})}//输出结果console.log(data)

ES10新特性

object.formEntries

用来创建一个对象,参数有点特殊,用来接收一个二维数组

实际就是将二维数组转换为对象,

而es8中 Object.entries 将对象转为二维数组

const result = object.formEntries([['name','北京大学']['subject','计算机, 美术,会计']
])
console.log(result) // name: 北京大学,subject:'计算机, 美术,会计',以 键:值 的形式输出

字符串扩展方法trimStart 与 trimEnd

es5trim 用于清除两侧的空白字符串

trimStart :清除开头空白

trimEnd: 清除尾部空白

flat 与 flatMap

flat :将多维数组转为低维数组如三维转二维

const  arr = [1,2,3,[5,6]]console.log(arr.flat())  // 1,2,3,5,6//三维转二维
const  arr2 = [1,2,3,[4,5,[6,7]]]
console.log(arr2.flat()) //1,2,3,4,5,[6,7]
//三维转一维,传递一个参数,数字,表示深度
console.log(arr2.flat(2))

flatMap

const arr = [1,2,3,4]
const result = arr.map(item => item * 10)
console.log(result) // 10,20,30,40//如果这个时候我们返回的是一个数组
const result = arr.map(item => [item * 10])
//结果就会变成一个二维数组0:10,1:20 ...
const result2 = arr.flatMap(item => [item * 10])  // 10,20,30,40
//flatMap,相当于flat 和 map 两步操作

ES11

私有属性

class girl {//共有属性name;// 私有属性需要前面加 # 号#age;#weight;constructor(){this.name = namethis.#age = agethis.#weight = weigth}intro() {console.log(girlA.#age)console.log(girlA.#weigth)}
}
// 实例化
const girlA = new girl('红尘雪',111,'45kg')
console.log(girlA) //name:红尘雪 age:111,weight:45kg// 单输出当个属性时
console.log(girlA.#age) //是拿不到这个值的
// 只有在内部访问才行,在内部定义方法,然后外部调用
girlA.intro()

Promise.allSettled

接收一个promise数组,数组的每个元素是一个对象,返回的结果也是promise对象,不过返回的永远是成功的promise

const p1 = new Promise ((resolve,reject) =>{setTimeout(()=>{resolve('p1')},1)
})
const p2 = new Promise ((resolve,reject) =>{setTimeout(()=>{resolve('p2')},1)
})// 调用 allSettled 方法
const result = Promise.allSettled([p1,p2])
console.log(result)
//结果,都是成功,fulfilled 表示成功
//0:{status:"fulfilled", value: "p1"}
//1:{status:"fulfilled", value: "p2"}// 如果有一个失败allSettled 的返回结果仍然是 resolve
//成功的值,是每个promise返回的结果// 还有一个类似的all 方法,也接收一个数组,不同的是,要接收的promise都成功,这个方法才返回成功
const res = Promise.all([p1,p2])
// 任何一个失败,就返回 失败的promise// 这两个都做批量处理promise

String.prototype.matchAll

对数据批量提取很有作用

let str = `<ul><li><a>肖生克的救赎</a><a>上映日期:1994-09-10</a></li><li><a>阿甘正传</a><a>上映日期:1994-07-06</a></li></ul>
`
const reg = /<li>.*?<a>(.*?)<\/a>.*?<p>(.*?)<\/p>/gs
const result = str.matchAll(reg) //返回一个可迭代对象,就可以for循环迭代,或扩展运算符打开
for(v of result) {console.log(v)
}

点星期,星光期,

动态import

a.html

<button id="btn">点击</button>
<script src="b.js"></script>

b.js

const btn = document.getElementById('btn')
btn.onclick = function(){// 以前通过import m form "./hello.js" 静态导入。不管你用不用,先导出// 现在通过import函数导入,用的时候再导入// 而这个import函数,返回的结果是一个promise对象// 且成功的值就是c.js 暴露的值import('./c.js').then(module => {module.hello()})
}

c.js

export function hello() {alert('hello emil')
}

BigInt 类型

比较大的整型数字

运用于大数值运算。

// 大数值运算
//在js中有一个最大安全数值
let max = Number.MAX_SAFE_INTEGER
console.log(max) //9007199254740991
console.log(max+1) //9007199254740992
console.log(max+2) //9007199254740992
// 到这儿,就不能表示更大的整数了
// 再要进行更大的运算就需要用到BigInt了
// 注意 BigInt 不能更普通的数据类型进行运算,需要转换console.log(BigInt(max) + BigInt(2)) //9007199254740993n   ,后面有个n表示他是BigInt类型

globalThis

globalThis 全局的this

他始终指向全局对象,不管执行环境是什么,他都指向全局

如果想对全局的变量做一个操作,可以忽略作用域直接操作全局变量

JavaScript(learn)相关推荐

  1. 前端开发面试题总结之——JAVASCRIPT(一)

    ___________________________________________________________________________________ 相关知识点 数据类型.运算.对象 ...

  2. [译] TypeScript:拥有超能力的 JavaScript(下)

    原文地址:TypeScript - JavaScript with superpowers - Part II 原文作者:Indrek Lasn 译文出自:掘金翻译计划 本文永久链接:github.c ...

  3. 学习笔记(六)——JavaScript(三)

    文章目录 一.鼠标事件 1.1.鼠标移动事件(onmousemove) 1.2.鼠标按键按下与松开事件(onmousedown/onmouseup) 1.3.鼠标滚轮事件(onmousewheel) ...

  4. 学习笔记(五)——JavaScript(二)

    文章目录 一.自定义对象 1.1.添加属性 1.2.读取属性 1.3.修改属性 1.4.删除属性 1.5.补充 二.函数对象 2.1.函数创建 2.2.注意事项 2.3.内置对象 三.数组对象 3.1 ...

  5. 学习笔记(四)——JavaScript(一)

    JavaScript 是互联网上最流行的脚本语言,这门语言可用于 HTML 和 web,更可广泛用于服务器.PC.笔记本电脑.平板电脑和智能手机等设备. 文章目录 一.了解 JavaScript 二. ...

  6. Java 之 JavaScript (一)

    1.JavaScript a.定义:JavaScript是脚本语言,是一种轻量级的编程语言 b.实现:①直接通过标签里面的onXX属性驱动js的执行 <input type="butt ...

  7. JavaScript(JS)常用正则表达式汇总

    文章目录 自定义字符串校验函数 常用正则表达式 自定义字符串校验函数 校验字符串是否全由数字组成,是则返回true,否则返回false: function isDigit(str) {var regE ...

  8. 你不知道的javascript(上卷)----读书笔记

    <!DOCTYPE html> <html><head><meta charset="utf-8"><title>你不知 ...

  9. JavaScript(八)—— PC 端网页特效

    本篇为 JavaScript 系列笔记第八篇,将陆续更新后续内容.参考:黑马程序员JavaScript核心教程,前端基础教程 系列笔记: JavaScript(一)-- 初识JavaScript / ...

最新文章

  1. urlconnection java_java成神之——HttpURLConnection访问api
  2. unity串口 连接多个串口崩溃_3分钟全面了解串口服务器原理技术
  3. C++轻量级微服务_『高级篇』docker容器来说什么是微服务(三)
  4. jax-ws 实现webservice
  5. nginx的源码编译及相关文件配置
  6. pycharm添加python注释头_Pycharm自动添加头注释
  7. ansible安装包和管理 playbook相关
  8. 成不了AI高手?因为你根本不懂数据!听听这位老教授多年心血练就的最实用统计学...
  9. DM6437 OSD调试记录
  10. 微信小程序商城系统订单管理功能介绍
  11. 12款好用超赞的国外搜索资源网站 ,开发者们的标配,你都知道吗?不知道就OUT了...
  12. linux marvell 网卡驱动,linux下安装Marvell Yukon网卡驱动
  13. 关于谷歌邮箱注册收不到验证码——”此电话号码无法用于验证“的问题
  14. openCv4Android之fitLine中各算法总结
  15. 大学三方协议约定的服务器,大学生签订三方协议要注意
  16. 详细解释基址寻址和变址寻址区别
  17. 笔记随笔(ing):计算机视觉部分
  18. linux 进入recovery模式,ubuntu进入recovery mode模式 – 运维那些事
  19. 微积分知识点回顾与总结(五):不定积分,定积分,反常积分
  20. 用map和ajax省市联动,jquery,ajax实现二级省市联动

热门文章

  1. 湘南学院第一届程序设计大赛题解(XNPC—2022)
  2. 【最新】正则表达式:识别手机号码,座机号码,400免费电话
  3. LaTeX 清理缓存文件脚本
  4. UG零基础学习UG编程从什么学起?如…
  5. Matlab 基础应用01 - Python 基础应用 n阶幻方【MagicSquare】和构造
  6. 【测试】使用iPerf进行网络吞吐量测试
  7. 苹果(iphone)手机怎么恢复手机通讯录
  8. websocket安全分析
  9. rx580 8g显卡相当于什么n卡 rx580 8g属于什么级别的
  10. 房地产已经绑架了中国经济和全中国的人民 ——何学林中国房地产大策划之一