JavaScript Web API 学习笔记

Web API 简介

API(Application Programming Interface,应用程序编程接口)指的是一些函数,这些函数对一些常用功能进行了封装,专门用来方便程序员在此基础上做开发,而不用重复造轮子。

例如 C 语言中有一个函数 fopen(),通过此函数接口可以打开硬盘上的文件,Java 中的 JDK 中各种函数,这些函数库可能由编程语言提供,也可能由一些第三方软件厂商提供,我们无需访问源码了解内部实现细节,只需要阅读 API 手册引用了对应的库文件就可以直接调用函数使用特定的功能。

Web API 特指的是浏览器厂商提供的一套用来操作浏览器和 HTML 页面中的元素的 API,以方便程序员在此基础上开发 Web 应用程序或网站。

Web API 根据操作对象不同又可以分为 DOM 和 BOM 两大类,Web API 主要用于 JavaScript,但也可能有例外。所有的 API 列表可以查阅 MDN:Web API 列表

DOM

简介

文档对象模型(Document Object Model,简称 DOM),是 W3C 组织推荐的处理可扩展标记语言(HTML 或者 XML)的标准编程接口,通过这些 DOM 接口可以改变网页的内容、结构和样式。

把 HTML 或 XML 文档解析映射成树形结构,这棵树称为 DOM 树,又称为文档树模型,这样可以更加方便对各个节点对象处理。

1550731974575

  • 文档:一整个 HTML 页面就是一个文档,DOM 中使用 document 表示。
  • 节点:网页中的所有内容,在文档树中都是节点(标签、属性、文本、注释等),使用 node 表示。
  • 标签节点:又称为元素节点,简称为“元素”,文档中的所有 HTML 标签括包裹起来的内容,使用 element 表示。

img

获取元素

如果我们想要操作网页上的某部分内容,需要先获取到该部分对应的元素,才能对其进行操作。

  • 根据 ID 获取:使用 getElementById() 方法可以根据元素 ID 属性值获取元素对象,因为 ID 属性值在整个文档中应该是唯一或者不存在的,这个方法返回的一个 HTML 对象 或 null。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    <body>
    <div id="year">2021</div>
    <!-- 因为我们文档页面从上往下加载,所以所以我们 script 写到元素的下面,否则将获取不到元素 -->
    <script>
    var time = document.getElementById('year');
    console.log(time);
    // 使用 console.dir() 可以打印对象里面的属性和方法详细信息
    console.dir(time);
    </script>
    </body>
  • 根据标签名获取:使用 getElementsByTagName() 方法可以返回带有指定标签名的对象的集合,以伪数组的形式存储,想要操作里面的元素常常需要遍历伪数组。即使整个文档只有一个该标签节点,返回的仍是伪数组而不是直接返回唯一的元素对象,如果整个文档没有这个标签节点,返回的是空的伪数组。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    <body>
    <div>
    <ul>
    <li></li>
    <li></li>
    <li></li>
    </ul>
    </div>
    <script>
    var divs = document.getElementsByTagName('div');
    console.log(divs);
    console.log(divs[0]);
    // 获取某个元素(父元素)内部所有指定标签名的子元素.
    var lis = divs[0].getElementsByTagName('li');
    console.log(lis);
    for (var i = 0; i < lis.length; i++) {
    console.log(lis[i]);
    }
    </script>
    </body>
  • 根据类名获取:使用 getElementsByClassName() 方法根据元素的 class 属性值返回元素对象的伪数组集合,此方法是 HTML5 新增的方法,可能存在兼容性问题。

    1
    2
    3
    4
    5
    6
    7
    8
    <body>
    <div class="box">盒子1</div>
    <div class="box">盒子2</div>
    <script>
    var boxs = document.getElementsByClassName('box');
    console.log(boxs);
    </script>
    </body>
  • 根据选择器获取:使用 querySelector() 方法返回指定选择器的第一个元素对象,使用 querySelectorAll() 方法返回指定选择器的所有元素对象的伪数组集合。传入的选择器字符串不要忘了带上符号,例如 .box #nav,这两个方法也是 HTML5 新增的方法,可能存在兼容性问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <body>
    <div id="nav">
    <div class="box">盒子1</div>
    <div class="box">盒子2</div>
    </div>
    <script>
    var nav = document.querySelector('#nav');
    console.log(nav);
    var boxs = document.querySelectorAll('.box');
    console.log(boxs);
    </script>
    </body>
  • 获取特殊元素:可以根据 document 对象的某些属性直接获取特殊元素对象。

    1
    2
    doucumnet.body 				// 返回body元素对象
    document.documentElement // 返回html元素对象

JavaScript 事件

事件是可以被 JavaScript 侦测到的行为。

