首先须要明确,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;
编程
序号2
中call
函数调用的做用是改变执行Person函数时的this
为obj
,
序号3
的做用是设置新建实例的原型(一个实例的__proto__
属性是这个实例的原型)。
记住,全部函数(如这里的Person)的prototype
属性默认都是一个Object
实例。
因此序号3
执行后,person.__proto__
正是Person.prototype
,
这解释了为何全部的引用类型都派生自Object
。浏览器
除此以外,从上面的介绍还应该意识到Person
的全部实例的__proto__
属性都是Person.prototype
。app
上面定义的属性都是实例的属性,也能够直接为某个类型添加类型的属性(记住,方法也是一个对象),
而这个属性没法经过类型的实例访问到,以下面的代码:函数
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
实例,
这是由于全部的方法(好比这里的Person
、Student
)的prototype属性默认都是一个Object类型的实例,
这也证实了为何全部的内置类型和自定义类型无一例外所有都派生自Object
类型。
当执行下面的代码时:
javascriptconsole.log(student instanceof Student); // true
其实是判断Student.prototype是否在student的原型链中出现,若是出现了则返回true。
这里Student.prototype
是person
,是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();