封面 pid:80641264

监听页面关闭/刷新

新版本的浏览器中,为了安全性,已经不支持自定义弹窗,如果需要在关闭/刷新页面时弹窗提醒,只能调出浏览器自带的提示确认窗口,通过绑定onbeforeunload事件来使用。

window.onbeforeunload = function() {
  return 1;
  //调出确认弹窗
}

onbeforeunload事件中以下情况不会弹出确认弹窗

  • 不return
  • return;
  • return undefined;
  • return null;

除此之外都会弹出确认弹窗


如果需要区分刷新和关闭两种行为可以使用时间差。两种行为都会先后触发onbeforeunload、onunload事件,但是刷新时两时间的时间差更长,可以在两个事件中记录时间,在onunload事件中计算时间差,如果时间差小于某个阈值(5ms)可以认为是关闭,否则是刷新。

设计模式

工厂模式

如其名所示,就是像工厂一样可以按照某个模板修饰对象并返回。

function CreatePerson(name,age,sex) {
    var obj = new Object();
    obj.name = name;
    obj.age = age;
    obj.sex = sex;
    obj.sayName = function(){
        return this.name;
    }
    return obj;
}

上面是一个简单实现,但是不能区分对象类型。为了解决这问题,可以采用父子类,父类作为抽象类处理一般性问题并且定义抽象接口,子类重写这些接口实现自己的逻辑。

单例模式

单例模式指确保只有一个实例,并提供全局访问(相当于全局变量)。惰性单例指的是在需要的时候才创建该单例,最大程度防止浪费。

懒惰单例可以通过闭包实现

let getSinglePlayer(function(){
    //确保只有一个播放器
    let player;
    return function(){
        if(!player){
            player = new Player();
        }
        return player;
    }
})()
getSinglePlayer()===getSinglePlayer();//true

更进一步地,可以把管理单例的逻辑抽象出来,构造通用的惰性单例,这在需要创建各类单例的时候非常高效

let getSingle = function(fn){
    let ret;
    return function(){
        return ret || (ret = fn.apply(this,arguments));
    }
}
// player,video 单例生产器
let getSinglePlayer = getSingle(()=>new Player());
let getSingleVideo = getSingle(()=>new Video());
getSinglePlayer()===getSinglePlayer();//true

模块模式

用于解决公有、私有变量和方法的封装问题,同时避免命名冲突。通常是一个自执行函数+闭包,数据初始化后,公开一些能够访问这些私有数据的方法或变量。

let mode = (function(){
  let _pvar = 1; //私有属性
  function pFun(){} //私有方法

  let getvar = function(){
    return _pvar;
  }
  return {
    getvar //对外开放私有变量的访问
  }
})()

代理模式

为一个对象提供一种代理以控制对这个对象的访问。代理模式最基本的形式是对访问进行控制,在接收到请求之后做出一些处理(拦截过滤,修饰...),然后再转交给被代理的对象。虚拟代理是把一些开销很大的对象,延迟到真正需要它的时候才去创建执行。

//被代理对象
var myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    return function(src){
      imgNode.src = src; 
    }
})();
// 代理
var ProxyImage = (function(){
    var img = new Image();
    img.onload = function(){
      myImage(this.src);
    };
    return function(src) {
      myImage("placeholder.png");
      img.src = src;
    }
})();
//使用代理
ProxyImage("target.png");

在ES6中提供了 Proxy 来实现代理

职责链模式

一系列可能会处理请求的对象被连接成一条链,请求在这些对象之间依次传递,直到遇到一个可以处理它的对象,这就是一种职责链模式,并且把这些对象称为链中的节点

这要求把各种处理方式独立成节点并“连”在一起,某节点接受到请求,要么处理成功,要么处理不了然后甩锅给下一个节点处理。这里的“连”操作实现有多种,比如给节点增加一个指针指向下一个节点,比如采用AOP面向切面编程

