JS设计模式5-创建型设计模式:原型模式、策略模式、代理模式

原型模式

原型模式(Prototype):用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。

因为JavaScript的特性, 这一点实现起来格外的简单

//父类
class Parent{
    constructor(x){
        this.x = x;
    }
    showX(){
        alert( this.x );
    }
}

//子类1继承
class ChildA extends Parent{
    constructor(x,y){
        super();
        this.y = y;
    }
    showY(){
        alert( this.y );
    }
}
//子类2继承
class ChildB extends Parent{
    constructor(x,z){
        super();
        this.z = z;
    }
    showZ(){
        alert( this.z );
    }
}

let obj = {
    sayHello(){
        alert( "Hello" );
    }
};

let objA = Object.create(obj,{
    name :{
        writable:true,
        configurable :true,
        enumerable:true,
        value : "AA"
    }
});

let objB = Object.create(obj,{
    name :{
        writable:true,
        configurable :true,
        enumerable:true,
        value : "BB"
    }
});

objA.sayHello()

多个类使用到了相同的属性或方法,那我们就可以通过原型继承的方式来创造出类或者实例对象

策略模式

在现实中,很多时候有多种途径到达同一个目的地。

比如我们要去某个地方旅游,可以根据具体的实际情况来选择出行的线路。

在程序设计中,我们也常常遇到类似的情况,要实现某一个功能有多种方案可以选择

这些算法灵活多样,而且可以随意互相替换。这种解决方案就是所谓的策略模式.

策略模式的定义是:定义一系列的算法,把它们一个个封装起来,并且使它们可以相互替换。

实例

针对不同绩效的员工发不同的工资

一个基于策略模式的程序至少由两部分组成。

  1. 第一个部分是一组策略类,策略类封装了具体的算法,并负责具体的计算过程。
  2. 第二个部分是环境类Context,Context 接受客户的请求,随后把请求委托给某一个策略类。要做到这点,说明Context 中要维持对某个策略对象的引用。
var strategies = {
    //策略类
    "S": function( salary ){
        return salary * 4;
    },
    "A": function( salary ){
        return salary * 3;
    },
    "B": function( salary ){
        return salary * 2;}
};
//环境类Context
var calculateBonus = function( level, salary ){//接受客户的请求
    return strategies[ level ]( salary );//委托类
};

console.log( calculateBonus( 'S', 20000 ) ); // 输出:80000
console.log( calculateBonus( 'A', 10000 ) ); // 输出:30000

mtween缓动动画

现在来分析实现这个程序的思路。在运动开始之前,需要提前记录一些有用的信息,至少包括以下信息:

  • 动画开始时,小球所在的原始位置;
  • 小球移动的目标位置;
  • 动画开始时的准确时间点;
  • 小球运动持续的时间。

运动曲线算法

  • c , b , d为常数, t为变量
    我们假设c为2, b为3,t的取值范围为[0, d]
    在这里插入图片描述
    在这里插入图片描述
/*
* 这是一些运动曲线, 模拟出近似的贝塞尔效果
* @t: 动画已消耗的时间
* @b: 初始位置
* @c: 结束位置
* @d: 动画持续的总时间
* */
var tween = {
    linear: function (t, b, c, d) {
        return c * t / d + b;
    },
    easeIn: function (t, b, c, d) {
        return c * (t /= d) * t + b;
    },
    strongEaseIn: function (t, b, c, d) {
        return c * (t /= d) * t * t * t * t + b;
    },
    strongEaseOut: function (t, b, c, d) {
        return c * ((t = t / d - 1) * t * t * t * t + 1) + b;
    },
    sineaseIn: function (t, b, c, d) {
        return c * (t /= d) * t * t + b;
    },
    sineaseOut: function (t, b, c, d) {
        return c * ((t = t / d - 1) * t * t + 1) + b;
    }
};

代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问
在这里插入图片描述
代理模式的关键是,当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象

