刚开始,代码只是代码而已。当代码多了,我们发明了函数,以便代码可以重用。到后来,我们的函数也越来越多,所以我们又发明了别的一些编程概念。但是当我们沉迷于这些新概念的同时,我们可能就错过了一些近在眼前的美丽事物……
类继承
类概念主要存在于面向对象编程中的,可能大家上学时都学过,比如Java、C#、Objective-C等面向对象语言。每个实体都需要先要有一个类,这个类对于我们来说是比较抽象的(不是说abstract class哦),然后子类还可以继承父类。
这感觉似乎有点亚里士多德形而上学和分类法的味道,但简单来说这就是面向对象的基础,在写出实际代码之前,你得先写出一些类、接口,然后类继承类、类实现接口……
实际上我们人类并不是非常擅长分类,打个比方,一个按钮(Button),它应该是一个矩形(Rectangle),还是一个控件(Control)呢?我们可以让Button继承Rectangle,让Rectangle继承Control……等等,是不是有什么不对?
所以,面向对象的概念可能会在项目刚开始时就把你压垮,但这并不是说面向对象就不好,只是有时候并不适合你的项目。
原型链继承
可能大家都知道,JavaScript是不支持class
关键字的,因为JavaScript本身就不作为一个面向对象语言来设计,但并不妨碍大家想出各种招来实现class
。
JavaScript是基于原型链(Prototype)继承的,但是其诡异的写法可能让好多初学者望而却步。
function Circle() {
this.radius = 7;
}
Circle.prototype = {
area: function () {
return Math.PI * this.radius * this.radius;
},
grow: function () {
this.radius++;
},
shrink: function () {
this.radius--;
}
};
在ES5之后,我们有了Object.create
方法,好理解了一些。
var circle = Object.create({
area: function () {
return Math.PI * this.radius * this.radius;
},
grow: function () {
this.radius++;
},
shrink: function () {
this.radius--;
}
}, {
radius: {
writable: true,
configurable: true,
value: 7
}
});
只是第二参数用起来有点复杂,其实就是跟Object.defineProperties()
的参数一样,但是我们现在又有了ES6呀:
class Cicle {
constructor() {
this.radius = 7;
}
area() {
return Math.PI * this.radius * this.radius;
}
grow() {
this.radius++;
}
shrink() {
this.radius--;
}
}
这都是一些使用JavaScript实现class
的例子,实际上,JavaScript还可以有另外一种重用代码的方式——Mixins。
Mixins
这就是我们今天的主题,近在眼前的美丽事物,但可不是什么新概念,用过Ruby或Python的同学,可能有所耳目,Mixins的字面意思就是把东西掺合在一起。
在JavaScript我们有call
和apply
,很容易切换上下文,将各种互不相干糅合,达到Mixins的目的。
假设我们要做一个圆形按钮(RoundButton),它有两个特性:
是圆的
可点击
我们可以把这两个特性分别写做2个函数:
// 是圆的
var withCircle = function () {
this.area = function () {
return Math.PI * this.radius * this.radius;
};
this.grow = function () {
this.radius++;
};
this.shrink = function () {
this.radius--;
}
}
// 可点击
var withClickable = function () {
this.hover = function () {
console.log('hovering');
};
this.press = function () {
console.log('button pressed');
};
this.release = function () {
console.log('button released');
};
this.fire = function () {
this.action.fire();
};
}
这是我们的圆形按钮:
var RoundButton = function(radius, label, action) {
this.radius = radius;
this.label = label;
this.action = action;
};
现在我们要让给这个圆形按钮附上那两个特性:
withCircle.call(RoundButton.prototype);
withClickable.call(RoundButton.prototype);
var button = new RoundButton(4, 'yes!', function() {
return 'you said yes!'
});
button1.fire(); // 输出 'you said yes!'
这样写,是不是瞬间显得既简洁又自然?让人一眼看懂代码在做什么。
当然这些附加特性的函数用的多了,也就创建了许多函数,这里可以简单的用一个立即执行函数(Immediately Invoked Function Expression)的闭包来对其进行优化一下,以withCircle
为例:
var withCircle = (function () {
function area() {
return Math.PI * this.radius * this.radius;
}
function grow() {
this.radius++;
}
function shrink() {
this.radius--;
}
return function () {
this.area = area;
this.grow = grow;
this.shrink = shrink;
};
})();
这样就不需要每次使用都新建函数了,从而节省更多的资源。
Advice
有时候,你无法确保某些函数可能会覆盖原有的功能,例如以下例子:
Button.prototype.press = function() {
console.log('pressed');
};
// 这时再用我们的 withClickable,就会覆盖掉 press
withClickable.call(Button.prototype);
这时候我们应该采用Advice,Twitter的Flight框架已经提供了此功能:
var withClickable = function () {
this.after('press', function () {
console.log('press again.');
});
};
withAdvice.call(Button.prototype);
withClickable.call(Button.prototype);
var button = new Button();
button.press(); // 输出 'pressed', 'press again.'
两个press
并不会互相冲突,而是有先后顺序的执行,就类似通过addEventListener
添加了多个事件而不是直接修改onclick
一样,具体细节可以参考Flight的API。
小结
作为一名程序员,我们或许在上学时就被灌输了面向对象的固有思想,毕竟面向对象从上世纪90年代到现在,经久不衰,自由它的优势。但是在JavaScript中,如果你并不善于面向对象的抽象思维,何不尝试一下Mixins呢?而且Mixins与类继承相比,还能更好的解耦合,可以用于任何Object之上,正好用上了JavaScript若类型的优势。
最近在读《Beautiful JavaScript》这本书,有一些好的内容,正好可以跟大家分享,但并不是全部,有兴趣的同学也可以自己读一下,请支持正版。
原文链接:http://t.cn/RteECIF
微信号:程序员晋级之路『code-learning』
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。