学习目标
- 原型
- 原型链
- 原型指向改变后是如何添加方法和属性
- 原型指向改变后的原型链
- 实例对象的属性和原型对象的属性重名
- 通过原型继承
- 组合继承
- 拷贝继承
一,原型
问题: 请看以下代码,如果我们要创建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();
复制代码
看运行结果:
八,拷贝继承
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();
复制代码
请看运行结果:
至此,本片文章的全部内容完毕!本人是一个前端新人,本片文章若哪里有不正确的地方,请各位前端大佬不吝斧正!咱们携手共同进步!再次感谢!
另外,本人将要参加实习找工作,若是那位大佬觉得小学弟可以的话,请给小学弟一个机会。邮箱:[email protected]