JavaScript 笔记(九):补充内容

在 JavaScript 中,ECMAScript 之后的内容是学习 window 与 document API,既然是 API,最好的了解方式莫过于学习文档,因此,之后不会再记录 DOM 或 BOM 相关 API 的笔记,取而代之的是记录一些相对重要的补充内容以及使用 DOM 和 BOM 时的一些注意事项

JavaScript 笔记(九):补充内容

函数

在 JavaScript 中,函数有很多不同的写法,如果以版本划分,在 ES6 之前,我们称之为普通函数,在 ES6 之后新增了一种函数 —— 箭头函数,函数在功能上相似,但是,在某些方面,不同的写法有不同的特性,普通函数/方法内 this 属性取值默认是调用此函数/方法的实例对象,不过箭头函数内 this 属性总是上一级作用域内 this 属性,而非调用此函数的实例对象,因此,不能通过 bind / call / apply 方法替换 this 属性取值

注意事项(DOM、BOM)

DOM

DOM 树

在 DOM(Document Object Model,文档对象模型)中,将一个网页的所有内容以树的形式存储并呈现,内容如下:

  • 所有 HTML 元素是元素节点
  • 所有 HTML 属性是属性节点
  • HTML 元素内的文本是文本节点
  • 注释是注释节点

此外,在 JavaScript 的 DOM 中,存在元素与节点之分,元素仅包含 HTML 元素,而节点则包含 HTML 元素、属性、文本、注释

属性

在 JavaScript 中,通过 DOM 动态添加或修改某个节点的属性时,可以使用 DOM 节点提供的属性访问,也可以使用 DOM 节点提供的 setAttribute 方法,区别在于使用 setAttribute 方法可以为某个节点添加或修改一个自定义属性

如果 HTML 标签中的属性名称与取值相同,那么 DOM 节点将返回 true,如果 HTML 标签中的属性名称与取值不同,那么 DOM 节点将返回 false

此处的属性是指类似于 input 标签 checkbox 类型的 checked 属性

内容

在 JavaScript 中,通过 DOM 动态添加或修改某个节点的内容时,可以使用 DOM 节点提供的属性访问,方式如下:

  • innerHTML
  • innerText
  • contentText

上述属性的区别之一在于,在添加或修改的内容中包含标签时,innerHTML 将内容中包含的标签作为实际的标签使用,innerText 和 contentText 将内容中包含的标签作为文本使用,以下是另外的注意点:

  • 使用 DOM 动态获取表单输入框中的内容时,必须访问 DOM 对象的 value 属性,而不是 innerHTML、innerText、contentText
样式

在 JavaScript 中,通过 DOM 动态添加或修改某个节点的样式时,通常是使用 DOM 节点提供的 style 属性访问,不过此方式只能添加、修改和访问行内样式,不能访问样式表中的样式,如果必须访问某个节点的样式表样式,可以使用 window 的 getComputedStyle 方法,也可以使用 DOM 节点的 currentStyle 方法

  • getComputedStyle
    • 宽高样式不包括边框与内边距
    • 不仅可以获取行内样式,也可以获取样式表样式
    • 属性只读,即不能修改
    • 存在兼容性问题,高级浏览器可用
  • currentStyle
    • 宽高样式不包括边框与内边距
    • 不仅可以获取行内样式,也可以获取样式表样式
    • 属性只读,即不能修改
    • 存在兼容性问题,低级浏览器可用
  • style
    • 宽高样式不包括边框与内边距
    • 尽可以获取行内样式,不可以获取样式表样式
    • 属性即可以读取,也可以修改
    • 不存在兼容性问题
家族
  1. offsetXXX

在 DOM 节点中,存在 offsetWidth 和 offsetHeight 属性,特性如下:

  • 宽高样式包括边框和内边距
  • 不仅可以获取行内样式,也可以获取样式表样式
  • 属性只读,即不能修改
  • 不存在兼容性问题

此外,DOM 节点存在 offsetLeft 和 offsetTop 属性,此属性获取当前节点相对于第一个具有定位特性的祖先节点的偏移位,如果不存在,则以 body 相对偏移,offsetParent 属性与 offsetLeft 类似,不过获取的是节点,而非偏移量

  1. clientXXX

在 DOM 节点中,存在 clientWidth 和 clientHeight 属性,特性如下:

  • 宽高样式只包括内边距
  • 不仅可以获取行内样式,也可以获取样式表样式
  • 属性只读,即不能修改
  • 不存在兼容性问题

此外,DOM 节点存在 clientLeft 和 clientTop 属性,此属性分别获取当前节点的左边框和上边框

  1. scrollXXX

在 DOM 节点中,存在 scrollWidth 和 scrollHeight 属性,当内容没有超出元素范围时,属性等价于 clientWidth 和 clientHeight,当内容超出元素范围时,宽高样式不仅包括内边距,同时包括内容超出的范围,此外,DOM 节点存在 scrollLeft 和 scrollTop 属性,当内存由于超出范围而可以滑动时,此属性表示在水平方向和垂直方向移出内边距的偏移量

