js面向对象及原型继承学习笔记。

建立对象

虽然Object构造函数或对象字面量均可以用来建立单个对象,可是这些方式有明显的缺点:使用同一个接口建立不少对象,会产生大量的重复代码。为解决这个问题,人们开始使用工厂模式的一种变体。设计模式

工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了建立具体对象的过程。考虑到ECMAScript中没法建立类,开发人员就发明了一种函数,用函数来封装以特定接口建立对象的细节。数组

function createPerson(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName=function(){
        console.log(this.name)
    }
    return o;
}
var person1 = createPerson('Nicholas',29,'SoftWare');
var person2 = createPerson('gres',27,'Doctor');

函数cretePerson()可以根据接受的参数来构建一个包含全部必要信息的Person对象。能够无数次的调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了建立多个类似对象的问题,可是却没有解决对象识别问题。浏览器

构造函数模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.sayName = function(){
        console.log(this.name);
    }
}
var person1 = new Person('Nicholas',29,'Software');
var person2 = new Person('gres',24,'Docotor');

在这个例子中,Person()函数取代了createPerson()函数。不一样之处:app

1.没有显式的建立对象。
2.直接将属性和方法赋给了this对象。
3.没有return语句。

要建立Person实例必须使用new操做符。以这种方式调用构造函数实际上会经历如下4个步骤:函数

1.建立一个对象。
2.将构造函数的做用域赋值给新的对象(所以this指向了这个新对象)。
3.执行构造函数中的代码。(为这个新对象添加属性)。
4.返回新的对象。

上面的例子:person1和person2分别保存着Person的一个不一样的实例。 这两个对象都有一个constructor属性,该属性指向Person。this

person1.constructor == Person//true
person2.constructor == Person//true

person1 instanceof Object;//true
person1 instanceof Person;//true

建立自定义的构造函数意味着未来能够将它的实例标示为一种特定的类型;而这正是构造模式赛过工厂模式的地方。(以这种方式定义的构造函数是定义在Global对象(在浏览器是window对象)中的)。
构造函数与其它函数的惟一区别,就在于调用它们的方式不一样。不过,构造函数毕竟也是函数,不存在定义构造函数的特殊语法。任何函数,只要经过new操做符来调用,那他就能够做为构造函数;任何函数,若是不经过new运算符调用,那它就跟普通函数没有区别。
构造函数的问题:prototype

使用构造函数的主要问题就是每一个方法都要在每一个实例上从新建立一遍。在上面的例子中,person1和person2都有一个名为sayName()的方法,但那两个方法不是同一个Function的实例。在js中函数也是对象,所以每定义一个函数,也就是实例化了一个对象。
function Person(name, age){
    this.name = name;
    this.age = age;
    this.sayName = new Function(console.log(this.name))
}

从这个角度上看构造函数,更容易明白每一个Perosn实例都包含一个不一样的Function实例的本质。说明白些,以这种方式建立函数,会致使不一样的做用域链和标识符解析,可是建立Function新实例的机制仍然相同。所以不一样实例上的同名函数是不相等的。设计

console.log(person1.sayName==person2.sayName);//false.

然而,建立两个完成一样任务的Function实例没有必要;何况有this对象在,根本不用在执行代码前就把函数绑定到特定对象上面。所以,能够向下面这样,经过把函数定义转移到构造函数外部来解决这个问题。指针

function Person(name,age){
    this.name = name;
    this.age = age;
    this.sayName = sayName;
}
function sayName(){
    console.log(this.name)
}
var person1 = new Person('ll',24);
var person2 = new Person('kk',25);

原型模式

咱们建立的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含能够有特定类型的全部实例共享的属性和方法。code

function Person(){}
Person.prototype.name = 'll';
Person.prototype.age = 24;
Person.prototype.sayName=function(){
    console.log(this.name)
}
var person1 = new Person();
var person2 = new Person();

在此咱们将sayName()方法和属性直接添加到Person的prototype属性中,构造函数变成了空函数。即便如此,也仍然能够经过调用构造函数来建立新的对象,并且新的对象还会具备相同的属性和方法。但与构造函数模式不一样的是,新对象的属性和方法是由全部实例共享的。
理解原型对象:

不管何时,只要建立了一个新函数,就会根据必定的规则为该函数建立一个prototype属性,这个属性指向函数的原型对象。在默认状况下,全部原型对象都会自动得到一个constructor属性,这个属性是一个指向prototype属性所在函数的指针。Person.prototype.constructor指向Person。而经过这个构造函数,咱们还能够继续为原型对象建立其它属性和方法。
建立了自定义构造函数以后,其原型对象默认只会获得constructor属性;至于其它属性和方法都是从Object对象继承而来的。当调用构造函数的一个新实例后,该实例内部将包含一个指针,指向构造函数的原型对象。(__proto__);person1和person2都包含一个内部属性,该属性仅仅指向了Person.prototype,和构造函数没有直接的关系。
每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具备给定名字的属性,搜索首先从对象实例自己开始,若是在实例中找到了具备给定名字的属性,则返回该属性的值;若是没有找到,则继续搜索指针指向的原型对象,若是在原型对象中找到了该属性,则返回该属性的值。
function Person(){}
Person.prototype.name = 'll';
Person.prototype.age = 24;
Person.prototype.sayName=function(){
    console.log(this.name)
}
var person1 = new Person();
var person2 = new Person();
person1.name = 'kk';
console.log(person1.name)//kk-来自实例
console.log(person2.name)//ll来自原型

当对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性。

原型模式的缺点:

原型中全部属性是被不少实例共享的,这种共享对于函数很是合适。对于那些包含基本值的属性也说的过去,经过在实例上添加一个同名属性能够隐藏原型中对应的属性。然而对于包含引用类型值的属性来讲,问题就比较突出了:
function Person(){}
Person.prototype={
    constructor:Person,
    name:'kk',
    age:24,
    friends:['ll','jj'],
    sayName:function(){
        console.log(this.name);
    }
}
var person1 = new Person();
var person2 = new Person();
person1.friends.push('aa');
console.log(person1.friends);//ll jj aa
console.log(person2.friends);//ll jj aa
console.log(person1.friends==person2.friends);//true

修改person1.friends引用的数组,person2一样会修改。

组合使用构造函数和原型模式

建立自定义类型的最多见方式,就是组合使用构造函数和原型模式。构造函数模式用于定义实例属性,原型模式用于定义共享的属性和方法。另外这种模式还支持向构造函数穿参数。

function Person(name,age){
    this.name = name;
    this.age = age;
    this.friends=['kk','ll'];
}
Person.prototype={
    constructor:Person,
    sayName:function(){
        console.log(this.name)
    }
}
var person1 = new Person('nnn',24);
var person2 = new Person('mmm',29);
person1.friends.push('aaa');
console.log(person1.friends);//kk ll aa
console.log(person2.friends);//kk ll 
console.log(person1.friends==person2.friends);//false
console.log(person1.sayName==person2.sayName);//true

寄生构造函数模式

function Person(name,age,job){
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName=function(){
        console.log(this.name);
    }
    return o;
}
var friend = new Person('kk',24,'software');

在这个实例中,Person函数建立了一个新对象,并以相应的属性和方法初始化该对象,而后又返回了这个对象。除了使用new操做符并把使用的包装函数叫作构造函数以外,这与工厂模式没有什么区别。构造函数在不返回值的状况下,默认会返回新对象实例。而经过在构造函数末尾添加一个return语句,能够重写调用构造函数时返回的值。
这个模式能够在特殊的状况下用来为对象建立构造函数。假设咱们想建立一个具备额外方法的特殊数组。因为不能直接修改Array的构造函数,所以可使用这个模式:

function SpecialArray(){
    console.log(this)
    var values = new Array();
    values.push.apply(values,arguments);
    values.toPipedString=function(){
        return this.join('|')
    }
    return values;
}
var colors = new SpecialArray('red','blue','pink');
console.log(colors.toPipedString())

