在 ES5 中,有的人可能对原型,原型对象,及其原型链不是很清楚,今天我就说说对这些的深入认识下。(如果有什么不懂得欢迎留言探讨,当然如果有什么写的不恰当的也希望大家留言备注。)
首先,再说原型与原型对象之前,当然有必要清楚构造函数,实例,原型与原型对象之间的关系。其实他们的关系也很简单。
构造函数,实例,原型与原型对象之间的关系:
构造函数有它自己的属性及其方法,其中包括自己定义的属性和方法外,还有两个特殊属性(prototype、constructor);而每个他的实例都会拥有它的所有属性和方法(包括prototype、constructor)constructor则是指向每个实例的构造函数,而prototype 原型 则是一个地址指向原型对象,这个原型对象创建了实例后,只会取得constructor属性,其他的都是从Object继承而来;在Firefox 、 chrome在对象上都支持一个属性"_proto_";这个原型对象的属性和方法是所有该类实例共享的任何该类实例够可以访问该原型对象的属性和方法(后面会介绍访问原型对象属性和方法的三个方式)
如上图,p1 ,p2的的实例都有Person的属性和方法,并且prototype都指向原型对象,p1\p2共享prototype原型对象的属性和方法,各自的constructor都指向Peson,这便是构造函数、实例、原型(对象)三者的关系。
现在我来说一说访问原型对象属性和方法的三个方式:
1.通过Person.prototype 属性
console.log(Person.prototype.name);//输出----->person
2.通过 属性屏蔽 delete (屏蔽构造函数属性或者方法)
p1.sayName(); //输出----->构造函数对象
delete p1.name;
console.log(p1.name); //输出----->原型属性
delete p1.sayName;
p1.sayName(); //输出 --->原型对象方法
3.通过Object.getPrototypeOf(p1)
console.log(Object.getPrototypeOf(p1).name);//输出----->原型属性
上面我们需要注意就是当实例调用属性或者方法时,有一个”属性搜索机制“,所谓”属性搜索机制“就是当实例访问属性或者方法时首先会现在自身的实例中搜索,看是否有对应属性,有,则返回;如果没有那么它会通过prototype 到原型对象中寻找对应的属性和方法;
原型链:
其基本思想是利用原型让一个引用类型继承另一个引用类型的属性和方法。我们知道,每个构造函数都有一个原型对象,每个原型对象都有一个指向构造函数的指针,而实例又包涵一个指向原型对象的内部指针。
如果我们让原型对象(A.prototype) = 另一个类型的实例(new B()),那么,该原型对象(A.prototype)就有一个指向另一个原型对象(B.prototype)的指针,相应的,另一个原型对象(B.prototype)也包含指向另一个构造函数(B)的指针。 ------------->如果另一个的原型(B.prototype)又是另一个类型(C)的实例,上诉关系依然成立,就构成了实例与原型的链条,这就是原型链。
实现原型链的基本方法:
function Person () {
this.name = "person";
}
Person.prototype.getPersonName = function () {
return this.name;
};
function Student () {
this.studentname = "student";
}
// 继承了Person
Student.prototype = new Person();
Student.prototype.getStudentName = function () {
return this.name;
};
var stu = new Student();
console.log(stu.getPersonName());//person
如上就是通过将 Student()的prototype = new Person() 即子类的原型对象等于父类的实例,从而Student.prototype有了Person的所有属性和方法,实现了继承。通过实现原型链,再结合 “属性搜索机制“,则
stu.getPersonName()
会经过三个阶段:1.搜索实例 2.搜索Student.prototype 3.搜索Person.prototype;如果再没就会到Object 对象的prototype对象上寻找。因为所有的function和对象等引用类型都继承Object;这也就说明了为什么不是通过Object直接实例的对象(自定义类型)会有valueof(),toString()等方法。
需要注意的是,子类有时需要重写父类的方法或者新增心得方法,这些都要 替换了原型之后(也就是实现继承之后)。-----------stu 指向Student的原型,Student的原型又指向了Person的原型。从而实现了stu 继承Student Student继承Person
function Person () {
this.name = "person";
}
Person.prototype.getPersonName = function () {
return this.name;
};
function Student () {
this.studentname = "student";
}
// 继承了Person
Student.prototype = new Person();
// 新增方法
Student.prototype.getStudentName = function () {
return this.name;
};
// 重写父类方法
Student.prototype.getPersonName = function () {
return false;
};
var stu = new Student();
console.log(stu.getPersonName());//false
还有一点需要注意的是:在通过原型链实现继承时,不能用对象字面量创建原型方法,因为这样会重写原型链。 刚刚把Person的实例赋值给原型,紧接着使用字面量导致出错。------->因为现在的原型包含的是一个Object的实例,不是Person的实例,原型链被切断。
function Person () {
this.name = "person";
}
Person.prototype.getPersonName = function () {
return this.name;
};
function Student () {
this.studentname = "student";
}
// 继承了Person
Student.prototype = new Person();
// 使用字面量添加新方法,会导致Student.prototype = new Person(); 无效
Student.prototype = {
getStudentName :function () {
return this.name;
},
otherMethod :function () {
return false;
}
};
var stu = new Student();
console.log(stu.getPersonName());//stu.getPersonName is not a function
会报错 stu.getPersonName is not a function 因为此时Student和Person已经没有关系了。
所以理想的继承方式是”寄生组合式继承“,所谓寄生组合式继承通过借用构造函数来继承属性(父类构造函数里的属性+方法),通过原型链的形式继承方法(父类原型里的方法)。
// 寄生组合式继承(理想的继承方式)
function inherPrototype (Subobject,Superobject) {
var prototype = Superobject.prototype;
prototype.constructor = Subobject;
Subobject.prototype = prototype;
}
function Person (name) {
this.name = name,
this.hand = ["right-hand","left-hand"],
this.say = function () {
alert("hi");
}
}
Person.prototype.sayName = function () {
alert(this.name);
}
function Student (name,age) {
Person.call(this,name);
this.age = age;
}
// 实现继承
inherPrototype(Student,Person);
Student.prototype.sayAge = function () {
alert(this.age);
}
var stu1 = new Student("jack",20);
// 继承了属性(构造函数里的属性+方法)
console.log(stu1.hand[0]); //----------->输出right-hand
stu1.say();//输出 hi
// 继承了原型里的方法
stu1.sayName();//输出 jack
要实现继承无非就是拥有父类对象的方法和属性,即拥有一个父类的”模板副本“,inherPrototype()方法做了如下三件事:1.创建父类的原型(prototype)副本,赋值给prototype 2.为创建的prototype副本添加constructor属性 ,弥补因为原型赋值而导致失去默认的constructor属性3.把这个副本赋值给子类原型。这样便实现了继承。