事件

在 JavaScript 中,通过 DOM 动态添加某个节点的事件属性时,如果为若干节点添加的事件函数基本相同,那么可以独立写一个函数,从而使节点的事件属性均引用此函数,以提高程序性能

默认情况下,可以为一个 DOM 节点同时添加多个相同类型的事件,此时并不会关闭浏览器默认的事件,而以一定次序执行,如果在用户自定义的事件函数中返回 false,那么将关闭浏览器默认的事件

  • onclick
  • onkeydown
  • onmouseenter
  • onmouseleave
  • onmousemove
  • onmouseover
  • onmouseout
  • onfocus
  • onblur
  • onchange
  • oninput

如果使用 JavaScript 代码为 input 标签添加数据,并不会触发 oninput 事件,此外 onmouseover 与 onmouseout 具有相同的特性,即事件冒泡

添加

示例如下:

let oBtn = document.querySelector("button");
oBtn.onclick = function() {
    console.log("Reyn");
}

上述示例中添加事件方式的本质是为 DOM 节点的 onclick 属性赋予一个函数,使用此方法时如果定义多次,仅有最后一次生效

let oBtn = document.querySelector("button");
oBtn.addEventListener("click", function() {
    console.log("Reyn");
})

此方式与使用属性赋予函数的不同之处在于,使用此方式可以添加多次,且均会生效,存在兼容性问题(高级浏览器可用)

let oBtn = document.querySelector("button");
oBtn.attachEvent("click", function() {
    console.log("Reyn");
})

此方式与上一种方式类似,仅低级浏览器可用

对象

事件对象是当某个事件被触发时,由系统自动生成的一个对象,可以用于获取一些信息,例如 onkeydown 事件捕获的键入信息保存在事件对象中,也可以配置一些功能,示例如下:

document.body.onkeydown = function(event) {
    console.log(event);
    console.log(typeof event);
    console.log(event.key);
}

在低级浏览器中事件对象必须使用 window.event 的方式获取(非参数)

  • offsetX/offsetY

当事件触发时,光标相对于元素自身左上角的位置

  • clientX/clientY

当事件触发时,光标相对于浏览器可视区域左上角的位置

  • pageX/pageY

当事件触发时,光标相对于浏览器页面左上角的位置

  • screenX/screenY

当事件触发时,光标相对于显示器左上角的位置

阶段
  1. 事件捕获

当某一个事件被触发时,事件从外向内传递事件,每一个节点判断是否是自己触发了事件,如果是,即下一步,否则将事件传递到子节点中,以此类推

  1. 目标执行

执行事件绑定的回调函数

  1. 事件冒泡

当事件绑定的回调函数被执行完毕后,将从内向外传递事件,每一个节点判断自己是否在此事件中再处理,如果是,则处理,否则将事件传递到父节点中,以此类推

在三个阶段中,只有两个阶段可以同时执行,即捕获与执行或执行与冒泡,以下是一个简单示例:

<html>

<head>
	<meta charset="utf-8">
	<title>...</title>
	<style>
		.reyn {
			width: 200px;
			height: 200px;
			background-color: blue;
		}

		.morales {
			width: 100px;
			height: 100px;
			background-color: red;
		}
	</style>
</head>

<body>
	<div class="reyn">
		<div class="morales"></div>
	</div>
	<script>
		let oReyn = document.querySelector(".reyn");
        let oMorales = document.querySelector(".morales");

        // /* click morales -> result: morales reyn (bubble - default)*/

        oReyn.addEventListener("click", function(){
            console.log("reyn");
        })
        oMorales.addEventListener("click", function() {
            console.log("morales");
        })

        /* click morales -> result: reyn morales (catch)*/

        // oReyn.addEventListener("click", function(){
        //     console.log("reyn");
        // }, true)
        // oMorales.addEventListener("click", function() {
        //     console.log("morales");
        // }, true)
	</script>
</body>

</html>

默认情况下,不论以何种方式添加事件,均为冒泡模式,仅通过 addEventListener 方法可以设置捕获模式,阻止事件冒泡可以通过 event 事件对象的 stopPropagation 方法,示例如下:

<html>

<head>
	<meta charset="utf-8">
	<title>...</title>
	<style>
		.reyn {
			width: 200px;
			height: 200px;
			background-color: blue;
		}

		.morales {
			width: 100px;
			height: 100px;
			background-color: red;
		}
	</style>
</head>

<body>
	<div class="reyn">
		<div class="morales"></div>
	</div>
	<script>
		let oReyn = document.querySelector(".reyn");
        let oMorales = document.querySelector(".morales");

        // /* click morales -> result: morales (bubble - default)*/

        oReyn.onclick = function() {
            console.log("reyn");
        }
        oMorales.onclick = function(event) {
            event.stopPropagation();
            console.log("morales");
        }
	</script>
</body>

</html>

方法 stopPropagation 仅支持高级浏览器,低级浏览器使用 cancelBubble 方法实现阻止冒泡