关于寄生构造函数模式,返回的对象与构造函数或则构造函数的原型属性之间没有关系。

稳妥构造函数模式

所谓稳妥对象,指的是没有公共属性,而其方法也不引用this的对象。稳妥构造函数模式与寄生构造函数模式相似,可是有两点不一样,一是建立对象的实例方法不引用this,二是不使用new操做符调用构造函数。

function Person(name,age,job){
    //建立要返回的对象
    var o = new Object();
    //能够在这里定义私有变量和属性
    //添加方法
    o.sayName=function(){
        console.log(name);
    }
    //返回对象
    return o;
}
var friend = Person('kk',24,'software');
friend.sayName();//kk

继承

每一个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。那么,假如咱们让原型对象等于另外一个类型的实例,此时的原型对象将包含一个指向另外一个原型的指针,相应的,另外一个原型中也包含着一个指向另外一个构造函数的指针。

function SuperType(){
    this.property = true;
}
SuperType.prototype.getSuperValue = function(){
    return this.property;
}
function SubType(){
    this.subProperty = true;
}
SubType.prototype = new SuperType();
console.log(SubType.prototype)
SubType.prototype.getSubValue = function(){
    return this.subProperty;
}
var instance = new SubType();
console.log(instance)

以上代码定义了两个类型:SuperType和SubType。每一个类型分别有一个属性和一个方法。它们的主要区别在于SubType继承了SuperType,而继承是经过建立SuperType的实例,并将该实例赋值给SubType.prototype实现的。实现的本质是重写原型对象,代之以一个新类型的实例。
在上面的代码中,咱们没有使用Subtype默认提供的原型,而是给它换了一个新的原型:这个新的原型就是SuperType的实例。因而新原型不只具备SuperType的实例所拥有的所有属性和方法,并且其内部还有一个指针,指向了SuperType的原型。
原型链的问题:

1.引用类型值的原型属性会被全部实例共享。
2.在建立子类型的实例时,不能向超类型的构造函数中传递参数。

借用构造函数

在解决原型中包含引用类型值所带来问题的过程当中,开发人员开始使用一种叫作借用构造函数的技术。这种技术的思想至关简单,即在子类构造函数的内部调用超类型构造函数。

function SuperType(){
    this.colors = ['red','blue','pink']
}
function SubType(){
    //继承了SuperType
    SuperType.call(this);
}
var instance = new SubType();
instance.colors.push('black');
console.log(instance.colors);//red blue pink black
var instance2 = new SubType();
console.log(instance2.colors);//red blue pink

同时,借用构造函数有一个很大的优点,便可以在子类构造函数中向超类型构造函数传递参数。

function SuperType(name){
    this.colors = ['red','blue','pink'];
    this.name = name;
}
function SubType(){
    //继承了SuperType
    SuperType.call(this,'kk');
}
var instance = new SubType();
console.log(instance.name);//kk

借用构造函数的问题:

方法都须要在构造函数中定义,没法复用。

组合继承

function SuperType(name){
    this.name = name;
    this.colors=['red','blue','pink']
}
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.constructor = SubType;
SubType.prototype.sayAge=function(){
    console.log(this.age);
}
var instance1 = new SubType('kk',24);
instance1.colors.push('black');
console.log(instance1.colors);//red blue pink black
instance1.sayName();//kk
instance1.sayAge();//24

var instance2 = new SubType('ll',26);
console.log(instance2.colors);//red blue pink
instance2.sayAge();//26
instance2.sayName();//ll

组合继承避免了原型链和借用构造函数的缺陷,融合了它们的优势。

原型式继承

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}

在object()函数内部,先建立了一个临时性的构造函数,而后将传入的对象做为这个构造函数的原型,而后返回了这个临时类型的一个实例。从本质上讲object()对传入中的对象进行了浅复制。

var Person={
    name:'kk',
    friends:['ll','aa','cc']
}
var instance1 = object(Person);
instance1.name = 'Greg';
instance1.friends.push('dd');
console.log(instance1)

var instance2 = object(Person);
instance2.name = 'Linda';
instance2.friends.push('oo');
console.log(instance2)

