原型和原型链以及继承

创建对象

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中查找,

原型的原型

原型对象是通过 Object 构造函数生成的,结合之前所讲,实例的 proto 指向构造函数的 prototype ,所以我们再更新下关系图

prototype4.png
image.png

当我们一层一层向上查找,直到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

不必为了指定子类型的原型而调用超类型的构造函数,我们需要的无非就是超类型的一个副本而已。
本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型。

你可能感兴趣的:(原型和原型链以及继承)