详解JavaScript原型与原型链
前置知识
在深入讲解JavaScript原型与原型链之前,需要了解以下概念:
- 对象
- 构造函数
- 实例
- 继承
原型
JavaScript中有一个对象,称为原型对象(prototype object),它指向一个JavaScript对象。每个JavaScript对象都有一个原型对象。
在对象定义时,可以通过Object.create()
方法,创建一个新对象,并指定newObject
的原型对象为someObject
。
var someObject = {
a: 1
};
var newObject = Object.create(someObject);
在上述代码中,newObject
的原型对象为someObject
。
原型链
原型对象也可以有自己的原型。这种关系可以形成链状结构,被称作原型链(prototype chain)。
如果在一个对象中查找属性或方法时,找不到,它会沿着原型链一直向上查找,直到找到匹配的属性或方法或者查找到原型链的顶部,也就是Object.prototype
为止,如果在整个原型链上都没找到,返回undefined。
构造函数
构造函数(constructor)是通过new
关键字创建对象的特殊函数。构造函数可以用来创建特定类型的对象,相当于类的概念。
在构造函数中,可以使用this
关键字给对象添加属性和方法,同时可以使用new
关键字创建实例。
function Person(name) {
this.name = name;
}
var p = new Person('张三');
在上述代码中,Person
可以看作是一个构造函数,可以通过new
关键字创建实例。p
就是一个实例。
实例属性与原型属性
构造函数中定义的属性和方法,每一个实例都有一个独立的副本,称之为实例属性或方法。而通过原型对象定义的属性和方法,则被所有实例共享,称之为原型属性或方法。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello');
};
var p = new Person('张三');
var q = new Person('李四');
p.sayHello(); // 输出 'Hello'
q.sayHello(); // 输出 'Hello'
在上述代码中,sayHello
方法是定义在Person
的原型对象上的,所以被所有实例共享。
对象继承
要实现继承,需要使用到原型链。
在JavaScript中,每个对象都有一个原型对象,可以通过重写对象的原型对象来实现继承。我们可以通过构造函数中的Object.create
方法,将父类的原型对象作为参数传入,生成一个新的对象,将这个新的对象作为子类的原型对象。
function Person(name) {
this.name = name;
}
Person.prototype.sayHello = function() {
console.log('Hello');
};
function Student(name, grade) {
Person.call(this, name);
this.grade = grade;
}
Student.prototype = Object.create(Person.prototype);
Student.prototype.constructor = Student;
Student.prototype.sayGoodbye = function() {
console.log('Goodbye');
};
var s = new Student('张三', '大一');
s.sayHello(); // 输出 'Hello'
s.sayGoodbye(); // 输出 'Goodbye'
在上述代码中,Person
是父类,Student
是子类,Student
通过Object.create(Person.prototype)
来实现继承。
示例说明
示例1
现在有一个 Car
构造函数,它的原型对象上有一个 brand
属性,同时原型对象上还有一个 run
方法,可以让汽车行驶。现在我们创建了一个 Honda
的实例,用 console.log
依次输出它的属性和方法。
function Car() {}
Car.prototype.brand = 'Honda';
Car.prototype.run = function() {
console.log('The car is running');
};
var honda = new Car();
console.log(honda.brand); // 输出 'Honda'
honda.run(); // 输出 'The car is running'
在上面的代码中,honda
是 Car
的一个实例,它继承了 Car
的原型对象上的 brand
属性和 run
方法。
示例2
现在有一个 Animal
构造函数,它拥有一个 type
属性。我们需要编写一个 Cat
构造函数,使它继承 Animal
,同时为 Cat
添加一个 name
属性。创建两个 Cat
的实例,用 console.log
依次输出它们的属性。
function Animal() {}
Animal.prototype.type = '动物';
function Cat(name) {
this.name = name;
}
Cat.prototype = Object.create(Animal.prototype);
Cat.prototype.constructor = Cat;
var cat1 = new Cat('小白');
var cat2 = new Cat('小黑');
console.log(cat1.type); // 输出 '动物'
console.log(cat1.name); // 输出 '小白'
console.log(cat2.type); // 输出 '动物'
console.log(cat2.name); // 输出 '小黑'
在上述代码中,Cat
通过 Object.create(Animal.prototype)
来实现继承 Animal
的原型对象,并重写它的构造函数。cat1
和 cat2
是 Cat
的两个实例,它们都拥有 Animal
的 type
属性和 Cat
的 name
属性。