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();
相關文章
相關標籤/搜索