Skip to content

Document 对象:连接 BOM 与 DOM 的桥梁

Document 对象的特殊地位

在浏览器的对象体系中,document 对象扮演着一个独特的角色——它既是 BOM 的一部分,也是 DOM 的入口。如果把浏览器比作一座大厦,window 是整栋建筑的总控制室,那么 document 就是连接控制室和每个房间(网页内容)的中枢系统。

通过 window.document 访问,document 对象代表当前加载在浏览器窗口中的 HTML 文档。它提供了:

  • 访问文档信息(标题、URL、域名、来源等)
  • 文档状态管理(加载状态、就绪状态等)
  • 文档元素集合(表单、图片、链接等)
  • DOM 操作的入口(getElementById、querySelector 等)

文档信息属性

document 对象提供了一系列属性来获取当前文档的各种信息。

基本文档信息

javascript
// 文档标题
console.log(document.title); // 页面的 <title> 内容
// 可以修改
document.title = "新标题";

// 完整的 URL
console.log(document.URL);
// 例如: 'https://example.com/page.html?id=123#section'

// 域名
console.log(document.domain);
// 例如: 'example.com'

// 来源页面(referrer)
console.log(document.referrer);
// 用户从哪个页面跳转过来的 URL

// 最后修改时间
console.log(document.lastModified);
// 例如: '12/29/2025 08:57:20'

// 字符集
console.log(document.characterSet); // 通常是 'UTF-8'

// 文档兼容模式
console.log(document.compatMode);
// 'CSS1Compat' (标准模式) 或 'BackCompat' (怪异模式)

实际应用:文档信息收集

javascript
class DocumentInfo {
  static getAll() {
    return {
      title: document.title,
      url: document.URL,
      domain: document.domain,
      referrer: document.referrer || "直接访问",
      lastModified: document.lastModified,
      charset: document.characterSet,
      mode: document.compatMode,
      readyState: document.readyState,
    };
  }

  static display() {
    const info = this.getAll();

    console.log("=== 文档信息 ===");
    Object.entries(info).forEach(([key, value]) => {
      console.log(`${key}: ${value}`);
    });
  }

  static logPageView() {
    const pageView = {
      title: document.title,
      url: document.URL,
      referrer: document.referrer,
      timestamp: new Date().toISOString(),
      userAgent: navigator.userAgent,
    };

    // 模拟发送到分析服务器
    console.log("记录页面访问:", pageView);

    // 实际应用中使用 fetch 发送数据
    /*
    fetch('/api/analytics/pageview', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(pageView)
    });
    */
  }
}

// 使用
DocumentInfo.display();
DocumentInfo.logPageView();

文档状态属性

readyState - 文档加载状态

document.readyState 表示文档的加载状态:

javascript
console.log(document.readyState);
// 可能的值:
// 'loading'    - 文档正在加载
// 'interactive' - 文档已解析,但子资源(图片、样式等)仍在加载
// 'complete'   - 文档和所有子资源都已加载完成

// 监听状态变化
document.addEventListener("readystatechange", () => {
  console.log("文档状态:", document.readyState);

  if (document.readyState === "interactive") {
    console.log("DOM 已就绪,可以操作 DOM 元素");
  }

  if (document.readyState === "complete") {
    console.log("页面完全加载,包括所有图片和样式");
  }
});

实际应用:智能初始化管理器

javascript
class PageInitializer {
  constructor() {
    this.tasks = {
      domReady: [], // DOM 就绪时执行
      pageLoaded: [], // 页面完全加载后执行
    };

    this.init();
  }

  init() {
    // 检查当前状态
    if (document.readyState === "loading") {
      // 如果还在加载,监听事件
      document.addEventListener("DOMContentLoaded", () => {
        this.runTasks("domReady");
      });

      window.addEventListener("load", () => {
        this.runTasks("pageLoaded");
      });
    } else if (document.readyState === "interactive") {
      // DOM 已就绪,但资源还在加载
      this.runTasks("domReady");

      window.addEventListener("load", () => {
        this.runTasks("pageLoaded");
      });
    } else {
      // 页面已完全加载
      this.runTasks("domReady");
      this.runTasks("pageLoaded");
    }
  }

