JavaScript设计模式与开发实战

news2024/11/24 22:42:55

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 (操作系统中的动态链接库)是其典型运用场景。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【JavaEE】spring boot快速上手

SpringBoot快速上手 文章目录 SpringBoot快速上手Maven会出现的一个官方bug创建完项目之后常用的的三个功能依赖管理Maven仓库中央仓库本地仓库国内源配置私服 springboot项目创建什么是springspring boot项目的创建Hello Worldweb服务器 SpringMVC什么是SpringWebMVC什么是MVC…

Sora:AI视频生产力的颠覆性跃进,让创意瞬间成“视界”!

在AI视频技术宇宙中&#xff0c;RunwayGen2、Stable Video Diffusion和Pika等明星产品早已名声在外。然而&#xff0c;今日横空出世的Sora犹如一颗璀璨新星&#xff0c;以其震撼性的创新突破&#xff0c;在视频制作领域掀起了一场革命&#xff01;相较于市面上同类AI视频神器&a…

【Java程序员面试专栏 分布式中间件】Redis 核心面试指引

关于Redis部分的核心知识进行一网打尽,包括Redis的基本概念,基本架构,工作流程,存储机制等,通过一篇文章串联面试重点,并且帮助加强日常基础知识的理解,全局思维导图如下所示 基础概念 明确redis的特性、应用场景和数据结构 什么是Redis,Redis有哪些应用场景 Redi…

互联网加竞赛 基于计算机视觉的身份证识别系统

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 基于机器视觉的身份证识别系统 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f9ff; 更多资料, 项目分享&#xff1a; https://gitee.com/dancheng-sen…

Sora:继ChatGPT之后,OpenAI的又一力作

关于Sora的报道&#xff0c;相信很多圈内朋友都已经看到了来自各大媒体铺天盖地的宣传了&#xff0c;这次&#xff0c;对于Sora的宣传&#xff0c;绝不比当初ChatGPT的宣传弱。自OpenAI发布了GPT4之后&#xff0c;就已经有很多视频生成模型了&#xff0c;不过这些模型要么生成的…

【JavaEE】_CSS选择器

目录 1. 基本语法格式 2. 引入方式 2.1 内部样式 2.2 内联样式 2.3 外部样式 3. 基础选择器 3.1 标签选择器 3.2 类选择器 3.3 ID选择器 4. 复合选择器 4.1 后代选择器 4.2 子选择器 4.3 并集选择器 4.4 伪类选择器 1. 基本语法格式 选择器若干属性声明 2. 引入…

C语言从零实现贪吃蛇小游戏

制作不易&#xff0c;点赞关注一下呗&#xff01;&#xff01;&#xff01; 文章目录 前言一. 技术要点二、WIN32API介绍三、贪吃蛇游戏设计与分析 1.游戏开始前的初始化 2.游戏运行的逻辑 总结 前言 当我们掌握链表这样的数据结构之后&#xff0c;我们就可以用它来…

Rust 学习笔记 - Hello world

前言 本文将讲解如何完成一个 Rust 项目的开发流程&#xff0c;从编写 “Hello, World!” 开始&#xff0c;到使用 Cargo 管理和运行项目。 编写 Hello world 开始一个新项目很简单&#xff0c;首先&#xff0c;创建一个包含 main.rs 文件的 hello_world 文件夹&#xff0c;…

GPT翻译网站的加载与使用

Sider: ChatGPT侧边栏 GPTs, GPT-4 Turbo, 联网, 绘图 sider.ai https://chromewebstore.google.com/detail/sider-chatgpt%E4%BE%A7%E8%BE%B9%E6%A0%8F-gpts-g/difoiogjjojoaoomphldepapgpbgkhkb?hlzh-CN 加入与移除 第二个翻译网站 https://chromewebstore.google.com/…

电商小程序08调用缓存

目录 1 将信息存入缓存中2 获取登录信息3 退出登录4 发布预览总结 小程序的登录功能里&#xff0c;如果只是将登录信息保存到全局变量中&#xff0c;存在的问题是如果小程序重新打开&#xff0c;用户的登录状态就丢失了。为了解决这个问题&#xff0c;我们需要用到微搭的缓存的…