网页中的每个元素都可以触发某些 JavaScript 事件,例如,用户鼠标点击某元素,鼠标滑过某元素,当事件发生时,然后去执行某些操作。

常见的鼠标事件:

1550734506084

事件三要素:

  • 事件源(谁):触发事件的 HTML 元素对象。
  • 事件类型(什么事件): 如何触发事件,比如鼠标点击元素,鼠标经过元素,键盘某个键按下。
  • 事件处理程序(做啥):事件触发后要执行的函数代码,即事件处理函数,可以通过给元素的事件属性赋函数值的来注册事件。
1
2
3
4
5
6
7
8
9
10
11
<body>
<div>123</div>
<script>
// 1. 获取事件源
var div = document.querySelector('div');
// 2.绑定事件处理程序
div.onclick = function() {
console.log('我被选中了');
}
</script>
</body>

操作元素属性

使用 DOM API 可以获得页面中 HTML 元素对象的属性,通过修改这些属性可以动态改变网页内容、结构和样式。

常见的元素属性有 innerText、innerHTML、src、href、id、alt、title、type、value、checked、selected、disabled、style、className 等。

innerText、innerHTML 两个属性用来改变元素的内容,两者的区别是 innerText 不会识别 HTML 标签,而 innerHTML 会识别 HTML 标签。

1
2
3
4
5
6
7
8
9
10
11
<body>
<div id="box1">box<strong>1</strong></div>
<div id="box2">box<strong>2</strong></div>
<script>
console.log(document.body);
var box1 = document.getElementById('box1');
var box2 = document.getElementById('box2');
console.log(box1.innerText); // box1
console.log(box2.innerHTML); // box<strong>2</strong>
</script>
</body>

通过 style 属性及其中的子属性可以直接修改元素的的行内样式表,优先级很高。注意 style 属性的子属性名遵循驼峰命名法,比如 fontSize 对应 CSS 中的 font-size 、backgroundColor 对应 CSS 中的 background-color。

1
2
3
4
5
6
7
8
<body>
<img src="images/tao.png" alt="">
<script>
var img = document.querySelector('img');
img.style.width = "300px";
// img.style = 'width:300px'; 与上面等效
</script>
</body>

在 JavaScript 中 class 是个保留字,因此使用元素对象的 className 属性来操作 HTML 元素的 class 属性值。修改元素的类名从而匹配 CSS 文件中预先写好不同类选择器样式表是更为常见的修改样式做法。注意 className 会直接更改元素的类名,会覆盖原先的类名,如果想要添加新的样式,应该在原来的类名字符串后面拼接新的样式类名。

1
element.className += 'newclass';

classList 属性是 HTML 5 新增的一个属性,返回元素对象的类名列表,IE 10 以上的版本才支持。通过该属性的方法可以方便地在元素中添加,移除及切换 CSS 类。

1
2
3
element.classList.add('current');		// 添加类
element.classList.remove('current'); // 移除类
focus.classList.toggle('current'); // 切换类

排他思想

如果有同一组元素,我们想要其中某一个元素实现某种样式, 需要用到循环的排他思想算法:

  1. 所有元素全部清除样式(干掉其他人)
  2. 给当前元素设置样式 (留下我自己)
  3. 注意顺序不能颠倒,首先干掉其他人,再设置自己
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<body>
<button>按钮1</button>
<button>按钮2</button>
<button>按钮3</button>
<button>按钮4</button>
<button>按钮5</button>
<script>
var btns = document.getElementsByTagName('button');
for (var i = 0; i < btns.length; i++) {
btns[i].onclick = function() {
// 先把所有的按钮背景颜色去掉,即干掉所有人
for (var i = 0; i < btns.length; i++) {
btns[i].style.backgroundColor = '';
}
// 然后让当前的元素背景颜色为 pink,即留下我自己
this.style.backgroundColor = 'pink';

}
}
</script>
</body>

自定义属性

有时候为了给 HTML 元素做一些特殊标记或者保存一小段数据,我们会用给元素添加一些自定义的属性。元素的自定义属性只能通过 getAttribute(attrName)setAttribute(name, value)) 方法读写,必要时使用 removeAttribute(attrName) 移除属性值。

注意:class 属性在通过这几个方法访问时不需要转换为 className,若要彻底移除一个属性的效果,应当使用 removeAttribute(),而不是使用 setAttribute() 将属性值设置为 null。对于许多属性,如果仅将其值设为 null,这不会造达成和预期一样的效果。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<body>
<div id='test' class = 'test'>
</div>
<script>
var div = document.querySelector('div');
console.log(div.id);
console.log(div.className);
console.log(div['id']);
console.log(div['className']);
console.log(div.getAttribute('id'));
console.log(div.getAttribute('class'));
div.setAttribute('id', 'demo');
console.log(div.id);
div.removeAttribute('id');
console.log(div.getAttribute('id'));
</script>
</body>

