1、原型的意义
JavaScript这门语言在设计之初并没有类的概念(目前也是),那么没有类是如何有对象的呢?回答这个问题之前,我们想先一下,类在创建对象的过程中发挥了什么作用呢?通过面向对象的学习,我们知道类提供的是数据模板,所谓的对象就是通过该数据模板生成的数据而已。而数据模板里面包含两个部分,变量和函数。但是变量和函数是有区别的,变量是对象私有的,换句话说每一个对象的属性都是自己独享的,但是函数应该是所有对象所共享的。那么按照这个思想,我们能否通过函数来实现一个伪类型呢?
比如:我要定义一个学生类,具备属性name和age,然后拥有run方法和say方法。
实现步骤:
- 定义一个函数,通过该函数来作为变量模板
//构造函数
//构造函数对象
function Student(name,age){
return { //实例对象
name:name,
age:age
}
}
- 通过调用该函数来创建对象
let student1=Student("张三",19);
console.log(student1);
let student2=Student("李四",20);
console.log(student2);
通过以上两个步骤,我们可以随时创建我们需要的对象,对象中的变量是自己独享的,但是我们还缺少公共的函数部分。函数部分是所有对象所共享的,所以我们将函数定义在一个对象A中,在创建对象时将该对象A保存到对象中。
- 定义对象保存两个函数
let methodDistrict={//保存一个类的所有方法的对象
run:function(){
console.log("running")
},
say:function(){
console.log("hello");
}
}
function Student(name,age){
return {
name:name,
age:age,
methods:methodDistrict//定义属性保存方法
}
}
- 调用函数
let student1=Student("张三",19);
console.log(student1);
student1.methods.run();
student1.methods.say()
let student2=Student("李四",20);
console.log(student2);
student2.methods.run();
student2.methods.say()
- 优化
通过以上步骤,我们成功的实现了一个类似于类的结构。当我们想要给类添加一个函数时,只需要在methodDistrict变量中添加一个函数即可。
methodDistrict.play=function(){
console.log("打篮球");
}
student1.methods.play();
这样去做是可以的但是不够好,既然我们使用函数模拟了一个类,那么我们为什么不直接将methodDistrict绑定到函数对象上呢,后期要添加函数,通过函数名称添加就可以了,这样做语义性是不是更强呢,函数名称即类名。
let methodDistrict={
run:function(){
console.log("running")
},
say:function(){
console.log("hello");
}
}
Student.methods=methodDistrict;//定义methods属性存储函数
function Student(name,age){
return {
name:name,
age:age,
_methods:Student.methods//对象中的属性也直接执行Student.methods,为了更好的区分名称前加上下划线
}
}
//调用过程
let student1=Student("张三",19);
student1._methods.run();
student1._methods.say()
Student.methods.play=function(){
console.log("打篮球");
}
student1._methods.play();
到此,我们通过一个函数来模拟类的功能就实现了。
上述功能的实现并非异想天开,事实上javascript本身就是通过这种方式来实现所谓的类的。在ES5中定义类创建对象的方式如下:
//定义函数对象,来实现伪类型
function Student(name,age){
this.name=name;
this.age=age;
}
//在Student这个对象中有一个prototype属性用于存储方法
Student.prototype.say=function(){
console.log(this.name);
}
let student=new Student("吴彦祖",12);
console.log(student);
student.say();//在对象中有一个属性__proto__保存了方法
哪怕是在ES6提出了类的概念之后,所谓的class也只是一个概念,其底层实现依然和ES5的方式是相同的。
也就是说,JS是通过函数来实现所谓的类。
函数本身也是一个对象,在函数对象中有一个prototype属性,该属性的值是一个用于保存函数的对象,这个属性就是对象的原型。这个对象是唯一的,也就是说每一个类的所有实例对象的原型都是同一个原型对象。
而通过new 函数()所创建出的对象,被称为实例对象,在实例对象中有一个__proto__属性,该属性也指向了原型对象,换句话说
实例对象.__proto__等于函数对象.prototype,这个属性被称为隐式原型。当我们通过实例对象调用函数时,js会默认通过__proto__属性去调用原型中的方法。
实例对象的__proto__指向了函数对象的prototype,他们构成的这条引用链,就是原型链。
现场图示:
通过原型实现给Date类型添加format方法用于格式化日期
let date=new Date();
Date.prototype.format=function(){
let year=date.getFullYear();
let month=date.getMonth()+1;
let day=date.getDate();
let hour=date.getHours();
let minute=date.getMinutes();
let second=date.getSeconds();
let time=`${year}-${month}-${day} ${hour}:${minute}:${second}`;
return time;
}
let time=date.format();
console.log(time);
let date1=new Date();
console.log(date1.format());
2、原型链的意义
原型链的第一个意义在于通过一个类的任何一个实例对象都可以调用属于所有对象共享的函数。
原型链的第二个意义是通过修改原型链的引用关系,可以实现面向对象的继承关系,事实上在js中的继承关系正是通过原型链来实现的。
让我们先来看一段简单的继承代码:
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
say() {
console.log(`${this.name},${this.age}`)
}
}
class Student extends Person {
constructor(name, age, no) {
super(name, no);
this.no = no;
}
sayNo() {
console.log(`${this.no}`)
}
}
let student = new Student("彦祖", 18, 9527);
student.sayNo();
student.say();
console.log(student);
查看输出结果,我们会发现实例对象的原型没有在指向以前的原型对象,而是指向了父类实例对象,同时属于子类自己的函数,从子类原型中拷贝了一份保存在了父类实例对象中。而父类实例对象的原型同样指向了它的父级实例对象,父类的函数也会保存到它的父级实例对象中。
现场图示:
在JS设计之初,定义了一个Object类,在该类中定义了所有对象都可以使用的通用函数。也就是说当我们一个类通过extends继承了某个类,那么它的原型对象就是父类对象的实例对象。如果这个类没有显式继承任何类,将会默认继承Object类。他们共同组成了一条庞大的原型链,通过这条原型链,可以使用自己超类的(父类、父类的父类)的所有方法。
无论你在学习上有任何问题,重庆蜗牛学院欢迎你前来咨询,联系QQ:296799112