https://developer.mozilla.org/zh-CN/docs/JavaScript/Guide/Inheritance_and_the_prototype_chain
本文内容
- 引入
- 构造函数创建对象
- Object.create创建对象
- 极简主义法
- 封装
- 继承
- 私有属性和私有方法
- 数据共享
引入
近 20 年前,也就是 Javascript 诞生时(1995 年),它只是一种简单的网页脚本语言,像如果你忘记填写用户名,就跳出一个警告,当时的网速只有 28Kbps,这样简单的交互也让 Web 服务器做显然不合适。
如今,Javascript 几乎无所不能,从前端到后端,各种匪夷所思、令人瞠目结舌的用途,程序员用它完成越来越庞大的项目,代码复杂度也在直线飙升。单个网页包含 10000 行 Javascript 代码,早就司空见惯。2010 年,一个工程师透露:Gmail 代码长度是 443000 行!
图 1 Gmail 代码段
编写和维护如此庞大复杂的代码,多年的工程实践表明必须使用模块化策略,而要采用模块化,主流做法是“面向对象编程”,它具有抽象,封装,多态和继承这四个特点。
可 Javascript 是一种基于对象(object-based)的语言,你遇到的所有一切几乎都是对象,它结合(简单的)函数式语言和(简单的)面向对象语言的特点,但其语法不支持“类(class)”,无法直接使用面向对象编程。如果要把“属性”和“方法”,封装成一个对象,甚至要从原型对象生成一个实例对象。因此,程序员们探索如何用 Javascript 模拟“类”?
在面向对象编程中,类(class)是对象(object)的模板。Javascript 语言虽然不支持“类”,但可以用一些变通的方法来模拟。本文总结了 Javascript 创建对象的三种方法,讨论每种方法的特点,并着重介绍了我眼中的最佳方法。
构造函数创建对象
在 Javascript 中,构造方法其实就是一个普通的函数。当使用 new 操作符来作用这个函数时,它就可以被称为构造方法(构造函数,Constructor),使用 this 和 prototype。该方法是经典方法。
代码段一:
function Cat() {
this.name = "大毛";
}
Cat.prototype.makeSound = function () {
alert("喵~");
}
var cat1 = new Cat();
alert(cat1.name);
alert(cat1. makeSound());
Object.create 创建对象
为了解决“构造函数法”的缺点,更方便地生成对象,ECMAScript 5(目前通行第三版),提出了一个新的方法 Object.create(),调用这个方法来创建一个新对象。新对象的原型就是调用 create方法时传入的第一个参数。
代码段二:定义 Cat 对象,用 Object.create 创建,不用 new
var Cat = {
name: "大毛",
makeSound: function () { alert("喵~"); }
};
var cat1 = Object.create(Cat);
alert(cat1.name);
cat1.makeSound();
目前,各大浏览器的最新版本(包括 IE9)都部署了这个方法。如果遇到老式浏览器,可以用下面的代码自行部署。
代码段三:
if (!Object.create) {
Object.create = function (o) {
function F() {}
F.prototype = o;
return new F();
};
}
极简主义法
荷兰程序员 Gabor de Mooij 提出了一种比 Object.create 更好的方法,他称这种方法为“极简主义法(minimalist approach)”,也是我推荐的方法。
封装
这种方法不使用 this 和 prototype,代码部署起来非常简单。
它也是先用一个对象模拟“类”。在这个类里面,定义一个构造函数 createNew,用来生成对象实例。
代码段四:
var Cat = {
createNew: function () {
// some code here
}
};
然后,在 createNew 函数,定义一个对象实例,把这个对象作为返回值。
代码段五:
var Cat = {
createNew: function () {
// some code here
var cat = {};
cat.name = "大毛";
cat.makeSound = function () { alert("喵~"); };
return cat;
}
};
使用时,调用 createNew 方法,就可以得到对象实例。
代码段六:
var cat1 = Cat.createNew();
cat1.makeSound();
继承
让一个类继承另一个类,实现起来很方便。只要在前者的createNew()方法中,调用后者的 createNew 方法即可。
先定义一个 Animal 类。
代码段七:
var Animal = {
createNew: function () {
var animal = {};
animal.sleep = function () { alert("睡懒觉"); };
return animal;
}
};
然后,在 Cat 的 createNew 方法中,调用 Animal 的 createNew 方法。
代码段八:
var Cat = {
createNew: function () {
var cat = Animal.createNew();
cat.name = "大毛";
cat.makeSound = function () { alert("喵~"); };
return cat;
}
};
这样,Cat 对象就会继承 Animal。
代码段九:
var cat1 = Cat.createNew();
cat1.sleep();
私有属性和私有方法
只要不是定义在 cat 对象上的方法和属性,都是私有的。
代码段十:
var Cat = {
createNew: function () {
var cat = {};
var sound = "喵~";
cat.makeSound = function () { alert(sound); };
return cat;
}
};
上面,sound 是内部私有变量,外部无法访问,只有通过 cat 的公有方法 makeSound 来读取。
代码段十一:
var cat1 = Cat.createNew();
alert(cat1.sound); // undefined
数据共享
有时候,我们需要所有实例对象,能够读写同一项内部数据。这个时候,只要把这个内部数据,封装在类对象的里面、createNew()方法的外面即可。
代码段十二:
var Cat = {
sound: "喵~",
createNew: function () {
var cat = {};
cat.makeSound = function () { alert(Cat.sound); };
cat.changeSound = function (x) { Cat.sound = x; };
return cat;
}
};
然后,生成两个实例对象:
var cat1 = Cat.createNew();
var cat2 = Cat.createNew();
cat1.makeSound(); // 喵~
这时,如果有一个实例对象,修改了共享的数据,另一个实例对象也会受到影响。
cat2.changeSound("啦~");
cat1.makeSound(); // 啦~