通过 JS 对象 .[] 操作符只能设置 Element 对象预先定义的内置属性,并不能访问到 HTML 元素的自定义属性。

1
2
3
4
5
6
7
8
9
10
<body>
<div index='1'>
</div>
<script>
var div = document.querySelector('div');
div.index = 2; // index 为自定义属性,实际是为 JS 中 div 对象本身新增了一个属性 index,并没有和 HTML 文档中的 div 元素的 index 属性关联起来,两个 index 属性是相互独立的。
console.log(div.getAttribute('index')); // 1
console.log(div.index); // 2
</script>
</body>

为了防止自定义属性和 Element 对象预先定义的内置属性混淆,H5 规定自定义属性应该以 data- 开头,遵循这个规定的自定义属性可以通过元素对象的 dataset 属性读写并自动映射到 HTML 元素的自定义属性上面(IE 11才开始支持)。

1
2
3
4
5
6
7
8
9
<body>
<div data-index='1'></div>
<script>
var div = document.querySelector('div');
div.dataset.index = '2';
console.log(div.getAttribute('data-index'));
console.log(div.dataset.index);
</script>
</body>

节点层级

网页中的所有内容都是节点,包括标签、属性、文本、注释等,在 DOM 中,节点使用 node 来表示。

1550970944363

任意一个节点至少拥有 nodeType(节点类型)、nodeName(节点名称)和nodeValue(节点值)这三个基本属性。

  • 元素节点 nodeType 为 1。
  • 属性节点 nodeType 为 2。
  • 文本节点 nodeType 为 3 (文本节点包含文字、空格、换行等)。

除了利用 DOM 提供的获取元素对象的方法,还可以利用节点层级关系来获取元素对象,这种方式逻辑性强, 但是兼容性稍差。DOM 树可以把节点划分为不同的层级关系,常见的是父子兄层级关系。

  • 父级节点:parentNode 属性可返回离该节点最近的父节点, 如果指定的节点没有父节点则返回 null

  • 子节点:childNodes 属性返回该节点的子节点的集合,该集合为即时更新的集合。返回值里面包含了所有的子节点,包括元素节点,文本节点等。如果只想要获得里面的元素节点,则需要通过节点的 nodeType 值做筛选处理。所以我们一般不提倡使用 childNodes 属性,而是使用 children 属性,children 是一个只读属性,返回该节点的子元素节点(伪数组)集合。它只返回子元素节点,其余节点不返回。

  • 首尾子节点:firstChild 属性返回第一个子节点,lastChild 属性返回最后一个子节点,找不到则返回 null,包括元素节点,文本节点等所有类型节点。firstElementChild 返回第一个子元素节点,lastElementChild 返回最后一个子元素节点,找不到则返回null。但是这两个属性存在兼容性问题,IE9 以上才支持。使用节点的 children 属性加上数组下标访问首尾子元素节点,既可以避开非元素节点,也可以完美兼容 IE 低版本。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    <ol>
    <li>我是li1</li>
    <li>我是li2</li>
    <li>我是li3</li>
    <li>我是li4</li>
    <li>我是li5</li>
    </ol>
    <script>
    var ol = document.querySelector('ol');
    // 1. firstChild 第一个子节点 不管是文本节点还是元素节点
    console.log(ol.firstChild);
    console.log(ol.lastChild);
    // 2. firstElementChild 返回第一个子元素节点 ie9才支持
    console.log(ol.firstElementChild);
    console.log(ol.lastElementChild);
    // 3. 实际开发的写法 既没有兼容性问题又返回第一个子元素
    console.log(ol.children[0]);
    console.log(ol.children[ol.children.length - 1]);
    </script>
  • 兄弟节点:nextSibling 属性返回当前元素的下一个兄弟元素节点,previousSibling 属性返回当前元素上一个兄弟元素节点找不到则返回null。和首尾子节点一样,也是包含所有的节点。nextElementSibling 属性返回当前元素下一个兄弟元素节点,previousElementSibling 属性返回当前元素上一个兄弟节点,找不到则返回null。同样这两个属性存在兼容性问题,IE9 以上才支持。可以通过判断 nodeType 自己封装一个的函数来解决兼容性问题。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    function getNextElementSibling(element) {
    var el = element;
    while (el = el.nextSibling) {
    if (el.nodeType === 1) {
    return el;
    }
    }
    return null;
    }

节点操作

document.createElement('tagName') 方法创建由 tagName 指定的 HTML 元素。

node.appendChild(child) 方法将一个节点添加到指定父节点的子节点列表末尾。类似于 CSS 里面的 after 伪元素。

