原型与原型链以及继承

原型 prototype

🌈 prototype是 func 的属性,其值是个对象

1
2
function Handphone() {}
console.log(Handphone.prototype);

prototype 是构造函数中的,构造出的每个实例对象的公共祖先

所有被该构造函数构造出来的对象,都可以继承原型上的属性和方法

1
2
3
4
5
6
7
8
9
10
11
12
function Handphone(color, brand) {
this.color = color;
this.brand = brand;
}
Handphone.prototype.rom = '64G';
Handphone.prototype.ram = '6G';

var hp1 = new Handphone('red', '小米');
var hp2 = new Handphone('black', 'huawei');

console.log(hp1.rom);
console.log(hp2.ram);

🌈 constructor 指向构造函数本身

1
2
3
4
5
6
function Handphone(color, brand) {
this.color = color;
this.brand = brand;
}
console.dir(Handphone);
console.dir(Handphone.prototype.constructor);

constructor 可以被修改

1
2
3
4
5
6
7
8
9
function Telephone() {}
function Handphone(color, brand) {
this.color = color;
this.brand = brand;
}
Handphone.prototype = {
constructor: Telephone
}
console.log(Handphone.prototype)

🌈 proto 是实例化以后的结果

1
2
3
4
5
6
7
8
function Car() {
var this = {
__proto__: Car.prototype
}
}
Car.prototype.name = 'Benz';
var car = new Car();
console.log(car);
1
2
3
4
5
6
7
8
9
10
11
function Person() {}
Person.prototype.name = '张三';

var p1 = {
name: 'lance'
}

var person = new Person();
console.log(person.name);
person.__proto__ = p1;
console.log(person.name);

🌈🌈🌈 实例化对象以后再来修改构造函数的prototype,不影响该对象的原型属性 ,因为修改后的 prototype 指向了新的对象,不影响原来的prototype,但影响原来 prototype 下的 constructor

1
2
3
4
5
6
7
8
9
10
11
12
Car.prototype.name = 'Benz';
function Car() {}
var car = new Car();
Car.prototype = { // 实例化之后赋值,car还是原来的
name: 'Mazda'
}
console.log(car.name); //答案: Benz

console.dir(Car);
console.dir(car);
console.log(car.__proto__.constructor === Car); //答案: true
console.log(Car.prototype.constructor === Object.prototype.constructor); // true
1
2
3
4
5
Car.prototype.name = 'Benz';
function Car() {}
var car = new Car();
Car.prototype.name = 'hhhh';
console.log(car.name); // hhhh

↑注意是直接替换 **Car.prototype** 还是改 **Car.prototype** 对象下边的属性↑

1
2
3
4
5
6
7
8
9
10
11
12
13
Car.prototype.name = 'GC';
function Car() {}
var car = new Car();
Car.prototype = { // 实例化之后赋值,car还是原来的
name: 'Lance'
}
console.log(car.name); // GC
var car2 = new Car();

console.log(Car);
console.log(car);
console.log(car2);
console.log(car.__proto__.constructor === Car); // true

原型链

沿着原型**__proto__**往上不断找属性的这条链条叫做原型链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}
var student = new Student();

console.log(student);

// student: {
// pSkill: 'HTML/CSS',
// __proto__: { // teacher
// mSkill: 'JS/JQ',
// __proto__: { // professor
// __proto__: { // Professor.prototype
// tSkill: 'Java',
// constructor: Professor,
// __proto__: Object.prototype
// }
// }
// }
// }

student.__proto__.__proto__.__proto__ === Professor.prototype; // true
student.__proto__.__proto__.__proto__.__proto__ === Object.prototype; // true

🌈 原型链的顶端是 Object.prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}
var student = new Student();

console.log(professor, Object.prototype);

修改原型上的值

🌈 原型的引用值的属性可以修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
this.success = {
alibaba: '28',
tencent: '20'
}
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}

var student = new Student();
console.log({ "修改student引用值之前的success": student.success });

// 🌰 此处chrome控制台先打印,再执行下边语句
// 修改引用值
student.success.baidu = '100';
console.log({ "修改student引用值之后的student": student });

🌈 修改基本值,不影响原型属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
this.success = {
alibaba: '28',
tencent: '20'
}
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}

var student = new Student();

// 修改基本类型
console.log({ "修改student基本类型之前的success": student.success });
student.success = 666;
console.log({ "修改student基本类型之后的student": student });

修改一个原型上不存在的引用值,报错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
this.success = {
alibaba: '28',
tencent: '20'
}
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}

var student = new Student();

// 修改不存在的引用类型
student.profile.name = 'lance';
// 报错: Cannot set property 'name' of undefined

🌈 原始值自增

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
Professor.prototype.tSkill = 'Java';
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.mSkill = 'JS/JQ';
this.students = 500;
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.pSkill = 'HTML/CSS';
}
var student = new Student();
console.log(student.students);
student.students++;
console.log(teacher);
console.log(student);

