整合营销服务商

电脑端+手机端+微信端=数据同步管理

免费咨询热线:

面试官:你能基于React/Vue手写一个全局提示(Message)组件吗?

本文是我写组件设计的第十一篇文章, 今天带大家实现一个同样比较特殊的组件——全局提示(Message)组件。 我们看到的组件效果可能是这样的:

由于全局提示组件的设计原理和我上一篇写的精通React/Vue系列之手把手带你实现一个功能强大的通知提醒框(Notification)是类似的,区别主要是布局和配置参数,所以说细节和实现原理部分就不在这篇文章介绍了,本文主要介绍设计思路和设计的方法。

基础组件库主要按以下分类来划分

  • 通用型组件: 比如Button, Icon等.
  • 布局型组件: 比如Grid, Layout布局等.
  • 导航型组件: 比如面包屑Breadcrumb, 下拉菜单Dropdown, 菜单Menu等.
  • 数据录入型组件: 比如form表单, Switch开关, Upload文件上传等.
  • 数据展示型组件: 比如Avator头像, Table表格, List列表等.
  • 反馈型组件: 比如Progress进度条, Drawer抽屉, Modal对话框等.
  • 其他业务类型

熟悉以上分类法是设计任何组件系统的前提,不管你是从零到一开发前端团队的UI库,还是基于已有组件库二次开发业务组件,以上分类法则同样适用。

本文将会使用React来开发该组件,也会使用到Javascript中常用的一些设计模式,比如单例模式,但是不管你使用什么框架来实现,原理都是通用的,如果感兴趣的朋友可以用vue也实现一下。如果对设计模式不是很了解,可以参考我往前的文章。

正文

在开始组件设计之前希望大家对css3和js有一定的基础,并了解基本的react/vue语法.我们先来解构一下Message组件, 一个Message分为以下几个部分:


每一个区块都可以自定义配置, 也可以组合其他组件.我们还可以配置全局提示出现在顶部的偏移量,类似于antd的组件一样。并且我们都知道,antd或者element这种组件库,会自带一些主题状态,来提高用户的使用效率,比如会有success(成功状态),warning(警告状态),error(错误状态),info(通知状态)等,那么我们自己实现的全局提示(Message)组件也因该具备这些功能。

以下是笔者使用React实现后的Message组件效果:


接下来我们来看看通知提醒框(Message)的具体设计思路。

1. Message组件设计思路

按照之前笔者总结的组件设计原则,我们第一步是要确认需求. 通知提醒框(Message)组件一般会有如下需求点:

  • 能控制Message自动关闭的时间
  • 能配置Message渲染节点的输出位置
  • 能自定义关闭图标
  • 可以手动选择全局提示类型
  • 能自定义全局提示的偏移量
  • 能设置全局提示的信息文本
  • 能自定义全局提示的Icon
  • 全局提示点击时提供回调函数
  • 全局提示关闭时提供回调函数
  • 能手动销毁通知框

需求收集好之后,作为一个有追求的程序员, 会得出如下线框图:

具体的设计细节可以参考我的上一篇Notification组件设计的文章。

2. 基于react实现一个全局提示(Message)组件

组件的核心部分我们还是采用React Notification的模式。

2.1 搭建通知提醒框(Notification)的基本骨架

首先按照笔者的代码风格,一般会考虑组件设计的框架,然后再一步步往里面填充内容和逻辑。通过这种渐进式的设计思路,能让我们逻辑更严谨,更清晰。具体代码如下:

import Notification from 'rc-notification'
import './index.less'

const xMessage = (function() {
  let message = null
  /**
     * notice类型弹窗
     * @param {config}  object 提示框配置属性
     *   @param {type} string 提示窗类型
     *   @param {btn}  ReactNode 自定义关闭按钮
     *   @param {className}  string 自定义 CSS class
     *   @param {duration}  number 默认 4.5 秒后自动关闭,配置为 null 则不自动关闭
     *   @param {getContainer}  HTMLNode 配置渲染节点的输出位置
     *   @param {icon}  ReactNode 自定义图标
     *   @param {key}  string 当前提示唯一标志
     *   @param {content}  string|ReactNode 提示标题,必选
     *   @param {onClose}  func 点击默认关闭按钮时触发的回调函数
     *   @param {onClick}  func 点击提示时触发的回调函数
     *   @param {top}  number 消息从顶部弹出时,距离顶部的位置,单位像素
     *   @param {closeIcon}  ReactNode 自定义关闭图标
     */
  const pop = (config) => {
    const {
      type, className,
      duration = 4.5,
      getContainer = () => document.body,
      icon, key, content, onClose, onClick,
      top, closable = true, closeIcon
    } = config
    message.notice({
      content: <div className={classnames('xMessage', className )}>
        <div className={classnames('iconWrap', type)}>
            <Icon type={iconType[type]} />
          </div>
        <div className="xNoticeTit">
          { content }
        </div>
      </div>,
      key, closable, getContainer,
      onClose() {
        onClose && onClose()
      },
      onClick() {
        onClick && onClick()
      },
      closeIcon, duration, style: { top }
    })
  }

  /**
     * 提示提示组件, 全局参数
     * @param {duration} number 默认自动关闭延时,单位秒
     * @param {getContainer} HTMLNode 配置渲染节点的输出位置,默认document.body
     * @param {closeIcon} HTMLNode 自定义关闭图标
  */
  const config = (config) => {}
  const remove = (key) => {}
  const destroy = () => {}

  if(message) {
    return {
      config, pop, remove, destroy
    }
  }
  // 如果为创建实例,则创建默认实例
  Notification.newInstance({}, (notice) => message = notice)

  return {
    config, pop, remove, destroy
  }
})()

export default xMessage

首先我们根据需求把属性罗列出来, 通过分析我们因该对外提供四个接口供开发者使用,分别为: config —— Message全局配置,用来控制全局的偏移量,样式,渲染容器等; pop —— 用来创建全局提示实例的方法,同时可以控制实例的属性 remove —— 用来删除指定实例 destroy —— 用来销毁全局的Message

首先我们来实现一下config:

const config = (config) => {
  const { duration, getContainer, closeIcon } = config

  Notification.newInstance({
    getContainer: getContainer,
    duration: duration || 4.5,
    closeIcon
  }, (notice) => message = notice)
}

当然我们还可以根据自己的需求去自定义扩展。

pop方法的实现:

const pop = (config) => {
    const {
      type,
      className,
      duration = 4.5,
      getContainer = () => document.body,
      icon,
      key,
      content,
      onClose,
      onClick,
      top,
      closable = true,
      closeIcon
    } = config
    message.notice({
      content: <div className={classnames('xMessage', className )}>
        {
          (icon || ['info', 'success', 'error', 'warning'].indexOf(type) > -1) &&
          <div className={classnames('iconWrap', type)}>
            {
              icon ? icon : <Icon type={iconType[type]} />
            }
          </div>
        }
        <div className="xNoticeTit">
          { content }
        </div>
      </div>,
      key,
      closable,
      getContainer,
      onClose() {
        onClose && onClose()
      },
      onClick() {
        onClick && onClick()
      },
      closeIcon,
      duration,
      style: { top }
    })
  }

该方法主要用来自定义创建全局消息的实例,我们可以这么调用:

xNotification.pop({type: 'success', content: '你的请求被审批通过啦!'})

实际效果如下:


antd同样的方式会这么调用:

