跳至正文

JavaScript入门教程笔记(12)-对象的继承

大部分面向对象的编程语言,都是通过“类”(class)来实现对象的继承。而JavaScript语言不一样,是通过“原型对象”(prototype)来实现的,本文介绍JavaScript的原型链继承。

ES6引入了class语法,基于class的继承将在后面介绍。

1 原型对象概述

1.1 构造函数的缺点

JavaScript通过构造函数生成新对象,构造函数内部可以定义对象的属性和方法。

function Person(name, age) {
    this.name = name;
    this.age = age;
}

var man1 = new Person('John', 25);
man1.name // "John"
man1.age // 25

上面代码中,Person函数是一个构造函数,定义了name和age属性,通过这个构造函数生成的所有实例对象都具有这两个属性。

这种方法虽然方便,但有一个缺点,即同一构造函数的多个实例之间,无法共享属性,造成系统资源的浪费。

function Person(name, age) {
    this.name = name;
    this.age = age;
    this.work = function() {
        console.log('work');
    }
}

var man1 = new Person('John', 25);
var man2 = new Person('Mark', 30);
man1.work === man2.work // false

上面代码中,man1和man2是同一构造函数的两个实例,它们都具有work方法。但是每生成一个实例,就会新建一个work方法,这既没有必要,又浪费系统资源,因为所有work方法都是相同行为,完全应该共享。

这个问题的解决方法,就是JavaScript的原型对象(prototype)。

1.2 prototype属性的作用

prototype机制的设计思想就是,原型对象的所有属性和方法,都能被实例对象共享。这样不仅节省了内存,还体现了实例对象之间的联系。

如何指定原型对象呢?每个函数都有一个prototype属性,指向一个对象。

function f() {}
typeof f.prototype // "object"

上面代码中,函数f默认具有prototype属性,指向一个对象。

对于普通函数来说,该属性没有作用。但对于构造函数来说,生成实例的时候,该属性会自动成为实例对象的原型。

function Person(name) {
    this.name = name;
}
Person.prototype.age = 20;

var man1 = new Person('John');
var man2 = new Person('Mark');

man1.age // 20
man2.age // 20

上面代码中,构造函数Person的prototype属性,就是实例对象man1和man2的原型对象。prototype对象添加的age属性,被所有实例对象共享。

修改原型对象,变动会立刻体现在所有实例对象上。

Person.prototype.age = 30;
man1.age // 30
man2.age // 30

上面代码中,原型对象的age属性变为30,两个实例对象的age属性立刻跟着变了。这是因为实例对象自身其实没有age属性,实际读取的是原型对象的age属性。也就是说,当实例对象自身没有某个属性或方法,它会到原型对象去寻找。这就是原型对象的特殊之处。

如果实例对象自身就有某个属性或方法,它就不会去原型对象中寻找了。如下所示:

man1.age = 18;

man1.age // 18
man2.age // 30
Person.prototype.age // 30

总结一下,原型对象的作用,就是定义所有实例对象共享的属性和方法。而实例对象可以视作从原型对象衍生出来的子对象。

1.3 原型链

JavaScript中所有对象都有自己的原型对象(prototype),由于原型对象也是对象,所以它也有自己的原型。因此,就会形成一个“原型链”。

如果一层一层地上溯,所有对象的原型最终都是Object.prototype。那么,Object.prototype对象有没有它的原型呢?有,就是null。null没有任何属性和方法,也没有自己的原型,因此,原型链的尽头就是null。

Object.getPrototypeOf(Object.prototype) // null

上面代码表示,Object.prototype对象的原型是null,null没有任何属性,原型链至此为止。

Object.getPrototypeOf方法返回参数对象的原型。

读取对象的某个属性时,JavaScript引擎首先寻找对象自己的属性,如果找不到,就到它的原型里找,如果还是找不到,就到原型的原型去找,一直找到最顶层的Object.prototype。如果还是找不到,则返回undefined。

如果对象自身和它的原型,都定义了同名属性,那么优先读取对象自身的属性。

1.4 constructor属性

prototype对象有一个constructor属性,默认指向prototype对象所在的构造函数。

function Person() {}
Person.prototype.constructor === Person // true

var p = new Person();
p.constructor == Person // true
p.constructor === Person.prototype.constructor // true
p.hasOwnProperty('constructor') // false

上面代码中,p自身没有constructor属性,其实读取的是原型链上面的Person.prototype.constructor属性。

constructor属性的作用是,可以得知某个实例对象,到底是哪个构造函数产生的。

function Person() {};
var p = new Person();

p.constructor === Person // true
p.constructor === RegExp // false

如果不确定constructor属性是什么函数,还可以通过name属性,从实例得到构造函数的名称。

function Person() {}
var p = new Person();
p.constructor.name // "Person"

2 instanceof运算符

instanceof运算符返回一个布尔值,表示对象是否为某个构造函数的实例。

var p = new Person();
p instanceof Person // true

上面代码中,对象p是构造函数Person的实例,因此返回true。

它还会检查右边构造函数的原型对象(prototype),是否在左边对象的原型链上。因此,下面两种写法是等价的。

p instanceof Person
// 等同于
Person.prototype.isPrototypeOf(p)

instanceof运算符的一个用处,就是判断值的类型。

var x = [1, 2, 3];
var y = {};
x instanceof Array // true
y instanceof Object // true

注:本文适用于ES5规范,原始内容来自 JavaScript 教程,有修改。

标签:

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注