node.insertBefore(child, 指定元素) 方法将一个节点添加到父节点的指定子节点前面。类似于 CSS 里面的 before 伪元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<ul>
<li>123</li>
</ul>
<script>
// 1. 创建节点元素节点
var li = document.createElement('li');
// 2. 添加节点 node.appendChild(child) node 父级 child 是子级 后面追加元素
var ul = document.querySelector('ul');
ul.appendChild(li);
// 3. 添加节点 node.insertBefore(child, 指定元素);
var lili = document.createElement('li');
ul.insertBefore(lili, ul.children[0]);
// 4. 我们想要页面添加一个新的元素 : 1. 创建元素 2. 添加元素
</script>

node.removeChild(child) 方法从 DOM 中删除一个子节点,返回删除的节点。

node.cloneNode(deep) 方法返回调用该方法的节点的一个副本。 也称为克隆节点/拷贝节点。deep 参数是可选的,如果括号参数为空或者为 false ,则是浅拷贝,即只克隆复制节点本身,不克隆里面的子节点(包括文本节点)。如果括号参数为 true ,则是深度拷贝,会复制节点本身以及里面所有的子节点。注意:克隆一个元素节点会拷贝它所有的属性以及属性值,也包括属性上绑定的事件。如果原始节点设置了 ID,并且克隆节点会被插入到相同的文档中,为了防止一个文档中出现两个 ID 重复的元素,那么应该更新克隆节点的 ID 以保证唯一性。

三种动态创建元素区别:

  • document.write():直接将内容写入页面的内容流,但是文档流执行完毕,则它会导致页面全部重绘。
  • element.innerHTML:是将内容写入某个 DOM 节点,不会导致页面全部重绘, 创建多个元素节点的效率更高(采取数组形式拼接字符串而不是使用 + 来拼接字符串),但是使用数组拼接起来稍微复杂。
  • document.createElement():创建多个元素效率稍低一点点,但是结构更清晰。·

事件注册

除了通过给元素对象的事件属性(onclickonmouseover 等)赋值一个函数对象这种传统的方式来给元素注册事件,还可以使用事件监听方式给元素绑定事件。

传统方式同一个元素的同一个事件属性只能注册一个事件处理函数,最后注册的处理函数将会覆盖前面注册的处理函数。事件监听方式可以给同一个元素同一个事件注册多个监听器。

eventTarget.addEventListener(type, listener[, useCapture]) 方法将指定的监听器注册到 eventTarget(目标对象)上,当该对象触发指定的事件时,就会执行事件处理函数。该方法接收三个参数:

  • type:事件类型字符串,比如 ‘click’ 、’mouseover’,注意这里不要带 on。
  • listener:事件处理函数,事件发生时,会调用该监听函数。
  • useCapture:可选参数,是一个布尔值,默认是 false,指定该事件在捕获阶段还是冒泡阶段触发。。

IE 9 之前的版本使用 eventTarget.attachEvent(eventNameWithOn, callback) 来添加事件监听器。该方法接收两个参数:

  • eventNameWithOn:事件类型字符串,比如 ‘onclick’、’onmouseover ‘,这里要带 on。
  • callback: 事件处理函数,当目标触发事件时回调函数被调用

可以封装如下的函数解决兼容性问题。

1
2
3
4
5
6
7
8
9
10
function addEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 addEventListener 方法
if (element.addEventListener) {
element.addEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.attachEvent) {
element.attachEvent('on' + eventName, fn);
} else {
// 相当于 element.onclick = fn;
element['on' + eventName] = fn;
}

事件删除

根据注册事件方法的不同,有下面三种的删除(解绑)事件的方式:

  • eventTarget.onclick = null;
  • eventTarget.removeEventListener(type, listener[, useCapture]);
  • eventTarget.detachEvent(eventNameWithOn, callback);

也可以封装以下的删除事件函数来解决兼容性问题。

