通过引入一个新的对象(如小图片和远程代理对象)来实现对真实对象的操作或者将新的对 象作为真实对象的一个替身,这种实现机制即为代理模式,通过引入代理对象来间接访问一 个对象,这就是代理模式的模式动机。
代理模式(Proxy Pattern) :给某一个对象提供一个代理,并由代理对象控制对原对象的引用。代理模式的英 文叫做Proxy或Surrogate,它是一种对象结构型模式。
火车票代购、房产中介、律师、海外代购、明星经纪人
代理模式包含如下角色:
时序图
一个例子-明星经纪人
abstract class Star {
abstract answerPhone(): void;
}
class Angelababy extends Star {
public available: boolean = true;
answerPhone(): void {
console.log('你好,我是Angelababy.');
}
}
class AngelababyAgent extends Star {
constructor(private angelababy: Angelababy) {
super();
}
answerPhone(): void {
console.log('你好,我是Angelababy的经纪人.');
if (this.angelababy.available) {
this.angelababy.answerPhone();
}
}
}
let angelababyAgent = new AngelababyAgent(new Angelababy());
angelababyAgent.answerPhone();
<body>
<ul id="list">
<li>1li>
<li>2li>
<li>3li>
ul>
<script> let list = document.querySelector('#list'); list.addEventListener('click',event=>{ alert(event.target.innerHTML); }); script>
body>
let express=require('express');
let path=require('path')
let app=express();
app.get('/images/loading.gif',function (req,res) {
res.sendFile(path.join(__dirname,req.path));
});
app.get('/images/:name',function (req,res) {
setTimeout(() => {
res.sendFile(path.join(__dirname,req.path));
}, 2000);
});
app.get('/',function (req,res) {
res.sendFile(path.resolve('index.html'));
});
app.listen(8080);
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
<style> .bg-container { width: 600px; height: 400px; margin: 100px auto; } .bg-container #bg-image { width: 100%; height: 100%; } style>
head>
<body>
<div id="background">
<button data-src="/images/bg1.jpg">背景1button>
<button data-src="/images/bg2.jpg">背景2button>
div>
<div class="bg-container">
<img id="bg-image" src="/images/bg1.jpg" />
div>
<script> let container = document.querySelector('#background'); class BackgroundImage { constructor() { this.bgImage = document.querySelector('#bg-image'); } setSrc(src) { this.bgImage.src = src; } } class LoadingBackgroundImage { static LOADING_URL= `/images/loading.gif`; constructor() { this.backgroundImage = new BackgroundImage(); } setSrc(src) { this.backgroundImage.setSrc(LoadingBackgroundImage.LOADING_URL); let img = new Image(); img.onload = () => { this.backgroundImage.setSrc(src); } img.src = src; } } let loadingBackgroundImage = new LoadingBackgroundImage(); container.addEventListener('click', function (event) { let src = event.target.dataset.src; loadingBackgroundImage.setSrc(src + '?ts=' + Date.now()); }); script>
body>
html>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Lazy-Loadtitle>
<style> .image { width: 300px; height: 200px; background-color: #CCC; } .image img { width: 100%; height: 100%; } style>
head>
<body>
<div class="image-container">
<div class="image">
<img data-src="/images/bg1.jpg">
div>
<div class="image">
<img data-src="/images/bg2.jpg">
div>
<div class="image">
<img data-src="/images/bg1.jpg">
div>
<div class="image">
<img data-src="/images/bg2.jpg">
div>
<div class="image">
<img data-src="/images/bg1.jpg">
div>
<div class="image">
<img data-src="/images/bg2.jpg">
div>
<div class="image">
<img data-src="/images/bg1.jpg">
div>
<div class="image">
<img data-src="/images/bg2.jpg">
div>
<div class="image">
<img data-src="/images/bg1.jpg">
div>
<div class="image">
<img data-src="/images/bg2.jpg">
div>
div>
body>
<script> const imgs = document.getElementsByTagName('img'); const clientHeight = window.innerHeight || document.documentElement.clientHeight; let loadedIndex = 0; function lazyload() { for (let i = loadedIndex; i < imgs.length; i++) { if (clientHeight - imgs[i].getBoundingClientRect().top > 0) { imgs[i].src = imgs[i].dataset.src; loadedIndex = i + 1; } } } lazyload(); window.addEventListener('scroll', lazyload, false); script>
html>
有些时候可以用空间换时间
一个正整数的阶乘(factorial)是所有小于及等于该数的正整数的积,并且0的阶乘为1
const factorial = function f(num) {
if (num === 1) {
return 1;
} else {
return (num * f(num - 1));
}
}
const proxy = function (fn) {
const cache = {}; // 缓存对象
return function (num) {
if (num in cache) {
return cache[num]; // 使用缓存代理
}
return cache[num] = fn.call(this, num);
}
}
const proxyFactorial = proxy(factorial);
console.log(proxyFactorial(5));
console.log(proxyFactorial(5));
console.log(proxyFactorial(5));
指的是这样一个数列:1、1、2、3、5、8、13、21、34。在数学上,斐波那契数列以如下被以递推的方法定义:F(1)=1,F(2)=1,F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)
let count = 0;
function fib(n) {
count++;
return n <= 2 ? 1 : fib(n - 1) + fib(n - 2);
}
var result = fib(10);
console.log(result, count);//55 110
let count = 0;
const fibWithCache = (function () {
let cache = {};
function fib(n) {
count++;
if (cache[n]) {
return cache[n];
}
let result = n <= 2 ? 1 : fib(n - 1) + fib(n - 2);
cache[n] = result;
return result;
}
return fib;
})();
var result = fibWithCache(10);
console.log(result, count);//55 17
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
<style> #container { width: 200px; height: 400px; border: 1px solid red; overflow: auto; } #container .content { height: 4000px; } style>
head>
<body>
<div id="container">
<div class="content">div>
div>
<script> function throttle(callback, interval) { let last; return function () { let context = this; let args = arguments; let now = Date.now(); if (last) { if (now - last >= interval) { last = now; callback.apply(context, args); } } else { callback.apply(context, args); last = now; } } } let lastTime = Date.now(); const throttle_scroll = throttle(() => { console.log('触发了滚动事件', (Date.now() - lastTime) / 1000); }, 1000); document.getElementById('container').addEventListener('scroll', throttle_scroll); script>
body>
html>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
<style> #container { width: 200px; height: 400px; border: 1px solid red; overflow: auto; } #container .content { height: 4000px; } style>
head>
<body>
<div id="container">
<div class="content">div>
div>
<script> function debounce(callback, delay) { let timer; return function () { let context = this; let args = arguments; if (timer) clearTimeout(timer); timer = setTimeout(() => { callback.apply(context, args); }, delay); } } let lastTime = Date.now(); const throttle_scroll = debounce(() => { console.log('触发了滚动事件', (Date.now() - lastTime) / 1000); }, 1000); document.getElementById('container').addEventListener('scroll', throttle_scroll); script>
body>
html>
<body>
<ul id="todos">
ul>
<script> let todos = document.querySelector('#todos'); window.onload = function(){ fetch('/todos').then(res=>res.json()).then(response=>{ todos.innerHTML = response.map(item=>`${item.completed?"checked":""} />${item.text}`).join(''); }); } function toggle(id){ fetch(`/toggle/${id}`).then(res=>res.json()).then(response=>{ console.log('response',response); }); } todos.addEventListener('click',function(event){ let checkbox = event.target; let id = checkbox.value; toggle(id); }); script>
body>
app.js
let express=require('express');
let app=express();
app.use(express.static(__dirname));
let todos=[
{id: 1,text: 'a',completed: false},
{id: 2,text: 'b',completed: false},
{id: 3,text: 'c',completed: false},
];
app.get('/todos',function (req,res) {
res.json(todos);
});
app.get('/toggle/:id',function (req,res) {
let id=req.params.id;
todos = todos.map(item => {
if (item.id==id) {
item.completed=!item.completed;
}
return item;
});
res.json({code:0});
});
app.listen(8080);
todos.html
<body>
<ul id="todos">
ul>
<script> let todos = document.querySelector('#todos'); window.onload = function(){ fetch('/todos').then(res=>res.json()).then(response=>{ todos.innerHTML = response.map(item=>`${item.completed?"checked":""} />${item.text}`).join(''); }); } function toggle(id){ fetch(`/toggle/${id}`).then(res=>res.json()).then(response=>{ console.log('response',response); }); } let LazyToggle = (function(id){ let ids = []; let timer; return function(id){ ids.push(id); if(timer){ clearTimeout(timer); } timer = setTimeout(function(){ toggle(ids.join(',')); ids = null; clearTimeout(timer); timer = null; },2000); } })(); todos.addEventListener('click',function(event){ let checkbox = event.target; let id = checkbox.value; LazyToggle(id); }); script>
app.js
app.get('/toggle/:ids',function (req,res) {
let ids=req.params.ids;
ids=ids.split(',').map(item=>parseInt(item));
todos = todos.map(item => {
if (ids.includes(item.id)) {
item.completed=!item.completed;
}
return item;
});
res.json({code:0});
});
proxy-server.js
const http = require('http');
const httpProxy = require('http-proxy');
//创建一个代理服务
const proxy = httpProxy.createProxyServer();
//创建http服务器并监听8888端口
let server = http.createServer(function (req, res) {
//将用户的请求转发到本地9999端口上
proxy.web(req, res, {
target: 'http://127.0.0.1:9999'
});
//监听代理服务错误
proxy.on('error', function (err) {
console.log(err);
});
});
server.listen(8888, '0.0.0.0');
real-server.js
const http = require('http');
let server = http.createServer(function (req, res) {
res.end('9999');
});
server.listen(9999, '0.0.0.0');
otherWindow.postMessage(message, targetOrigin, [transfer]);
window.addEventListener("message", receiveMessage, false);
origin.js
let express=require('express');
let app=express();
app.use(express.static(__dirname));
app.listen(3000);
target.js
let express = require('express');
let app = express();
let bodyParser = require('body-parser');
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname));
let users = [];
app.post('/register', function (req, res) {
let body = req.body;
let target = body.target;
let callback = body.callback;
let username = body.username;
let password = body.password;
let user = { username, password };
let id = users.length == 0 ? 1 : users[users.length - 1].id + 1;
user.id = id;
users.push(user);
res.status(302);
res.header('Location', `${target}?callback=${callback}&args=${id}`);
res.end();
});
app.listen(4000);
reg.html
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script type="text/javascript"> window.addEventListener('message', function (event) { console.log(event.data); if (event.data.receiveId) { alert('用户ID=' + event.data.receiveId); } }) script>
<iframe name="proxyIframe" id="proxyIframe" frameborder="0">iframe>
<form action="http://localhost:4000/register" method="POST" target="proxyIframe">
<input type="hidden" name="callback" value="receiveId">
<input type="hidden" name="target" value="http://localhost:3000/target.html">
用户名<input type="text" name="username" />
密码<input type="text" name="password" />
<input type="submit" value="提交">
form>
body>
html>
target.html
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Documenttitle>
head>
<body>
<script> window.onload = function () { var query = location.search.substr(1).split('&'); let callback, args; for (let i = 0, len = query.length; i < len; i++) { let item = query[i].split('='); if (item[0] == 'callback') { callback = item[1]; } else if (item[0] == 'args') { args = item[1]; } } try { window.parent.postMessage({ [callback]: args }, '*'); } catch (error) { console.log(error); } } script>
body>
html>
html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>jquery proxytitle>
head>
<body>
<button id="btn">点我变红button>
<script src="http://libs.baidu.com/jquery/2.0.0/jquery.min.js">script>
<script> let btn = document.getElementById('btn'); btn.addEventListener('click', function () { setTimeout($.proxy((function () { $(this).css('color', 'red'); }), this), 1000); }); script>
body>
html>
function proxy(fn, context) {
return function () {
return fn.call(context, arguments);
}
}
let wang={
name: 'wanglaoshi',
age: 29,
height:165
}
let wangMama=new Proxy(wang,{
get(target,key) {
if (key == 'age') {
return wang.age-1;
} else if (key == 'height') {
return wang.height-5;
}
return target[key];
},
set(target,key,val) {
if (key == 'boyfriend') {
let boyfriend=val;
if (boyfriend.age>40) {
throw new Error('太老');
} else if (boyfriend.salary<20000) {
throw new Error('太穷');
} else {
target[key]=val;
return true;
}
}
}
});
console.log(wangMama.age);
console.log(wangMama.height);
wangMama.boyfriend={
age: 41,
salary:3000
}
Vue2 中的变化侦测实现对 Object 及 Array 分别进行了不同的处理,Object 使用了 Object.defineProperty API,Array使用了拦截器对 Array 原型上的能够改变数据的方法进行拦截。虽然也实现了数据的变化侦测,但存在很多局限 ,比如对象新增属性无法被侦测,以及通过数组下边修改数组内容,也因此在 Vue2 中经常会使用到 $set 这个方法对数据修改,以保证依赖更新。
Vue3 中使用了 es6 的 Proxy API对数据代理,没有像 Vue2 中对原数据进行修改,只是加了代理包装,因此首先性能上会有所改善。其次解决了 Vue2 中变化侦测的局限性,可以不使用 $set 新增的对象属性及通过下标修改数组都能被侦测到。
适配器提供不同接口,代理模式提供一模一样的接口
装饰器模式原来的功能不变还可以使用,代理模式改变原来的功能