JavaScript原型和继承

原型prototype

每个函数中都有一个叫prototype的属性,prototype指向函数的原型对象,每个原型对象都有一个constructor属性,包含原型对象所在函数的指针。

原型链

每个构造函数都包含一个原型对象,(每个原型对象都包含一个指向构造函数的指针),每个实例都包含指向原型对象的指针,若让原型对象等于另一个类型的实例,则原型对象将包含一个指向另一个原型的指针,若另一个原型又是另一个类型的实例,则原型和实例的这种关系依然存在,就是原型链。

__proto__

__proto__是一个对象拥有的内置属性(请注意:prototype是函数的内置属性,proto是对象的内置属性),是JS内部使用寻找原型链的属性。

Object.prototype.__proto__ === null;//true
Object.constructor === Function;
Object.constructor === Function.constructor;
Object.constructor === Function.prototype.constructor

用chrome和FF都可以访问到对象的proto属性,IE不可以。

new 的过程

var Person = function(){};
var p = new Person();
  • var p={}; 也就是说,初始化一个对象p
  • p.__proto__ = Person.prototype;
  • Person.call(p); 也就是说构造p,也可以称之为初始化p
  • 若上一步中Person有Object类型的返回值o,则返回o,否则返回p。

js只有函数对象具备类的概念,要创建对象,必须使用函数对象,函数对象内部的[[constructor]]用于构造对象,[[call]]用于调用对象。

  • var obj = new Object()使用内置的Object函数实例化对象。
  • var obj = {}或者var obj = []使用js引擎触发Object和Array的构造过程。
  • function fn() {};var obj = new fn{}使用用户自定义类型实例化对象。

new创建的对象和函数的prototype对象之间存在继承关系

function A(){
    this.a = 'a in A func';
};
A.prototype.b = 'b in A prototype';
var i = new A();

i.b;//'b in A prototype';
i instanceof A;//true;

A.prototype = {};
i.b;//'b in A prototype';
i instanceof A;//false;
i.__proto__;
// {b: "b in A prototype", constructor: ƒ}

从上面代码看出,将A的prototype置为{}空对象后,i instanceof A;返回false,可见继承关系是通过prototype实现的。

由于new创建对象时将构造函数的prototype属性赋值给实例的__proto__属性,因此__proto__属性和prototype指向同一引用,如果将prototype同对象字面量方式赋值为其他对象的话,则切断了__proto__prototype之间的联系,因为prototype和其最初的引用之间没有关系了,这里考虑引用类型的存储即可明白,__proto__依旧指向之前prototype的引用,所以__proto__属性上存在的属性不会消失。

Aprototype置为{}空对象后对A的实例i来说,i的隐式__proto__属性并未改变,即i被实例化后__proto__不受prototype的影响,相当于实例化后,通过父类prototype继承来的属性和方法都会保存在自身的__proto__属性上,与父类没有关系,因此也不能通过实例更改父类原型上的方法,这里的前提都是A.prototype = {}。如下:

i.b = 123;//查找属性时,实例属性优先
//123
i.__proto__.b
//"b in A prototype"

Object,Function,Number,String,等的构造器constructor都是Function,实例对象的构造器会从原型链上继承自父类。

Number.constructor === Function;//true
Number.__proto__ === Function.prototype;//true

Function是最顶层的构造器,他具有自举性,

typeof Function.prototype;//"function"
Function.constructor === Function;//true
Function.__proto__ === Function.prototype;//true

所有函数的__proto__属性都指向Function.prototype,Functionproto也指向Function.prototype,

function A(){}
A.__proto__ === Function.prototype
//true
Function.__proto__ === Function.prototype;//true

继承

1. 原型链继承:

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){}
SubType.prototype = new SuperType();

原型链继承的缺点:若原型中包含引用类型的变量,则原型链继承会导致此引用变量被共享,因此可能会无意中被修改

2.借用构造函数继承

function SuperType(){
    this.colors = ["red", "blue", "green"];
}
function SubType(){
    SuperType.call(this)
}

原型链上的属性是共享的,而构造函数中的属性是每个实例自己的,因此,借用父类的构造函数后即可解决原型链继承的缺陷。

借用构造函数继承缺点:方法都在构造函数中定义,复用无从谈起。

3.组合继承

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
};
function SubType(name, age){ 
    SuperType.call(this, name);
    this.age = age;
}
SubType.prototype = new SuperType();
SubType.prototype.sayAge = function(){
    console.log(this.age);
};
var son = new SubType();

组合继承最常用,原型链继承共享属性和方法,借用构造函数继承实例属性

以上3种继承方式都必须使用new关键字定义子对象

3.原型式继承

  function object(o) {
        function F() {}
        F.prototype = o;
        return new F();
    }
    var obj = {friends:[1,2,3]}
    var another = object(obj);
    another.friends.push(4);
    console.log(obj.friends);//1,2,3,4

若传入的参数o中包含引用类型,则返回的对象可修改该引用类型,ES5规范了原型式继承,Object.create(baseobject,{name:'\**'}),第二个参数可修改baseobject的属性,也可以定义额外属性。

4. 寄生式继承


 function createAnother(original) {
        var clone = object(original);
        clone.sayHi = function () {}
        return clone;
    }
    var another = createAnother(original);
    another.sayHi();

寄生式继承通过将original赋值给空构造函数F的原型后,返回F的实例, 再在F的实例上添加方法,最后返回F的实例。

5.寄生组合式继承

由于组合继承中会调用两个SuperType构造函数

SubType.prototype = new SuperType();//SubType原型中保留name和colors
new SubType('zy','13');//实例中保留name和colors和age,因此有两组name和colors。

寄生组合式继承(最佳):

function inheritePrototy(subType,superType) {
       var  p = object(superType.prototype);
       p.constructor = subType;
       subType.prototype = p;  
 }

举例:

function SuperType(name){
    this.name = name;
    this.colors = ["red", "blue", "green"];
}
SuperType.prototype.sayName = function(){
    console.log(this.name);
};
function SubType(name, age){ 
    SuperType.call(this, name);
    this.age = age;
}
inheritePrototy(SuType,SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
};
  • 还是利用借用构造函数继承父类构造函数中的属性:SuperType.call(this);这一步将SuperType构造函数上的实例属性继承下来。
  • 父类原型上的方法不在通过subType.prototype = new SuperType();实现,而是直接将subType.prototype = (new SuperType()).__proto__;,避免了通过new创建对象时将SuperType构造函数上的实例属性传递到subType.prototype上去。

题目

function Mike(){this.sayMike=function(){};}

function Tom(){};
Tom.prototype=new Mike();
Tom.prototype.sayTom=function(){};

var tom = new Tom();

则:

tom.__proto__=== Tom.prototype;

以上因为tomTOM的实例,因此tom.__proto__就是Tom.prototype;

tom.__proto__.__proto__ === Mike.prototype;

以上因为Tom.prototypeMike的实例,因此:

Tom.prototype.__proto__ === Mike.prototype;
tom.__proto__.__proto__ === Mike.prototype;

tom.__proto__.__proto__.__proto__ === Object.prototype;

以上因为Mike.prototype并未赋任何值,所以他是个空对象,因此:

Mike.prototype.__proto__ === Object.prototype;
tom.__proto__.__proto__.__proto__ === Object.prototype;

tom.__proto__.__proto__.__proto__.__proto__ === null;
以上因为Object.prototype.__proto__ === null;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值