闭包

闭包是一种特殊的函数,如果在一个函数的内部的函数中使用了此函数内的数据时,那么内部的函数就是闭包,即必须满足如下条件:

  1. 函数嵌套
  2. 内函数使用外函数的数据

闭包的一个特点是,只要闭包还在使用外函数的数据,那么外函数的数据就一直不会被释放,即可以延长外函数数据的生命周期,此外,必须注意的是,不再使用闭包时,必须将闭包设置为 null,否则将出现内存泄漏

示例如下:

function fnc() {
    var name = "Reyn";
    return function demo() {
        console.log(name);
    }
}

let rtn = fnc();
fnc();  // Reyn
console.log(name);
循环索引同步

在 JavaScript 中,使用循环为 DOM 对象动态添加事件属性时,在事件函数中使用了循环索引时,必须注意循环索引同步,由于此类事件函数通常定义在在循环体内部,而调用事件函数(事件触发)通常则发生在循环体外部,此时可能导致一些问题

var

在 JavaScript 中,在非函数作用域中使用 var 定义的均为全局变量,可能存在如下情形:

for (var i = 0; i < 3; i++) {
    function demo() {
        console.log(i);
    }
}

console.log(i); // 3

demo(); // 3

在上述示例中,由于 i 为全局变量,因此在循环体外部可以访问 i,此时为循环结束时 i 最后的值,由于 demo 也为全局变量,而由于循环的存在定义了多次,不过仅有最后一次定义生效,不论定义了多少次,在函数体中访问 i 时,由于局部作用域中不存在,所以使用全局作用域中 i 最后的值,以下是实现循环索引同步的示例:

for (var i = 0; i < 3; i++) {
    function demo() {
        console.log(i);
    }
    demo(); // 0 1 2
}
for (var i = 0; i < 3; i++) {
    (function demo() {
        console.log(i);
    })();   // 0 1 2
}
for (var i = 0; i < 3; i++) {
    (function demo(val) {
        console.log(val);
    })(i);  // 0 1 2
}
let

在 JavaScript 的 ES6 之后,使用 let 定义变量,使用循环时,可能出现如下情形:

for (let i = 0; i < 3; i++) {
    function demo() {
        console.log(i);
    }
}

// console.log(i); // Error

demo(); // 2

在上述示例中,由于使用 let 定义的变量在块级作用域中为局部变量,因此不能在循环体外部访问 i,除此之外,使用 let 定义循环索引时,在每执行完一次循环体之后,均会重新定义一个循环索引,即每一次执行循环体时,均有一个属于自己的循环索引,以下是一个例子:

let list = [];
for (let i = 0; i < 3; i++) {
    let fn = function () {
        console.log(i);
    }
    list.push(fn);
}
list[0]();  // 0
list[1]();  // 1
list[2]();  // 2

此外,在上上个例子中,在循环体外部调用 demo 函数时,说明可以访问 let 循环索引,亦即:

ES6 之后,在块级作用域内定义函数,且此函数引用了块级作用域中的数据,那么此函数即为闭包

BOM

常见对象
  • window

浏览器窗口

  • Navigator

浏览器信息

  • Location

地址栏信息

  • History

历史信息

  • Screen

屏幕信息

网页大小

在高级浏览器中,使用 window.innerXXX 属性,在低级浏览器中(IE9 以下)且以标准模式渲染(CSS1Compat),使用 document.documentElement.clientXXX 属性,以混杂模式渲染,使用 document.body.clientXXX 属性

可以使用 document.compatMode 属性获取浏览器的渲染模式

移出距离

在高级浏览器中,使用 window.pageXXXOffset 属性,在低级浏览器中(IE9 以下)且以标准模式渲染(CSS1Compat),使用 document.documentElement.scrollXXX 属性,以混杂模式渲染,使用 document.body.scrollXXX 属性

函数防抖

函数防抖用于优化高频执行 JavaScript 代码,可以使被调用函数在一次连续的高频操作中只被调用一次,示例如下:

let oInput = document.querySelector("input[type=text]");

let timer = null;
oInput.oninput = function() {
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
        console.log("Reyn");
    }, 1000);
}

在上述方式的执行过程中,如果在每一秒用户连续输入,那么事件执行的延迟事件将被延长,直到用户停止输入

函数节流

函数防抖用于优化高频执行 JavaScript 代码,可以使被调用函数在一次连续的高频操作中只被调用若干次,示例如下:

let oInput = document.querySelector("input[type=text]");

let timer = null, isContinue = true;
oInput.oninput = function() {
    if (!isContinue) {
        return;
    }
    isContinue = false;
    timer && clearTimeout(timer);
    timer = setTimeout(() => {
        isContinue = true;
        console.log("Reyn");
    }, 1000);
}

在上述方式的执行过程中,如果在若干秒内用户连续输入,当事件首次开启计时之后,事件将无法持续关闭和开启,只有当首次开启的事件执行之后,才能开启下一个计时器,即事件将以每个固定时间执行一次

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值