JavaScript面向对象编程简明教程

JavaScript面向对象编程

如何定义自定义类型

首先须要明确,JavaScript并非传统意义上的OO语言,它并无class的概念,
而是包含了另外一套异常强大的原型机制。它的类型体系、继承体系都创建在原型基础之上。
为了迎合传统的OO开发者,JavaScript语言的设计者经过这套原型体系模拟了传统面向对象语言的编码风格。javascript

简单来讲,创建一个自定义类型只须要编写类型的构造函数便可:java

javascriptfunction 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;编程

序号2call函数调用的做用是改变执行Person函数时的thisobj
序号3的做用是设置新建实例的原型(一个实例的__proto__属性是这个实例的原型)。
记住,全部函数(如这里的Person)的prototype属性默认都是一个Object实例。
因此序号3执行后,person.__proto__正是Person.prototype
这解释了为何全部的引用类型都派生自Object浏览器

除此以外,从上面的介绍还应该意识到Person的全部实例的__proto__属性都是Person.prototypeapp

上面定义的属性都是实例的属性,也能够直接为某个类型添加类型的属性(记住,方法也是一个对象),
而这个属性没法经过类型的实例访问到,以下面的代码:函数

javascriptPerson.country = "Canada";

另外一个例子是ECMAScript 5引入的Object类型的getPrototypeOf方法,它能够得到一个实例的原型变量:ui

javascriptObject.getPrototypeOf = function(instance) {
  // some code..
};

若是想定义同一个类型全部实例共享的属性(好比方法),能够定义在类型的原型中:this

javascriptPerson.prototype.logName = function() {
  console.log(this.name);
};

须要注意,经过Person的实例只能读取原型中的属性,而不能重写;
若是尝试重写,其实是在实例中定义了一个同名的属性,从而屏蔽了原型中的属性:编码

javascript// 并无改变 Person.prototype.logName的值
person.logName = function() {
  // some code..
}

形成屏蔽的缘由是当使用对象.属性时,
是从对象开始查找属性,若是没有找到再在其原型中查找,
若是尚未找到,再查找其原型的原型,以此类推在原型链上不断向上查找,
第一次查找到属性后查找过程就结束了。
若是想恢复被屏蔽的原型属性,可使用delete操做符:prototype

javascriptdelete person.logName;

最佳的实践是将实例的属性定义在构造函数中,将方法定义在原型中。
这样每一个实例独享本身的属性,并和其余同类型的实例共享方法:

javascript// 构造函数
function Person(name, age) {
    this.name = name;
    this.age = age;
}
// 原型
Person.prototype.logName = function() {
  console.log(this.name);
}

以上这种方式定义的Person类型,能够经过instanceof来判断一个实例是不是Person类型的:

javascriptPerson 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的原理,假设执行下面的代码:

javascriptfunction 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实例,
这是由于全部的方法(好比这里的PersonStudent)的prototype属性默认都是一个Object类型的实例,
这也证实了为何全部的内置类型和自定义类型无一例外所有都派生自Object类型。

当执行下面的代码时:

javascriptconsole.log(student instanceof Student);    // true

其实是判断Student.prototype是否在student的原型链中出现,若是出现了则返回true。
这里Student.prototypeperson,是student的原型,因此返回true。
再看:

javascriptconsole.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();
相关文章
相关标签/搜索