保护代理和虚拟代理

  • 当客户发送至代理的一个请求在经过分析后觉得不符合条件, 结果被拒绝了, 这个就是保护代理
    在这里插入图片描述
  • 当客户发送至代理的一个请求, 该请求需要代理判断某些条件满足后才会向目标发送最终的指令, 那么这个就是虚拟代理
    在这里插入图片描述

实例:虚拟代理实现图片懒加载

    <script>
        var myImage = (function () {
            var imgNode = document.createElement('img');
            document.body.appendChild(imgNode);
            return {
                setSrc: function (src) {
                    imgNode.src = src;
                }
            }
        })();

        var proxyImage = (function () {
            var img = new Image;
            img.onload = function () {
                myImage.setSrc(this.src);
            };
            return {
                setSrc: function (src) {
                    myImage.setSrc('./1.png');
                    img.src = src;
                }
            }
        })();

        proxyImage.setSrc('./2.png');

    </script>

在Web 开发中,图片预加载是一种常用的技术

如果直接给某个img 标签节点设置src 属性,由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。

常见的做法是先用一张loading 图片占位,然后用异步的方式加载图片

等图片加载好了再把它填充到img 节点里,这种场景就很适合使用虚拟代理

代理的意义:单一职责原则

就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。

如果一个对象承担了多项职责,就意味着这个对象将变得巨大,引起它变化的原因可能会有多个。

面向对象设计鼓励将行为分布到细粒度的对象之中,如果一个对象承担的职责过多,等于把这些职责耦合到了一起,这种耦合会导致脆弱和低内聚的设计。

当变化发生时,设计可能会遭到意外的破坏。

缓存代理

缓存代理可以为一些开销大的运算结果提供暂时的存储,在下次运算时,如果传递进来的参数跟之前一致,则可以直接返回前面存储的运算结果。

var multi = function () {
    console.log('开始计算乘积');
    var a = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
        a = a * arguments[i];
    }
    return a;
};

var proxyMulti = (function () {
    var cache = {};
    return function () {
        var args = Array.prototype.join.call(arguments, ',');
        if (args in cache) {
            return cache[args];
        }
        return cache[args] = multi.apply(this, arguments);
    };
})();

console.log(proxyMulti(1, 2, 3, 4));
console.log(proxyMulti(1, 2, 3, 4));
开始计算乘积
24
24

Proxy es6内置的代理模式

Proxy 是 ES6 中新增的功能,它可以用来自定义对象中的操作。 Vue3.0 中将会通过 Proxy 来替换原本的 Object.defineProperty 来实现数据响应式。

let p = new Proxy(target, handler)

target 代表需要添加代理的对象,handler 用来自定义对象中的操作,比如可以用来自定义 set 或者 get 函数。

let onWatch = (obj, setBind, getLogger) => {
  let handler = {
    set(target, property, value, receiver) {
      setBind(value, property)
      return Reflect.set(target, property, value)
    },
    get(target, property, receiver) {
      getLogger(target, property)
      return Reflect.get(target, property, receiver)
    }
  }
  return new Proxy(obj, handler)
}

let obj = { a: 1 }
let p = onWatch(
  obj,
  (v, property) => {
    console.log(`监听到属性${property}改变为${v}`)
  },
  (target, property) => {
    console.log(`'${property}' = ${target[property]}`)
  }
)
p.a = 2 // 控制台输出:监听到属性a改变
p.a // 'a' = 2

自定义 set 和 get 函数的方式,在原本的逻辑中插入了我们的函数逻辑,实现了在对对象任何属性进行读写时发出通知。

当然这是简单版的响应式实现,如果需要实现一个 Vue 中的响应式,需要我们在 get 中收集依赖,在 set 派发更新,之所以 Vue3.0 要使用 Proxy 替换原本的 API 原因在于 Proxy 无需一层层递归为每个属性添加代理,一次即可完成以上操作,性能上更好,并且原本的实现有一些数据更新不能监听到,但是 Proxy 可以完美监听到任何方式的数据改变,唯一缺陷可能就是浏览器的兼容性不好了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值