浅谈浏览器对象和事件

679 阅读4分钟

目标

  • 了解浏览器的对象模型
  • 分析浏览器的Event Model

要点

什么是浏览器对象模型

BOM :Browser Object Model(浏览器对象模型),浏览器模型提供了独立于内容的、可以与浏览器窗口进行滑动的对象结构。

常见API

  1. Window 对象 window 对象是整个浏览器对象模型的核心,其扮演着既是接口又是全局对象的角色

Event

  • alert()
  • confirm()
  • prompt()
  • open()
  • onerror()
  • setTimeout()
  • setInterval()
  • ......

窗口位置

  • screenLeft
  • screenTop
  • screenX
  • screenY
  • moveBy(x,y)
  • moveTo(x,y)
  • ......

窗口大小

  • innerWidth
  • innerHeight
  • outerWidth
  • outerHeight
  • resizeTo(width, height)
  • resizeBy(width, height)
  • ......
  1. Location 对象 提供当前窗口中的加载的文档有关的信息和一些导航功能。既是 window 对象属性,也是 document 的对象属性

主要属性

  • hash
  • host
  • hostname
  • href
  • pathname
  • port
  • protocol
  • search
  • ......
  1. Navigation 对象 navigator 表示用户代理的状态和标识,允许脚本查询它和注册自己进行一些活动

常见属性

  • appName
  • appVersion
  • bluetooth
  • clipboard
  • connection
  • keyboard
  • language
  • userAgent
  • ......
  1. History 对象 history 对象保存着用户上网的历史记录,从窗口被打开的那一刻算起,history 对象是用窗口的浏览历史用文档和文档状态列表的形式表示

常见属性

  • go()
  • back()
  • forword()
  • length

浏览器事件捕获,冒泡

浏览器事件模型中的过程主要分为三个阶段:捕获阶段、目标阶段、冒泡阶段

事件模型.jpg

  1. 第三个参数

这里要注意addEventListener的第三个参数, 如果为true,就是代表在捕获阶段执行。如果为false,就是在冒泡阶段进行

// html
<body>
    <div id="yeye" class="flex-center">
        爷爷
        <p id="baba" class="flex-center">
            爸爸
            <span id='erzi' class="flex-center">
                儿子
                <a href="https://www.baidu.com" id="a-baidu">测试点击</a>
            </span>
        </p>
    </div>
  </body>
// js
const yeye = document.getElementById("yeye");
const baba = document.getElementById("baba");
const erzi = document.getElementById("erzi");