// antd
Notification.info({//...})

笔者之所以会这么做是因为info,success,warning这样的状态其实dom结构完全可以复用,所以通过配置方式可以极大的减少冗余代码。

remove和destroy方法都比较简单,我们直接上代码:

const remove = (key) => {
    message.removeNotice(key)
  }

const destroy = () => {
  message.destroy()
}

由上可以看出他们的实现都是基于message实例自带的API。

2.2 实现通知框类型type和自定义icon

首先我们先定义一个类型和icon的映射关系:

const iconType = {
    success: 'FaRegCheckCircle',
    warning: 'FaRegMeh',
    info: 'FaRegLightbulb',
    error: 'FaRegTimesCircle'
 }

这四种类型对应着不同的icon图标类型,那么我们就可以根据用户传入的类型来展示不同icon图标了:

<div className={classnames('iconWrap', type)}>
    <Icon type={iconType[type]} />
</div>

不过我们还需要考虑的一点就是如果用户传入了自定义的icon,我们理论上应该展示自定义icon,所以type因该和icon这两个属性是有联系的。还有一种情况就是如果用户即没有配置type,有没有传入icon,那么实际上是不需要显示icon的,综合考虑之后我们的代码如下:

{
  (icon || ['info', 'success', 'error', 'warning'].indexOf(type) > -1) &&
  <div className={classnames('iconWrap', type)}>
    {
      icon ? icon : <Icon type={iconType[type]} />
    }
  </div>
}

实现效果如下图:

通过以上步骤, 全局提示(Message)组件就完成了.实现方式和Notification组件有很多相似点,如果不懂的可以在评论区提问,笔者看到后会第一时间解答.

2.3 使用全局提示(Message)组件

我们可以通过如下方式使用它:

<Button type="warning" onClick={
        () => {
            message.pop({
                type: 'error',
                content: '趣谈前端学习打卡'
            })
        }
    }>错误信息通知(error)</Button>

配置全局属性:

import { message } from '@alex_xu/xui'

message.config({ duration: 6 })

笔者已经将实现过的组件发布到npm上了,大家如果感兴趣可以直接用npm安装后使用,方式如下:

npm i @alex_xu/xui

// 导入xui
import { 
  Button, Skeleton, Empty, Progress,
  Message, Tag, Switch, Drawer, Badge, Alert
} from '@alex_xu/xui'

该组件库支持按需导入,我们只需要在项目里配置babel-plugin-import即可,具体配置如下:

// .babelrc
"plugins": [
  ["import", { "libraryName": "@alex_xu/xui", "style": true }]
]

npm库截图如下:


最后

后续我将会继续实现

  • table(表格),
    tooltip(工具提示条),
  • Skeleton(骨架屏),
  • form(form表单),
  • switch(开关), 日期/日历, *
  • 二维码识别器组件

等来复盘多年的组件化之旅.

如果对于react/vue组件设计原理不熟悉的,可以参考我的之前写的组件设计系列文章.

笔我已经将组件库发布到npm上了, 大家可以通过npm安装的方式体验组件.

如果想获取组件设计系列完整源码, 或者想学习更多H5游戏, webpacknodegulpcss3javascriptnodeJScanvas数据可视化等前端知识和实战,欢迎在《趣谈前端》学习讨论,共同探索前端的边界。

时的工作中常会遇到一些系统集成的需求,需要在软件平台集成视频监控系统。而软件开发者往往不懂安防弱电系统,不知道如何在自己的软件界面中集成一些监控的实时画面。而监控厂家提供的SDK比较复杂,很难在短时间完成集成的任务。最终导致软件平台的一些功能无法实现,影响项目的质量。

本文提供的方法主要基于VLC播放器的ActiveX插件,通过这个插件,在网页中调用摄像机的RTSP流,实现图像的实时预览,音频的监听等等功能。文章以海康的IP网络摄像机为例给出具体的调用方法,供大家学习参照。

VLC软件下载

登录VLC官网 https://www.videolan.org/,选择windows(32位)版本下载。

下载VLC软件

VLC软件安装(务必勾选插件)

运行安装文件

选择软件安装位置

一定记得要勾选网页浏览器插件

完成安装

网页编辑

可选用记事本(notepad)或专业的编辑器,输入如下代码,保存为html网页文件。

<html>
<body>

<title>TESTVDEIO-1-TEST</title>
<head>
<table>
            <tbody>
            <caption>视频监控演示</caption>
        <tr>
            <td>
            <object type='application/x-vlc-plugin' pluginspage="http://www.videolan.org/" id='vlc' events='false' width="720" height="410">
    <param name='mrl' value='rtsp://admin:q66668888@172.16.200.88:554/h264/ch1/main/av_stream' />
    <param name='volume' value='50' />
    <param name='autoplay' value='true' />
    <param name='loop' value='false' />
    <param name='fullscreen' value='false' />
    <param name='controls' value='false' />
            </td>
            <td>
            <object type='application/x-vlc-plugin' pluginspage="http://www.videolan.org/" id='vlc' events='false' width="720" height="410">
    <param name='mrl' value='rtsp://admin:q66668888@172.16.200.89:554/h264/ch1/main/av_stream' />
    <param name='volume' value='50' />
    <param name='autoplay' value='true' />
    <param name='loop' value='false' />
    <param name='fullscreen' value='false' />
    <param name='controls' value='false' />
            </td>
        </tr>
         <tr>
            <td>
            <object type='application/x-vlc-plugin' pluginspage="http://www.videolan.org/" id='vlc' events='false' width="720" height="410">
    <param name='mrl' value='rtsp://admin:q66668888@172.16.200.89:554/h264/ch1/main/av_stream' />
    <param name='volume' value='50' />
    <param name='autoplay' value='true' />
    <param name='loop' value='false' />
    <param name='fullscreen' value='false' />
    <param name='controls' value='false' />
            </td>
            <td>
            <object type='application/x-vlc-plugin' pluginspage="http://www.videolan.org/" id='vlc' events='false' width="720" height="410">
    <param name='mrl' value='rtsp://admin:q66668888@172.16.200.88:554/h264/ch1/main/av_stream' />
    <param name='volume' value='50' />
    <param name='autoplay' value='true' />
    <param name='loop' value='false' />
    <param name='fullscreen' value='false' />
    <param name='controls' value='false' />
            </td>
        </tr>
    </tbody>
</table>
            </object>
</body>
</html>

代码编辑截图

海康威视RTSP调用格式


具体请参看海康专业文档

浏览器测试

先用Google Chrome浏览器测试,提示插件不支持。

Chrome浏览器提示插件不受支持

用微软IE测试,需要安装插件。

IE浏览器提示要安装ActiveX插件

确认安装插件

浏览器只显示了第一个画面。

IE浏览器显示不完整

用编辑器测试,2种内核都能正常显示。

编辑器里测试效果

改用360浏览器,呈现2X2的画面,实现最终的显示效果。

360浏览器显示的最终效果图

结语

本文参考了一些专业文章,就不一 一列出了,在这一并谢过!

由于本人水平有限,有不对的地方敬请指正。文章旨在抛砖引玉,通过讨论,相互学习,共同进步。


我是WoNew弱电蜗牛,一名从业多年的弱电工程师,在头条传播弱电专业知识和行业信息,分享工作中的经验和心得。
喜欢我的文章或视频,欢迎点赞和转发。有疑问或建议,也欢迎留言,我会尽力解答。

内容是《Web前端开发之Javascript视频》的课件,请配合大师哥《Javascript》视频课程学习。

Document 接口描述了任何类型的文档的通用属性与方法,根据不同的文档类型(例如HTML、XML)提供了不同的API,比如,使用 "text/html" 作为内容类型的HTML文档,实现了 HTMLDocument,而XML文档则实现了XMLDocument,HTMLDocument和XMLDocument接口都是继承自Document接口;

Javascript通过Document类型表示文档;在浏览器中,document对象是Document的一个实例,更具体一点,是HTMLDocument的一个实例,其表示整个HTML页面;并且document对象也是window对象的一个属性,可以将其作为全局对象来访问;因此document对象,既属于BOM又属于DOM的对象;

Document节点的特征:

  • nodeType的值为9
  • nodeName的值为#document
  • nodeValue的值为null
  • parentNode的值为null

其子节点可能是一个DocumentType(最多一个)、Element(最多一个,即html)、ProcessingInstruction或Comment;

console.log(document);  // 在FF控制台可以看出是属于HTMLDocument类型
console.log(document.nodeType); // 0
console.log(document.nodeName); // #document
console.log(document.nodeValue);  // null
console.log(document.parentNode);  // null
console.log(document.childNodes.length);  // 2

文档的子节点:

DOM标准规定Document节点的子节点可以是DocumentType、Element、ProcessingInstruction或Comment;

<!-- 这是一个comment -->
<html lang="en">
<head>
    <title>Document</title>
</head>
<body>
<script>
console.log(document.childNodes); // 使用FF查看 NodeList(3)
</script>
</body>
</html>

documentElement属性:

返回文档直接子节点,始终指向HTML页面中的<html>元素,也就是文档的根元素,并且它一定是文档的根元素;

// 注意:在HTML中的第二行有可能是注释
console.log(document.childNodes[2]);  // <html>
console.log(document.documentElement);  // <html>
console.log(document.lastChild);  // <html>

借助这个只读属性,能方便地获取到任意文档的根元素;

body属性:

作为HTMLDocument的实例,document对象还有一个body属性,直接指向<body>;

console.log(document.body);

对于一个拥有<frameset>元素的文档来说,返回的是最外层的<frameset>元素;

另外,该属性是可写的,且为该属性赋的值必须是一个<body>元素;

// 如果HTML结构为body id="oldBody",则:
console.log(document.body.id);  // oldBody
var newBody = document.createElement("body");
newBody.id = "newBody";
document.body = newBody;
console.log(document.body.id);  // newBody

head属性:

指向<head>元素,这个Document对象扩展的一个属性,类型为HTMLHeadElement;

如果有多个<head>元素,则返回第一个;

document.head 是个只读属性,为该属性赋值只会静默失败,如果在严格模式中,则会抛出TypeError异常;

需要注意的是,如果文档源代码中未显式的包含<head>和<body>元素,浏览器将隐式的创建它们;

doctype属性:

该属性是DocumentType 类型,表示了一个包含文档类型的节点对象,指向<!DOCTYPE>标签;

文档当中最多只有一个DocumentType元素;

console.log(document.doctype);  // <!DOCTYPE html>
console.log(document.doctype.nextSibling); // <html>
console.log(document.childNodes[0]); // <!DOCTYPE html>

如果存在文档声明,则将其作为document的第一个子节点,解析DOCUMENTTYPE_NODE类型,如果没有声明,则为null;

注:DocumentType对象不能动态创建,它是只读的;

查找元素(选取文档元素):

在DOM中,取得特定的某个或某组元素,并执行一些操作,这是最重要的应用了;

为了获取文档中的这些元素Element对象,DOM定义了许多方式,如:用指定的id属性、name属性、标签名、CSS类或CSS选择器;

取得元素的操作可以使用document对象的几个方法来完成;Document类型为此提供了两个方法;

getElementById()方法:

该方法接收一个参数,即要获取的元素的ID;如果找到就返回这个元素,类型是HTMLElement,如果不存在,则返回null;该参数ID是区分大小写的;

如果页面中多个元素的ID值相同,只返回文档中第一次出现的元素;

var mydiv = document.getElementById("mydiv");
console.log(mydiv);
可以封装一个通过ID查找多个元素的函数,如:
/**
* 函数接受任意多的字符串参数
* 返回一个对象
* 如果一个id对应的元素未定义,则抛出错误
*/
function getElements(/*ids...*/){
    var elements = {};  // 一个空map映射对象
    for(var i=0; i<arguments.length; i++){
        var id = arguments[i];
        var elt = document.getElementById(id);
        if(elt == null)
            throw new Error("No element with id:" + id);
        elements[id] = elt;
    }
    return elements;
}
console.log(getElements("mydiv","mylist"));
console.log(getElements("mydiv","mylist")["mydiv"]);

getElementById方法不会搜索不在文档中的元素;当创建一个元素,并且分配ID后,必须要使用appendChild、insertBefore等方法把元素插入到文档中,之后才能使用getElementById()方法获取到;

var elt = document.createElement("div");
elt.id = "myelt";
document.body.appendChild(elt);  // myelt,添加到文档对后,才能被获取到到
var el = document.getElementById("myelt");
console.log(el);  // null

getElementsByName()方法:

返回给定name 属性的所有元素NodeList集合对象;

var mytexts = document.getElementsByName("mytext");
console.log(mytexts); // dom2.html:17 NodeList(2) [p, div]

该方法定义在HTMLDocument类中,而不在Document类中,所以它只针对HTML文档可用,也就是只有HTMLDocument类型才有的方法,在XML文档中不可用;

<fieldset>
    <legend>选择你最喜欢的城市</legend>
    <ul>
        <li><input type="radio" value="北京" name="city" id="beijing" />
            <label for="beijing">北京</label></li>
        <li><input type="radio" value="南京" name="city" id="nanjing" />
            <label for="nanjing">南京</label></li>
        <li><input type="radio" value="蚌埠" name="city" id="bengbu" />
            <label for="bengbu">蚌埠</label></li>
    </ul>
</fieldset>
<script>
var citys = document.getElementsByName("city");
console.log(citys);
</script>

该方法返回的是NodeList对象,在NodeList中返回的元素按照在文档中的顺序排序的,所以可以使用方括号语法来取得每个元素;

console.log(document.getElementsByName("city")[0]);

但IE与Edge返回的是HTMLCollection;但namedItem()方法是属于HTMLCollection类型的,所以,非IE调用namedItem()方法会抛出异常;并且在IE中namedItem()只会返回第一项,因为每一项的name特性都是相同的;

console.log(citys.namedItem("city"));

为某些HTML元素设置name属性,将自动在windows对象中创建对应的属性,对Document对象也是类似的;比如,为<form>、<img>、<iframe>、<applet>、<embed>或<object>元素设置name属性,即在Document对象中创建以此name属性值为名字的属性;

<form name="myform"></form>
<script>
console.log(document.myform);
</script>

即使如此,建议不要用这种方式来获取元素,最好还是显式的使用getElementsByName()方法;

getElementsByTagName()方法:

该方法是通过标签名来获取元素,其接收一个参数,即要取得元素的标签名;返回拥有零个或多个元素的HTMLCollection集合对象,其是动态集合,与NodeList非常类似;;

var spans = document.getElementsByTagName("span");
console.log(spans); // HTMLCollection

其拥有length属性,可以通过方括号或者item()方法来访问HTMLCollection对象中的项;还拥有一个namedItem()方法,该方法可以通过元素的name属性取得集合中的项,但可以简写为使用方括号访问;即如果是数值,则调用item(),如果是字符串索引,就调用namedItem();

var divs = document.getElementsByTagName("div");
console.log(divs);
console.log(divs[0]);
console.log(divs.item(1));
console.log(divs.namedItem("innerDiv"));
console.log(divs["innerDiv"]);
console.log(images[0].src);
images.item(1).src = "images/3.jpg";

如果传入“*”,则取得页面中所有元素;(在JS和CSS中,*就是匹配所有的通配符);

var nodes = document.getElementsByTagName("*");
console.log(nodes);

会打印出页面中所有的元素,按照它们出现的顺序(没有层次之分);

在HTML中,该方法参数不需要区分大小写,而在XML中,区分大小写;

Element类也定义了getElementsByTagName()方法,其原理和Document一样,但是它只选取调用该方法的元素的后代元素;

var mydiv = document.getElementById("mydiv");
// 也可以通过getElementsByTagName来获取
// var mydiv = document.getElementsByTagName("div")[0];
var spans = mydiv.getElementsByTagName("span");
console.log(spans);

文档信息:

作为HTMLDocument的一个实例,document对象还有一些标准的Document对象所没有的属性;这些属性提供了document对象所表现的网页的一些信息;

从BOM角度来看document对象是window对象的属性,由一系列集合构成,这些集合可以访问文档的各个部分,并提供页面自身的信息,可以认为document即为加载的html文档;

document.title:包含着<title>元素中的文本,通过它,可以获取或修改当前页面的标题;

var originalTitle = document.title;
console.log(originalTitle);
document.title = "zeronetwork title";
// 标题跑马灯效果
var str="北京零点网络科技有限公司-";
document.title = str;
function titleMove(){
    str = str.substring(1,str.length) + str.substring(0,1);
    document.title = str;
}
setInterval("titleMove()",1000);

lastModified:返回文档被最后修改的日期和时间;

cookie:允许Javascript读写的HTTP cookie的特殊属性;

location:与window对象的location属性引用同一个Location对象;

URL:包含页面完整的URL;一般情况下,该属性的值与location.href 属性相同;但不包含Location对象的动态变化,即在 URL 重定向发生的时候,这个URL属性保存了文档的实际URL,不会发生变化,而 location.href会发生变化;location.href 是可写的,document.URL是只读的;

console.log(document.URL);
console.log(location.href);
document.URL = "demo.html";  // 无效
console.log(document.URL);
// location.href = "demo1.html";  // 跳转

referrer:来源页面的URL(链接到当前页面的那个页面),如果没有来源,referrer为空字符串;该属性是只读的;

console.log(document.referrer);

这个属性在分析网站SEO数据时特别有用;

var referrer = document.referrer;
var origin = location.origin;
console.log(referrer);
console.log(origin);
if(referrer){
    if(referrer.indexOf(origin) == 0){
        document.write("站内浏览");
    }else{
        document.write("从外链:" + referrer + "进入");
    }
}else{
    document.write("直接打开");
}

查找搜索关键词:

var ref = document.referrer;
console.log(ref);
if(ref.indexOf("http://127.0.0.1:5500/search.html?") == 0){
    var args = ref.substring(ref.indexOf("?") + 1).split("&");
    for(var i=0; i<args.length; i++){
        if(args[i].substring(0,2) == "q="){
            document.write("<h2>搜索的是:</h2>");
            keys = args[i].substring(2).split("+");
            for(var k in keys){
                document.write(decodeURIComponent(keys[k]) + "<br>");
            }
            break;
        }
    }
}

domain属性:获取或设置当前文档的域名;

该属性允许当Web页面之间交互时,相同域名下互相信任的Web服务器之间协作放宽同源策略安全限制,其是可读的,但有安全限制,即不能为domain赋任意值;

不能将该属性设置为URL中不包含的域;

// FF提示“The operation is insecure.”,Chrome提示不是127.0.0.1的子域名
// document.domain = "www.zeronetwork.cn";  // 异常
console.log(document.domain);

document对象的集合:

除了属性和方法,document对象还有一些特殊的集合;这些集合都是HTMLCollection对象,为访问文档的常用部分提供了快捷方式,包括:

document.anchors:包含文档中所有带name特性的<a>元素;这个属性已经从Web标准中删除了,但浏览器还支持,所以尽量不要再使用;

<!-- link.html页面 -->
<h1>零点程序员</h1>
<h2><a name="one">Web前端开发</a></h2>
<div style="height: 1000px;background-color: purple;">DIV</div>
<h2><a name="two">后端开发</a></h2>
<div style="height: 1000px;background-color: green;">DIV</div>
<h2><a name="three">苹果开发</a></h2>
<div style="height: 1000px;background-color: blue;">DIV</div>
<!-- 主页面 -->
<p>
    <input type="button" value="Web前端开发" onclick="jump('one')" />
    <input type="button" value="后端开发" onclick="jump('two')" />
    <input type="button" value="苹果开发" onclick="jump('three')" />
</p>
<script>
var win = window.open("link.html","newWin","width=250,height=150");
function jump(name) {
    console.log(name);
    if(win.document.anchors[name]){
        win.location.hash = name;
        win.focus();
    }else{
        alert("锚不存在")
    }
}
</script>

document.links:包含文档中所有带href特性的<a>元素或<area>元素;

var mydiv = document.getElementById("mydiv");
var links = document.links;
for(var i=0,len=links.length; i<len; i++){
    var aNode = document.createElement("a");
    aNode.href = links[i].href;
    var href = document.createTextNode(links[i].innerText);
    aNode.appendChild(href);
    mydiv.appendChild(aNode);
}

打开外链进行提醒:

var links = document.links;
for(var i=0,len=links.length; i<len; i++){
    var link = links[i];
    link.addEventListener("click",function(e){
        e.preventDefault();
        if(e.target.host !== location.host)
            jump(e.target.innerText, e.target.href);
        else
            location.href = e.target.href;
            
    },false);
}
function jump(title,href){
    var div = document.createElement("div");
    div.id = "jumpDiv";
    document.body.appendChild(div);
    var str = document.createTextNode("你要访问的:" + href + "不属于本网站,你要继续访问吗?");
    div.appendChild(str);
    var aGo = document.createElement("a");
    aGo.href = href;
    aGo.target = "_blank";
    aGo.appendChild(document.createTextNode("继续"));
    div.appendChild(aGo);
    var aBack = document.createElement("a");
    aBack.href = "Javascript:void(0);";
    aBack.appendChild(document.createTextNode("关闭"));
    aBack.onclick = closeJumpDiv;
    div.appendChild(aBack);
}
function closeJumpDiv(){
    var jumpDiv = document.getElementById("jumpDiv");
    if(jumpDiv)
        jumpDiv.parentNode.removeChild(jumpDiv);
}

document.applets:包含文档中所有的<applet>元素;

document.forms:包含文档中所有的<form>元素,与document.getElementsByTagName(“form”)得到的结果是相同的;

<form name="myform">
    <!-- id也可为以-->
    <input type="text" id="username" />
</form>
<form name="yourform"></form>
<script>
console.log(document.forms); // HtmlCollection
console.log(document.forms.myform);
console.log(document.forms[0]);
console.log(document.forms["myform"]);
console.log(document.forms.myform.username);
</script>

document.embeds:包含文档中所有的<embeds>元素;

document.plugins:包含文档中所有的插件;注意和navigator.plugins的区别;

console.log(document.plugins);  // HTMLCollection
console.log(navigator.plugins);  // pluginArray

document.images:包含文档中所有的<img>元素实时集合,集合中的每个元素代表了一个image元素的HTMLImageElement,与document.getElementsByTagName(“img”)得到的结果是相同的;

document.scripts:返回一个HTMLCollection对象,包含了当前文档中所有<script>元素的集合;

document.all:只读属性,返回一个HTMLAllCollection,包含了页面上的所有元素;已从Web标准中删除,但是浏览器都支持,但建议不要使用;与document.getElementsByTagName(“*”)得到的结果基本相同的;

console.log(document.all);  // HTMLAllCollection
console.log(document.getElementsByTagName("*"));  // HTMLCollection
console.log(document.all[9]);
document.all[9].innerHTML = "零点程序员";
// id或name为"mydiv"的元素,但是有区别的,为name的元素只是插入text
document.all["mydiv"].innerHTML = "<h2>零点程序员</h2>"; 

以上这些集合始终都可以通过HTMLDocument对象访问到,而且,它们都是动态的;但现在已经被 document.getElementsByTagName()所取代,部分已经废弃不应该再使用,但是仍然常常被使用,因为他们使用起来很方便;

文档写入:

document对象,可以将文本字符串写入文档流中;

write()与writeln()方法:

这两个都接受一个字符串参数,即要写入到输出流中的文本;write()会原样写入,writeln()则会在字符串的末尾添加一个换行符(\n);

这两个方法会将其字符串参数连接起来,然后将结果字符串插入到文档中调用它们的脚本元素的位置;

document.write("<h1>" + document.title + "</h1>");
document.write("<strong>" + (new Date()).toString() + "</strong>");

不光输出内容,还会解析为DOM元素;

参数可以是多种形式,如:

// 部分替换为writeln()方法
var username = "wangwei";
document.write("我的名字:" + username + "<br/>");
document.write("sum:" + (5 + 4));
document.write("<ul>");
document.write("<li>HTML</li>","<li>CSS</li>","<li>Javasc </li>");
document.write("</ul>")
var arr = ["<p>zeronetwork</p>","<p>wangwei</p>","<p>web</p>"];
document.write(arr.join(""));
function func(){
    return "func";
}
document.write(func + "<br>");
document.write(function(){
    return "Happy";
}());

只有在解析文档时才能使用write()方法输出HTML到当前文本,如果把write()方法放到一个函数内或者在文档加载结束后再调用document.write(),那么输出的内容将会重写整个页面;如:

window.onload = function(){
    document.write("零点程序员");
}

这里有个问题,理论上来说,重写了整个页面后,原页面中的所有变量和值都应该被清除,但事实却不是这样的,IE是这样的行为,但其他浏览器会保留原内容中的变量和值;

var num = 10;
function fun(){
    document.write("fun");
}
var mydiv = document.getElementById("mydiv");
window.onload = function(){
    document.write("重写了整个页面");
    console.log(num);
    fun();
    document.body.appendChild(mydiv);
}
正因为它们不兼容,所以不要在新文档流中调用原内容中的变量和值;

open()与close()方法:

可以使用write()方法在其他的窗口或框架页中来创建整个全新文档,但一般会配合close和open方法一起使用;

open()和close()分别用于打开和关闭网页的输出流;如果是在页面加载期间使用write()或writeln()方法,则不需要用到这两个方法,因为文档此时是打开状态;向一个已经加载完成的文档写入数据时,会自动调用 document.open,并且会擦除该文档的所有内容,因为已经加载完成的文档,它的文档流已经关闭,就是已经自动调用了document.close();

可以多次调用write()来逐步建立新文档的内容;传递给write的内容可能缓存起来,所以一旦完成了数据写入,建议调用 document.close()来结束该写序列,以告诉HTML解析器,文档已经达到了文件的末尾,写入的数据会被解析到文档结构模型(DOM)里,此时应该结束解析并显示新文档;

btn.onclick = function(){
    document.open();  // 可以省略
document.write("重写了整个页面");
    document.close();
}

此时,如果在close()方法后,再调用write()方法就会重写整个新窗口的内容,如:

// 在onclick事件处理函数中继续添加
document.write("文档又被重写了");

注意:无法关闭系统创建的文档流,如果,我们在全局环境中直接执行close()方法,会发现没有效果,原因就是无法关闭系统创建的文档流,我们只能关闭自己打开的文档流;

在新窗口也是同样的道理;

var oNewWin=window.open("about:blank","newwindow","width=400,width=200");
oNewWin.document.open();
oNewWin.document.write("<h1>新窗</h1>");
oNewWin.document.write("<div>这是一个新窗口</div>");
oNewWin.document.close();

在使用write()方法时,最好一次性写入;

<input type="button" id="btn" value="写入">
<script>
var newWin = null;
function makeNewWin(){
    newWin = window.open("about:blank","newWin","width=400,height=300");
}
var btn = document.getElementById("btn");
btn.onclick = function(){
    // 如果newWin不存在或已关闭,再次打开
    if(!newWin || newWin.closed)
        makeNewWin();
    newWin.focus();
    var title = "零点程序员";
    var newStr = "<!DOCTYPE html>";
    newStr += "<html><head>";
    newStr += "<title>" + title + "</title></head>";
    newStr += "<body>";
    newStr += "<h1>零点程序员</h1>";
    newStr += "<div>这是内容</div>";
    // 把body拆分成</b和ody>,否则会抛出异常
    newStr += "</b" + "ody></html>"; 
    newWin.document.write(newStr);
    newWin.document.close();
}
</script>

document.open()另类的用法:

2个参数的open()方法,语法为document.open(type, replace),如:

document.open("text/html","replace");

type指定了所需写入的数据的MIME类型,默认值为”text/html”,replace(如有设置,值为一个字符串“replace”)指定了新文档从父文档继承历史条目;

使用open()方法,在IE中会产生历史条目,其他浏览器不会产生,因此这种用法只会在IE中有效果;而如果指定了replace参数,IE就不会产生历史条目,也就是父文档的记录被覆盖了;

这种形式现在已经弃用;

3个参数的open()方法,是Window.open()的一个别名;

var doc = document.open("https://www.zeronetwork.cn/","","width=400;");// 打开新窗口
console.log(doc);  // Window
doc.document.write("<h1>零点程序员</h1>");  // 重写
doc.document.close();
// doc.close();  // 会关闭窗口

动态加载外部资源:

使用write()和writeln()两个方法,还可以动态加载外部资源,如Javascript文件等,但要注意,不能直接包含字符串“</script>”,因为会导致代码不能正确的解析;

document.write("<script type=\"text/javascript\" src=\"file.js\">" + "<\/script>");
// 或者
document.write("<script type=\"text/javascript\" src=\"file.js\">" + "</scr" + "ipt>");

这种方法经常在广告和统计功能的第三方中使用;

为什么不直接使用<script>标签,而直接使用动态载入呢?原因有两个:

一是脚本的URL不能写死,比如要动态添加一些参数,用户设备的分辨率啊,当前页面 URL 啊,防止缓存的时间戳之类的,这些参数只能先用 JS 获取到,再比如国内常见的网站统计代码:

<script>
var cnzz_protocol = (("https:" == document.location.protocol) ? " https://" : " http://");
document.write(unescape("%3Cspan id='cnzz_stat_icon_1279580898'%3E%3C/span%3E%3Cscript src='" + 
cnzz_protocol +  "s9.cnzz.com/stat.php%3Fid%3D1279580898' type='text/javascript'%3E%3C/script%3E"));
</script>

它之所以使用write()方法,而不使用<script>元素,就是为了先用 JS 判断出该用http还是https 协议;

另外,有可能需要在脚本里加载另外一个脚本文件或嵌入的JS内容有可能会修改页面内容,而这些操作只需要第三方自己维护,不需要自己的网站去维护;

在实际开发中,write()并不常用,innerHTML属性和其他DOM技术提供了更好的方法来为文档增内容;比如,可以利用innerHTML来输出内容:

var mydiv = document.getElementById("mydiv");
mydiv.innerHTML = "<h1>零点程序员</h1><p>开设了Web前端课程</p>";
// 重写body内所有内容
document.body.innerHTML = "<h1>零点程序员</h1><p>开设了Web前端课程</p>";
包装一个对象,类似于write()和close()方法:
// 为设置元素的innerHTML定义简单的"流式"API
function ElementStream(elt){
    if(typeof elt === "string")
        elt = document.getElementById("elt");
    this.elt = elt;
    this.buffer = "";
}
// 连接所有的参数,添加到缓存中
ElementStream.prototype.write = function(){
    this.buffer += Array.prototype.join.call(arguments, "");
};
// 类似write(),只是多增加了换行符
ElementStream.prototype.writeln = function(){
    this.buffer += Array.prototype.join.call(arguments, "") + "\n";
};
// 从缓存设置元素的内容,然后清空缓存
ElementStream.prototype.close = function(){
    this.elt.innerHTML = this.buffer;
    this.buffer = "";
}
var mydiv = document.getElementById("mydiv");
var elt = new ElementStream(mydiv);
elt.write("<h1>零点程序员</h1>");
elt.writeln("<p>王唯</p>");
elt.close();

Web前端开发之Javascript-零点程序员-王唯