【JS设计模式系列】代理模式

·  阅读 1041

什么是代理模式

代理模式是为一个对象提供一个代用品或占位符,以便控制对它的访问。

代理模式的划分

按职责来划分的话,分为以下8种代理:

1、缓存代理

2、虚拟代理

3、写时复制Copy-on-Write 代理

4、保护(Protect or Access)代理

5、Cache代理

6、防火墙(Firewall)代理

7、同步化(Synchronization)代理

8、智能引用(Smart Reference)代理

在js中常用到的是缓存代理虚拟代理

虚拟代理

  • 不使用代理模式写图片预加载
// 不使用代理的预加载图片函数如下
var myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    var img = new Image();
    img.onload = function(){
        imgNode.src = this.src;
    };
    return {
        setSrc: function(src) {
            imgNode.src = "loading.gif";
            img.src = src;
        }
    }
})();
// 调用方式
myImage.setSrc("pic.png");
复制代码
  • 使用代理模式写图片预加载
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("loading.gif");
            img.src = src;
        }
    }
})();
// 调用方式
ProxyImage.setSrc("pic.png");
复制代码
  • 方案一步骤:

1、创建img标签

2、插入img标签

3、创建img对象

4、书写onloading方法

5、返回设置图片对象。

缺点

1、代码耦合度比较大,一个函数内负责做了几件事情。未满足面向对象设计原则中单一职责原则;

2、当某个时候不需要图片预加载的时候,需要从myImage 函数内把代码删掉,这样代码耦合性太高。

  • 方案二步骤:

1、myImage中创建img标签

2、myImage中插入img标签

3、myImage中返回设置imgNode的src方法

4、ProxyImage中创建img对象

5、ProxyImage中书写onload方法

6、ProxyImage中返回设置图片的方法。

优点

1、myImage 函数只负责做一件事。创建img元素加入到页面中,其中的加载loading图片交给代理函数ProxyImage 去做。

2、加载成功以后,代理函数ProxyImage 会通知及执行myImage 函数的方法。

3、当以后不需要代理对象的话,我们直接可以调用本体对象的方法即可

缓存代理

缓存代理,就是将前面使用的值缓存下来,后续还有使用的话,就直接拿出来用。

var add = function(){
    var sum = 0
    for(var i = 0, l = arguments.length; i < l; i++){
        sum += arguments[i]
    }
    return sum
}
var proxyAdd = (function(){
    var cache = {} //缓存运算结果的缓存对象
    return function(){
        var args = Array.prototype.join.call(arguments)
        if(cache.hasOwnProperty(args)){//等价 args in cache
            console.log('使用缓存结果')
            return cache[args]//直接使用缓存对象的“值”
        }
        console.log('计算结果')
        return cache[args] = add.apply(this,arguments)//使用本体函数计算结果并加入缓存
        console.log(cache);
    }
})()
console.log(proxyAdd(1,2,3,4,5))
console.log(proxyAdd(1,2,3,4,5))
console.log(proxyAdd(1,2,3,4,5))

// 输出结果
计算结果
15
使用缓存结果
15
使用缓存结果
15
复制代码

两者的职责划分:add函数提供计算功能。proxyAdd提供访问add函数的功能和缓存功能。

es6中的proxy

proxy的用法

const target = {}, handler = {}
const proxy = new Proxy(target, handler)
复制代码

target是目标对象,handler是处理函数。

handler的内建方法集

var handler = {
    get: function() {},
    set: function() {},
    apply: function() {},
    construct: function() {},
}
复制代码

handler的get方法

参数:目标对象、属性名、proxy实例本身

例子

var person = {
  name: "张三"
};

var proxy = new Proxy(person, {
  get: function(target, property) {
    if (property in target) {
      return target[property];
    } else {
      throw new ReferenceError("Property \"" + property + "\" does not exist.");
    }
  }
});

proxy.name // "张三"
proxy.age // 抛出一个错误
复制代码

handler的set方法

参数:目标对象、属性名、属性值、proxy实例本身

例子

let validator = {
  set: function(obj, prop, value) {
    if (prop === 'age') {
      if (!Number.isInteger(value)) {
        throw new TypeError('The age is not an integer');
      }
      if (value > 200) {
        throw new RangeError('The age seems invalid');
      }
    }

    // 对于满足条件的 age 属性以及其他属性,直接保存
    obj[prop] = value;
  }
};

let a = {}

let person = new Proxy(a, validator);

person.age = 100;

a.age // 100
person.age = 'young' // 报错
person.age = 300 // 报错
复制代码

handler的apply方法

参数:目标对象,目标对象的上下文,参数组 例子

var target = function () { return 'I am the target'; };
var handler = {
  apply: function () {
    return 'I am the proxy';
  }
};

var p = new Proxy(target, handler);

p() // I am the proxy
复制代码

当调用p函数时,就会被proxy拦截,返回I am the proxy

handler的construct方法

construct方法用于拦截new指令。

参数:目标对象、构造函数的参数对象 例子

var P = new Proxy(function () {}, {
  construct: function(target, args) {
    console.log('called: ' + args.join(', '));
    return { value: args[0] * 10 };
  }
});

const p = new P(1);
// "called: 1"
p.value
// 10
复制代码

proxy重写图片预加载

let myImage = (function(){
    var imgNode = document.createElement("img");
    document.body.appendChild(imgNode);
    return {
        setSrc: function(src) {
            imgNode.src = src;
        }
    }
})();
let myImageProxy = new Proxy(myImage, {
    apply(target, ctx, arguments) {
        let img = new Image
        img.onload = function(){
            // 图片加载完成,正式加载图片
            target.call(ctx, ...arguments)
        }
        // 图片未被载入时,加载一张提示图片
        target.call(ctx, 'file://c:/loading.png')
        img.src = arguments[0]
    }
})

// 调用
let myImg = new myImageProxy(document)
myImg.setSrc('http://images/qq.jpg')
复制代码
分类:
前端
标签:
收藏成功!
已添加到「」, 点击更改