window.addEventListener("click", function (e) {
    // e.target.nodeName 指当前点击的元素, e.currentTarget.nodeName绑定监听事件的元素
    console.log("window 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

yeye.addEventListener("click", function (e) {
    console.log("yeye 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

baba.addEventListener("click", function (e) {
    console.log("baba 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

erzi.addEventListener("click", function (e) {
    console.log("erzi 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

window.addEventListener("click", function (e) {
    console.log("window 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

yeye.addEventListener("click", function (e) {
    console.log("yeye 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

baba.addEventListener("click", function (e) {
    console.log("baba 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

erzi.addEventListener("click", function (e) {
    console.log("erzi 冒泡", e.target.nodeName, e.currentTarget.nodeName);
}, false);

结果呢❓

微信截图_11111.png

  1. 阻止事件传播 如果有多个相同类型事件的事件监听函数绑定到同一个元素,当该类型的事件触发时,它们会按照被添加的顺序执行。如果其中某个监听函数执行了 event.stopImmediatePropagation() 方法,则当前元素剩下的监听函数将不会被执行。

e.stopPropagation()

baba.addEventListener("click", function (e) {
    // baba 阻止事件传播 -> 向下捕获
    e.stopPropagation()
    console.log("baba 捕获", e.target.nodeName, e.currentTarget.nodeName);
}, true);

1.png

  1. 阻止默认行为 意思是阻止事件的默认行为发生。 默认行为是指:点击a标签就转跳到其他页面、拖拽一个图片到浏览器会自动打开、点击表单的提交按钮会提交表单等等,因为有的时候我们并不希望发生这些事情,所以需要阻止默认行为。

e.preventDefault()

document.getElementById('a-baidu').addEventListener("click", function (e) {
    // 阻止了a标签的默认跳转行为
    e.preventDefault()
});
  1. 兼容性

attachEvent —— 兼容:IE7、IE8; 不支持第三个参数来控制在哪个阶段发生,默认是绑定在冒泡阶段 addEventListener —— 兼容:firefox、chrome、IE、safari、opera;

手写封装一下兼容性🔻

class XuSir_BomEvent {
    constructor(element) {
        // 传递过来的
        this.element = element;
    }

    /**
     * 绑定监听
     * @param {*} type 事件类型
     * @param {*} handler 回调函数
     */
    addEvent(type, handler) {
        if (this.element.addEventListener) {
            //事件类型、需要执行的函数、是否捕捉
            this.element.addEventListener(type, handler, false);
        } else if (this.element.attachEvent) {
            // 兼容 IE9及一下版本
            this.element.attachEvent('on' + type, function () {
                handler.call(element);
            });
        } else {
            this.element['on' + type] = handler;
        }
    }

    /**
     * 移除监听
     * @param {*} type 事件类型
     * @param {*} handler 回调函数
     */
    removeEvent(type, handler) {
        if (this.element.removeEnentListener) {
            this.element.removeEnentListener(type, handler, false);
        } else if (element.datachEvent) {
            this.element.detachEvent('on' + type, handler);
        } else {
            this.element['on' + type] = null;
        }
    }
}
// 阻止事件 (主要是事件冒泡,因为IE不支持事件捕获)
function stopPropagation(ev) {
    if (ev.stopPropagation) {
        ev.stopPropagation(); // 标准w3c
    } else {
        ev.cancelBubble = true; // IE
    }
}
// 取消事件的默认行为
function preventDefault(event) {
    if (event.preventDefault) {
        event.preventDefault(); // 标准w3c
    } else {
        event.returnValue = false; // IE
    }
}

使用方法:老司机都知道💯

事件委托

  • 🔴它还有一个名字叫事件代理,JavaScript高级程序设计上讲:事件委托就是利用事件冒泡,只指定一个事件处理程序,就可以管理某一类型的所有事件。
  • 🟠 事件委托是利用事件的冒泡原理来实现的,何为事件冒泡呢?就是事件从最深的节点开始,然后逐步向上传播事件,举个例子:页面上有这么一个节点树,div>ul>li>a;比如给最里面的a加一个click点击事件,那么这个事件就会一层一层的往外执行,执行顺序a>li>ul>div,有这样一个机制,那么我们给最外面的div加点击事件,那么里面的ul,li,a做点击事件的时候,都会冒泡到最外层的div上,所以都会触发,这就是事件委托,委托它们父级代为执行事件。
  • 🔵举例
<ul id="ul">
    <li>1</li>
    <li>2</li>
    <li>3</li>
    <li>4</li>
    <li>5</li>
    <li>6</li>
    <li>7</li>
    <li>8</li>
</ul>
// 方式一
// 缺点❓ 为每一个li元素都添加了一个监听,性能差
<script type="text/javascript">
const liList = document.getElementsByTagName("li");
for(let i = 0; i<liList.length; i++){
    liList[i].addEventListener('click', function(e){
        alert(`内容为${e.target.innerHTML}, 索引为${i}`);
   })
}
</script>
// 方式二
<script type="text/javascript">
const ul = document.querySelector("ul");
ul.addEventListener('click', function (e) {
    const target = e.target;
  if (target.tagName.toLowerCase() === "li") {
    const liList = this.querySelectorAll("li");
        // 获取index 方式1
    // const index = Array.prototype.indexOf.call(liList, target);
        // 方式2 -- 更符合现代浏览器处理方式
        const realLiList = Array.from(liList)
        const index = realLiList.indexOf(target)
    alert(`内容为${target.innerHTML}, 索引为${index}`);
  }
})
</script>

欢迎纠错,童鞋们的❤