JavaScript设计模式与开发实战

JavaScript设计模式与开发实践

第一章、面向对象的JavaScript

1.1 多态

类似java面向对象,通过继承共有特征,来实现不同方法。JavaScript的多态就是把“做什么”和“谁去做”分离,消除类型间的耦合关系。

他的作用就是把过程化的条件分支语句转换为对象的多态性,从而消除这些分支语句。例如,拍电影,导演说“action”后,所有人员各司其职,提前知道自己要干什么,而不是导员挨个现场去分配。

1.2封装

封装的目的是将信息隐藏。但JavaScript并没有提供对private、protected、public这些关键字的支持,我们只能依赖变量的作用域(一般通过函数作用域)来实现封装特性,而且只能模拟出public和private这两种封装性

1.3原型继承

原型:如果A对象是从B对象克隆过来的,那么B就是A的原型。准确地说,这些属性和方法定义在Object的构造器函数(constructor functions)之上的prototype属性上,而非实例对象本身。

原型链:存在多种克隆关系(A->B->C),那么从当前A对象向上寻找属性和方法的过程就是一条原型链。在对象实例和它的构造器之间建立一个链接(它是__proto__属性,是从构造函数的prototype属性派生的),之后通过上溯原型链,在构造器中找到这些属性和方法。

原型编程范性的一些规则:

  • 所有的数据都是对象;
  • 要得到一个对象,不是通过实例化类,而是找到一个对象作为原型并克隆它;
  • 对象会记住它的原型;
  • 如果对象无法响应某个请求,它会把这个请求委托给它自己的原型

JavaScript中的原型继承:事实上,JavaScript中的根对象是Object.prototype对象。Object.prototype对象是一个空的对象;JavaScript的函数既可以作为普通函数被调用,也可以作为构造器被调用。当使用new运算符来调用函数时,此时的函数就是一个构造器。用new运算符来创建对象的过程,实际上也只是先克隆Object.prototype对象,再进行一些其他额外操作的过程;就JavaScript的真正实现来说,其实并不能说对象有原型,而只能说对象的构造器有原型。对于“对象把请求委托给它自己的原型”这句话,更好的说法是对象把请求委托给它的构造器的原型

obj._porto_ = Cuonstructor.prototype

;虽然JavaScript的对象最初都是由Object.prototype对象克隆而来的,但对象构造器的原型并不仅限于Object.prototype上,而是可以动态指向其他对象;留意一点,原型链并不是无限长的;

第二章、this、call、applay

2.1.tihs

JavaScript的this总是指向一个对象,而具体指向哪个对象是在运行时基于函数的执行环境动态绑定的,而非函数被声明时的环境

  • 作为对象的方法调用:this指向该对象
  • 作为普通函数调用:this指向全局对象(window);在ECMAScript5的strict模式下,这种情况下的this已经被规定为不会指向全局对象,而是undefined;
  • 构造器调用:当用new运算符调用函数时,该函数总会返回一个对象,通常情况下,构造器里的this就指向返回的这个对象
  • Function.prototype.call或Function.prototype.apply调用可以动态的改变传入函数的this
  • 丢失的this:当用另一个变量getName2来引用obj.getName,并且调用getName2时,此时是普通函数调用方式,this是指向全局window的,所以程序的执行结果是undefined

2.2call和apply

**call和apply的区别:**区别仅在于传入参数形式的不同;

  • apply接受两个参数,第一个参数指定了函数体内this对象的指向,第二个参数为一个带下标的集合;
  • call传入的参数数量不固定,第一个参数也是代表函数体内的this指向,从第二个参数开始往后,每个参数被依次传入函数;
  • call是apply的一颗语法糖,使用call或apply的时候如果我们传入的第一个参数为null函数体内的this会指向默认的宿主对象,严格模式下函数体内的this还是null。

call和apply的用途:

  • 1.改变this指向(最常见的用途);
  • 2.Function.prototype.bind(大部分高级浏览器都实现了内置的Function.prototype.bind,用来指定函数内部的this指向);
  • 3.借用其他对象的方法:Array.prototype.push要满足:对象本身要可以存取属性、对象的length属性可读写。

第三章、闭包和高阶函数

3.1闭包

闭包的形成与变量的作用域以及变量的生存周期密切相关

  • 变量的作用域:指变量的有效范围,从内到外查询

  • 变量的生存周期:全局变量的生成周期是永久的,除非主动销毁它;在函数内用var关键字声明的局部变量,当退出函数时,随着函数调用的结束而被销毁;可以用闭包来延长生命周期

  • 闭包的更多作用:1.封装变量(闭包可以帮助把一些不需要暴露在全局的变量封装成“私有变量”);2.延长局部变量的寿命;

  • 闭包和面向对象设计可以使用闭包来实现一个完整的面向对象系统

var extent = function(){ 
 var value = 0; 
 return { 
 call: function(){ 
 value++; 
 console.log( value ); 
   }
  }
 }
var extent = extent(); 
extent.call(); // 输出:1 
extent.call(); // 输出:2 
extent.call(); // 输出:3 
如果换成面向对象的写法,就是:
var extent = { 
 value: 0, 
 call: function(){ 
 this.value++; 
 console.log( this.value ); 
 } 
}; 
extent.call(); // 输出:1 
extent.call(); // 输出:2 
extent.call(); // 输出:3 
或者:
var Extent = function(){ 
 this.value = 0; 
}; 
Extent.prototype.call = function(){ 
 this.value++; 
 console.log( this.value ); 
}; 
var extent = new Extent(); 
extent.call(); 
extent.call(); 
extent.call();
  • 用闭包实现命令模式:命令模式的意图是把请求封装为对象,从而分离请求的发起者和请求的接收者之间的耦合关系
  • 闭包与内存管理:一种怂人听闻的说法是闭包会造成内存泄露,所以要尽量减少闭包的使用;局部变量本来应该在函数退出的时候被解除引用,但如果局部变量被封闭在闭包形成的环境中,那么这个局部变量就能一直生存下去;在基于引用技术策略的垃圾回收机制中,如果两个对象之间形成了循环引用,那么这两个对象都无法被回收,但循环引用造成的内存泄露在本质上也不是闭包造成的;
  • 如果要解决循环引用带来的内存泄露问题,我们只需要把循环引用中的变量设为 null 即可。

3.2高阶函数

高阶函数是指至少满足下列条件之一的函数:

  • 1.函数作为参数传递;
  • 2.函数作为返回值输出;
3.2.1函数作为参数传递

第四章、单例模式

单例模式的核心:唯一的实例,在全局能访问到

全局变量不是单例模式,但会把全局变量当作单例模式使用。

减少全局模式的使用方法:
1.使用命名空间:

let A = {
	add(){}
}
A.add()

2.使用闭包封装私有变量

把一些变量封装到闭包内部,只暴露一些接口

const user = (function() {
	var name = 'a',
        age = 29
    return {
		getUserInfo : function() {
			return name+age;
        }
    }
})()

4.1惰性单例

在需要的时候才去创对象实例

应用场景:弹窗、购物车等

let timeTool = (function() {
   let _instance = null;
   function init() {
       //私有变量
       let now  = new Date();
       //共有属性方法
       let name = '时间处理工具';
       this.getTime = function() {
           return now.toTimeString();
       }
   }
    return function() {
        if(!_instance) {
            _instance = new init();
        }
        return _instance;
    }
 })();
let instance1 = timeTool();
let instance2 = timeTool();
console.log(instance1 === instance2); //true

上面的timeTool实际上是一个函数,_instance作为实例对象最开始赋值为nullinit函数是其构造函数,用于实例化对象,立即执行函数返回的是匿名函数用于判断实例是否创建,只有当调用timeTool()时进行实例的实例化,这就是惰性单例的应用,不在js加载时就进行实例化创建, 而是在需要的时候再进行单例的创建。 如果再次调用, 那么返回的永远是第一次实例化后的实例对象。

4.2扩展 ES6中创建单例模式

import export就是单例模式

使用ES6的语法将constructor改写为单例模式的构造器。

class SingletonApple {
  constructor(name, creator, products) {
    //首次使用构造器实例
    if (!SingletonApple.instance) {
      this.name = name;
      this.creator = creator;
      this.products = products;
      //将this挂载到SingletonApple这个类的instance属性上
      SingletonApple.instance = this;
    }
    return SingletonApple.instance;
  }
}
/*
  constructor(name, creator, products) {
      this.name = name;
      this.creator = creator;
      this.products = products;
  }
  //静态方法
  static getInstance(name, creator, products) {
    if(!this.instance) {
      this.instance = new SingletonApple(name, creator, products);
    }
    return this.instance;
  }
}
*/

let appleCompany = new SingletonApple('苹果公司', '乔布斯', ['iPhone', 'iMac', 'iPad', 'iPod']);
let copyApple = new SingletonApple('苹果公司', '阿辉', ['iPhone', 'iMac', 'iPad', 'iPod']);

console.log(appleCompany === copyApple);  //true

单例模式案例——登录框 (codepen.io)

第五章、策略模式

定义:定义一些列算法,把他们一一封装,并且他们可以相互替换。

核心:将算法的实现和算法的使用分离

一个基于策略模式的程序至少由两部分组成。第一个部分是一组策略类,第二个部分是环境类Context。

  • 策略类:策略类封装了具体的算法,并负责具体的计算过程。
  • 环境类Context:环境类Context接受客户的请求,随后把请求委托给某一个策略类。

案例:计算奖金,绩效分别为S、A、B,奖金分别为4、3、 2倍;

最初代码实现

var calculateBunus = function(preformancelevel, salary) {
    if(preformancelevel === 'S') {
        return salary * 4;
    }
    if(preformancelevel === 'A') {
        return salary * 3;
    }
    if(preformancelevel === 'B') {
        return salary * 2;
    }
}
calculateBunus('B', 2000) // 4000;
calculateBunus('S', 4000) // 16000;
  • 代码简单明了,但缺点也显而易见:
    calculateBunus函数比较庞大,包含了很多if-else语句,这些语句需要覆盖所有逻辑的分支。
  • calculateBunus函数缺乏弹性,如果新增一个“C”字段,或者把"S"的奖金系数改为5倍,必须在calculateBunus函数内部修改,这样就违反了开放-封闭的原则。
  • 算法的复用性差,如果程序中其他地方需要重用这些计算奖金的算法呢?只能CTRL+CV;

利用策略模式重构代码:

我们把所有的绩效以及奖金分别写成单独的函数

var performanceS = function(){};
performanceS.prototype.calculate = function (salary) {
	return salary * 4;
}
var performanceA = function(){};
performanceA.prototype.calculate = function (salary) {
	return salary * 3;
}
var performanceB = function(){};
performanceB.prototype.calculate = function (salary) {
	return salary * 2;
}

接下来定义Bonus类:

var Bonus = function() {
    this.salary = null; //原始工资
    this.strategy = null; //绩效等级对应的策略对象
};
Bonus.prototype.setSalary = function(salary) {
    this.salary = salary; //设置员工的原始工资
}Bonus.prototype.setstrategy = function(strategy) {
    this.strategy = strategy; //设置员工绩效等级对应的策略对象
}
Bonus.prototype.getBonus = function() { //取得奖金数额
    if(!this.strategy) {
        throw new Error('未设置‘strategy属性');
    }
    return this.strategy.calculate(this.salary); //把计算奖金的操作委托给对应的策略对象
}

调用:

var bonus = new Bonus();
bonus.setSalary(1000);
bonus.setStrategy(new performanceS()); //设置策略对象
console.log(bonus.getBonus()) //输出:4000

Javascript版的策略模式

上面所说的是让strategy对象从各个策略类中创建而来,在js中,可以把strategy直接定义成函数:

var strategies = {
	"S":function(salary) {
        return salary * 4;
    }
    "A":function(salary) {
        return salary * 3;
    }
	"B":function(salary) {
        return salary * 2;
    }
};
//同样Context也没必要定义成Bonus类来表示,依然用calculateBonus函数充当Context来接收客户请求
var calculateBonus = function(level, salary) {
    return strategies[level](salary);
}
console.log(calculateBonus('S',20000))  //输出:80000
console.log(calculateBonus('A',10000))  //输出:30000

策略模式的优缺点

  • 策略模式利用组合、委托、和多态等技术和思想,可以有效地避免多重条件选择语句。
  • 策略模式提供了对开放-封闭原则的完美支持,将算法封装在独立的strategy中,使得他们易于切换,易于理解易于扩展
  • 策略模式中的算法也可以复用在系统的其他地方,复用性高。
  • 在策略模式中利用组合和委托来让Context拥有执行算法的能力,也是继承的一种更轻便的替代方案

缺点:

  • 使用策略模式会在程序中增加许多策略类和策略对象,但实际上这比把它们负责的逻辑堆砌在Context中要好
  • 另外,还需要对strategy熟悉,比如旅游规划,出行方式的选择:飞机、火车、驾车等细节。
  • 第六章、代理模式

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

代理模式的关键是,当客户不方便直接访问一个对象或者不满足需求时,提供一个替身对象来控制对这个对象的访问,客户实际上访问的是替身对象。

在这里插入图片描述

6.1举个栗子

小明想要对女神A表白,送鲜花,可是他害羞不敢自己送,这时找来B,让B替小明送给A。这个模式就是代理模式。

var Flower = function(){}; 
 
var xiaoming = { 
    sendFlower: function( target){ 
    var flower = new Flower(); 
        target.receiveFlower( flower ); 
    } 
}; 
 
var B = { 
    receiveFlower: function( flower ){ 
        A.receiveFlower( flower );
         } 
}; 
 
var A = { 
    receiveFlower: function( flower ){ 
        console.log( '收到花 ' + flower ); 
    } 
}; 
 
xiaoming.sendFlower( B );

这样就是简单的代理结构,但似乎这样做代理并没有什么作用,反而显得代码冗余。但是有了代理的思想就是一个好的起点。

再看这个场景:

小明表白的成功概率和女神A的心情有关,A心情好的时候成功概率是60%,心情不好的表白成功时候几乎为0。而小明不知道A是什么心情,B知道A的心情,就可以通过把鲜花交给B,由B判断A的心情好坏再把鲜花转交给女神A,代码如下:

var Flower = function(){}; 
 
var xiaoming = { 
    sendFlower: function( target){ 
        var flower = new Flower(); 
        target.receiveFlower( flower ); 
    } 
}; 
 
var B = { 
    receiveFlower: function( flower ){ 
        A.listenGoodMood(function(){    // 监听A的好心情 
            A.receiveFlower( flower ); 
        }); 
    } 
}; 
 
var A = { 
    receiveFlower: function( flower ){ 
        console.log( '收到花 ' + flower ); 
    }, 
    listenGoodMood: function( fn ){ 
        setTimeout(function(){    // 假设10秒之后A的心情变好
               fn();         
        }, 10000 ); 
    } 
}; 
 
xiaoming.sendFlower( B ); 

6.2保护代理和虚拟代理

比如送花的人年龄太大或者没有本次车,这种请求就可以直接在代理B处过滤掉,这种代理叫做保护代理

另外,假设现实中的花价格不菲,导致在程序世界里,new Flower也是一个代价昂贵的操作, 那么我们可以把new Flower的操作交给代理B去执行,代理B会选择在A心情好时再执行new Flower,这是代理模式的另一种形式,叫作虚拟代理。虚拟代理把一些开销很大的对象,延迟到 真正需要它的时候才去创建。代码如下:

var B = { 
    receiveFlower: function( flower ){ 
        A.listenGoodMood(function(){    // 监听A的好心情 
             var flower = new Flower();    // 延迟创建flower 对象 
             A.receiveFlower( flower ); 
        }); 
    } 
}; 

保护代理用于控制不同权限的对象对目标对象的访问,但在JavaScript并不容易实现保护代 理,因为我们无法判断谁访问了某个对象。而虚拟代理是最常用的一种代理模式,本章主要讨论 的也是虚拟代理。

6.3虚拟代理实现图片预加载

在Web开发中,图片预加载是一种常用的技术,如果直接给某个img标签节点设置src属性, 由于图片过大或者网络不佳,图片的位置往往有段时间会是一片空白。常见的做法是先用一张 loading图片占位,然后用异步的方式加载图片,等图片加载好了再把它填充到img节点里,这种 场景就很适合使用虚拟代理。

让我们实现一个虚拟代理,首先创建一个普通对象,负责在页面中创建一个img标签,并提供一个setSrc接口对外暴露。

src属性:

var myImage = (function(){ 
    var imgNode = document.createElement( 'img' ); 
    document.body.appendChild( imgNode ); 
 
    return { 
        setSrc: function( src ){ 
            imgNode.src = src;         
        }  
    } 
})();  
 
myImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' );

我们把网速调制5KB/s,然后通过MyImage.setSrc,这是可以看到在图片加载好之前,页面中有一段空白时间。

这时候引入代理对象proxyImage,通过这个代理对象,在图片被真正加载好之前,页面先出现一张占位的longing图片来提示用户图片正在加载,代码如下:

var myImage = (function() {
    var imgNode = document.createEleement('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( 'file:// /C:/Users/svenzeng/Desktop/loading.gif' ); 
            img.src = src;         
        }
    }
})
proxyImage.setSrc('http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg');

这时候我们可以通过proxyImage间接地访问MyImage。proxyImage控制了对客户MyImage的访问,并在次过程加入了额外的操作,比如在图片加载出之前,先把img节点的src设置为一张本地的loading图片。

6.4代理的意义

先实现一个不用代理的预加载图片函数:

var MyImage = (function() {
    var imgNode = document.createElement('img');
    document.body.appendChild(imgNode);
    var img = new Image;
    img.onload = function() {
        imgNode.src = img.src;
    };
    return {
        setSrc: function(src) {
            imgNode.src = 'file:// /C:/Users/svenzeng/Desktop/loading.gif'
            img.src = src;
        }
    }
})();
MyImage.setSrc( 'http:// imgcache.qq.com/music/photo/k/000GGDys0yA0Nk.jpg' ); 

为了说明代理的意义,下面我们引入一个面向对象设计的原则——单一职责原则。

单一职责原则指的是,就一个类(通常也包括对象和函数等)而言,应该仅有一个引起它变化的原因。如果一个对象承担了多个职责,耦合程度就加大了,当发生变化时,可能会出现意外的破坏。面向对象设计鼓励将行为分布到细粒度的对象当中。也就是单一职责。

职责被定义为**“引起变化的原因”**。上段代码中的MyImage对象除了负责给img节点设置src 外,还要负责预加载图片。我们在处理其中一个职责时,有可能因为其强耦合性影响另外一个职 责的实现。

另外,在面向对象的程序设计中,大多数情况下,若违反其他任何原则,同时将违反开放— 封闭原则。如果我们只是从网络上获取一些体积很小的图片,或者5年后的网速快到根本不再需 要预加载,我们可能希望把预加载图片的这段代码从MyImage对象里删掉。这时候就不得不改动 MyImage对象了。 实际上,我们需要的只是给img节点设置src,预加载图片只是一个锦上添花的功能。如果 能把这个操作放在另一个对象里面,自然是一个非常好的方法。于是代理的作用在这里就体现出 来了,代理负责预加载图片,预加载的操作完成之后,把请求重新交给本体MyImage。

纵观整个程序,我们并没有改变或者增加MyImage的接口,但是通过代理对象,实际上给系 统添加了新的行为。这是符合开放—封闭原则的。给img节点设置src和图片预加载这两个功能, 被隔离在两个对象里,它们可以各自变化而不影响对方。何况就算有一天我们不再需要预加载, 那么只需要改成请求本体而不是请求代理对象即可。

6.5代理和本体接口的一致性

上一节说到,如果有一天我们不再需要预加载,那么就不再需要代理对象,可以选择直接请 求本体。其中关键是代理对象和本体都对外提供了 setSrc 方法,在客户看来,代理对象和本体 是一致的, 代理接手请求的过程对于用户来说是透明的,用户并不清楚代理和本体的区别,这 样做有两个好处。

  • 用户可以放心地请求代理,他只关心是否能得到想要的结果。
  • 在任何使用本体的地方都可以替换成使用代理。

6.6虚拟代理合并HTTP请求

再举个栗子:有多个复选框,每点击一个就会发送一次请求,如果一秒钟点多个,那么请求会带来很大的开销。

解决方案:可以通过一个代理来收集一段时间内的请求,最后一次性发送给服务器。比如我们等待2秒之后才把这个2秒之内需要同步文件的ID打包发送给服务器。

6.7虚拟代理在惰性加载中的应用

假设有一个迷你控制台的项目——miniConsole.js,它有一个log函数,专门用于打印参数。

复制代码// miniConsole.js代码

let miniConsole = {
    log: function(){
        // 真正代码略
        console.log( Array.prototype.join.call( arguments ) );
    }
};

export default miniConsole

因为这个控制台项目,是只在控制台展示的时候才需要的,我们希望他在有必要的时候才开始加载它,比如按F2的时候,加载miniConsole.js,就可以使用代理模式,惰性加载miniConsole.js。

大致的步骤是:

  1. 在用户敲击F2的时候,才去动态引入miniConsole.js的script标签
  2. 在用户敲击F2之前执行过的log命令,都会被缓存到代理对象内部的cache缓存数组内
  3. 等动态引入miniConsole.js的操作完成后,再从中逐一取出并执行。

详细代码如下:

复制代码// proxyMiniConsole.js代码

// miniConsole的代理对象
let proxyMiniConsole = (function(){
    // 存储每次执行log时的回调函数
    let cache = [];
    let handler = function( ev ){
        // 如果用户按了F2唤出了控制台
        if ( ev.keyCode === 113 ){
            // 执行引入miniConsole.js的操作
            let script = document.createElement( 'script' );
            script.src = 'miniConsole.js';
            document.getElementsByTagName( 'head' )[0].appendChild( script );
            document.body.removeEventListener( 'keydown', handler );// 只加载一次miniConsole.js
            script.onload = function(){
                // 如果miniConsole.js的script标签引入并加载完成
                for ( var i = 0, fn; fn = cache[ i++ ]; ){
                    // 遍历所有缓存的回调函数并执行
                    fn();
                }
            };
        }
    };
    
    // 监听键盘按键敲击事件
    document.body.addEventListener( 'keydown', handler, false );

    return {
        // 返回代理后的方法
        log: function(){
            // 获取传入的所有参数
            let args = arguments;
            // 向缓存列表加入要打印的参数
                cache.push( function(){
                    return miniConsole.log.apply( miniConsole, args );
                });
          }
    }
})();

miniConsole.log( 11 );      // 开始打印log

6.8缓存代理

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

举个栗子:

1.计算乘积

先创建一个用于求乘积的函数:  
var mult = function(){ 
    console.log( '开始计算乘积' ); 
    var a = 1; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a * arguments[i];  
    }  
    return a; 
}; 
 
mult( 2, 3 );    // 输出:6 
mult( 2, 3, 4 );    // 输出:24 
现在加入缓存代理函数: 
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 ); 
    } 
})(); 
 
 proxyMult( 1, 2, 3, 4 );    // 输出:24 
 proxyMult( 1, 2, 3, 4 );    // 输出:24 
当我们第二次调用proxyMult( 1, 2, 3, 4 )的时候,本体mult函数并没有被计算,proxyMult
直接返回了之前缓存好的计算结果。 
通过增加缓存代理的方式,mult函数可以继续专注于自身的职责——计算乘积,缓存的功能
是由代理对象实现的。

6.9用高阶函数动态创建代理

/**************** 计算乘积 *****************/ 
var mult = function(){ 
    var a = 1; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a * arguments[i];  
    } 
    return a; 
}; 
 
/**************** 计算加和 *****************/ 
var plus = function(){ 
    var a = 0; 
    for ( var i = 0, l = arguments.length; i < l; i++ ){ 
        a = a + arguments[i];  
    } 
    return a; 
}; 
 
/**************** 创建缓存代理的工厂 *****************/ 
var createProxyFactory = function( fn ){ 
    var cache = {}; 
    return function(){ 
        var args = Array.prototype.join.call( arguments, ',' ); 
        if ( args in cache ){ 
            return cache[ args ]; 
        } 
        return  cache[ args ] = fn.apply( this, arguments ); 
    } 
}; 
 
var proxyMult = createProxyFactory( mult ), 
proxyPlus = createProxyFactory( plus ); 
 
alert ( proxyMult( 1, 2, 3, 4 ) );    // 输出:24 
alert ( proxyMult( 1, 2, 3, 4 ) );    // 输出:24 
alert ( proxyPlus( 1, 2, 3, 4 ) );    // 输出:10 
alert ( proxyPlus( 1, 2, 3, 4 ) );    // 输出:10 

6.10其他代理模式

  • 防火墙代理:控制网络资源的访问,保护主题不让“坏人”接近。
  • 远程代理:为一个对象在不同的地址空间提供局部代表,在Java中,远程代理可以是另 一个虚拟机中的对象。
  • 保护代理:用于对象应该有不同访问权限的情况。
  • 智能引用代理:取代了简单的指针,它在访问对象时执行一些附加操作,比如计算一个 对象被引用的次数。
  • 写时复制代理:通常用于复制一个庞大对象的情况。写时复制代理延迟了复制的过程, 当对象被真正修改时,才对它进行复制操作。写时复制代理是虚拟代理的一种变体,DLL (操作系统中的动态链接库)是其典型运用场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/392138.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

TIM编码器接口(编码器测速)

定时器编码器接口自动计次--------->对应手册14.3.12编码器接口模式 应用场景&#xff1a; 电机控制PWM驱动电机&#xff0c;编码器测电机速度&#xff0c;PID算法闭环控制 ------------------------------------------------------------------------------------------…

【Linux】程序地址空间 -- 详解 Linux 2.6 内核进程调度队列 -- 了解

一、程序地址空间回顾 在学习 C/C 时&#xff0c;我们知道内存会被分为几个区域&#xff1a;栈区、堆区、全局/静态区、代码区、字符常量区等。但这仅仅是在语言层面上的理解&#xff0c;是远远不够的。 如下空间布局图&#xff0c;请问这是物理内存吗&#xff1f; 不是&…

LV.23 D3 STM32 GPIO口操作平台介绍 学习笔记

一、GPIO的基本概念及寄存器介绍 1.1 GPIO接口简介 通用输入输出接口GPIO是嵌入式系统、单片机开发过程中最常用的接口&#xff0c;用户可以通过编程灵活的对接口进行控制&#xff0c;实现对电路板上LED、数码管、按键等常用设备控制驱动&#xff0c;也可以作为串口的数据收发…

文件上传漏洞--Upload-labs--Pass02--Content-Type绕过

一、什么是 Content-Type 我们在上传文件时利用 Burpsuite 进行抓包&#xff0c;如下图所示&#xff1a; 上传文件后台的源代码可能会对 Content-Type 进行规定&#xff0c;设置白名单 或 黑名单&#xff0c;这时就要利用Content-Type绕过上传含有恶意代码的 php文件。 二、代…

2023年中国数据智能管理峰会(DAMS上海站2023):核心内容与学习收获(附大会核心PPT下载)

随着数字经济的飞速发展&#xff0c;数据已经渗透到现代社会的每一个角落&#xff0c;成为驱动企业创新、提升治理能力、促进经济发展的关键要素。在这样的背景下&#xff0c;2023年中国数据智能管理峰会&#xff08;DAMS上海站2023&#xff09;应运而生&#xff0c;汇聚了众多…

解锁Spring Boot中的设计模式—03.委派模式:探索【委派模式】的奥秘与应用实践!

委派模式 文章目录 委派模式1.简述**应用场景****优缺点****业务场景示例** 2.类图3.具体实现3.1.自定义注解3.2.定义抽象委派接口3.3.定义具体执行者3.4.定义委派者(统一管理委派任务)3.5.定义委派者管理类 4.测试4.1.controller层4.2.测试不同场景4.2.1.测试生产部门计算费用…

排序前言冒泡排序

目录 排序应用 常见的排序算法 BubbleSort冒泡排序 整体思路 图解分析 ​ 代码实现 每趟 写法1 写法2 代码NO1 代码NO2优化 时间复杂度 排序概念 排序&#xff1a;所谓排序&#xff0c;就是使一串记录&#xff0c;按照其中的某个或某些关键字的大小&#xff0c;递…

python in Vscode

背景 对于后端的语言选择&#xff1a; python&#xff0c;java&#xff0c;JavaScript备选。 选择Python 原因&#xff1a;可能是非IT专业的人中&#xff0c;会Python的人比较多。 目的 之前使用的IDE是VSCODE&#xff0c;在WSL的环境下使用。现在需要在在WSL的VSCODE下使…

【开源】SpringBoot框架开发创意工坊双创管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 管理员端2.2 Web 端2.3 移动端 三、系统展示四、核心代码4.1 查询项目4.2 移动端新增团队4.3 查询讲座4.4 讲座收藏4.5 小程序登录 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的创意工坊双创管理…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之NavRouter组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之NavRouter组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、NavRouter组件 导航组件&#xff0c;默认提供点击响应处理&#xff0c;不需要…

MySQL数据库⑪_C/C++连接MySQL_发送请求

目录 1. 下载库文件 2. 使用库 3. 链接MySQL函数 4. C/C链接示例 5. 发送SQL请求 6. 获取查询结果 本篇完。 1. 下载库文件 要使用C/C连接MySQL&#xff0c;需要使用MySQL官网提供的库。 进入MySQL官网选择适合自己平台的mysql connect库&#xff0c;然后点击下载就行…

相机图像质量研究(27)常见问题总结:补光灯以及遮光罩对成像的影响--遮光罩

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

视频生成模型作为世界模拟器

我们探索了在视频数据上大规模训练生成模型。具体来说&#xff0c;我们联合训练文本条件扩散模型&#xff0c;处理不同持续时间、分辨率和宽高比的视频和图像。我们利用一种在时空补丁上操作视频和图像潜码的transformer架构。我们最大的模型&#xff0c;Sora&#xff0c;能够生…

【初始RabbitMQ】了解和安装RabbitMQ

RabbitMQ的概念 RabbitMQ是一个消息中间件&#xff1a;他可以接受并转发消息。例如你可以把它当做一个快递站点&#xff0c;当你要发送一个包 裹时&#xff0c;你把你的包裹放到快递站&#xff0c;快递员最终会把你的快递送到收件人那里&#xff0c;按照这种逻辑 RabbitMQ 是 …

数据的力量:构筑现代大型网站之数据库基础与应用

目录 数据库基础知识--前言 大型网站架构特点 DBA数据库管理员 什么是数据? 数据存储 什么是数据库 数据表的概念 为什么需要mysql这样的数据库管理工具&#xff1f;★ DBMS 收费数据库与免费数据库 运维和数据库 开发与运维的不同阶段 数据库类别 数据库具体应用…

【Linux】进程信号的保存 | 自定义捕捉

文章目录 三、信号的阻塞&#xff08;信号的保存&#xff09;1. 信号相关其他常见概念2. 在内核中的表示3. sigset_t类型4. 信号集操作函数函数列表注意事项 5. 读取/修改block位图 - sigprocmask6. 读取pending位图 - sigpending 四、信号捕捉1. 信号捕捉的初步认识自定义捕捉…

安卓AndroidStdio控制台乱码解决

安卓AndroidStdio控制台乱码解决 情况&#xff1a; 在AndroidStudio中新建了一个Java Module&#xff0c;但是点击 Run ‘app’之后&#xff0c;Build Output 控制台输出的中文都是乱码&#xff0c;都是问号一样的字符 第一个解决方案 File Encodings 改为UTF-8&#xff1f; …

鸿蒙语言ArkTS(更好的生产力与性能)

ArkTS是鸿蒙生态的应用开发语言 ArkTS提供了声明式UI范式、状态管理支持等相应的能力&#xff0c;让开发者可以以更简洁、更自然的方式开发应用。 同时&#xff0c;它在保持TypeScript&#xff08;简称TS&#xff09;基本语法风格的基础上&#xff0c;进一步通过规范强化静态检…

5G——小区搜索流程

小区搜索流程 小区搜索目标&#xff1a;读取到SIB1. 小区搜索流程概述&#xff1a;SIB1在PDSCH信道承载&#xff0c;承载SIB1的信道在哪个位置由PDCCH告诉&#xff0c;而PDCCH的基本信息由MIB告诉&#xff0c;MIB信息由广播信道PBCH广播出去&#xff0c;物理信道解调需要解调…

Codeforces Round 926 (Div. 2) C. Sasha and the Casino (Java)

Codeforces Round 926 (Div. 2) CC. Sasha and the Casino (Java) 比赛链接&#xff1a;Codeforces Round 926 (Div. 2) C题传送门&#xff1a;C. Sasha and the Casino 题目&#xff1a;C. Sasha and the Casino **Example ** input 2 1 7 2 1 1 2 3 15 3 3 6 4 4 5 5 4 7…