# 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: ƒ}
1
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));
1
2
3
  • 可以通过 对象.__proto__ 获取到原型对象,但是千万不要通过 对象.__proto__ = value 来修改原型对象,会导致问题;
  • 还可以通过 Object.getPrototypeOf(obj) 的方式来获取原型对象。

JavaScript中为什么需要原型呢?

  1. 将方法定义在原型上,所有对象可以共享同一份方法,而不是每个实例都创建独立的副本,这样可以节省内存。

所以同一个类的多个对象的原型对象指向的是同一个对象,举个栗子:

class Person { 
  constructor(name) {
    this.name = name;
  }     
}

let p1 = new Person('Doubi');
let p2 = new Person('Niubi');

console.log(p1.__proto__ === p2.__proto__)  // true
1
2
3
4
5
6
7
8
9
10
  1. 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);
1
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 {}
1
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}
1
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
1
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
1
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


下面来仔细说一下原型链。

内容未完......