obejct()会返回一个新对象,这个对象将Person做为原型,因此它的原型中包含一个基本类型值属性和一个引用类型值属性。这就意味着friends不只是Person的,也同时被instance1和instance2共享。

寄生式继承

寄生式继承思路和寄生式构造函数和工厂模式相似,即建立一个仅用于封装继承过程的函数,该函数在内部以某种方式来加强对象,最后返回对象。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}

function createAnother(original){
    var clone = object(original);
    clone.sayName=function(){
        console.log(this.name)
    };
    return clone;
}
var Person={
    name:'ll',
    friens:['kk','cc','aaa']
}
var instance = createAnother(Person);
console.log(instance)
instance.sayName();

寄生组合式继承

组合继承是js最经常使用的继承模式;不过,他也有本身的不足。组合继承最大的问题就是不管什么状况下,都会调用两次超类型构造函数:一次是在建立子类型原型的时候,另外一次是在子类型构造函数内部。

function SuperType(name){
    this.name = name;
    this.colors=['red','blue','pink'];
}
SuperType.prototype.sayName=function(){
    console.log(this.name)
}
function SubType(name,age){
    SuperType.call(this,name);//第二次调用SuperType()
    this.age = age;
}
SubType.prototype = new SuperType()//第一次调用SuperType()
SubType.prototype.constructor = SubType;
SubType.prototype.sayAge=function(){
    console.log(this.age);
}

所谓寄生式组合继承,即经过借用构造函数来继承属性,经过原型链的混成形式来继承方法。其背后的基本思路:没必要为了指定子类型的原型而调用超类型的构造函数,咱们须要的无非是超类型原型的一个副本而已。本质上,就是使用寄生式继承来继承超类型的原型,而后将结果指定给子类型的原型。

function object(o){
    function F(){};
    F.prototype = o;
    return new F();
}
function inheritPrototype(subType,superType){
    var prototype = object(superType.prototype);//建立对象;
    prototype.constructor = subType;//加强对象
    subType.prototype = prototype;//指定对象
}
function SuperType(name){
    this.name = name;
    this.colors = ['red','blue','pink'];
}
SuperType.prototype.sayName=function(){
    console.log(this.name);
}
function SubType(name,age){
    SuperType.call(this,name);
    this.age = age;
}
var aaa = inheritPrototype(SubType,SuperType);
SubType.prototype.sayAge = function(){
    console.log(this.age);
}
var instance = new SubType('kk',24);
console.log(instance)

小结

在没有类的状况下,能够采用下列模式建立对象。
1.工厂模式,使用简单的函数建立对象,为对象添加属性和方法,而后返回对象。这个模式后来被构造函数模式所替代。
2.构造函数模式,能够建立自定义引用类型,能够像建立内置对象实例同样使用new操做符。不过,构造函数也有缺点,即他的每一个成员都没法获得复用,包括函数。
3.原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式,使用构造函数定义实例的属性,而使用原型定义共享的属性和方法。

JavaScript主要经过原型链实现继承。原型链的构建是经过将一个类型的实例赋值给另外一个构造函数的原型来实现的。这样,子类型就可以访问超类型的全部属性和方法,这一点基于类的继承很类似。原型链的问题是对象实例共享全部属性和方法,所以不宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数内部调用超类型构造函数。这样就能够作到每一个实例都具备本身的属性,同时还能保证只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而经过借用构造函数来继承实例属性。1.原型式继承,能够在没必要预先定义构造函数的状况下实现继承,其本质是执行对给定对象的浅复制。而复制获得的副本还能够获得进一步的改造。2.寄生式继承,与原型式继承很是类似,也是基于某个对象或某些信息建立一个对象,而后加强对象,最后返回对象。为了解决组合继承模式因为屡次调用超类型构造函数而致使的低效率问题,能够将这个模式和组合继承一块儿使用。3.寄生组合式继承,集寄生式继承和组合继承的优势与一身,是实现基于类型继承的最有效方法。

相关文章
相关标签/搜索