JavaScript学习笔记——避免代码重复的技巧总结(重用、善用函数、善用原型)

如何避免代码重复

每当发现重复的代码时,警报就会响起:既然已经编写过知道如何完成这些工作的代码,为何不让它去做呢?因此,我们不应重复既有的代码

消除重复代码的理念:
DRY (Don’t Repeat Yourself,不要自我重复),所有厉害的程序员都这么说

为避免代码重复,应该善用函数原型和掌握好重用的思想:

  • 对于函数,要注意:每个函数只做一件事,并且把这件事做好;另外,多个函数的功能不要重复
  • 原型也是消除重复的重要途径
  • 对于重用,要注意:“重用”不仅是代码的编写量减少,更重要的是执行代码时,计算机生成的代码量减少,且不要让计算机执行多余重复的操作

善用函数

将重复的代码封装在同一个函数中

  • 最基本的,我们知道如果有多行执行相同功能的代码,可以把它封装到函数中

核心思想:每个函数只做一件事,并且把这件事做好

  • 两个函数的功能相近,这也是另一层面上的代码重复

因此,避免代码重复有下面几种方法

多个相似的函数合并为一个函数:向函数传递函数

虽然要让每个函数只做一件事,但同时也要注意:各个函数的功能不要有过多重复

多个函数的代码有相似部分,可以将它们合并一个函数,传入不同参数(可以是普通变量,也可以是函数,这里关注传入函数的情况)实现不同功能

  • 保留共有的相似代码;
  • 而有差异的那部分代码(用于实现各自不同的功能),这部分代码可以由参数控制,这里的“参数”就是函数引用——详见向函数传递函数
//passengers传入一个对象数组
//test传入一个函数引用,指向一个能判断乘客是否满足某个条件的函数
function processPassengers(passengers, test) {
	for (var i = 0; i < passengers.length; i++) {//所需的共同部分:遍历乘客
		if (test(passengers[i])) {//不同部分:判断乘客是否满足特定某个条件
			return false;
		}
	}
	return true;
}
//-------------------传入的test函数可以是下面这些函数-------------------------
function checkNoFlyList(passenger) {
	return (passenger.name === "Dr. Evel");
}
//可编写判断其他条件的函数,以同样的方法使用
//function checkNotPaid(passenger) {...}
//function printPassenger(passenger) {...}

//-------------------------------------------------------------------------
调用 processPassengers(passengers,checkNoFlyList);可检查是否有乘客在禁飞名单

单个函数不要过于复杂和庞大

有时,将多种功能封装在同一个函数内(根据传入参数的不同,执行不同的功能),效果反而不好
这里出于这样的考虑:

  • 从代码编写的角度考虑:如果单个函数的功能过于复杂,那么改动起来将极为麻烦;因此应该保证函数的简洁
  • 从代码运行的角度考虑:如果需要多次调用某个函数,应该保证函数高效完成其必要的工作,而不为计算机带来额外的、不必要的工作量(例子见后文)

总之,还是要让每个函数只做一件事,且保证简洁高效
因此,有时我们应该将复杂的函数拆为简洁的函数(这样甚至为计算机减少重复的不必要工作,见下面)

例子:创建多个细节稍有不同的“定制化函数”:从函数返回函数

在飞机上,需要多次调用getDrinkOrder为乘客供应饮料,然后此函数过于复杂

  • 改造前:getDrinkOrder为乘客供应饮料,但是每次供应饮料时都需要在getDrinkOrder中重复判断乘客的舱位,根据舱位不同供应饮料