//AOP实现,通过函数动态切入形成链
Function.prototype.after = function (fn) {
  let self = this;
  return function () {
    let ret = self.apply(this, arguments);
    if (ret===false) {
      return fn.apply(this, arguments);
    }
    return ret;
  }
};
let checkBalance = function(wallet,price){
  if (wallet.balance>=price){
    wallet.balance -= price;
    console.log('从余额扣除');
  }else
    return false;
};
let checkHuabei = function(wallet,price){
  if (wallet.huabei>=price){
    wallet.huabei -= price;
    console.log('花呗欠款');
  }else
    return false;
};
//当所有节点都不能处理时,最后节点的处理
let tailGuard = function() {
  throw Error('穷到吃土了')
};
//抽一单SSR要658¥,你不在乎在从哪里扣,你只想快点抽到你老婆/老公
let consume = checkBalance.after(checkHuabei).after(tailGuard);
consume({balance:500,huabei:1000},658);
//花呗欠款

在这种模式下消息的发送者和接受者得到了解耦,后续删除和新增处理都将变得容易,处理顺序也可以随意改动。因为不是所有节点都有用到,因该避免职责链过长。

命令模式

请求的发送者不知道请求的接收者是谁,也不知道请求的操作是什么。这时候引入命令对象,命令对象对发送者开放一个事先指定的方法用于执行命令,传入接收者初始化命令对象后可以在该方法中则调用接收者处理请求的方法。这样请求的发送者的耦合得到了消除。

宏命令是一组命令的集合,通过执行宏命令的方式,可以实现命令序列的执行,如下是一个例子。

//这里用了现成命令对象,如果有数据相关(传参),还是使用构造函数new对象吧
let collectMoneyCommand = {
  execute(){
    console.log('基建收钱')
  }
};
let collectClueCommand = {
  execute(){
    console.log('线索搜集')
  }
};
//明日方舟助手,可以定制命令顺序执行
let ArkAutoBot = function(){
    this.commandList = [];
};
ArkAutoBot.prototype.add = function (command) {
  this.commandList.push(command);
};
ArkAutoBot.prototype.start = function () {
  for(let command of this.commandList)
    command.execute()
};
let bot = new ArkAutoBot();
bot.add(collectMoneyCommand)
bot.add(collectClueCommand)
bot.start();//先收钱,然后收集线索

策略模式

策略模式的目的是将算法的使用与算法的实现分离开来。算法的各种实现被包装成对象/函数,成为策略组;中介负责根据用户的请求从中选择对应的策略,将请求委托给该策略然后获取结果。

这种委托的思想使得算法不用扎堆在一起判断执行,也不会因为重复的业务而重复代码,而是独立与策略组中,便于扩展修改,完美诠释了开放—封闭原则。

在表单验证这类大量重复判断的业务中策略模式可谓是大放异彩了,如下

export default class Validator {
  //存放结果
  validateResult = [];
  //策略组
  strategys = {
    notEmpty(value,errMsg){
      if(!value)
        return errMsg
    },
    notContains(value,target,errMsg){
      if (value.indexOf(target)>-1)
        return errMsg;
    },
    isEmail(value,errMsg){
      if (!/^[a-zA-Z0-9_-]+@[a-zA-Z0-9_-]+(\.[a-zA-Z0-9_-]+)+$/.test(value))
        return errMsg
    },
    isQQ(value,errMsg){
      if (!/^[1-9]\d{4,12}$/.test(value))
        return errMsg;
    },
    isEn(value,errMsg){
      if (!/^\w+$/.test(value))
        return errMsg;
    }
  };

  check(value,rules,optional=false){
    //optional表示可选,可以为空,但是不为空就要满足规则
    rules = (rules instanceof Array)?rules:[rules];
    if (optional&&!value)return;
    for (let rule of rules){
      let strategyArgs = rule.strategy.split(':');
      let errMsg = rule.errMsg;
      let strategy = strategyArgs.shift();
      strategyArgs.unshift(value);
      strategyArgs.push(errMsg);
      //根据请求选择策略,获取结果
      let result = this.strategys[strategy].apply(null,strategyArgs);
      //如果有结果加入结果集
      result && this.validateResult.push(result)
    }
  }

