JavaScript面向对象编程
如何定义自定义类型
首先需要明确,JavaScript并不是传统意义上的OO语言,它并没有class
的概念,
而是包含了另一套异常强大的原型机制。它的类型体系、继承体系都建立在原型基础之上。
为了迎合传统的OO开发者,JavaScript语言的设计者通过这套原型体系模拟了传统面向对象语言的编码风格。
简单来说,建立一个自定义类型只需要编写类型的构造函数即可:
javascript
function Person(name, age) { this.name = name; this.age = age; } // 实例化 Person 类型 Person person = new Person("John", 34);
Person
构造函数与一般的函数没有任何区别,只是调用方式不太一样,
通过使用new
关键字,改变了一般函数调用的行为,有点类似于下面这样:
1. Object obj = new Object();
2. Person.call(obj, "John", 34);
3. obj.__proto__ = Person.prototype;
4. return obj;
序号2
中call
函数调用的作用是改变执行Person函数时的this
为obj
,
序号3
的作用是设置新建实例的原型(一个实例的__proto__
属性是这个实例的原型)。
记住,所有函数(如这里的Person)的prototype
属性默认都是一个Object
实例。
所以序号3
执行后,person.__proto__
正是Person.prototype
,
这解释了为什么所有的引用类型都派生自Object
。
除此之外,从上面的介绍还应该意识到Person
的所有实例的__proto__
属性都是Person.prototype
。
上面定义的属性都是实例的属性,也可以直接为某个类型添加类型的属性(记住,方法也是一个对象),
而这个属性无法通过类型的实例访问到,如下面的代码:
javascript
Person.country = "Canada";
另一个例子是ECMAScript 5引入的Object
类型的getPrototypeOf
方法,它可以获得一个实例的原型变量:
javascript
Object.getPrototypeOf = function(instance) { // some code.. };
如果想定义同一个类型所有实例共享的属性(比如方法),可以定义在类型的原型中:
javascript
Person.prototype.logName = function() { console.log(this.name); };
需要注意,通过Person
的实例只能读取原型中的属性,而不能重写;
如果尝试重写,实际上是在实例中定义了一个同名的属性,从而屏蔽了原型中的属性:
javascript
// 并没有改变 Person.prototype.logName的值 person.logName = function() { // some code.. }
造成屏蔽的原因是当使用对象.属性
时,
是从对象
开始查找属性
,如果没有找到再在其原型中查找,
如果还没有找到,再查找其原型的原型,以此类推在原型链上不断向上查找,
第一次查找到属性
后查找过程就结束了。
如果想恢复被屏蔽的原型属性,可以使用delete
操作符:
javascript
delete person.logName;
最佳的实践是将实例的属性定义在构造函数中,将方法定义在原型中。
这样每个实例独享自己的属性,并和其他同类型的实例共享方法:
javascript
// 构造函数 function Person(name, age) { this.name = name; this.age = age; } // 原型 Person.prototype.logName = function() { console.log(this.name); }
以上这种方式定义的Person
类型,可以通过instanceof
来判断一个实例是否是Person
类型的:
javascript
Person person = new Person("John", 34); console.log(john instanceof Person); // true
实际上instanceof
是通过实例的原型链来判断一个对象是否某个类型的实例的,具体的细节后面会详细介绍。
这里首先介绍一下如何获得一个实例的原型对象:
1. isPrototypeOf()
你可以判断一个对象是否在另一个对象的原型链上出现:
Person person = new Person("John", 34);
console.log(Person.prototype.isPrototypeOf(person)); // true
2. Object.getPrototypeOf()
你可以得到一个对象的原型。
这个方法是ECMAScript 5引入的,某些IE浏览器并不支持:
// get the prototype of person instance
Object.getPrototypeOf(person);
3. __proto__
JavaScript中每个对象都有一个指向其原型的内部属性,
在某些浏览器(如Chrome)中可以使用它们。
既然可以将属性定义在实例本身或它的原型链中,那么可不可以判断某个属性具体是在哪里定义呢?当然可以:
1. hasOwnProperty()
如果属性在实例本身出现,则返回true
:
console.log(person.hasOwnProperty("name")); // true
console.log(person.hasOwnProperty("logName")); // false
2. in
操作符,如果属性在实例或其原型链中出现,则返回true
alert("logName" in person); // true
利用in
操作符,我们还可以枚举出一个实例和它原型链中所有可枚举的属性:
for (var propertyName in person) {
// log all property name and its value
console.log(propertyName + "\t\t" + person[propertyName]);
}
最后来介绍一下instanceof
的原理,假设执行下面的代码:
javascript
function Person(name, age) { this.name = name; this.age = age; } function Student(name, age, school) { Person.call(this, name, age); this.school = school; } // Student 继承了 Person Student.prototype = new Person(); Student student = new Student("Adam", 30, " School");
关于继承的细节之后再详细讨论,这里只需要明确我们将Student
类型的原型赋值为一个Person
实例。
此时student
的原型链可以表示为:
student.__proto__
==>Person
实例(假设为person
)person.__proto__
==>Object
实例(假设为object
)object.__proto__
==>null
(到顶了)
也许会有人疑惑person
的原型为什么是Object
实例,
这是因为所有的方法(比如这里的Person
、Student
)的prototype属性默认都是一个Object类型的实例,
这也证明了为什么所有的内置类型和自定义类型无一例外全部都派生自Object
类型。
当执行下面的代码时:
javascript
console.log(student instanceof Student); // true
实际上是判断Student.prototype是否在student的原型链中出现,如果出现了则返回true。
这里Student.prototype
是person
,是student的原型,所以返回true。
再看:
javascript
console.log(student instanceof Person); // true
同理因为存在Person.prototype === student.__proto__.__proto__
,所以返回true。
看到这里相信你应该对prototype
和__proto__
的关系有了比较清楚的理解了。
它们之间的关系可以总结为:
__proto__
:__proto__
is the actual object that is used in the lookup chain to resolve methods.
It is a property that all objects have.
This is the property which is used by the JavaScript engine for inheritance.
According to ECMA specifications it is supposed to be an internal property,
however most vendors allow it to be accessed and modified.prototype
:prototype
is a property belonging only to functions.
It is used to build__proto__
when the function
happens to be used as a constructor with the new keyword.
继承
可以说JavaScript是Python的另一个极端
——There's always more than one way to do it.
实现继承也不例外,不同的实现模式有不同的使用场景,
各有优势和不足,这里只介绍一个最常被使用的模式——组合继承模式,直接看例子:
javascript
/* * 基类,定义属性 */ function Person(name, age) { this.name = name; this.age = age; } /* * 基类,定义方法 */ Person.prototype.selfIntroduce = function () { console.log("name: " + this.name); console.log("age: " + this.age); } /* * 子类,定义子类的属性 */ function Student(name, age, school) { // 调用基类的构造函数 Person.call(this, name, age); this.school = school; } // 使子类继承基类 Student.prototype = new Person(); /* * 定义子类的方法 */ Student.prototype.goToSchool = function() { // some code.. } /* * 扩展并调用了超类的方法 */ Student.prototype.selfIntroduce = function () { Student.prototype.__proto__.selfIntroduce.call(this); console.log("school: " + this.school); } var student = new Student("John", 22, "My School"); student.selfIntroduce();
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。