1
2
3
4
5
6
7
8
9
function removeEventListener(element, eventName, fn) {
// 判断当前浏览器是否支持 removeEventListener 方法
if (element.removeEventListener) {
element.removeEventListener(eventName, fn); // 第三个参数 默认是false
} else if (element.detachEvent) {
element.detachEvent('on' + eventName, fn);
} else {
element['on' + eventName] = null;
}

DOM 事件流

事件发生时会在元素节点之间按照特定的顺序传播,这个传播过程即 DOM 事件流。

1551169007768

事件可能处于三个阶段:

  1. 捕获阶段(capturing phase)
  2. 目标阶段(target phase)
  3. 冒泡阶段(bubbling phase)

对于事件目标元素注册的事件来说,事件会处于目标阶段。

事件目标元素的父元素对象如果注册了事件也会触发,但是只能在捕获或者冒泡其中的一个阶段被调用。通过 onclickattachEvent() 注册的事件在冒泡阶段触发。addEventListener(type, listener[, useCapture])第三个参数如果是 true,表示在事件捕获阶段调用事件处理程序;如果是 false(不写默认就是 false),表示在事件冒泡阶段调用事件处理程序。 有些事件是没有冒泡的,比如 onbluronfocusonmouseenteronmouseleave

实际开发中我们很少使用事件捕获,我们更关注事件冒泡。

事件对象

事件对象代表事件的状态,事件发生后,跟事件相关的一系列信息数据的集合都放到这个对象里面,这个对象就是事件对象 event,它有很多属性和方法。比如就鼠标事件而言,谁绑定了这个鼠标事件、鼠标的位置、鼠标按钮的状态这些信息都可以由事件对象得到。

1
2
eventTarget.onclick = function(event) {}
eventTarget.addEventListener('click', function(event) {})

给元素注册事件处理函数的时候可以为事件对象指定形参 event,或者写成 e 或者 evt。当事件发生时事件对象就会被浏览器自动创建,并传递给事件监听器(事件处理函数)。

在 IE 6 ~ 8 中,浏览器不会给监听器传递事件对象,如果需要的话,需要到window.event 中获取。可以使用以下的方法做兼容性处理。

1
e = e || window.event;

事件对象的常见属性和方法:

1551169931778

e.targetthis 的区别:

  • this 是事件绑定的元素对象, 这个函数的调用者(绑定这个事件的元素),而这个函数可能是在冒泡阶段或者捕获阶段执行。
  • e.target 是真正触发事件的具体元素对象。
  • 当事件在目标阶段被调用执行时,e.targetthis 指向同一个元素对象。

利用事件对象的 preventDefault() 方法可以阻止对象的默认行为,例如 a 标签点击后默认会跳转到新地址,表单提交按钮点击后默认会提交整个表单。IE 6 ~ 8 设置事件对象 returnValuefalse 也可以阻止默认行为,直接在事件处理函数中 return false; 也可以阻止默认行为,不存在兼容性问题。

利用事件对象的 stopPropagation() 方法可以阻止事件冒泡,IE 6 ~ 8 设置事件对象 cancelBubble 属性可以阻止冒泡。阻止事件冒泡的兼容性写法:

利用事件对象的 stopPropagation() 方法可以阻止事件冒泡,IE 6 ~ 8 设置事件对象 cancelBubble 属性可以阻止冒泡。阻止事件冒泡的兼容性写法:

1
2
3
4
5
if(e && e.stopPropagation){
e.stopPropagation();
}else{
window.event.cancelBubble = true;
}

事件委托

事件委托也称为事件代理, 在 jQuery 里面称为事件委派。

事件委托即当多个子元素需要注册相同或者接近的事件时,不直接给子元素注册事件,而是给它们的父元素注册事件,利用事件冒泡,当子元素的事件冒泡到父元素,执行父元素绑定的事件处理函数。

这样只操作了一次 DOM 就给所有子元素注册了事件,提高了程序的性能,并且以后新创建的子元素,也自动拥有了这个事件。

鼠标事件

1551172699854

mouseenter 事件和 mouseover 很相似,当鼠标移动到元素上时就会触发它们。两者之间的差别:mouseover 事件会在冒泡阶段触发,鼠标经过元素自身会触发,经过子元素也会触发。 mouseenter 事件不冒泡,只会经过元素自身触发。mouseenter 行为方式与 CSS 中的 :hover 伪类非常相似。

mouseout 鼠标离开事件也有对应的 mouseleave 事件不会冒泡。

禁止鼠标右键菜单:contextmenu 主要控制应该何时显示上下文菜单,主要用于取消默认的上下文(鼠标右键)菜单。

1
2
3
document.addEventListener('contextmenu', function(e) {
e.preventDefault();
})

禁止鼠标选中:selectstart 控制开始选中文字。

1
2
3
document.addEventListener('selectstart', function(e) {
e.preventDefault();
})

鼠标事件对象常用属性:

image-20210322222524116

键盘事件

常用键盘事件:

1551318122855

  1. 如果使用 addEventListener() 注册事件需要去掉 on。
  2. onkeypress 和其他两个键盘事件的区别是,它不识别功能键,比如左右箭头,shift 等。
  3. 三个事件的执行顺序是: keydown –> keypress –> keyup。

键盘事件对象的 keyCode 属性返回该键的 ASCII 码的数值。

注意:onkeydownonkeyup 不区分字母大小写,onkeypress 区分字母大小写。
在实际开发中,我们更多的使用 keydown 和 keyup, 它能识别所有的键(包括功能键),Keypress 不识别功能键,但是它的 keyCode 属性能区分大小写,返回不同的 ASCII 值。

BOM

简介

BOM(Browser Object Model)即浏览器对象模型,它提供了与浏览器窗口进行交互的对象,BOM 由一系列相关的对象构成,并且每个对象都提供了很多方法与属性,其核心对象是 window

1551319344183

DOM 就是把整个 HTML 文档当做一个对象来看待,DOM 的顶级对象 document。BOM 把整个浏览器标签页当做一个对象来看待,BOM 的顶级对象是 windowdocumentwindow 对象的一个属性。

window 是一个全局对象(global object),定义在全局作用域中的变量、函数都会变成 window 对象的属性和方法。但是使用 var 声明的全局变量,var 声明的函数表达式,function 声明的函数,不能被 delete 删除。详见:MDN:delete 操作符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
var a1 = 1;
a2 = 2;
function f1() {
console.log('in f1()');
}
var f2 = function () {
console.log('in f2()');
}
f3 = function () {
console.log('in f3()');
}
f4 = function f4() {
console.log('in f4()');
}
console.log(delete a1); // false
console.log(delete a2); // true
console.log(delete f1); // false
console.log(delete f2); // false
console.log(delete f3); // true
console.log(delete f4); // true

window 事件

  • 窗口加载事件:当文档内容(包括图像、脚本文件、CSS文件等)完全加载完成会触发该事件, 调用处理函数。可以把 JS 代码放到回调函数里,这样 script 标签就可以放在文档的任意位置,等页面内容全部加载完毕,再去执行处理函数,避免了获取元素失败的情况。

    1
    2
    window.onload = function(){}
    window.addEventListener("load",function(){});

    IE 9 以上支持 DOMContentLoaded 事件,仅当 DOM 加载完成就触发时间,不包括样式表,图片,flash 等等。

    1
    document.addEventListener('DOMContentLoaded',function(){})

    如果页面的要加载的资源很多或者网络情况不好的话, 从用户访问到 onload 事件触发可能需要较长的时间,很多 JS 效果就不能生效,影响用户的体验,此时用 DOMContentLoaded 事件比较合适。

  • 调整窗口大小事件:当浏览器窗口大小发生变化,就会触发这个事件。利用这个事件配合 window.innerWidth(当前屏幕的宽度)属性可以完成响应式布局。

    1
    2
    window.onresize = function(){}
    window.addEventListener('resize',function(){});
  • 页面滚动事件:常结合 window.pageYOffsetwindow.pageXOffset 这两个属性使用。

    1
    2
    window.onscroll = function(){}
    window.addEventListener('scroll',function(){})

window 属性

  • location:用于获取或设置窗体的 URL,并且可以用于解析 URL 。 因为
    这个属性返回的是一个对象,所以我们将这个属性也称为 location 对象。URL (Uniform Resource Locator, URL) 的格式为:protocol://host[:port]/path/[?query]#fragment
    1551322387201
    location 对象的属性:
    1551322416716

    利用 location.search 可以在页面跳转中传递少量参数。

    1
    2
    3
    4
    5
    6
    7
    <script>
    // 1.先去掉? substr('起始的位置',截取几个字符);
    var params = location.search.substr(1); // uname=andy
    console.log(params);
    // 2. 利用=把字符串分割为数组 split('=');
    var arr = params.split('=');
    </script>

    location 对象的方法:
    1551322750241

  • navigator:该对象包含有关浏览器的信息,我们最常用的是 userAgent 属性,该属性是一个只读的字符串,返回浏览器用于 HTTP 请求的用户代理头的值。利用该值可以对用户的终端信息进行判断,实现跳转不同的页面。

    1
    2
    3
    4
    5
    if((navigator.userAgent.match(/(phone|pad|pod|iPhone|iPod|ios|iPad|Android|Mobile|BlackBerry|IEMobile|MQQBrowser|JUC|Fennec|wOSBrowser|BrowserNG|WebOS|Symbian|Windows Phone)/i))) {
    window.location.href = ""; // 跳转到手机页面
    } else {
    window.location.href = ""; // 跳转到电脑页面
    }
  • history:使用该对象与浏览器历史记录进行交互。该对象包含用户(在浏览器窗口中)访问过的 URL。
    1551322885216

  • sessionStoragelocalStorage:这两个对象可以将少量数据以键值对的形式存储在用户本地浏览器中,sessionStorage 的容量约 5 MB、localStorage 约 20 MB,不同浏览器的大小不统一。键和值都只能是字符串形式,如果要存储对象可以先使用 JSON.stringify() 编码后再存储。 sessionStorage 的生命周期为关闭浏览器窗口,在同一个窗口(页面)下的数据可以共享,localStorage 存储的数据永久生效,关闭了浏览器也会依旧存在,除非手动删除数据,同一浏览器的多个窗口(页面)之间可以共享 localStorage 存储的数据,前提是这些页面访问的域名相同(同源策略)。

    1
    2
    3
    4
    5
    6
    7
    8
    sessionStorage.setItem(key, value);		// 存储数据
    sessionStorage.getItem(key); // 获取数据
    sessionStorage.removeItem(key); // 删除数据
    sessionStorage.clear(); // 删除所有数据
    localStorage.setItem(key, value); // 存储数据
    localStorage.getItem(key); // 获取数据
    localStorage.removeItem(key); // 删除数据
    localStorage.clear(); // 删除所有数据

定时器

window 对象提供了 2 个设定定时器方法:setTimeout()setInterval()

window.setTimeout(调用函数, [延迟的毫秒数])方法用于设置一个定时器,该定时器在定时器到期后执行调用函数,window 可以省略。 这个调用函数也称为回调函数(callback),可以直接写匿名函数表达式,或者写函数名或者字符串形式的‘函数名()’三种形式,第三种不推荐。 延迟时间单位只能是毫秒,只需写数值不用写单位,默认值是 0。该方法返回值 timeoutID 是一个正整数,表示定时器的编号。这个值可以传递给 window.clearTimeout(timeoutID) 来取消该定时器。

回调函数原理:函数可以作为一个参数。将这个函数作为实参传到另一个函数里面,当那个函数执行完之后,再执行传进去的这个函数,这个过程就叫做回调。

window.setInterval(回调函数, [间隔的毫秒数])方法重复调用一个函数,每隔这个时间,就去调用一次回调函数。其他两个参数的用法和 setTimeout() 一样,但是如果这个时间参数值小于 10,则默认使用值为 10 ms,真正延迟的时间或许更长。该方法返回值 intervalID 是一个正整数,表示计时器的编号。这个值可以传递给 window.clearTimeout(intervalID) 来取消该计时器。

对于同一个 window 对象,setTimeout() 或者 setInterval() 在后续的调用不会重用同一个定时器编号,不同的 window 对象使用独立的编号池。

注意:setTimeout()setInterval() 共用一个编号池,技术上,clearTimeout()clearInterval() 可以互换。但是,为了避免混淆,不要混用取消定时函数。

事件循环

JavaScript 是单线程的非阻塞的脚本语言。这是因为 Javascript 这门脚本语言诞生的使命所致——JavaScript 是为处理页面中用户的交互,以及操作 DOM 而诞生的。比如我们对某个 DOM 元素进行添加和删除操作,不能同时进行。 应该先进行添加,之后再删除。HTML 5 提出 Web Worker 标准,允许 JavaScript 脚本创建多个线程,但是子线程完全受主线程控制,且不得操作DOM。所以,这个新标准并没有改变 JavaScript 单线程的本质。

单线程意味着,对于一个 JS 程序而言,都只有一个主线程来处理所有的任务。

非阻塞则是当 JS 需要进行一项无法立刻返回结果异步任务的时候,主线程会挂起(pending)这个任务,然后在异步任务返回结果的时候再根据一定规则去执行相应的回调。

一般而言,异步任务有以下三种类型:
1、普通事件,如 click、resize 等。
2、资源请求加载,如 load、error 等。
3、定时器,包括 setInterval、setTimeout 等。

尽管 JS 是单线程执行的,但是(宿主环境)浏览器是多线程的,浏览器中的其他线程去协助 JS 引擎的运行实现了 JS 在浏览器运行环境的非阻塞。

浏览器中的线程举例:

  1. GUI 渲染线程
  2. JS 引擎线程
  3. 定时触发器线程(setTimeout和setInteval)
  4. 浏览器事件触发线程(onclick)
  5. 异步 HTTP 请求线程(ajax)
  6. 事件轮询处理线程(event loop)

JS 执行步骤

  1. 执行主线程执行栈中的同步任务。
  2. 遇到异步任务交给异步线程(定时器触发线程 (setTimeout)、http 异步线程(ajax )、浏览器事件线程(onclick))执行。
  3. 异步成功后,异步任务的回调函数放入任务队列(也叫消息队列)中。
  4. 当执行栈中的所有同步任务执行完毕,事件循环(event loop)线程,就会按入队次序读取(消息队列)中的回调函数进入执行栈,开始执行。

JS 主线程从任务队列中读取任务,执行任务,这个过程是循环不断的,所以这种运行机制又称为事件循环(event loop)。

img

网页特效相关

元素偏移量 offset 系列

使用 offset 系列相关属性可以动态的得到该元素距离带有定位的父元素的偏移量、元素自身的大小(宽度高度)等。注意: 返回的都是数字型不带单位。

图片2

offset 系列常用属性:

图片1

offset 系列属性 与 style 属性区别 :

  • offset 系列可以得到任意样式表中的样式值,style 只能得到行内样式表中的样式值。
  • offset 系列获得的数值是没有单位的,style.width 获得的是带有单位的字符串。
  • offsetWidth 包含padding+border+width,style.width 获得不包 padding 和 border 的值。
  • offset 系列等属性是只读属性,只能获取不能赋值,style.width 是可读写属性,可以获取也可以赋值。

想要获取元素大小位置,用 offset 系列属性更合适,想要给更改元素样式,则需要用 style 属性。注意:通过 style 属性赋值时一定要记得补上单位字符串。

元素可视区 client 系列

使用 client 系列的相关属性来获取元素可视区的相关信息。通过 client 系列的相关属性可以动态的得到该元素的边框大小、元素自身的大小(不包含边框)。

图片4

client 系列常用属性:

图片3

元素滚动 scroll 系列

使用 scroll 系列的相关属性可以动态的得到该元素的大小、滚动距离等。

图片6

scroll 系列常用属性:

图片5

如果元素的高(或宽)度不足以显示整个元素内容时,会自动出现滚动条。当滚动条向下滚动时,内容上面被隐藏掉的高度,我们就称为页面被卷去的头部。滚动条在滚动时会触发 onscroll 事件。如果是整个页面滚动,事件源是 document 对象,如果是某个元素的内容滚动则事件源是该元素对象。

注意:给元素添加 overflow:auto 样式才会在内容溢出时显示滚动条。

页面被卷去的头部:可以通过 window.pageYOffset 获得 如果是被卷去的左侧 window.pageXOffset。元素被卷去的头部是 element.scrollTop,如果是被卷去的左侧是 element.scrollLeft

window.pageYOffsetwindow.pageXOffset 这两个属性 IE 9 开始支持,如果文档开头声明了 DOCTYPE,IE 9 以下可以使用document.documentElement.scrollTop,没声明 DTD 可以使用 document.body.scrollTop。封装如下的兼容性函数:

1
2
3
4
5
6
7
function getScroll() {
return {
left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft||0,
top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop || 0
};
}
// 通过 getScroll().left 和 `getScroll().top 使用

三个系列对比:

图片7

client 系列不仅不包括边框,如果有滚动条也不包括滚动条。scroll 系列则计算元素完整内容的大小,尽管这些内容可能发生了溢出。

  1. offset 系列主要使用 offsetLeftoffsetTop 获取元素位置 。
  2. client 系列主要使用 clientWidthclientHeight 获取元素大小 。
  3. scroll 系列主要使用 scrollTopscrollLeft 获取滚动距离。
  4. 整个页面滚动的距离通过 window.pageXOffset 获得。

动画函数封装

动画实现原理:通过定时器 setInterval() 不断移动元素位置。

缓动动画:让元素运动速度有所变化,最常见的是让移动速度慢慢停下来,既让每步的步长慢慢变小,步长公式:(目标值 - 现在的位置) / 10,但是在接近终点时步长会无线趋近于 0 而始终无法准确到达终点,所以要对步长往数轴两端取整。如果步长是正值,利用 Math.ceil() 往上取整,如果是负值,则利用 Math.floor() 向下取整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function animate(obj, target, callback) {
// 先清除以前的定时器,只保留当前的一个定时器执行,
// 让每一个元素至始至终只有一个定时器,防止多次调用会使动画加速
clearInterval(obj.timer);
obj.timer = setInterval(function() {
// 步长公式
var step = (target - obj.offsetLeft) / 10;
step = step > 0 ? Math.ceil(step) : Math.floor(step);
// 到达目标则停止动画 本质是停止定时器
if (obj.offsetLeft == target) {
clearInterval(obj.timer);
// 回调函数写到定时器结束里面
callback && callback(); // 等价为 if (callback) {callback();}
}
obj.style.left = obj.offsetLeft + step + 'px';
}, 15);
}

使用封装号的动画函数需要传递 3 个参数,动画对象(obj)、移动到的距离(target)和回调函数(callback)。

callback && callback ()

移动端触屏事件

移动端没有鼠标点击的概念,取而代之的是手指触屏事件 touch(也称触摸事件),触屏事件可响应用户手指(或触控笔)对屏幕或者触控板的操作。

常见的触屏事件如下:

image-20210515130450548

触摸事件对象(TouchEvent)保存触屏事件有关的状态信息,通过该对象的属性和方法可以检测触点的移动,触点的增加和减少等等。

touchstart、touchmove、touchend 三个事件都有各自的事件对象,但都有以下属性:

image-20210515132352287

大多数情况使用 targetTouches 属性,该属性本质是一个伪数组,当只有一个手指触摸时使用 e.targetTouches[0].pageXe.targetTouches[0].pageY 来获取触点在页面上的坐标。

参考

  1. JavaScript Event order

  2. 你所不知道的 setTimeout

  3. 你所不知道的 setInterval

  4. 详解 JavaScript中 的 Event Loop(事件循环)机制

  5. JS 线程、Event Loop、事件循环、任务队列、宏任务

  6. JavaScript 运行机制详解:再谈Event Loop