最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 『面试的底气』—— 设计模式之装饰者模式(二)|8月更文挑战

    正文概述 掘金(红尘炼心)   2021-08-24   425

    这是我参与8月更文挑战的第23天,活动详情查看:8月更文挑战

    前言

    在面试高级前端时,往往会遇到一些关于设计模式的问题,每次都回答不太理想。恰逢8月更文挑战的活动,准备用一个月时间好好理一下关于设计模式方面的知识点,给自己增加点面试的底气。

    在 上一篇简单实现了JavaScript中类和普通对象的装饰者模式,本文来实现JavaScript中一等对象-函数的装饰者模式。

    为啥函数需要装饰者模式

    在开发过程中,想要为函数添加一些功能,最简单粗暴的方式就是直接修改该函数,但是这是最不好的方法,直接违反了开放-封闭原则。

    很多时候我们也不想去修改原函数,只因原函数是其他同事开发的,里面的逻辑复杂无比,怕修改出问题来,根据开放-封闭原则一般这么解决这类问题。

    window.onload = function () {
       // 复杂无比的逻辑
    }
    var _onload = window.onload || function () { };
    window.onload = function () {
      _onload(arguments);
      // 新增功能
    }
    

    以上根据开放-封闭原则提供的解决方式,似乎也实现了在不修改原函数window.onload的前提下给原函数新增一些功能。

    但是以上的解决方式还有两个严重的弊端。

    • 必须维护_onload这个中间变量;
    • this被劫持的问题,这个问题在给window.onload的函数添加新功能中是没有问题的,但是要给document.getElementById添加一些功能,就会出现this被劫持的问题。
    const _getElementById = document.getElementById; 
    document.getElementById = function(id){ 
      // 新增的功能
      return _getElementById(id);
    } 
    const button = document.getElementById('button'); 
    

    执行后会在控制台报 Uncaught TypeError: Illegal invocation 的错误。错误发生在_getElementById( id )这句代码上,此时_getElementById是一个全局函数,当调用一个全局函数时,this 是指向window的,而document.getElementById方法的内部实现需要使用this引用,this在这个方法内预期是指向document,而不是window。 按以下修改就正常了。

    const _getElementById = document.getElementById; 
    document.getElementById = function(id){ 
      // 新增的功能
      return _getElementById.apply(this,arguments);;
    } 
    const button = document.getElementById('button'); 
    

    这样显然是非常不方便,要利用装饰者模式来实现一个简便的动态增加功能的方法。

    利用 AOP 来装饰函数

    AOP(面向切面编程)的主要作用是把一些跟核心业务逻辑模块无关的功能抽离出来,把这些功能抽离出来之后,再通过动态织入的方式掺入业务逻辑模块中。这样可以保持业务逻辑模块的纯净和高内聚性,其次是可以很方便地复用这些抽离出来的功能模块。

    在 JavaScript 中实现 AOP,具体的实现技术有很多,这里通过扩展 Function.prototype 来实现。

    Function.prototype.before = function (fn) {
      // 保存原函数的引用
      let __self = this;
      // 返回包含了原函数和函数fn的"代理"函数
      return function () {
        // 用apply来绑定this执行函数fn,保证this不被劫持,函数fn接受的参数也会被原封不动地传入原函数,
        // 函数fn在原函数之前执行
        fn.apply(this, arguments);
        // 用apply来绑定this执行原函数并返回原函数的执行结果,保证this不被劫持
        return __self.apply(this, arguments);
      };
    };
    Function.prototype.after = function (fn) {
      // 保存原函数的引用
      let __self = this;
      // 返回包含了原函数和函数fn的"代理"函数
      return function () {
        // 用apply来绑定this执行原函数并返回原函数的执行结果,保证this不被劫持
        let ret = __self.apply(this, arguments);
        // 用apply来绑定this执行函数fn,保证 this 不被劫持,函数fn接受的参数也会被原封不动地传入原函数,
        // 函数fn在原函数之后执行
        fn.apply(this, arguments);
        return ret;
      };
    };
    

    以上给Function的原型上增加beforeafter来给函数进行装饰,使得函数具有动态增加功能的能力。

    Function.prototype.before的参数是一个函数fn函数fn里面是给原函数新添加的功能代码。把当前的this保存起来到__self,这个this就是原函数。然后返回一个函数,执行这个函数,会分别执行作函数fn和原函数,且可以控制它们的执行顺序,让函数fn在原函数之前执行(前置装饰),这样就实现了动态装饰的效果。

    且通过Function.prototype.apply执行作为函数fn和原函数,在第一参数传入正确的this,此时的this和原函数执行时的this是一致的,保证了函数在被装饰之后,this不会被劫持。

    Function.prototype.after的原理跟Function.prototype.before 一模一样,唯一不同的地方在 于让函数fn在原函数执行之后再执行(后置装饰)。

    使用 Function.prototype.after 可以很轻松地document.getElementById扩展。

    document.getElementById = document.getElementById.after(() =>{
      console.log(2)
    })
    document.getElementById('button');
    

    不污染原型的实现装饰函数

    如果你不喜欢污染函数的原型链来实现函数装饰,可以采用以下方式来装饰函数。

    const before = function(fn, beforefn) {
      return function() {
        beforefn.apply(this, arguments);
        return fn.apply(this, arguments);
      }
    }
    const a = () =>{
      console.log(1)
    }
    const b = before(a, function() {
      console.log(2)
    });
    
    b();
    
    const after = function(fn, afterfn) {
      return function() {
        const ret = fn.apply(this, arguments);
        afterfn.apply(this, arguments);
        return ret
      }
    }
    const a = () =>{
      console.log(1)
    }
    const b = after(a, function() {
      console.log(2)
    });
    
    b();
    

    起源地下载网 » 『面试的底气』—— 设计模式之装饰者模式(二)|8月更文挑战

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元