发布订阅模式(事件总线)
描述:实现一个发布订阅模式,拥有 on, emit, once, off
方法
class EventEmitter {
constructor() {
// 包含所有监听器函数的容器对象
// 内部结构: {msg1: [listener1, listener2], msg2: [listener3]}
this.cache = {
};
}
// 实现订阅
on(name, callback) {
if(this.cache[name]) {
this.cache[name].push(callback);
}
else {
this.cache[name] = [callback];
}
}
// 删除订阅
off(name, callback) {
if(this.cache[name]) {
this.cache[name] = this.cache[name].filter(item => item !== callback);
}
if(this.cache[name].length === 0) delete this.cache[name];
}
// 只执行一次订阅事件
once(name, callback) {
callback();
this.off(name, callback);
}
// 触发事件
emit(name, ...data) {
if(this.cache[name]) {
// 创建副本,如果回调函数内继续注册相同事件,会造成死循环
let tasks = this.cache[name].slice();
for(let fn of tasks) {
fn(...data);
}
}
}
}
复制代码
原型修改、重写
function Person(name) {
this.name = name
}
// 修改原型
Person.prototype.getName = function() {
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
// 重写原型
Person.prototype = {
getName: function() {
}
}
var p = new Person('hello')
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // false
复制代码
可以看到修改原型的时候p的构造函数不是指向Person了,因为直接给Person的原型对象直接用对象赋值时,它的构造函数指向的了根构造函数Object,所以这时候p.constructor === Object
,而不是p.constructor === Person
。要想成立,就要用constructor指回来:
Person.prototype = {
getName: function() {
}
}
var p = new Person('hello')
p.constructor = Person
console.log(p.__proto__ === Person.prototype) // true
console.log(p.__proto__ === p.constructor.prototype) // true
复制代码
为什么 0.1 + 0.2 != 0.3,请详述理由
因为 JS 采用 IEEE 754 双精度版本(64位),并且只要采用 IEEE 754 的语言都有该问题。
我们都知道计算机表示十进制是采用二进制表示的,所以 0.1
在二进制表示为
// (0011) 表示循环
0.1 = 2^-4 * 1.10011(0011)
复制代码
那么如何得到这个二进制的呢,我们可以来演算下
小数算二进制和整数不同。乘法计算时,只计算小数位,整数位用作每一位的二进制,并且得到的第一位为最高位。所以我们得出 0.1 = 2^-4 * 1.10011(0011)
,那么 0.2
的演算也基本如上所示,只需要去掉第一步乘法,所以得出 0.2 = 2^-3 * 1.10011(0011)
。
回来继续说 IEEE 754 双精度。六十四位中符号位占一位,整数位占十一位,其余五十二位都为小数位。因为 0.1
和 0.2
都是无限循环的二进制了,所以在小数位末尾处需要判断是否进位(就和十进制的四舍五入一样)。
所以 2^-4 * 1.10011...001
进位后就变成了 2^-4 * 1.10011(0011 * 12次)010
。那么把这两个二进制加起来会得出 2^-2 * 1.0011(0011 * 11次)0100
, 这个值算成十进制就是 0.30000000000000004
下面说一下原生解决办法,如下代码所示
parseFloat((0.1 + 0.2).toFixed(10))
复制代码
事件流
事件流是网页元素接收事件的顺序,"DOM2级事件"规定的事件流包括三个阶段:事件捕获阶段、处于目标阶段、事件冒泡阶段。
首先发生的事件捕获,为截获事件提供机会。然后是实际的目标接受事件。最后一个阶段是时间冒泡阶段,可以在这个阶段对事件做出响应。
虽然捕获阶段在规范中规定不允许响应事件,但是实际上还是会执行,所以有两次机会获取到目标对象。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>事件冒泡</title>
</head>
<body>
<div>
<p id="parEle">我是父元素 <span id="sonEle">我是子元素</span></p>
</div>
</body>
</html>
<script type="text/javascript">
var sonEle = document.getElementById('sonEle');
var parEle = document.getElementById('parEle');parEle.addEventListener('click', function () {
alert('父级 冒泡');}, false);parEle.addEventListener('click'