  onDOMReady(callback) {
    this.tasks.domReady.push(callback);
    return this;
  }

  onPageLoaded(callback) {
    this.tasks.pageLoaded.push(callback);
    return this;
  }

  runTasks(phase) {
    console.log(`执行 ${phase} 阶段任务`);

    this.tasks[phase].forEach((callback) => {
      try {
        callback();
      } catch (error) {
        console.error(`${phase} 任务执行失败:`, error);
      }
    });

    // 清空已执行的任务
    this.tasks[phase] = [];
  }
}

// 使用示例
const initializer = new PageInitializer();

initializer
  .onDOMReady(() => {
    console.log("DOM 就绪,初始化表单验证");
    // 初始化不需要图片的功能
  })
  .onDOMReady(() => {
    console.log("DOM 就绪,绑定事件监听器");
  })
  .onPageLoaded(() => {
    console.log("页面完全加载,启动图片懒加载");
    // 初始化需要图片的功能
  })
  .onPageLoaded(() => {
    console.log("页面完全加载,启动性能监控");
  });

文档元素访问

document 对象提供了快捷方式来访问文档的关键元素。

文档根元素

javascript
// <html> 元素
console.log(document.documentElement);
// 返回: <html>...</html>

// 获取 HTML 元素的语言属性
console.log(document.documentElement.lang); // 例如: 'zh-CN'

// <head> 元素
console.log(document.head);
// 返回: <head>...</head>

// <body> 元素
console.log(document.body);
// 返回: <body>...</body>

// 实际应用:添加全局 class
function setTheme(theme) {
  document.documentElement.setAttribute("data-theme", theme);

  // 或者
  document.documentElement.classList.remove("theme-light", "theme-dark");
  document.documentElement.classList.add(`theme-${theme}`);
}

setTheme("dark"); // 应用暗色主题

activeElement - 当前焦点元素

javascript
// 获取当前拥有焦点的元素
console.log(document.activeElement);

// 检查特定元素是否有焦点
function hasFocus(element) {
  return document.activeElement === element;
}

// 实际应用:焦点管理
class FocusManager {
  static trackFocus() {
    document.addEventListener("focusin", (event) => {
      console.log("焦点移到:", event.target);
      console.log("当前焦点元素:", document.activeElement);
    });

    document.addEventListener("focusout", (event) => {
      console.log("焦点离开:", event.target);
    });
  }

  static trapFocus(containerSelector) {
    const container = document.querySelector(containerSelector);

    if (!container) return;

    const focusableElements = container.querySelectorAll(
      'a[href], button, textarea, input, select, [tabindex]:not([tabindex="-1"])'
    );

    const firstElement = focusableElements[0];
    const lastElement = focusableElements[focusableElements.length - 1];

    container.addEventListener("keydown", (event) => {
      if (event.key !== "Tab") return;

      if (event.shiftKey) {
        // Shift + Tab
        if (document.activeElement === firstElement) {
          event.preventDefault();
          lastElement.focus();
        }
      } else {
        // Tab
        if (document.activeElement === lastElement) {
          event.preventDefault();
          firstElement.focus();
        }
      }
    });
  }
}

// 使用:在模态框中限制焦点
FocusManager.trapFocus(".modal");

文档元素集合

document 对象提供了几个预定义的集合,可以快速访问特定类型的元素。

forms - 表单集合

javascript
// 获取所有表单
console.log(document.forms); // HTMLCollection

// 通过索引访问
const firstForm = document.forms[0];

// 通过 name 或 id 访问
const loginForm = document.forms["loginForm"];
// 或
const loginForm2 = document.forms.loginForm;

// 遍历所有表单
Array.from(document.forms).forEach((form, index) => {
  console.log(`表单 ${index}:`, form.name || form.id);
});

// 实际应用:表单验证管理器
class FormValidator {
  static validateAll() {
    const results = [];

    Array.from(document.forms).forEach((form) => {
      const isValid = this.validateForm(form);
      results.push({
        form: form.name || form.id,
        valid: isValid,
      });
    });

    return results;
  }

  static validateForm(form) {
    // 简单的验证示例
    const requiredFields = form.querySelectorAll("[required]");

    for (const field of requiredFields) {
      if (!field.value.trim()) {
        console.log(`表单 ${form.name}: 字段 ${field.name} 为空`);
        return false;
      }
    }

    return true;
  }

  static setupAutoValidation() {
    Array.from(document.forms).forEach((form) => {
      form.addEventListener("submit", (event) => {
        if (!this.validateForm(form)) {
          event.preventDefault();
          alert("请填写所有必填字段");
        }
      });
    });
  }
}

// 使用
FormValidator.setupAutoValidation();

images - 图片集合

javascript
// 获取所有图片
console.log(document.images); // HTMLCollection

// 遍历所有图片
Array.from(document.images).forEach((img, index) => {
  console.log(`图片 ${index}:`, img.src);
  console.log(`  alt:`, img.alt);
  console.log(`  尺寸:`, img.width, "x", img.height);
});

// 实际应用:图片懒加载
class LazyImageLoader {
  static init() {
    const images = Array.from(document.images);

    images.forEach((img) => {
      // 如果图片有 data-src 属性,使用懒加载
      if (img.dataset.src) {
        this.observeImage(img);
      }
    });
  }

  static observeImage(img) {
    const observer = new IntersectionObserver(
      (entries) => {
        entries.forEach((entry) => {
          if (entry.isIntersecting) {
            this.loadImage(entry.target);
            observer.unobserve(entry.target);
          }
        });
      },
      {
        rootMargin: "50px", // 提前 50px 开始加载
      }
    );

    observer.observe(img);
  }

  static loadImage(img) {
    const src = img.dataset.src;

    if (!src) return;

    console.log("加载图片:", src);

    img.src = src;
    img.removeAttribute("data-src");

    img.addEventListener("load", () => {
      img.classList.add("loaded");
    });

    img.addEventListener("error", () => {
      console.error("图片加载失败:", src);
      img.src = "/images/placeholder.png";
    });
  }

  static preloadImages(selector) {
    const images = document.querySelectorAll(selector);

    images.forEach((img) => {
      const tempImg = new Image();
      tempImg.src = img.dataset.src || img.src;
    });
  }
}

// 使用
LazyImageLoader.init();
javascript
// 获取所有带 href 属性的 <a> 和 <area> 元素
console.log(document.links);

// 遍历所有链接
Array.from(document.links).forEach((link, index) => {
  console.log(`链接 ${index}:`, link.href);
  console.log(`  文本:`, link.textContent);
});

// 实际应用:外部链接处理
class LinkManager {
  static markExternalLinks() {
    const currentDomain = window.location.hostname;

    Array.from(document.links).forEach((link) => {
      const linkDomain = new URL(link.href).hostname;

      // 如果是外部链接
      if (linkDomain && linkDomain !== currentDomain) {
        // 添加外部链接图标
        link.classList.add("external-link");

        // 在新标签页打开
        link.target = "_blank";

        // 安全属性
        link.rel = "noopener noreferrer";

        // 添加提示
        if (!link.title) {
          link.title = "在新标签页打开(外部链接)";
        }
      }
    });
  }

  static trackLinkClicks() {
    Array.from(document.links).forEach((link) => {
      link.addEventListener("click", (event) => {
        console.log("用户点击链接:", {
          href: link.href,
          text: link.textContent.trim(),
          timestamp: new Date().toISOString(),
        });

        // 发送到分析服务器
        /*
        fetch('/api/analytics/link-click', {
          method: 'POST',
          body: JSON.stringify({
            href: link.href,
            text: link.textContent.trim()
          })
        });
        */
      });
    });
  }

  static addDownloadAttributes() {
    Array.from(document.links).forEach((link) => {
      const href = link.href.toLowerCase();

      // 检查是否是文件下载链接
      const downloadExtensions = [
        ".pdf",
        ".zip",
        ".doc",
        ".docx",
        ".xls",
        ".xlsx",
      ];

      if (downloadExtensions.some((ext) => href.endsWith(ext))) {
        link.download = ""; // 触发下载
        link.classList.add("download-link");
      }
    });
  }
}

// 使用
LinkManager.markExternalLinks();
LinkManager.trackLinkClicks();
LinkManager.addDownloadAttributes();

文档写入方法

虽然不推荐在现代开发中使用,但了解这些方法仍然很重要。

document.write() 和 writeln()

javascript
// 向文档写入内容(会覆盖现有内容)
document.write("<h1>Hello World</h1>");

// writeln() 会在末尾添加换行符
document.writeln("<p>Line 1</p>");
document.writeln("<p>Line 2</p>");

// ⚠️ 注意:如果在文档加载完成后调用 write(),会覆盖整个文档
window.addEventListener("load", () => {
  // 这会清空整个页面!
  // document.write('<h1>页面被覆盖了</h1>');
});

// 更好的替代方案:使用 DOM 方法
function addContent(html) {
  const container = document.getElementById("content");
  container.innerHTML += html;
}

// 或者
function addElement(tag, content, parent = document.body) {
  const element = document.createElement(tag);
  element.textContent = content;
  parent.appendChild(element);
}

addElement("h1", "Hello World");

虽然 Cookie 管理有专门的文章,但 document.cookie 是访问 Cookie 的入口。

javascript
// 读取所有 Cookie
console.log(document.cookie);
// 例如: 'user=John; session=abc123; theme=dark'

// 设置 Cookie
document.cookie = "username=Sarah; path=/; max-age=3600";

// Cookie 管理工具类(简化版)
class CookieManager {
  static get(name) {
    const cookies = document.cookie.split("; ");
    const cookie = cookies.find((c) => c.startsWith(name + "="));
    return cookie ? decodeURIComponent(cookie.split("=")[1]) : null;
  }

  static set(name, value, options = {}) {
    let cookieString = `${encodeURIComponent(name)}=${encodeURIComponent(
      value
    )}`;

    if (options.maxAge) {
      cookieString += `; max-age=${options.maxAge}`;
    }

    if (options.path) {
      cookieString += `; path=${options.path}`;
    }

    if (options.secure) {
      cookieString += "; secure";
    }

    if (options.sameSite) {
      cookieString += `; samesite=${options.sameSite}`;
    }

    document.cookie = cookieString;
  }

  static delete(name) {
    this.set(name, "", { maxAge: -1 });
  }
}

// 使用
CookieManager.set("user", "John", { maxAge: 3600, path: "/" });
console.log("用户名:", CookieManager.get("user"));

实际应用综合示例

页面元数据管理器

javascript
class PageMetadata {
  // 设置文档标题(支持模板)
  static setTitle(title, append = false) {
    if (append) {
      document.title = `${title} | ${this.getSiteName()}`;
    } else {
      document.title = title;
    }
  }

  static getSiteName() {
    return 'TechCorp';  // 可以从配置读取
  }

  // 动态更新 meta 标签
  static setMeta(name, content) {
    let meta = document.querySelector(`meta[name="${name}"]`);

    if (!meta) {
      meta = document.createElement('meta');
      meta.name = name;
      document.head.appendChild(meta);
    }

    meta.content = content;
  }

  // 设置 Open Graph 标签
  static setOGMeta(property, content) {
    let meta = document.querySelector(`meta[property="${property}"]`);

    if (!meta) {
      meta = document.createElement('meta');
      meta.setAttribute('property', property);
      document.head.appendChild(meta);
    }

    meta.content = content;
  }

  // 添加 canonical URL
  static setCanonical(url) {
    let link = document.querySelector('link[rel="canonical"]');

    if (!link) {
      link = document.createElement('link');
      link.rel = 'canonical';
      document.head.appendChild(link);
    }

    link.href = url;
  }

