学习内容
- 单例模式
- 观察者模式
- 事件轮询机制 (PPT)
- 垃圾回收机制
单例模式
//设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
//单个实例,只有一个对象,多次创建,返回同一个对象。
//单例模式的核心:确保只有一个实例,并提供全局访问。
//
<input type="button" value="飞秋" id="btn">
<script>
/*
内存管理:
五个区:
1. 栈区 : a = 3 由系统自动分配空间,自动释放空间
2. 堆区: 由程序员分配空间,由程序员释放空间(C语言)
3. 全局区(静态区):作用范围,整个页面 生存周期:同页面
4. 常量区 : 3
5. 代码区
*/
// var a = 3;
//面向对象中的静态属性,存放在内存的静态区
//直接通过构造函数名或类名调用的属性,叫做静态属性
//一、获取页面元素
const btn = document.querySelector('#btn');
//二、创建构造函数
function FQ(){
//instance: 实例
if(!FQ.instance){ //空 instance:实例的意思,是我自己定义的名字。
FQ.instance = { //对象 instance属性中存放的是一个对象
//设置属性
ele: document.createElement('div'),
//设置方法
//ES5对象
//init: function(){}
//ES6对象简写方式
init(){ //init: 初始化的意思,我们自己定义的名字
//设置样式
this.ele.style.width = '100px';
this.ele.style.height = '100px';
this.ele.style.background = '#' + Math.floor(Math.random() * 0xffffff).toString(16);
document.body.appendChild(this.ele);
}
}
}
return FQ.instance; //对象
}
//三、添加事件
btn.ondblclick = function(){
new FQ().init();
}
</script>
//
<input type="button" value="此电脑" id="btn">
<script>
//每双一次此电脑,打开一个窗口(单例模式)无论双击多少次,页面中只能出现一个窗口.
//一、获取页面元素
const btn = document.querySelector('#btn');
//二、添加事件
btn.ondblclick = (function(){
let div = null;
return function(){ //闭包函数,也是事件触理函数
//第一次触发事件时,div中的值为null
if(!div){
//如果div中是空的,则创建内容
//创建一个元素
div = document.createElement('div');
//设置属性
div.style.width = 100 + 'px';
div.style.height = '100px';
div.style.background = '#' + Math.floor(Math.random() * 0xffffff).toString(16);
//添加到页面中
document.body.appendChild(div);
}else{ //当前第二次或多次触发事件时,div中已经有上一次创建的对象,将上一次对象换一种颜色即可
div.style.background = '#' + Math.floor(Math.random() * 0xffffff).toString(16);
}
}
})(); //匿名函数自运行,页面打开时,立即运行。
//让一个局部变量能够全局访问----闭包
</script>
观察者模式
//观察者模式又叫发布-订阅模式,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知。
/*
小明最近看上了一套房子,到了售楼处之后才被告知,该楼盘的房子早已售罄。
好在售楼MM 告诉小明,不久后还有一些尾盘推出,开发商正在办理相关手续,手续办好后便可以购买。
但到底是什么时候,目前还没有人能够知道。
于是小明记下了售楼处的电话,以后每天都会打电话过去询问是不是已经到了购买时间。
除了小明,还有小红、小强、小龙也会每天向售楼处咨询这个问题。
一个星期后,售楼MM 决定辞职,因为厌倦了每天回答 1000 个相同内容的电话。
当然现实中没有这么笨的销售公司,实际上故事是这样的:小明离开之前,把电话号码留在了售楼处。
售楼 MM 答应他,新楼盘一推出就马上发信息通知小明。
小红、小强和小龙也是一样,他们的电话号码都被记在售楼处的花名册上,新楼盘推出的时候,售楼 MM 会翻开花名册,遍历上面的电话号码,依次发送一条短信来通知他们。
——这种发送短信通知就是一个典型的发布-订阅模式(观察者模式)。
小明、小红等购买者都是订阅者,他们订阅了房子开售的消息。
售楼处作为发布者,会在合适的时候遍历花名册上的电话号码,依次给购房者发布消息。
*/
<script>
//发布-订阅
//主要角色:发布者
//1. 有订阅者的联系方式(花名册)
//2. MM 接待人员 -让你把信息留下
//3. 发布消息的人员
//售楼处
let salesOffice = {
arr: [], //花名册,用来存储用户的联系方式
listenUser(fn){
this.arr.push(fn);
},
publishMsg(size,money){
//遍历数组
this.arr.forEach(fn => {
fn(size,money);
})
}
}
//小明
salesOffice.listenUser((size,money) => {
console.log('小明收到消息:' + size + '平方' + money + '元');
})
//小龙
salesOffice.listenUser((size,money) => {
console.log('小龙收到消息:' + size + '平方' + money + '元');
})
//小红
salesOffice.listenUser((size,money) => {
console.log('小红收到消息:' + size + '平方' + money + '元');
})
//发布消息
salesOffice.publishMsg(80,600000);
</script>
/*
新冠肺炎疫情期间,小明去药店买口罩,然而到店之后却被店员告知口罩已经售罄。
这时,小明给店员留了一个电话,告诉店员等口罩到货后打电话通知他一下, 这样小明就可以在口罩到货后的第一时间去药店买口罩了。
小明买口罩的过程就是对发布-订阅模式的一次实践:小明将电话留给店员,让店员在口罩到货后通知他,便是一次“订阅”;店员在口罩到货后,打电话告诉小明,便是一次“发布”。
不过,在上面的例子中发布-订阅模式并不是唯一的解决方案,其优势也并没有体现出来。
比如,小明不想把自己的电话留给药店,而是把药店的电话记了下来,每天给药店打电话去问。
从目前的情况看,这种方式也是可以工作的。
但往往我们需要面临更复杂的情况:当前疫情严重,不止小明一人需要口罩,小红、小白、小黑等许许多多的人也都需要口罩,他们也都想在口罩到货的第一时间得到消息,于是他们每个人每天都需要给药店打电话询问。
这样药店每天都会收到几百个电话,店员们每天都被一个简单却重复的问题搞得很疲惫,而小明也每天都担心电话打晚了会被别人先得到消息“抢”走口罩。
当有许多人都想获得“口罩到货”这一相同消息时,发布-订阅模式的优势就显示出来了。
只要所有想知道这个消息的用户都在药店留一个电话,当口罩到货后,店员再给“订阅”这一事件的所有用户都打电话告知一下就可以了。
这样用户就不用每天都给药店打电话询问,药店也不需要每天都花大量的时间接听电话了。
*/
<script>
//发布者-药店
let drugstore = {
//1. 花名册
roster: {},
//2. 记录用户信息的方法
listenUser(eventName,fn){
//用户需求分类事件
if(!this.roster[eventName]){
//不同的需求,都初始化一个空的数组
this.roster[eventName] = [];
}
//将用户的连接方式按分类存入数组
this.roster[eventName].push(fn);
},
//3. 发布信息的方法
publish(eventName,data){
//遍历花名册
//this : 药店对象
//roster : 属性(对象)
//eventName : 需要分类 []
this.roster[eventName].forEach(fn => {
//fn : 从数组中取出来的第一个用户的联系方式
fn(data); //data: 是药店传递给用户的信息
})
}
}
//小明
function xm(data){
console.log('口罩到货了,我得赶紧去买点');
}
//小龙
function xl(data){
if(data.price < 10){
console.log('真实惠,我得屯点');
}else if(data.price < 20){
console.log('还可以,少买点');
}else{
console.log('太贵了,我不出门了');
}
}
//小强
function xq(data){
if(data.type === '普通医用口罩'){
console.log('我去买点');
}else if(data.type === 'N95'){
console.log('不买了');
}
}
//订阅
drugstore.listenUser('mask',xm);
drugstore.listenUser('mask',xl);
drugstore.listenUser('mask',xq);
//药店发布的产品信息
let mask_data = {
name: '口罩',
type : 'N95',
price: 50,
num: 1000
}
//发布消息
drugstore.publish('mask',mask_data);
//小红
function xh(data){
console.log('赶紧去,就10盒');
}
//小花
function xhua(data){
console.log('少买点');
}
drugstore.listenUser('lianhua',xh);
drugstore.listenUser('lianhua',xhua);
//莲花清瘟胶囊
let lianhuaqingwen_data = {
price: 25,
num: 10
}
drugstore.publish('lianhua',lianhuaqingwen_data);
</script>
垃圾回收机制
/*
浏览器的 Javascript 具有自动垃圾回收机制(GC:Garbage Collection),也就是说,执行环境会负责管理代码执行过程中使用的内存。
其原理是:垃圾收集器会定期(周期性)找出那些不在继续使用的变量,然后释放其内存。
但是这个过程不是实时的,因为其开销比较大并且GC时停止响应其他操作,所以垃圾回收器会按照固定的时间间隔周期性的执行。
*/
//1. 通常情况下有两种实现方式:**标记清除**和**引用计数**。引用计数不太常用,标记清除较为常用。
//2. js中最常用的垃圾回收方式就是标记清除。
//目前,IE9+、Firefox、Opera、Chrome、Safari的js实现使用的都是标记清除的垃圾回收策略或类似的策略,只不过垃圾收集的时间间隔互不相同。
//3. Netscape Navigator3是最早使用引用计数策略的浏览器,但很快它就遇到一个严重的问题:循环引用。循环引用指的是对象A中包含一个指向对象B的指针,而对象B中也包含一个指向对象A的引用。
//在标记清除方式下是没有问题的,但是在引用计数策略下,因为a和b的引用次数不为0,所以不会被垃圾回收器回收[内存](https://so.csdn.net/so/search?q=内存&spm=1001.2101.3001.7020),如果fn函数被大量调用,就会造成内存泄露。在IE7与IE8上,内存直线上升。
//IE中有一部分对象并不是原生js对象。例如,其中DOM和BOM中的对象就是使用C++以COM对象的形式实现的,而COM对象的垃圾回收机制采用的就是引用计数策略。因此,即使IE的js引擎采用标记清除策略来实现,但js访问的COM对象依然是基于引用计数策略的。换句话说,只要在IE中涉及COM对象,就会存在循环引用的问题。
//要注意的是,IE9+并不存在循环引用导致Dom内存泄露问题,可能是微软做了优化,或者Dom的回收方式已经改变。
//4. 内存管理,什么时候触发垃圾回收?
//Javascript引擎基础GC方案是(simple GC):mark and sweep(标记清除),即:
//1. 遍历所有可访问的对象。
//2. 回收已不可访问的对象。