Javascript 笔记:原型链继承
这篇文章记录 Javascript 中原型链继承的相关概念。
概念
普通对象与函数对象
JavaScript
中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object
、Function 是 JS 自带的函数对象。下面举例说明: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var o1 = {};
var o2 =new Object();
var o3 = new f1();
function f1(){};
var f2 = function(){};
var f3 = new Function('str','console.log(str)');
console.log(typeof Object); //function
console.log(typeof Function); //function
console.log(typeof f1); //function
console.log(typeof f2); //function
console.log(typeof f3); //function
console.log(typeof o1); //object
console.log(typeof o2); //object
console.log(typeof o3); //object
在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。
构造函数
构造函数是一种用于创建和初始化类对象实例的特殊方法。代码如下所示:
1
2
3
4
5
6
7
8
9function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function() { alert(this.name) }
}
var person1 = new Person('Nicholas', 28, 'Software Engineer');
var person2 = new Person('Greg', 23, 'Doctor');
要创建 Person 的新实例,需要经过四个步骤:
- 创建 new 一个新对象;
- 将构造函数的 this 作用域赋值给新对象;
- 执行构造函数中的代码;
- 返回新对象。
person1 和 person2 分别保存着 Person 的不同实例,这两个对象都有一个 constructor (构造函数)属性,该属性指向 Person:
1 | console.log(person1.constructor == Person); //true |
原型对象
在 JavaScript
中,每当定义一个对象(函数也是对象)时候,对象中都会包含一些预定义的属性。其中每个函数对象都有一个
prototype
属性,这个属性指向函数的原型对象。
使用原型对象的好处就是可以让所有对象共享他所包含的属性或方法。
1 | function Person() {} |
默认情况下,所有原型对象都会自动获得一个
constructor(构造函数)
属性,通过这个构造函数,我们还可以继续为原型对象添加属性。同时,由于实例的
constructor
指向构造函数,可以得出以下两个结论:
1 | console.log(Person.prototype.constructor == Person); //true |
__proto__属性
当通过构造函数创建对象时,所创建的对象中都会有一个隐藏的属性,该属性指向构造函数的的原型对象,我们可以通过
__proto__
来访问该属性。
1 | var person1 = new Person(); |
原型链
原型链的基本思想就是利用原型让一个引用类型继承另一个引用类型的属性和方法。原型链的核心是继承。
它是由 __proto__
串联起来的链状结构。每一个函数对象都有一个 prototype
属性,也有一个 __proto__
,每一普通对象都只有一个
__proto__
属性;一般我们把属性放在构造函数中,把方法放在prototype
下面,对象的 __proto__
指向构造函数的
prototype
,原型链的终点为
Object.prototype
,再往上
Object.prototype.__proto__=null
。
说明: 构造函数是一个函数对象,它也会包含
__proto__
属性,该属性指向 Function
函数的原型对象。
想象一下,既然每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,那么,我们让原型对象等于另一个类型的实例会怎样?
显然,此时的原型对象将包含一个指向另一个原型的指针,相应地,另一个原型中也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例,那么上述关系依然成立,如此层层递进,就构成了实例与原型的链条。这就是所谓原型链的基本概念。
1 | function Father(){ |
内置构造函数
可以使用如下创建一个对象:
1 | var obj = {} |
它等同于下面这样: 1
var obj = new Object()
obj 是构造函数 Object 的一个实例。所以:
1 | obj.constructor === Object |
新对象 obj
是使用 new
操作符后跟一个构造函数来创建的。构造函数 Object
本身就是一个函数(也是一个函数对象),它和上面的构造函数
Person
差不多。
同理,可以创建对象的构造函数不仅仅有 Object
,也可以是
Array
,Date
,Function
等等。
1 | let b = new Array(); |
所有的构造函数的实例的 __proto__
都指向构造函数的
prototype
,所以又可得:
1 | Number.__proto__ === Function.prototype // true |
那么 Function.prototype
的
__proto__
是谁呢? 1
console.log(Function.prototype.__proto__ === Object.prototype) // true
函数本身也是对象,作为构造函数或者实例同时也继承了
Object.prototype
上的所有方法:toString
、valueOf
、hasOwnProperty
等等。
那么 Object.prototype.__proto__
指向谁?
1 | Object.prototype.__proto__ === null // true |
原型链的尽头,为 null
。
最终的关系如下图所示:
原型链继承
原型链是实现继承的重要方式。通过将子类的原型设置为父类的实例,可以实现子类继承父类的属性和方法。
代码实例: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23function Animal(name) {
this.name = name;
}
Animal.prototype.eat = function() {
console.log(`${this.name} is eating.`);
};
function Dog(name) {
Animal.call(this, name); // 继承属性
}
// 继承方法
Dog.prototype = Object.create(Animal.prototype);
Dog.prototype.constructor = Dog;
Dog.prototype.bark = function() {
console.log(`${this.name} says Woof!`);
};
const dog = new Dog('Buddy');
dog.eat(); // 输出: Buddy is eating.
dog.bark(); // 输出: Buddy says Woof!
Object.create(Animal.prototype)
创建了一个以
Animal.prototype
为原型对象的对象,并作为 Dog
对象的原型对象,从而 Dog
对象可以继承
Animal
原型对象中的方法,并设置原型对象的构造函数为
Dog
函数。
参考: