原型和原型链以及继承
创建对象
1、工厂模式:
function CreatPerson(name,age,job){
var o =new Object;
o.name=name;
o.age=age;
o.job=job
o.sayName=function(){
}
return o
}
通过函数的形式传入参数,返回必要的信息的Person 对象,可以多次调用。
工厂模式解决了创建多次相似对象的问题,但是没有解决对象识别的问题。
2、构造函数模式:
构造函数可用来创建特定类型的对象,
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.sayName=function(){
}
}
var person1 = new Person('lei',22,'Doc');
var person2 = new Person('lei2',28,'Doc2');
-
Person 和 CreatPerson 对比:
- 1、没有显式的创建对象;
- 2、 将属性和方法赋值给了this 对象;
- 3、 没有return
-
创建Person 实例 ,new Person()过程
- 1、创建一个新对象
- 2、将构造函数的作用域赋给新的对象 (因此this 指向了新对象)
- 3、执行构造函数中的代码(为这个对象添加属性)
- 4、返回新的对象;
上面例子 person1 和person2 保存着2个不同的实例,这两个对象都有一个constructor 属性,该属性指向Person
person1.constructor==Person // true
console.log( person1 instanceof Object) // true
console.log( person1 instanceof Person) // true
person1和person2 之所以都是Object 实例,是因为所有的对象都继承至 Object,
构造函数还是有确定的:每个方法都要在每个实例上重新创建一次,因为不同实例上的同名函数是不相等的。
3、原型模式:
创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象。 这个对象包含了实例共享的属性和方法。
不必在构造函数中定义实例的信息,可以将这些信息添加到原型对象中
- 1、只要创建新函数,会为函数创建一个一个prototype属性,这个属性指向函数的原型对象;
- 2、原型对象都会自动获取constructor属性,通过这个构造函数,还可以继续为原型对象添加其他的属性和方法。
- 3、Person.prototype 指向原型对象,Person.prototype.constructor 又指回Person,原型对象除了包含constructor 还包含其他的属性。
- 4、person1.sayName()会先查看实例上有属性吗?有则返回,没有就查找person1的原型上找找。
- 5、当为对象实例添加属性,这个属性会屏蔽原型对象中保存的同名属性,意思就是会阻止我们访问原型中的属性,不过可以通过delete 删除
- 6、 使用hasOwnProperty() 方法可以检测一个属性是存在于实例当中还是存在于原型当中,(这个方法是从Object继承来的),
只在给定的属性存在对象实例中才会返回true
function Person(){}
// 字面量重写整个原型对象,相当于是一个新的对象
Person.prototype={
name:'lei',
job:'dasd',
sayName:function(){
console.log(this.name)
}
}
var friend= new Person()
constructor 属性不在指向Person了,而这里相当于重写了默认的prototype 对象,而constructor 属性也变成了 新的constructor 属性(指向了Object构造函数)不再指向Person函数
console.log(friend.contructor == Person) // false
console.log(friend.contructor ==Object) // true
function Person(){}
var friend= new Person()
// 字面量重写整个原型对象,相当于是一个新的对象
Person.prototype={
name:'lei',
job:'dasd',
sayName:function(){
console.log(this.name)
}
}
friend.sayName() // error
因为先创建的实例, friend 指向的原型还不包含该命名的属性,重写原型对象切断了现有原型与之前存在的对象实例之间 的联系。但是引用的仍然是最初的原型。
4、组合使用构造函数模式和原型模式:
这种方式应该是最常见的方式;构造函数模式定义实例属性;原型链模式用于定义方法和共享的属性;
有属于自己的一份实例属性,还共享对方的方法引用。极大的节省了内存。
function Person(name,age,job){
this.name=name;
this.age=age;
this.job=job;
this.friends=['dsd','dsddd','llyan']
}
Person.prototype={
constructor:Person;
sayName:function(){
}
}
var person1=new Person('kk',23,'ddd')
var person2=new Person('kk',23,'ddd')
person1.friends.push('Vam');
// 修改了person1.friends 不会影响person2.friends,他们分别引用了不同的数组;
5、动态原型模式:
把所有的信息都封装在构造函数中,,通过构造函数初始化原型。
使用动态模式时候,不能使用对象字面量重写原型,不然会切断现有实例和原型之间的关系
6、寄生构造函数模式:
这种模式的基本思想:创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后返回新的对象
function Person(name,age,job){
var o =new Object();
o.name=name;
o.age=age;
o.job=job
o.sayName=function(){
alert(this.name)
}
return o;
}
var friend = new Person('lei',29,'DDD');
friend.sayName() // 'lei'
- 这个模式和工厂模式一样,在构造函数不返回值的情况下,默认返回一个对象,在末尾添加一个return语句,可以重写调用构造函数返回的值。
- 构造函数返回的对象与构造函数或者构造函数的原型没有关系。
- 不能依赖instanceof 操作确定对象类型。
js 原型和原型链
js 中万物皆对象,对象分为二种,普通对象(Object)和函数对象(Function)
任何对象都具有隐式原型属性(proto),只有函数对象有显式原型属性(prototype)
1、因为构造函数实例对象,无法共享属性和方法,所以后来引入 prototype。
所有实例对象需要共享的属性和方法,都放在这个对象里面;那些不需要共享的属性和方法,就放在构造函数里面。
2、其中函数对象的一个属性就是原型对象 prototype。注:普通对象没有prototype,但有proto属性。
3、原型对象其实就是普通的对象;
function f1(){};
console.log(f1.prototype) // {constructor: ƒ}
console.log(typeof f1. prototype) //Object
prototype
每个函数都有一个 prototype 属性
function Person() {
}
// 虽然写在注释里,但是你要注意:
// prototype是函数才会有的属性
Person.prototype.name = 'Kevin';
var person1 = new Person();
var person2 = new Person();
console.log(person1.name) // Kevin
console.log(person2.name) // Kevin
函数的 prototype 属性指向了一个对象,这个对象正是调用该构造函数而创建的实例的原型,也就是这个例子中的 person1 和 person2 的原型。
每一个Javascript 对象(null除外)在创建的时候就会与之关联另一个对象,这个对象就是我们所说的原型,每一个对象都会从原型"继承"属性。
proto
这是每一个Javascript对象(除了 null )都具有的一个属性,叫proto,这个属性会指向该对象的原型。
上面 提到函数的prototype 指向实例的原型也就是: Person.prototype===person1.proto
function Person() { }
var person = new Person();
console.log(person.__proto__ === Person.prototype); // true
constructor
一个构造函数可以生成多个实例,但是原型指向构造函数倒是有的,这就要讲到第三个属性:constructor,每个原型都有一个 constructor 属性指向关联的构造函数。
function Person() { }
console.log(Person === Person.prototype.constructor); // true
prototype3.png
function Person() {
}
var person = new Person();
console.log(person.__proto__ == Person.prototype) // true
console.log(Person.prototype.constructor == Person) // true
// 顺便学习一个ES5的方法,可以获得对象的原型
console.log(Object.getPrototypeOf(person) === Person.prototype) // true
实例和原型
读取实例的属性时,如果找不到,就会查找与对象关联的原型中的属性,如果还查不到,就去找原型的原型,一直找到最顶层为止
function Person() {
}
Person.prototype.name = 'Kevin';
var person = new Person();
person.name = 'Daisy';
console.log(person.name) // Daisy
delete person.name;
console.log(person.name) // Kevin
- 给实例对象 person 添加了 name 属性;所以打印person.name时,从对象上读取name 属性,
- 删除了 person 的 name 属性后,读取 person.name,从 person 对象中找不到 name 属性。
- 对象中找不到,然后从person 的原型也就是 person._proto,也就是Person.prototype中查找,
原型的原型
prototype4.png image.png原型对象是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图
当我们一层一层向上查找,直到null ,
因为console.log(Object.prototype.proto === null) // true
所以 Object.prototype.__proto__ 的值为 null 跟 Object.prototype 没有原型,其实表达了一个意思。
所以查找到 Object.prototype 就可以停止查找了
js 继承:
原型链:
很多面试官 会问到 原型链的理解:
基本思想:一个引用类型继承另外一个引用类型的属性和方法;
-
每个实例对象(object)都有一个私有属性 (__proto__) 指向它构造函数的原型对象(prototype)
-
该原型对象也有一个自己的原型对象(__proto__),层层向上直到一个对象的原型对象为 null
。
function Person(){}
var person = new Person()
person.__proto__ == Person.prototype
Person.prototype.__proto__ == Object.prototype
Object.prototype.__proto__ // null
Person.__proto__ == Function.prototype
Function.prototype.__proto__ == Object.prototype
Object.prototype.__proto__ // null
假如我们让原型对象等于另外一个类型的实例此时:
1、原型对象包含一个指针指向另外一个对象原型;( 因为实例有个指向原型对象的内部指针
)
2、另外一个原型包含一个指向另外一个构造函数的指针。
3、层层递进,构成了实例和原型的链条。 所谓原型链的基本概念。
1、原型链继承
核心:A 原型的实例是B 原型的属性
优点:父类新增原型方法或者原型属性,子类都能访问
缺点:
1、无法实现多继承,
2、来自原型对象的引用属性和实例是所有子类共享的
3、 创建子类实例的时候,无法向父构造函数传参
function Parent(name) {
this.name=name;//属性
this.sleep=function () {//实例方法
console.log(this.name+'正在睡觉')
}
}
//原型方法 getSuperValue
Parent.prototype.getSuperValue=function (food) {
console.log(this.name+'正在吃'+food)
};
//原型链继承---核心:将父类的实例作为子类的原型
function Child() {
}
//继承Parent,即以Parent的实例为中介,使Child.prototype指向Parent的原型
// 本质是重写了原型对象,给Child 换了一个新的原型,这个新的原型不仅有作为Parent所拥有的全部的属性和方法,还有一个内部指针指向Parent 的原型。
Child.prototype=new Parent();
Child.prototype.getSubValue = function() { //添加新方法
return this.sub;
}
Child.prototype.getSuperValue = function() { // 重写超类中的方法
return this.sub;
}
2、构造函数继承:
解决原型中包含引用类型所带来的问题
在子类型构造函数的内部调用超类型的构造函数,函数不过时在特定环境下执行代码的对象,因为可以使用apply() 和 call() 方法
function Super(){
this.color=['red','blue','green'];
}
function Sub(){
// 继承了Super,实际是在将要创建的 Sub实例的环境下调用 Super构造函数。
Super.call(this);
}
var instancel1 = new Sub();
instancel1.color.push('block');
alert(instancel1.color) // 'red, blue, green, block '
var instancel2 = new Sub();
alert(instancel2.color) // 'red, blue, green '
在子类型构造函数中向超类型构造函数 传递参数。
优点:
1、可以向超类传递参数
2、解决了原型中包含引用类型值被所有实例共享的问题
缺点:
方法都在构造函数中定义,函数复用无从谈起,另外超类型原型中定义的方法对于子类型而言都是不可见的。
function Super(name){
this.name=name
}
function Sub(){
// 继承了Super, 传递参数
Super.call(this,'lyan');
this.age=25
}
var instancel1 = new Sub();
alert(instancel1.name) // 'lyan'
alert(instancel1.age) // 25
3、组合继承(原型链 + 借用构造函数):
WechatIMG538.png指的是将原型链和借用构造函数的技术组合在一起
使用原型链实现对原型属性和方法的继承,通过借用构造函数来实现对实例属性的继承,既通过在原型上定义方法来实现了函数复用,又保证了每个实例都有自己的属性
缺点:
无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部。
优点:
1、可以向超类传递参数
2、每个实例都有自己的属性
3、实现了函数复用
4、原型式继承:
借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
缺点:
同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
function object(o){
function F(){}
F.prototype = o
return new F()
}
5、寄生式继承:
WechatIMG540.png寄生式继承是与原型式继承紧密相关的一种思路。寄生式继承的思路与寄生构造函数和工厂模式类似,即创建一个仅用于封装继承过程的函数,该函数在内部已某种方式来增强对象,最后再像真地是它做了所有工作一样返回对象
- 基于 person 返回了一个新对象 -—— person2,新对象不仅具有 person 的所有属性和方法,而且还有自己的 sayHi() 方法。寄生式继承也是一种有用的模式
缺点:
1、使用寄生式继承来为对象添加函数,会由于不能做到函数复用而效率低下。
2、同原型链实现继承一样,包含引用类型值的属性会被所有实例共享。
6、寄生组合式继承:
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name); //第二次调用SuperType()
this.age = age;
}
SubType.prototype = new SuperType(); //第一次调用SuperType()
SubType.prototype.sayAge = function(){
alert(this.age);
}
- 在第一次调用SuperType构造函数时,SubType.prototype会得到两个属性: name和colors;
- 当调用SubType构造函数时,又会调用一次SuperType构造函数,这一次又在新对象上创建了实例属性name和colors。
组合继承有一个小bug,实现的时候调用了两次超类(父类),性能上不合格啊有木有!怎么解决呢?于是“寄生继承”就出来了。
“寄生组合继承”用了“寄生继承”修复了“组合继承”的小bug
- 寄生组合式继承就是为了解决这一问题
function inheritPrototype(subType, superType){
var clone = Object.create(superType.prototype); //创建对象 一个副本
clone.constructor = subType; //增强对象
subType.prototype = clone; //指定对象
}
function SuperType(name){
this.name = name;
this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
alert(this.name);
}
function SubType(name, age){
SuperType.call(this, name);
this.age = age;
}
inheritPrototype(SubType, SuperType)
SubType.prototype.sayAge = function(){
alert(this.age);
}
var instance = new SubType("Bob", 18);
instance.sayName(); // Bob
instance.sayAge(); // 18
不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。