13-k8s的控制器资源-rc控制器replicationcontrollers

一、rc控制器资源的概述 replicationcontrollers控制器资源&#xff0c;简称&#xff1a;rc控制器&#xff1b; 简单理解&#xff0c;rc控制器就是控制相同的pod副本数量&#xff1b; 使用rc控制器资源创建pod&#xff0c;就可以设定创建pod的数量&#xff1b; 二、rc控制器资源…

C++中对象的构造与析构顺序

一、对象的构造顺序 对象的构造&#xff0c;先被创建的对象&#xff0c;先被构造&#xff0c;先调用其构造函数 class A { private:int _a 0; public://构造函数A(int a 0){_a a;cout << "A(int a 0)" << " " << _a << endl…

NVIDIA 刚刚揭秘了他们的最新大作——Eos,一台跻身全球十强的超级计算机

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

HCIA-HarmonyOS设备开发认证V2.0-内核扩展组件

目录 一、CPU 占用率1.1、CPU 占用率基本概念1.2、CPU 占用率运行机制1.3、CPU 占用率开发流程 二、动态加载2.1、 动态加载基本概念2.2、动态加载运行机制 坚持就有收获 一、CPU 占用率 1.1、CPU 占用率基本概念 CPU&#xff08;中央处理器&#xff0c;Central Processing U…

Java 学习和实践笔记(11)

三大神器&#xff1a; 官方网址: http://www.jetbrains.com/idea/ 官方网址: https://code.visualstudio.com/ 官方网址: http://www.eclipse.org 装好了idea社区版&#xff0c;并试运行以下代码&#xff0c;OK&#xff01; //TIP To <b>Run</b> code, press &l…

洛谷AT_abc034_a[ABC034A] テスト

#先看题目 题目描述 输入格式 无 输出格式 无 题意翻译 输入两个数字 x 和 y&#xff0c;如果 y>x 输出Better&#xff0c;否则输出Worse。 输入输出样例 无 题目链接https://www.luogu.com.cn/problem/AT_abc034_a #思路 没有 #最后附上完整代码 #include&l…

(02)Hive SQL编译成MapReduce任务的过程

目录 一、架构及组件介绍 1.1 Hive底层架构 1.2 Hive组件 1.3 Hive与Hadoop交互过程 二、Hive SQL 编译成MR任务的流程 2.1 HQL转换为MR源码整体流程介绍 2.2 程序入口—CliDriver 2.3 HQL编译成MR任务的详细过程—Driver 2.3.1 将HQL语句转换成AST抽象语法树 词法、语…

二叉树的锯齿形层序遍历

1.题目 这道题是2024-2-16的签到题&#xff0c;题目难度为中等。 考察知识点为BFS算法和双端队列。 题目链接&#xff1a;二叉树的锯齿形层序遍历 给你二叉树的根节点 root &#xff0c;返回其节点值的 锯齿形层序遍历 。&#xff08;即先从左往右&#xff0c;再从右往左进行…

VScode写LaTeX配置,实测有效

环境配置请看LaTeX环境配置-TexLive&#xff0c;实测有效http://t.csdnimg.cn/0txlL VScode写LaTeX配置 0.smatra pdf下载 如果使用外部pdf查看器&#xff0c;比如我用的sumatra pdf,官网是Sumatra PDF reader download page 下载对应版本&#xff0c;比如64位&#xff0c;下…

【STM32 CubeMX】I2C中断方式与DMA方式

文章目录 前言一、I2C中断方式1.1 CubeMX配置I2C中断1.2 I2C中断函数使用Master模式Mem模式 1.3 DMA方式发送和接收CubeMX配置IIC DMA方式Master模式Mem模式 总结 前言 在STM32 CubeMX环境中&#xff0c;I2C&#xff08;Inter-Integrated Circuit&#xff09;通信协议的实现可…