  // 动态加载样式表
  static loadStylesheet(href) {
    return new Promise((resolve, reject) => {
      const link = document.createElement('link');
      link.rel = 'stylesheet';
      link.href = href;

      link.onload = () => resolve(link);
      link.onerror = () => reject(new Error(`Failed to load ${href}`));

      document.head.appendChild(link);
    });
  }

  // 动态加载脚本
  static loadScript(src, async = true) {
    return new Promise((resolve, reject) => {
      const script = document.createElement('script');
      script.src = src;
      script.async = async;

      script.onload = () => resolve(script);
      script.onerror = () => reject(new Error(`Failed to load ${src}`));

      document.head.appendChild(script);
    });
  }

  // 设置网页描述
  static setDescription(description) {
    this.setMeta('description', description);
    this.setOGMeta('og:description
', description);
  }

  // 设置网页关键词
  static setKeywords(keywords) {
    const keywordString = Array.isArray(keywords)
      ? keywords.join(', ')
      : keywords;

    this.setMeta('keywords', keywordString);
  }
}

// 使用示例
PageMetadata.setTitle('产品详情', true);
// 文档标题变为: "产品详情 | TechCorp"

PageMetadata.setDescription('这是一款创新的产品,具有多项先进功能');
PageMetadata.setKeywords(['创新', '产品', '技术']);
PageMetadata.setCanonical('https://example.com/products/123');

// 动态加载资源
async function loadResources() {
  try {
    await PageMetadata.loadStylesheet('/css/product.css');
    console.log('样式表加载成功');

    await PageMetadata.loadScript('/js/product.js');
    console.log('脚本加载成功');
  } catch (error) {
    console.error('资源加载失败:', error);
  }
}

Document 对象与 DOM 的关系

虽然 document 对象属于 BOM,但它也是 DOM 操作的起点:

javascript
// BOM 层面的 document 属性
console.log(document.URL); // BOM 功能
console.log(document.domain); // BOM 功能
console.log(document.referrer); // BOM 功能

// DOM 层面的 document 方法
const element = document.getElementById("myElement"); // DOM 操作
const elements = document.querySelectorAll(".class"); // DOM 操作
const newDiv = document.createElement("div"); // DOM 操作

// 两者的结合使用
function updatePageInfo() {
  // 使用 BOM 功能获取信息
  const title = document.title;
  const url = document.URL;

  // 使用 DOM 功能显示信息
  const infoDiv = document.getElementById("page-info");
  infoDiv.innerHTML = `
    <h3>页面信息</h3>
    <p>标题: ${title}</p>
    <p>URL: ${url}</p>
  `;
}

最佳实践

1. 检查文档就绪状态

javascript
// 好的做法:检查文档状态再执行
function initApp() {
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", runApp);
  } else {
    runApp();
  }
}

function runApp() {
  console.log("应用初始化");
  // 应用逻辑...
}

2. 谨慎使用 document.write()

javascript
// ❌ 避免这样做
document.write("<div>内容</div>");

// ✅ 使用 DOM 方法
const div = document.createElement("div");
div.textContent = "内容";
document.body.appendChild(div);

// ✅ 或使用 innerHTML(如果信任内容来源)
document.getElementById("container").innerHTML = "<div>内容</div>";

3. 优化集合访问

javascript
// ❌ 每次都访问 DOM
for (let i = 0; i < document.images.length; i++) {
  console.log(document.images[i].src);
}

// ✅ 缓存集合
const images = Array.from(document.images);
images.forEach((img) => {
  console.log(img.src);
});

小结

document 对象是连接 BOM 和 DOM 的重要桥梁。它提供了:

文档信息:

  • 基本信息(title, URL, domain, referrer
  • 文档状态(readyState, compatMode

元素访问:

  • 根元素(documentElement, head, body
  • 焦点元素(activeElement
  • 元素集合(forms, images, links

文档操作:

  • 写入方法(write(), writeln(),不推荐使用)
  • Cookie 访问(document.cookie

最佳实践:

  1. 检查文档就绪状态再执行代码
  2. 使用 DOM 方法替代 document.write()
  3. 缓存集合引用以提高性能
  4. 合理利用文档信息进行页面分析