  checkResult(){//返回结果集的第一个非空值,如果都为空会返回undefine
    for (let item of this.validateResult)
      if (item)
        return item;
  }
}
//使用
let validator = new Validator();
validator.check('这是中文',{strategy:'isEn',errMsg:'不允许中文'});
//可以继续check
console.log(validator.checkResult())//不允许中文

发布——订阅模式

发布—订阅模,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都将得到通知,在Js通过事件实现。在异步编程中,这是一种替代传递回调函数的方案;同时也能实现跨模块通信,实现解耦

一个全局的发布——订阅管理机构(第三者)如下:任何对象都可以,通过订阅方法提交订阅的事件和回调函数(回调函数被缓存在列表中);通过发布方法发布事件,遍历该事件的回调函数列表并执行。

注:与观察者模式不同,发布订阅模式是有第三者的,通信双方完全解耦。

//一个全局的发布——订阅机构
let Event = {
  //当然,可以通过闭包只返回方法从而隐藏该列表,不过懒得改了
  clientList: {},
  //订阅
  listen(key, fn) {
    if (!this.clientList[key]) {
      this.clientList[key] = [];
    }
    this.clientList[key].push(fn);
  },
  //取消订阅
  remove(key, fn) {
    let fns = this.clientList[key];
    if (!fns) { // 该事件没有订阅者,返回
      return false;
    }
    if (!fn) { // 没传fn,认为是取消该事件的所有订阅者
      fns && (fns.length = 0);
      return true;
    }
    for (let i = fns.length - 1; i >= 0; i--) { // 反向遍历订阅的回调函数列表
      if (fns[i] === fn) {
        fns.splice(i, 1); // 删除订阅者的回调函数
      }
    }
  },
  //发布
  trigger() {
    let key = Array.prototype.shift.call(arguments); // 把事件名提取,剩下参数
    let fns = this.clientList[key];
    if (!fns || fns.length === 0) { // 该事件没有订阅者,返回
      return false;
    }
    for (let fn of fns) {
      fn.apply(this, arguments); // 发布信息,将数据交给订阅回调函数
    }
  }
}; 

//在A中
Event.listen('windy',()=>console.log("隔壁薯片半价"));
//在B中
Event.trigger('windy');
//A:隔壁薯片半价

中介者模式

N个对象要相互沟通,他们处于不同的状态,某个对象状态的改变有可能要通知其他对象作相应变化,因此他们都要保存其他对象的引以便遍历通知。当对象数量很大时,沟通成本就会变得很高。这时候可以引入一个中介者,中介者包含所有对象的引用(因此收集了各对象的状态),承包所有对象的沟通请求,让对象专心干自己的事而不用因为沟通的事和其他对象耦合在一起。

image.png

中介者和对象间的沟通可以通过中介者暴露接口实现,也可以通过订阅——发布模式实现

组合模式

组合模式就是用小的子对象来构建更大的对象,组合成树形结构,以表示“部分-整体”的层次结构(但不是继承,是聚合关系),如图

image.png

叶对象是最小的单位,组合对象包含叶对象/组合对象。这两种对象都有相同的接口(方法),因此上层对象可以把请求委托给它所包含的所有直接下层对象,如此传递,表现为一种深度遍历。

一个例子如下

  • 假如组合对象和叶对象都有方法 fun
  • 顶层组合对象执行 fun,将遍历其包含的对象(组合对象/叶对象)并执行他们的 fun

组合模式使用户可以忽略组合对象和叶对象的区别,作为用户你只管最顶层的对象即可

    添加新评论 | #

    Markdown Supported
    简单数学题:NaN + NaN =
    表情

    Comments | ?? 条评论

    • 单身为狗 24 年

    • 朝循以始 夜继以终

    • Blog: Von by Bersder

    • Alive: 0 天 0 小时 0 分

    Support:

    frame ssr server DNS music player

    © 2019-2024 ᛟᛊᚺᛁᚾᛟ

    back2top