分析:学生的students值变了,professor的students值没变。过程是:

  1. student.students++; => student.students = student.students + 1

  2. =赋值右边 student 自身没有 students,在原型上读取 students 值,再 + 1 计算

  3. =赋值左边 student.students 是个原始值,则在自身上添加 students 属性

  4. 最后赋值

谁调用,this指向谁

1
2
3
4
5
6
7
8
9
10
11
12
13
function Car() {
this.brand = 'Benz';
}
Car.prototype = {
brand: 'Mazda',
intro: function() {
console.log('我是' + this.brand + '车');
}
}
var car = new Car();
car.intro();
console.log(car);
Car.prototype.intro();
1
2
3
4
5
6
7
8
9
10
11
12
13
function Person() {
this.smoke = function() {
this.weight--;
}
}
Person.prototype = {
weight: 130
}
var person = new Person();
person.smoke();
person.weight;
console.log(person);
console.log(Person.prototype);

🌈 继承

实例化对象继承

  • 特点:继承了原型链上的所有属性
  • 缺点:继承了一些没必要继承的属性,例如 name
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Professor.prototype = {
name: 'Mr.Zhang',
tSkill: 'Java'
}
function Professor() {}
var professor = new Professor();

Teacher.prototype = professor;
function Teacher() {
this.name = 'Mr.Wang';
this.mSkill = 'JS';
}
var teacher = new Teacher();

Student.prototype = teacher;
function Student() {
this.name = 'Mr.Li';
this.pSkill = 'HTML';
}
var student = new Student();
console.log(student);

利用 call/apply 借用构造函数

  • 本质:call/apply 是通过改变 this 指向,来借用构造函数的属性和方法
  • 此方法实例化的 student 无法继承 Teacher.prototype 原型上的属性
1
2
3
4
5
6
7
8
9
10
11
12
Teacher.prototype.wife = 'Ms.Liu';
function Teacher(name, mSkill) {
this.name = name;
this.mSkill = mSkill;
}
function Student(name, mSkill, age, major) {
Teacher.apply(this, [name, mSkill]);
this.age = age;
this.major = major;
}
var student = new Student('Mr.B', 'JS', 18, 'Computer');
console.log(student);

Student.prototype = Teacher.prototype

  • 特点:能够继承 Teacher 原型
  • 缺点:Student 和 Teacher 共用原型,修改 Student.prototype 会导致 Teacher.prototype 改变
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function Teacher() {
this.name = 'Mr.Wang';
this.mSkill = 'JS';
}
Teacher.prototype = {
pSkill: 'JQ'
}
var teacher = new Teacher();

Student.prototype = Teacher.prototype;
function Student() {
this.name = 'Mr.Li';
}

var student = new Student();
Student.prototype.name = 'student';
console.log(student);
console.log(Teacher.prototype);

🌈 圣杯模式继承(圣杯中间的部位就是连接两头的中间件)

方案:

  1. 创建一个空的构造函数

  2. 使其继承父类构造函数的原型

  3. 由此构造函数实例化出一个对象

  4. 把此对象赋值给子类构造函数的 prototype 原型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function Teacher() {
this.name = 'Mr.Wang';
this.mSkill = 'JS';
}
Teacher.prototype = {
pSkill: 'JQ'
}
var teacher = new Teacher();

function Student() {
this.name = 'Mr.Li';
}

function Buffer() {}
Buffer.prototype = Teacher.prototype;
var buffer = new Buffer();

Student.prototype = buffer;
Student.prototype.age = 18;
var student = new Student();
console.log(teacher);
console.log(buffer);
console.log(student);

封装继承方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Teacher.prototype.name = 'Mr.Wang';
function Teacher() {}
function Student() {}

inherit(Student, Teacher);
var s = new Student();
var t = new Teacher();
console.log(t);
console.log(s);

function inherit(Target, Origin) {
function Buffer() {}
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
// 还原构造器
Target.prototype.constructor = Target;
// 指定继承源
Target.prototype.super_class = Origin;
}

圣杯模式+IIFE+闭包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Teacher.prototype.name = 'Mr.Wang';
function Teacher() {}
function Student() {}

inherit(Student, Teacher);
var s = new Student();
var t = new Teacher();
console.log(t);
console.log(s);

var inherit = (function () {
function Buffer() {}
return function (Target, Origin) {
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
// 还原构造器
Target.prototype.constructor = Target;
// 指定继承源
Target.prototype.super_class = Origin;
}
})();

🌈 圣杯模式 + call/apply 组合继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function Person(name, age) {
this.name = name;
this.age = age;
this.sayHi = function() {
console.log("sayHi");
}
}

Person.prototype.eat = function() {
console.log("eat");
}

function Student(name, age, score) {
Person.call(this, name, age);
this.score = score;
this.study = function() {
console.log("study");
}
}

var inherit = (function () {
function Buffer() {}
return function (Target, Origin) {
Buffer.prototype = Origin.prototype;
Target.prototype = new Buffer();
// 还原构造器
Target.prototype.constructor = Target;
// 指定继承源
Target.prototype.super_class = Origin;
}
})();


inherit(Student, Person);
var stu1 = new Student("Lance", 19, 100);
console.dir(stu1);