JavaScript - 代理模式

109 阅读3分钟

概念

代理模式由于一个对象不能直接引用另外一个对象,所以需要通过代理来让两个对象之间起到中介的作用。

比如 Vue 中的响应式设计,我们不能去操作你的原生对象一样,实际上去操作 Vue 内部实现的代理对象。如果去直接操作原对象,数据和节点之间就不能存在响应。

所以代理模式 的本意就是当客户不方便直接访问一个对象或者不满足需要的时候,提供一个替身对象来控制对这个对象的访问。客户实际上访问的是替身对象。替身对象对请求做出一些处理之后,再把请求转交给本体对象。

虚拟代理

虚拟代理是为了解决一个对象直接生成的代价太过于昂贵,利用其他对象对他一部分功能进行代理,达到优化的效果。

比如我们去实现一个图片预加载的时候,如果我们的图片很大,用户就会看到页面很长一段时间是空白 我们可以想到的改进是图片加载完成之前都展示loading图片。

var CreateImage = ( function() {
    var ImageNode = document.createElement("img")
    document.body.appendChild( ImageNode )
    
    var img = new Image()
    // 模拟图片加载
    img.onload = function() {
        setTimeout( function() {
            ImageNode.src = img.src
        },1000 )
    }

    return {
        setSrc:function( src ) {
            img.src = src
            ImageNode.src = "https://hbimg.b0.upaiyun.com/efd962de4532ee6b867e4938cfdba8cf44b87d27416a4-0yryg5_fw658"
        }
    }
} )()

CreateImage.setSrc( "https://picx.zhimg.com/v2-821962f0ca11d1b9ac1d73397e21bd87_l.jpg?source=32738c0c" )

可以看到,上面的代码中 CreateImage 对象同时承担了加载图片和预加载图片的两种职责,大大增加了对象生成的代价。同时也违背了开放封闭原则,如果我们以后不需要预加载图片了,那我们不得不修改整个对象。

我们可以使用 虚拟代理 来对代码进行改性,减少对象生成代价,同时也符合符合代码职责单一原则。

var CreateImage = (function () {
    var ImageNode = document.createElement("img")
    document.body.appendChild(ImageNode)

    return {
        setSrc: function (src) {
            ImageNode.src = src
        }
    }
})()

var ProxyImage = (function () {
    var img = new Image()
    // 模拟图片加载
    img.onload = function () {
        setTimeout(function () {
            CreateImage.setSrc( img.src )
        }, 1000)
    }

    return {
        setSrc: function( src ) {
            img.src = src
            CreateImage.setSrc( "https://hbimg.b0.upaiyun.com/efd962de4532ee6b867e4938cfdba8cf44b87d27416a4-0yryg5_fw658" )
        }
    }
})()

ProxyImage.setSrc("https://picx.zhimg.com/v2-821962f0ca11d1b9ac1d73397e21bd87_l.jpg?source=32738c0c")

虚拟代理合并HTTP请求

假设我们在做一个文件同步的功能,当我们选中一个checkbox的时候,它对应的文件就会被同步到另外一台服务器上面,此时,我们的思路就是当checkbox被选中时,把选中的文件传到另一台服务器。代码如下:

<body>
    <input type="checkbox" id="1"></input>1
    <input type="checkbox" id="2"></input>2
    <input type="checkbox" id="3"></input>3
    <input type="checkbox" id="4"></input>4
    <input type="checkbox" id="5"></input>5
    <input type="checkbox" id="6"></input>6
    <input type="checkbox" id="7"></input>7
    <input type="checkbox" id="8"></input>8
    <input type="checkbox" id="9"></input>9
</body>
function synchronousFile(id) {
    console.log("开始同步文件,id为:" + id);
}

var checkboxElements = document.getElementsByTagName("input")
var length = checkboxElements.length
for (var i = 0; i < length; i++) {
    (function () {
        var ele = checkboxElements[i]
        ele.addEventListener("click", function () {
            synchronousFile(ele.getAttribute("id"))
        })
    })()
}

虽然现在功能已经实现了,但是问题还存在问题,当我们去每次选择的时候,都会调用 synchronousFile 函数,并发送 http 请求,这会带来极大的消耗。

function synchronousFile(id) {
    console.log("开始同步文件,id为:" + id);
}

var checkboxElements = document.getElementsByTagName("input")
var length = checkboxElements.length
for (var i = 0; i < length; i++) {
    (function () {
        var ele = checkboxElements[i]
        ele.addEventListener("click", function () {
            proxySynchronousFile(ele.getAttribute("id"))
        })
    })()
}

// 收集一段时间的请求
var proxySynchronousFile = (function () {
    var cacheIds = [], timeId = 0
    return function( id ) {
        if( cacheIds.indexOf( id ) < 0 ) {
            cacheIds.push( id )
        }
        clearTimeout( timeId )
        timeId = setTimeout( function() {
            synchronousFile( cacheIds.join(",") )
            cacheIds = []
        },1000 )
    }
})()

通过 proxySynchronousFile 代理函数,收集一段时间内的请求,最后统一发给服务器,如果不是实时性要求很高的系统,有一点延迟并不会带来太大的副作用,却能大大减轻服务器的压力。

缓存代理

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

function mult() {
    var a = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
        a *= arguments[i]
    }

    return a
}

console.log( mult( 2,3 ) );		// 6
console.log( mult( 2,3,4 ) );	// 24

加入缓存代理:

function mult() {
    var a = 1;
    for (var i = 0, l = arguments.length; i < l; i++) {
        a *= arguments[i]
    }

    return a
}

// 缓存代理
var ProxyMult = ( function() {
    var cache = []
    return function() {
        var args = Array.prototype.join.call( arguments,"," )
        if( args in cache ) {
            return cache[args]
        }
        return cache[args] = mult.apply( this,arguments )
    }
} )()

console.log( ProxyMult( 1,2,3,4 ) );	// 24
console.log( ProxyMult( 1,2,3,4 ) );	// 24