JavaScript的面向对象设计-1
阮一峰老师的三篇文章:
“类”与对象
Javascript是一种基于对象(object-based)的语言,你遇到的所有东西几乎都是对象。但是,它又不是一种真正的面向对象编程(OOP)语言,因为它的语法中没有
class
(类)。
它没有”子类”和”父类”的概念,也没有”类”(class)和”实例”(instance)的区分,全靠一种很奇特的”原型链”(prototype chain)模式,来实现继承。
为什么没有class?
为了解决浏览器的效率问题,网景公司急需一种与浏览器网页互动的脚本语言。JavaScript的开发者Brendan Eich鉴于当时的需求,认为没有必要将这门语言设计得很复杂,只需要完成一些简单的操作就行了。
当时正是面向对象思想最兴盛的时期,所以Brendan Eich将JS中所有数据类型都设置为对象,但是如果再加上和C++一样的继承特性,就太过于复杂了,超出了当时的需求。于是Brendan Eich使用了自己的方案。
没有class怎么new?
C++和Java的new后面都跟上class名,JS没有class,于是便使用构造函数来代替class。
1
2
3
4
function Cat(name) {
this.name = name;
}
let cat1 = new Cat("Cat1");
如何将同一class的对象联系在一起?
生成对象有很多方法,直接量就是其中一种。我们可以不通过new来创建对象。
所以我们可以通过一个特定的规格,来生成一些列对象。这个特定的规格就像是class一样,表示一类。
1
2
3
4
let cat1 = {};
cat1.name = "Cat1";
let cat2 = {};
cat2.name = "Cat2";
我们可以写一个函数来解决代码重复的问题
1
2
3
4
5
function Cat(name) {
return {
name: name
}
}
这种方法有一个弊端:没有表现出cat1和cat2的关系,他们应该是同一类,即都是猫
于是我们可以使用构造函数模式
为了解决从原型对象生成实例的问题,Javascript提供了一个构造函数(Constructor)模式。
所谓”构造函数”,其实就是一个普通函数,但是内部使用了
this
变量。对构造函数使用new
运算符,就能生成实例,并且this
变量会绑定在实例对象上。
1
2
3
4
5
function Cat(name) {
this.name = name;
}
let cat1 = new Cat("Cat1");
let cat2 = new Cat("Cat2");
这时cat1和cat2会自动含有一个constructor属性,指向它们的构造函数(准确地说,是cat1和cat2的__proto__
指向的对象有constructor属性,关于原型对象下面再做介绍)。这样就将这两个对象联系在一起。
为什么需要原型对象?
上面创建方式存在浪费内存的问题
下面的代码中,eat函数对于每个Cat都一样,但是每个Cat的实例却没有共享这个函数,而是每个实例创建一个
1
2
3
4
5
6
7
8
9
function Cat(name) {
this.name = name;
this.eat = function() {
console.log("eat");
}
}
let cat1 = new Cat("Cat1");
let cat2 = new Cat("Cat2");
console.log(cat1.eat == cat2.eat()); // false
如果使用C++或者Java,我们可以设置函数、属性为静态(static)。这样函数、属性就变为类所有的,所有该类的实例都共用一个函数、属性。
在JS中,即使用原型(prototype)来实现。创建一个原型对象,Cat构造函数有一个prototype属性来指向这个原型对象,Cat的实例直接调用和访问该原型对象的函数和属性。
在原型对象中设置相应的函数和属性,这样Cat的所有实例都共用这一个原型对象中的函数和属性了。
1
2
3
4
5
6
7
8
9
function Cat(name) {
this.name = name;
}
Cat.prototype.eat = function() {
console.log("eat");
}
let cat1 = new Cat("Cat1");
let cat2 = new Cat("Cat2");
console.log(cat1.eat == cat2.eat()); // true
访问原型对象有三种方法
obj.__proto__
obj.constructor.prototype
Object.getPrototypeOf(obj)
其中在实际开发中并不推荐使用obj.__proto__
,最建议使用的是Object.getPrototypeOf(obj)