学习目标
- 原型
- 原型链
- 原型指向改变后是如何添加方法和属性
- 原型指向改变后的原型链
- 实例对象的属性和原型对象的属性重名
- 通过原型继承
- 组合继承
- 拷贝继承
一 原型
「问题:」
请看以下代码,如果我们要创建100个对象,应该怎么创建?
function Person(name, sex) {
this.name = name;
this.sex = sex;
this.drink () {
console.log('我想喝手磨咖啡!!')
}
}
for (let i = 0; i < 100; i++) {
var per = new Person('苏大强', '男');
per.drink();
}
从上面的代码可以看出,如果我们要创建100个Person对象,这样要开一百个内存空间,每次都要调用drink()函数,由于drink()函数都是一样的,每个内存空间里都有它太过于浪费空间,那我们怎样才能避免这种情况,减少内存呢?我们接下来引入原型prototype
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
//为原型添加方法
Person.prototype.drink = function () {
console.log('我想喝手磨咖啡!!')
}
//实例化对象
let per = new Person('苏大强', '男');
per.drink();
我们运用了原型prototype,可以共享数据,减少内存空间。
二 原型链
我们既然清楚了原型,那我们再来看看原型链。首先我们打印一下构造函数Person和实例对象per。
console.dir(Person);//构造函数
console.dir(per);//实例对象
从图上看,构造函数中的prototype中的属行和实例对象per中的__proto__中的属性一模一样,那我们想想它们相等吗?我们可以验证一下。
console.log(per.__proto__ === Person.prototype);
由此我们可以判断出,构造函数Person中的prototype原型和实例对象per中的__proto__原型指向是相同的,我们一般是先有构造函数再有实例对象,实例对象由构造函数创建,所以说实例对象中的__proto__原型指向的是构造函数中的原型prototype
「实例对象中」proto「是原型,浏览器使用的。构造函数中的 prototype 是原型,程序员使用的」
那接下来我们看一幅图来看看原型链到底是什么?
我们来分析分析整张图
- 「首先,构造函数中的 prototype 属性指向自己的原型对象」
- 「然后,原型对象中的构造器指向的是,原型对象所在的构造函数」
- 「再然后,实例对象中的」proto「指向的是,它所在构造函数中 prototype 属性所指向的原型对象」
所以从上图我们可以得到以下几点:
- 实例对象的原型指向了构造函数中 prototype 属性所指向的原型对象,所以实例对象和原型对象之间有关系,它和构造函数是一个间接的关系。
- 我们从代码中也可以得出,实例对象可以直接访问原型对象中的属性或方法。
- 实例对象和原型对象之间有关系,它们的关系是通过原型「proto」来连接的。
「最终我们可以得出,原型链:它是一种关系,实例对象和原型对象之间的关系,关系是通过原型」proto「来联系的」
三 原型指向改变后是如何添加方法和属性
原型改变添加方法也无非就是两种:1.在原型改变前添加加方法。2.在原型改变以后添加方法。
首先,我们来看第一种:
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
Person.prototype.drink = function () {
console.log('我想喝水!!')
}
function Student(name, sex) {
this.name = name;
this.sex = sex;
}
Student.prototype.eat = function () {
console.log('我想吃东西!!')
}
//改变原型指向
Student.prototype = new Person('人', '男');
let stu = new Student('学生', '女');
stu.drink();
stu.eat();
我们来运行以下:
我们可以看到图中的信息,stu.eat()不是一个函数,刚才我们明明将eat()添加到了Student的原型上,怎么现在报错了?
「原因是:由于 Student 的原型指向改变了,它指向了 new Person('人', '男'),并且 Person 的原型上并没有 eat(),所以报错,那么第一种情况在原型改变之前添加是错误的!」
我们再来看第二种情况:在原型改变之后添加方法。
function Person(name, sex) {
this.name = name;
this.sex = sex;
}
Person.prototype.drink = function () {
console.log('我想喝水!!')
}
function Student(name, sex) {
this.name = name;
this.sex = sex;
}
//改变原型指向
Student.prototype = new Person('人', '男');
//为原型添加方法
Student.prototype.eat = function () {
console.log('我想吃东西!!')
}
let stu = new Student('学生', '女');
stu.drink();
stu.eat();
我们来运行以下:
「可以看出来,两个方法都被成功的调运了,所以说:如果原型指向改变了,那么就应该在原型改变指向之后添加原型方法。」
四 原型指向改变后的原型链
那么,当原型指向改变之后,原型链会发生怎样的改变呢?
那我们来们分析以下:
- 原型指向改变之前
- 原型指向改变之后
我们先来分析原型指向改变之前:
//人的构造函数
function Person(name) {
this.name = name;
}
//为原型添加方法
Person.prototype.drink = function () {
console.log('我想喝水!!')
}
//学生的构造函数
function Student(name) {
this.name = name;
}
//为原型添加方法
Student.prototype.eat = function () {
console.log('我想吃东西!!')
}
//实例对象
let per = new Person('老师');
let stu = new Student('学生');
console.dir(Person);//构造函数
console.dir(per);//实例对象
console.dir(Student);//构造函数
console.dir(stu);//实例对象
我们运行以下这段代码:
请看每个prototype和__proto__,我们可以得到它们的原型链图:
「由此图,我们可以看出,原型没有改变之前,实例对象的」proto「都指向自己构造函数中 prototype 属性所指向的原型对象。」
我们再来看看原型指向改变之后:
//人的构造函数
function Person(name) {
this.name = name;
}
//为原型添加方法
Person.prototype.drink = function () {
console.log('我想喝水!!')
}
//学生的构造函数
function Student(name) {
this.name = name;
}
//为原型添加方法
Student.prototype.eat = function () {
console.log('我想吃东西!!')
}
//改变学生的原型指向
Student.prototype = new Person('老师');
//实例对象
let stu = new Student('学生');
console.dir(Person);//构造函数
console.dir(new Person('老师'))//实例对象
console.dir(Student.prototype)//Student的原型对象
console.dir(Student);//构造函数
console.dir(stu);//实例对象
我们来看看运行结果:
我们来分析分析:
「我们代码和图结合来看,当 Student.prototype = new Person('老师');之后,① 学生构造函数的 prototype 属性会断开指向原型对象,② 原型对象中的构造器也会断开指向构造函数,③ 实例对象的」proto「会断开指向原型对象」
这里的序号没有任何意义,相当于起的名字!!!
还没有完,我们再来看图:
「当原型指向改变之后,学生的构造函数中的 prototype 属性指向了 new Person('老师');,随后,学生的实例化对象中的」proto「属性指向了学生构造函数中 prototype 属性所指向的 new Person('老师');」
「原型链改变完毕!」
五 实例对象的属性和原型对象的属性重名
当实例对象中的属性和原型对象中的属性重名时应该先访问那个?
我们来看一看代码:
//人的构造函数
function Person(age, sex) {
this.age = age;
this.sex = sex;
}
//为原型添加属性
Person.prototype.sex = "女";
//实例化对象
var per = new Person(10,"男");
console.log(per.sex);
看图:
「从代码的运行结果来看,当实例对象中的属性和原型中的属性重名时,它会先访问实例对象中的属性。」
如果在实例对象中找不到呢?我们来看代码:
function Person(age) {
this.age = age;
}
//为原型添加属性
Person.prototype.sex = "女";
//实例化对象
var per = new Person(10);
console.log(per.sex);
我们来看运行结果:
「从这段代码,我们可以看出,当实例对象中访问不到属性时,它会向上查找原型对象上的属性」
六 通过原型继承
//js中通过原型来实现继承
//人的构造函数
function Person(name, age, sex) {
this.name = name;
this.sex = sex;
this.age = age;
}
//为原型添加方法
Person.prototype.eat = function () {
console.log("人吃东西");
};
Person.prototype.sleep = function () {
console.log("人在睡觉");
};
Person.prototype.play = function () {
console.log("生活就是编代码!");
};
//学生的构造函数
function Student(score) {
this.score = score;
}
//改变学生的原型的指向即可==========>学生和人已经发生关系
Student.prototype = new Person("小明", 10, "男");
//为原型添加方法
Student.prototype.study = function () {
console.log("学习很累很累的哦.");
};
var stu = new Student(100);
console.log(stu.name);
console.log(stu.age);
console.log(stu.sex);
stu.eat();
stu.play();
stu.sleep();
console.log("下面的是学生对象中自己有的");
console.log(stu.score);
stu.study();
看运行结果:
「原型继承的精髓就是:使用原型,改变原型的指向进行继承。」
七 组合继承
//组合继承:原型继承+借用构造函数继承
//人的构造函数
function Person(name, age, sex) {
this.name = name;
this.age = age;
this.sex = sex;
}
Person.prototype.sayHi=function () {
console.log("你好吗?");
};
function Student(name, age, sex, score) {
//借用构造函数:属性值重复的问题
Person.call(this, name, age, sex);
this.score = score;
}
//改变原型指向----继承
Student.prototype = new Person();//不传值
Student.prototype.eat = function () {
console.log("吃东西");
};
//实例对象
var stu = new Student("金仔", 20, "男", "100分");
console.log(stu.name, stu.age, stu.sex, stu.score);
stu.sayHi();
stu.eat();
var stu2=new Student("含仔", 20, "女", "100分");
console.log(stu2.name, stu2.age, stu2.sex, stu2.score);
stu2.sayHi();
stu2.eat();
看运行结果:
「组合继承的精髓:首先在 Student 构造函数中使用 call()函数将属性传入 Person 构造函数中,并改变 this,然后改变 Student 的原型指向」
八 拷贝继承
function Person() {};
Person.prototype.age = 10;
Person.prototype.sex = "男";
Person.prototype.height = 100;
Person.prototype.play = function () {
console.log("玩耍!");
};
var Student = {};
//Person的构造中有原型prototype,prototype就是一个对象,那么里面,age,sex,height,play都是该对象中的属性或者方法
for (let key in Person.prototype) {
Student[key] = Person.prototype[key];
}
console.dir(Student);
Student.play();
请看运行结果: