【JS設計模式系列】代理模式

什麼是代理模式

代理模式是爲一個對象提供一個代用品或佔位符,以便控制對它的訪問。es6

代理模式的劃分

按職責來劃分的話,分爲如下8種代理:數組

一、緩存代理緩存

二、虛擬代理app

三、寫時複製Copy-on-Write 代理函數

四、保護(Protect or Access)代理ui

五、Cache代理this

六、防火牆(Firewall)代理spa

七、同步化(Synchronization)代理prototype

八、智能引用(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");
複製代碼
  • 方案一步驟:

一、建立img標籤

二、插入img標籤

三、建立img對象

四、書寫onloading方法

五、返回設置圖片對象。

缺點

一、代碼耦合度比較大,一個函數內負責作了幾件事情。未知足面向對象設計原則中單一職責原則;

二、當某個時候不須要圖片預加載的時候,須要從myImage 函數內把代碼刪掉,這樣代碼耦合性過高。

  • 方案二步驟:

一、myImage中建立img標籤

二、myImage中插入img標籤

三、myImage中返回設置imgNode的src方法

四、ProxyImage中建立img對象

五、ProxyImage中書寫onload方法

六、ProxyImage中返回設置圖片的方法。

優勢

一、myImage 函數只負責作一件事。建立img元素加入到頁面中,其中的加載loading圖片交給代理函數ProxyImage 去作。

二、加載成功之後,代理函數ProxyImage 會通知及執行myImage 函數的方法。

三、當之後不須要代理對象的話,咱們直接能夠調用本體對象的方法便可

緩存代理

緩存代理,就是將前面使用的值緩存下來,後續還有使用的話,就直接拿出來用。

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')
複製代碼
相關文章
相關標籤/搜索