多次调用该函数,这显然带来了重复的if lese判断,在复杂的实际程序中,这样多余的重复工作将耗费额外的时间
因此,我们应该将一个函数拆分为多个功能专一化的函数(“每个函数只做一件事”)

  • 改造后:首先用函数生成器createDrinkOrder生成定制化函数(根据乘客舱位不同,返回不同的供应饮料函数getDrinkOrder

在这里我们希望得到多个细节稍有不同的函数

此处有一个巧妙的方法:用“函数生成器”返回“定制化函数”:传入参数,根据参数返回定制化的函数——详见从函数返回函数

改造后代码如下:

function serveCustomer(passenger) {
	var getDrinkOrder = createDrinkOrder(passenger);
	//createDrinkOrder返回一个“定制化”的函数

	getDrinkOrder();//供应饮料	
	//getDinnerOrder();// 供应晚餐
	getDrinkOrder();//供应饮料	
	// 播放电影
	getDrinkOrder();//供应饮料	
	//pickupTrash();// 清理垃圾
}

//“定制化函数”的生成器,passenger的舱位不同,返回的函数就不同
function createDrinkOrder(passenger) {
	var orderFunction;//定义一个变量作为返回值,它保存了函数引用
	if (passenger.ticket === "firstclass") {
		orderFunction = function() {
			alert("Would you like a cocktail or wine?");
		};
	} else if (passenger.ticket === "premium") {
		orderFunction = function() {
			alert("Would you like wine, cola or water?");
		};
	} else {
		orderFunction = function() {
			alert("Your choice is cola or water.");
		};
	}
	return orderFunction;//从函数返回函数
}

某种程度上,这里也体现了重用的思想:
这里的“重用”是执行代码角度上的重用:不在于是代码的编写量减少,而是在于没有让计算机执行多余重复的操作
在这个例子中,之前已经做过的判断,之后就没有重复判断:对于每位乘客,在函数生成器中,根据其舱位的不同为此生成了定制化函数,之后就一直使用定制化函数为乘客服务(而不是像之前那样每次服务时都需要重复判断乘客的舱位)


善用原型:消除重复

原型的核心思想:消除重复,一旦有重复的部分,就将其包含在原型中

  • 将所有对象中相同的属性和方法(希望重用的那部分)包含在原型中;
    另外,想要将所有对象的某个属性统一设置初始值,也可使用原型
  • 对于那些随对象而异的属性和方法,等到继承原型创建新对象时,再针对不同对象添加其特有的属性

真正的代码重用:将方法定义在原型中(而非构造函数中)

使用构造函数的主要目的:试图重用对象的行为(即 对象支持的一系列方法)

为此,我们通过构造函数创建对象实例,创建的对象共用相同的方法(这些方法只需在构造函数中定义一次即可),从而减少编写代码的重复,减小编写代码的工作量

然而,使用构造函数,这仅仅是在代码层面实现重用行为的目的。

由构造函数创建的对象,在运行阶段,每个对象实例都将单独获得一个全新的方法副本,这样的副本是多余的,白白占用了内存资源
详见用构造函数创建大量对象,存在什么问题?

通过原型可以实现真正的代码重用
继承了原型的大量对象实例,当他们重用相同的方法时,不仅只需要在一个地方编写代码,而且所有对象实例在运行阶段都使用同一个方法,从而减小了运行阶段开销

具体如何创建和使用原型,详见创建原型


可见,重用代码应该从更深层次(即:执行代码的角度)来考虑

  • “重用”不仅是代码的编写量减少,更重要的是执行代码时,计算机生成的代码量减少,且不要让计算机执行多余重复的操作

进阶:在原型链中,构造函数中也不要有重复的代码

什么是原型链、怎么创建一个原型链,详见原型链

对于(原型链中)继承其他原型的原型,很多情况下,他们的构造函数中都有为相同属性赋值的语句(这些语句是重复的)
可以通过call方法为构造函数消除这些重复代码:详见通过方法call优化构造函数

其他一些代码技巧

匿名函数:让代码更简洁

匿名函数是没有名称的函数表达式(表达式的结果是一个函数引用

  • 任何需要函数引用的地方,都可以使用函数表达式
  • 使用匿名函数,可让代码更简洁精练,意图更清晰,效率更高,甚至更易于维护(虽然初学时会觉得这样使代码更难阅读和理解)

如果某个函数在代码中从头到尾只被使用一次,那么没有必要创建[用于存储函数引用]的变量,而可以使用匿名函数(用函数表达式创建),使代码更紧凑

例如,需要向函数传递函数时

  • 我们没必要先定义function A(),再将变量A(其值为函数引用)传递给另一函数B(A);
  • 更简洁的写法:B(function(){...});

详见匿名函数

要传入函数的参数过多时,“打包”传入

要传入的参数过多,可以用一个容器(即:对象)传递所有参数

  • 多个参数“打包”,变为一个参数:对象(保存了所有参数)
  • 其实就是用对象字面量创建一个对象,然后把该对象传递给函数
  • 这样的好处是:
    函数只有一个参数,阅读容易;且不必担心实参与形参的顺序问题;

详见用一个对象传递所有参数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值