这是我参与更文挑战的第11天,活动详情查看:更文挑战
原创声明
文章曾被多个网站转载,特此声明一下,图灵社区原文和掘金本文,均为本人创作,其余出处为他人转载。
本篇为学习《JavaScript DOM编程艺术》的学习笔记。
前言
在JavaScript引擎正式编译之前,会进行一次预编译,在这个过程中,会将变量声明及函数声明提升至当前作用域的最顶端,其后才进行接下来的处理。
变量提升
未使用块级作用域:
源代码:
console.log(msg);// undefined
var msg = "This is a message";
console.log(msg);// This is a message
function fn () {
console.log(hello); // undefined
var hello = 'hello world';
console.log(hello); // hello world
}
fn();
复制代码
JavaScript解析器的解析流程:
var msg;// 变量提升,全局作用域范围内,此时只是声明,并没有赋值
console.log(msg);// undefined
msg = "This is a message";// 此时才赋值
console.log(msg);// 打印出This is a message
function fn () {
var hello;// 变量提升,函数作用域范围内,此时只是声明,并没有赋值
console.log(hello); // undefined
hello = 'hello world';// 此时才赋值
console.log(hello); // hello world
}
fn();
复制代码
ES5只有全局作用域和函数作用域,而ES6开始提供了块级作用域。使用ES6语法的let创建的变量和const语法创建的常量,均不存在变量提升。
使用块级作用域:
console.log(msg);//Uncaught ReferenceErrord
let msg = "This is a message";//没有被执行
console.log(msg);//没有被执行
function fn () {
console.log(hello); //Uncaught ReferenceErrord
let hello = 'hello world'; //没有被执行
console.log(hello); //没有被执行
}
fn();
复制代码
IIFE与块级作用域
一般的JavaScript函数有三种写法。
1.函数关键字,也叫函数声明语句写法
function foo(){}; foo();
复制代码
2.函数字面量,也叫函数表达式写法
var foo = function(){}; foo();
复制代码
3.funtion()构造函数
var foo = new function(): foo();
复制代码
有时需要在定义函数之后,立即调用该函数。这种函数就叫做立即执行函数,全称为立即调用的函数表达式IIFE(Imdiately Invoked Function Expression)。 通过立即调用的函数表达式,能够实现块级作用域的效果。
JavaScript引擎规定,如果function关键字出现在行首,一律解释成函数声明语句,而且声明语句必须要有一个函数名。所以如下的代码demo会报错。
function () {}; //Uncaught SyntaxError: Unexpected token (
复制代码
正确的写法是给函数声明语句提供一个函数名。
function foo() {}; //undefined
复制代码
接下来将函数声明语句与JavaScript分组操作符进行组合,看上去这样的组合好像并没有什么意义,并且会抛出一个错误:
function foo() {} (); //Uncaught SyntaxError: Unexpected token )
复制代码
错误在于JavaScript分组操作符需要指定一个值,不能为空。
正确的组合写法:
function foo() {} (0);//0
复制代码
或
function foo() {}; (0); //0 两种写法等价。
复制代码
这样也仅仅是实现了函数声明语句与不报错的分组操作符的组合。
所以需要将函数声明语句改成函数表达式写法,将function使用分组操作符进行组合。这样就不需要再指定一个函数名。这个表达式将会在加载网页时立即执行,而不需要单独调用函数。执行后,函数内代码块声明的变量将只在局部作用域内有效。无法被外层访问到。
(function () {} () );
复制代码
或者
(function () {}) ();
复制代码
在ECMAScript6中,由于块级作用域的出现,实际上使得获得广泛应用的立即执行函数表达式(IIFE)不再必要了。
// IIFE 写法
(function () { var tmp = 0; }());
// 块级作用域写法
{ let tmp = 0; }
复制代码
函数提升
函数提升与变量提升效果一致,但JavaScript只有函数声明语句才存在函数提升.
源代码:
console.log(msg1); // function msg1() {};
console.log(msg2); // undefined
function msg1() {}
var msg2 = function() {}
复制代码
JavaScript解析器的解析流程:
function msg1() {}; // 函数提升,整个代码块提升到文件的最开始
console.log(msg1);
console.log(msg2);
var msg2 = function() {}
复制代码
参考来源:
DOM节点
节点类型
DOM节点总共有三种类型:
- 元素节点:HTML文件中所有元素,如p标签,ul标签。
- 文本节点:元素节点中的文本内容,如p标签中的hello world。
- 属性节点:标签中的属性key和value对,如img标签的src属性。
获取元素节点
通过DOM获取元素节点通常用3种DOM方法:
//获取ID为divFirst的元素节点对象
var divFirst =document.getElementById('divFirst');
//获取class为message的所有元素节点对象数组
var message = document.getElementsByClassName('message');
//通过getElementsByClassName获取的元素节点通常为值组成的数组集合,可以通过length获取数组长度
var messageLength = message.length;
//遍历所有class为message的元素节点
for(let i = 0; i < messageLength - 1;i++){
document.write(message[i]);
}
//获取所有li标签的元素节点对象数组
var li = document.getElementsByTagName('li');
//通过getElementsByTagName获取的元素节点通常为值组成的数组集合,可以通过length获取数组长度
var liLength = li.length;
//遍历所有li的元素节点
for(let i = 0; i < liLength - 1;i++){
document.write(li[i]);
}
复制代码
而新DOM标准效仿jQuery,依照CSS选择器提供了一套新的DOM获取元素节点的方法,它能做到的不仅仅是获取id,class以及元素标签,还能以各种CSS选择器的形式进行元素筛选的选取。
//获取ID为divFirst的元素节点对象
var divFirst = document.querySelector('#divFirst');
//获取Class为message的首个元素节点对象
var message = document.querySelector('.message');
//获取li标签的首个元素节点对象
var li = document.querySelector('li');
//获取class为message的所有元素节点对象数组
var message = document.querySelectorAll('.message');
var messageLength = message.length;
for(let i = 0; i < messageLength - 1;i++){
document.write(message[i]);
}
//获取所有li标签的元素节点对象数组
var li = document.querySelectorAll('li');
var liLength = li.length;
for(let i = 0; i < liLength - 1;i++){
document.write(li[i]);
}
//还能依照其它CSS选择器进行元素节点的获取
复制代码
获取和修改属性节点
//获取id为message的title属性值
var messageTitle = document.querySelector('#message').getAttribute('title');
//如果title存在,则返回title的文本值,如果不存在,则返回null
document.write(messageTitle);
//修改或设置id为message的title属性值
var messageTitle = document.querySelector('#message').setAttribute('title','This is a message');
//如果title存在,则修改title的文本值,如果不存在,则创建并设置title的文本值
document.write(messageTitle);
```
# 获取所有子节点
在一颗滑稽树(不对~节点树)上,childNodes属性可以用来获取任何一个元素的所有子元素,它是一个包含这个元素所有子元素的数组:
```javascript
//选取body元素下所有子节点
var body_element = document.getElementsByTagName("body")[0].childNodes;
//获取数组长度
var body_element_length = body_element.length;
//打印数组长度
alert(body_element_length);
复制代码
获取单个类型的所有子节点
通过childNodes属性获取到的节点子元素个数极为庞大,其实是因为文档的节点类型并非只有元素节点,它还包括一开始说到的属性节点和文本节点。
- 元素节点的nodeType属性值是1
- 属性节点的nodeType属性值是2
- 文本节点的nodeType属性值是3
通过这个规则,获取body元素内所有元素节点的方式可以这样做:
//选取body元素的所有子节点
var body_element = document.getElementsByTagName("body")[0].childNodes;
//获取数组长度
var body_element_length = body_element.length;
//获取单个类型的所有子节点
for(let i = 0; i < body_element.length;i++){
if(body_element.nodeType == 1){
document.write(body_element[i]);
}
}
复制代码
查看/修改文本节点属性值
假设有一个id为hello的p标签,含有文本hello world,利用nodeValue属性,能够获取或修改该标签的文本。
var pElement = document.querySelector('#hello').nodeValue;
//查看该文本节点的值
document.write(pElement);
复制代码
firstCHild和lastChild属性
firstChild和lastChild分别代表childNodes的首个节点和末尾节点,可说是childNodes的语义化属性。
//firstChild等同于node.childNodes[0]
var body_element = document.getElementsByTagName("body")[0].childNodes[0];
var body_element_firstChild = document.getElementsByTagName("body")[0].firstChild;
//lastCHild等同于node.childNodes[node.childNodes.length-1]
var body_element = document.getElementsByTagName("body")[0].childNodes[body_element.length-1];
var body_element_firstChild = document.getElementsByTagName("body")[0].lastChild;
复制代码
平稳退化
平稳退化的概念是在访问者访问不支持JavaScript的浏览器,仍然能过正常顺利地浏览你网站的基本功能。在2017年,前端离不开javascript的今天,可能听起来很荒谬,不过,无论是对特殊设备的访问者还是蜘蛛爬虫而言,平稳退化都不是一个过时的概念。
渐进增强
所谓渐进增强,就是用一些额外的信息层去包裹原始数据,按照“渐进增强”的原则创建的网页,几乎都符合“平稳退化原则”,简而言之,就是尽可能去语义化文本标签。