# JavaScript教程 - 9 面向对象
# 9.5 原型简介
讲解原型之前,先看一个栗子。
class Person {
name = 'Doubi';
sayHello() {
console.log('Hello');
}
sleep = function() {
console.log('sleep');
}
}
let person = new Person();
person.age = 13; // 添加属性
person.eat = function() { // 添加方法
console.log('eat');
}
console.log(person); // Person {name: 'Doubi', age: 13, sleep: ƒ, eat: ƒ}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 在上面的代码中,通过 Person 类创建了 person 对象,并给 person 对象添加了属性和方法。
但是在打印 person 对象的时候,发现并没有 sayHello()
方法,这是为什么呢?
因为对象中存储属性的区域实际有两个:
对象自身
在类中通过赋值的方式(
x = value
)添加的属性,位于对象自身中;直接通过
对象.属性 = value
的方式添加的属性,位于对象自身中。原型对象
对象中还有一些内容,会存储在别的对象中,也就是原型对象,也就是说原型对象中也会存储对象的属性和方法,当我们通过对象来访问属性或方法的时候,会先在对象自身中寻找,如果找不到,就会去原型对象中寻找。
所以上面通过
sayHello(){}
添加的方法,实际是添加到原型对象中了。
在对象中,会有一个属性存储原型对象的引用,这个属性叫 __proto__
:
我们可以通过如下方式访问原型对象:
let person = new Person();
console.log(person.__proto__);
console.log(Object.getPrototypeOf(person));
2
3
- 可以通过
对象.__proto__
获取到原型对象,但是千万不要通过对象.__proto__ = value
来修改原型对象,会导致问题; - 还可以通过
Object.getPrototypeOf(obj)
的方式来获取原型对象。
JavaScript中为什么需要原型呢?
- 将方法定义在原型上,所有对象可以共享同一份方法,而不是每个实例都创建独立的副本,这样可以节省内存。
所以同一个类的多个对象的原型对象指向的是同一个对象,举个栗子:
class Person {
constructor(name) {
this.name = name;
}
}
let p1 = new Person('Doubi');
let p2 = new Person('Niubi');
console.log(p1.__proto__ === p2.__proto__) // true
2
3
4
5
6
7
8
9
10
- JavsScript的继承是使用原型实现的,这个下面细讲。
# 9.6 原型链与继承
# 1 构造函数
在前面我们讲创建对象的方式,有以下三种方式:
// --方式1
let person1 = {
name: "Doubi",
age: 13,
};
// --方式2
let person2 = new Object();
person2.name = "Doubi";
person2.age = 13;
// --方式3
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let person3 = new Person("Doubi", 13);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
在 ES6 之前,创建对象是通过构造函数来创建的,class
是 ES6 中引入的,class
其实是构造函数的语法糖(更简洁更易读的语法),而 class
实现的继承底层仍是原型,新的 class
写法只是让原型的写法更加的清晰、更像面向对象编程的语法而已。
所以既然 class 是语法糖,那就先从构造函数讲起。
创建一个 Person 的构造函数,用来创建 Person 对象:
// 函数
function Person() {
}
let person = new Person(); // 通过 new 使用
console.log(person); // Person {}
2
3
4
5
6
- 上面的
Person()
函数就是一个构造函数,构造函数和普通的函数没有本质区别,只是调用方式不同。当我们使用new
来调用一个函数,那么它就是被当做构造函数,返回的就是一个对象。
当然我们想把一个函数作为构造函数来创建对象,那么会在对象中添加属性、方法,是我们使用函数的方式不同,才区分了构造函数和普通函数:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
let person = new Person('Doubi', 13);
console.log(person); // Person {name: 'Doubi', age: 13}
2
3
4
5
6
7
8
使用构造函数创建的实例,都有一个 constructor
属性指向构造函数:
// 构造函数
function Person(name, age) {
this.name = name;
this.age = age;
}
let p1 = new Person("Doubi", 13);
let p2 = new Person("Niubi", 18);
console.log(p1.constructor === Person); // true
console.log(p2.constructor === Person); // true
console.log(p1.constructor === p2.constructor); // true
2
3
4
5
6
7
8
9
10
11
12
class
是构造函数的语法糖,那么使用类也是一样的:
// 构造函数
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
}
let p1 = new Person("Doubi", 13);
let p2 = new Person("Niubi", 18);
console.log(p1.constructor === Person); // true
console.log(p2.constructor === Person); // true
console.log(p1.constructor === p2.constructor); // true
console.log(p1.hasOwnProperty("constructor")); // false
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但是上面 person1.hasOwnProperty("constructor")
判断对象是否有 constructor
返回的是 false
,也就是说实例本身是没有 constructor
属性的,这个 constructor
属性是继承自实例的原型对象。
也就是:p1.constructor -> p1.__proto__.constructor -> Person
。
下面来仔细说一下原型链。