JS

JavaScript 需要理清的概念

By John Han
Picture of the author
Published on
async 异步

JavaScript 需要理清的概念

本文主要讨论原生 JS 对 HTMLEmement 创建、选择、设置、删除等。

1.基本概念

1.1 JavaScript 实现

JS组成
JS组成

JavaScript 包括:

  • ECMAScript: 我们写的 JS 代码,由 ECMA-262 定义并提供核心功能
  • BOM: 提供与浏览器交互的方法和接口
  • DOM: 提供与网页内容相交互的方法和接口

所有 JS 相关的问题都逃不出这三点。

1.2 Window 和 window

console.log(Window)
// 输出
ƒ Window() { [native code] }
// 结论:Window 是个浏览器实现的接口函数

console.log(window)
// 输出
Window {0: Window, window: Window, self: Window, document: document, name: "", location: Location, …}
// 结论:window 是个Window 的实例

console.log(window.window === window.self === window)
// 输出
true
// 结论:window 是个全局变量,且window.window 和 window.self 都指向它本身

console.log(window.document === document)
// 输出
true
// 结论: window的属性是全局变量

结论


  • Window 是浏览器实现的接口,是个构造函数

  • window 是 Window 的一个实例

  • window.window 和 window.self 都指向 window 自身

  • window 的属性是全局变量,所以可以直接打印出 document

1.3 Document 和 document

console.log(Document)
// 输出
ƒ Document() { [native code] }
// 结论: Document 是浏览器接口函数

console.log(document)
// 输出
#document
// 结论:document 是当前窗口内的文档节点

console.log(document instanceof Document)
// 输出
true
// 结论:document 是 Document的实例,它继承了 Document 的属性

定义

Document接口表示任何在浏览器中载入的网页,并作为网页内容的入口,也就是DOM 树。

document是当前窗口内的文档节点。

特性

Document 接口描述了任何类型的文档的通用属性与方法。

根据不同的文档类型(例如 HTML、XML、SVG,...),还能使用更多 API:使用 "contentType = text/html"的 HTML 文档,还实现了 HTMLDocument 接口。

通俗点说就是 Document 接口有操作文档的方法和属性,例如:在 HTML 文档中,document 的构造函数是 HTMLDocument,而 HTMLDocument 继承与 Document.

验证一下我们上面的说法:

console.log(document.__proto__.constructor)
// 输出
ƒ HTMLDocument() { [native code] }

console.log(document.__proto__.__proto__.constructor)
// 输出
ƒ Document() { [native code] }

继承关系:

document继承关系图
document继承关系图

Document 的属性和方法

1> 属性

  • Document.body
  • Document.contentType
  • Document.scripts

2> 继承自 HTMLDocument HTML的 Document 接口继承自 HTMLDocument 接口(从 HTML5 扩展):

  • Document.cookie
  • Document.domain
  • Document.URL
  • Document.title
  • ...

3> 继承自 Node 的属性

  • Document.nextSibling
  • Document.firstChild
  • Document.nodeName:返回一个包含该节点名字的 DOMString,Text 节点对应的是 '#text' 还有 Document 节点对应的是 '#document'。
  • Document.parentNode
  • ...

4> 继承自 Node 和 EventTarget 的方法

Document 的方法: 该接口同样继承了 Node 和 EventTarget 接口。

  • Document.addEventListener()
  • Document.createAttribute()
  • Document.createElement()
  • Document.createTextNode()
  • Document.getElementsByClassName()
  • Document.hasStorageAccess()
  • ...

5> 实例 document 继承自 ParentNode 方法

Document 的实例 document从 ParentNode 继承的拓展:

  • document.getElementById(String id)
  • document.querySelector()
  • document.querySelectorAll()
  • document.createExpression()
  • document.createNSResolver()
  • document.evaluate()

6> 监听事件 Event Handle

  • Document.oncopy
  • Document.onselectionchange
  • Document.onfullscreenchange
  • ...

还有很多 HTML5 的拓展等相关的属性和方法,实在太多了,用到就去找吧。

1.3 Node 和 Element

元素继承于节点,节点是 DOM 概念,元素是 HTML 概念。

先来看一下不完全的接口继承图:

浏览器文档和元素部分继承关系
浏览器文档和元素部分继承关系

可以看出:

HTMLElement 接口表示所有的 HTML 元素。

HTMLElement 继承自父接口 Element 和 GlobalEventHandlers。具体看这里

示例:

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <div id="root" onClick="console.log(2)">
      <span>Hello, world!
    </div>
  </body>
  <script>
    let el = document.getElementById("root");
    // 看一下 <div id="root">节点 的 nodeName 属性
    console.log(el.nodeName);
    // 输出	"DIV"

    // 看一下 <div id="root"></div>元素 的 tagName 属性
    console.log(el.tagName);
    // 输出	"DIV"

    // 看一下 document节点 的 nodeName 属性
    console.log(document.nodeName);
    // 输出	"#document"

    // 看一下 document节点 有没有 tagName属性
    console.log(document.tagName);
    // 输出	"undefined"
  </script>
</html>

结论:

document 是节点,不是元素。

div 即是节点,又是元素。


2.DOM

DOM 是 Document Object Model 的缩写,全称文档对象模型。

文档对象模型 (DOM) 将 web 页面与脚本或编程语言连接起来。

2.1 什么是 DOM?

DOM 模型用一个逻辑树来表示一个文档,树的每个分支的终点都是一个节点(node),每个节点都包含着对象(objects)。

DOM 的方法(methods)让你可以用特定方式操作这个树,用这些方法你可以改变文档的结构、样式或者内容。

节点可以关联上事件处理器,一旦某一事件被触发了,那些事件处理器就会被执行。

一个 web 页面是一个文档。

2.2 DOM 和 JavaScript

JavaScript 使用 document 或 window 元素的API来操作文档本身或获取文档的子类。

DOM 的方法和属性主要是用来操作元素。

DOM 操作示例:

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Document</title>
</head>
<body>
  <div
    id="root"
    class="root"
    title="iamdiv"
    onclick="console.log('click div#root')"
    style="font-size: 16px; background-color: yellowgreen;"
  >
    <span>Hello, world!
    <p class="remove">I will be remove!</p>
    <br />
    <a>我是链接</a>
    <br />
  </div>
</body>
<script>
  // ------------------------------DOM 查询操作-----------------------------
  // getElementById方法由 Document接口扩展于 ParentNode接口,Document <- document
  // ParentNode 是个原始接口,混合了所有(拥有子元素的) Node 对象包含的共有方法和属性。
  // https://developer.mozilla.org/zh-CN/docs/Web/API/Document 查看getElementById属性
  let el = document.getElementById("root");

  // getElementsByTagName方法来自 Document 接口
  let spanList = document.getElementsByTagName("span");
  let span1 = spanList && spanList[0];

  // getElementsByClassName 方法来自 Document 接口
  let removes = document.getElementsByClassName("remove");
  let remove1 = removes && removes[0];

  // querySelector 方法来自 Element, Element <- HTMLElement
  let a = el.querySelector("a");

  // ------------------------------DOM 新增操作-----------------------------
  // createElement方法来自于Document接口, Document <- document
  let p1 = document.createElement("p");

  // createTextNode方法来自于Document, Document <- document
  let textP1 = document.createTextNode("Hello, boy");

  // appendChild方法来自于Node, Node <-- Element <- HTMLElement
  // 因为 textP1 不是对文档中现有节点的引用,appendChild方法会将其附加到p1的末尾处
  p1.appendChild(textP1);

  // append方法来自于 ParentNode, ParentNode <- Element <- HTMLElement
  // 和appendChild 区别是:appendChild 只能接收 Node 参数,append 可以接收多个节点和字符串
  el.append(p1, "More Text");

  // insertBefore方法来自于 Node接口,Node <- Element <- HTMLElement
  // 因为 p1 是对文档中现有节点的引用,insertBefore() 会将其从当前位置移动到新位置
  el.insertBefore(p1, span1);

  // ------------------------------DOM 修改操作-----------------------------
  // setAttribute方法设置元素的属性, Element <-- setAttribute
  p1.setAttribute("class", "p1");
  el.setAttribute("title", "iAmDivTitle");
  // 直接修改某个属性
  p1.style.color = "red";

  // 获取文本内容
  let span1Text = span1.textContent;
  // 修改文本

  span1.textContent = span1Text + "Add Something Text";

  // 添加一个默认的“冒泡”阶段的监听事件
  // addEventListener方法来自 EventTarget,
  function evClickP(e) {
    e.stopPropagation();
    console.log("click p1");
  }
  p1.addEventListener("click", evClickP, false);

  // 阻止默认事件
  a.addEventListener("click", (e) => {
    e.preventDefault;
    console.log("a的默认跳转事件被阻止了!");
  });

  // ------------------------------删除操作-----------------------------
  // 删除节点, Node <- Element <- HTMLElement
  el.removeChild(remove1);
  //等同于
  // remove 方法来自于 ChildNode, ChildNode <- Element <- HTMLElement
  // remove 方法不兼容IE,需要polyfill https://developer.mozilla.org/zh-CN/docs/Web/API/ChildNode/remove#Polyfill
  // remove1.remove();

  // 删除节点属性
  el.removeAttribute("class");

  // 删除事件
  // 删除使用 EventTarget.addEventListener() 方法添加的事件
  // 注意 “事件捕获”和“事件冒泡” 不匹配是无法删除的
  p1.removeEventListener("click", evClickP);

  // 删除元素自带属性
  el.onclick = null;
</script>
</html>

3.BOM

Stay Tuned

Want to become a Next.js pro?
The best articles, links and news related to web development delivered once a week to your inbox.