JS 面向对象的程序设计

  • 面向对象的程序设计有个标志。即他们都有类的概念,而通过类可以创建任意多个具有相同属性和方法的对象
  • 每个对象都是基于一个引用类型创建的,这个引用类型可以是原生类型,也可以是开发人员定义的类型

一  理解对象

1.1属性类型

ES中有两种属性:数据属性访问器属性

1.数据属性:数据属性包含一个数据值的位置,在这个位置可以读取和写入值。

  • 数据属性有四个描述其行为的属性:
  1. Configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性。默认值为true
  2. Enumerable:表示能否通过for-in循环返回属性
  3. Writable:表示能否修改属性的值。默认值为true
  4. Value:包含这个属性的数据值。默认值为undefined

Object.defineProperty()方法:修改属性默认的特性。

  • 接收三个参数:属性所在的对象,属性的名字,一个描述符对象(描述符对象的属性configurable,enumerable,writable,value
  • Object.defineProperty()方法创建一个属性时,configurable,enumerable,writable特性默认值为false
var person={};
Object.defineProperty(person,"name",{
    writable:false,
    value:"Nicholas"
});
alert(person.name);   //Nicholas
person.name="Greg";
alert(person.name);   //Nicholas
//configurable设置为false,则不能从对象中删除属性
var person={};
Object.defineProperty(person,"name",{
    configurable:false,
    value:"Nicholas"
});
alert(person.name);    //Nicholas
delete person.name;
alert(person.name)    //Nicholas
//一旦把属性定义为不可配置,就不能再变回可配置。此时在调用Object.defineProperty()方法修改除writable之外的特性,都会报错
var person={};
Object.defineProperty(person,"name",{
    configurable:false,
    value:"Nicholas"
})
Object.defineProperty(person,"name",{
    configurable:true,   //报错
    value:"Nicholas"
})

2.访问器属性

  • 访问器属性不包含数据值。它包含一对getter和setter函数
  • 在读取访问器属性时,会调用getter函数,函数负责返回有效的值
  • 在写入访问器属性时,会调用setter函数,函数负责决定如何处理数据

特性:

  • Configurable:表示能否通过delete删除属性从而重新定义属性,能否修改属性的特性,或能否把属性修改为访问器属性。默认值为true
  • Enumerable:表示能否通过for-in循环返回属性
  • Get:在读取属性时调用的函数。默认值为undefined
  • Set:在写入属性时调用的函数。默认值为undefined 访问器属性不能直接定义,必须适用Object.d
var book={
    _year:2004,
    edition:1
}
Object.defineProperty(book,"year",{
    get:function(){
        return this._year;
    },
    set:function(newValue){
        if(newValue>2004){
            this._year=newValue;
            this.edition+=newValue-2004;
        }
    }
});
book.year=2005;
alert(book.edition);   //2
  • 不一定同时指定getter和setter。只指定getter意味着属性不能写,尝试写入属性会被忽略。只指定stter函数不能读efineProperty()定义

1.2定义多个属性

Object.defineProperties()方法:这个方法可以通过描述符一次定义多个属性

接收两个对象参数:要添加和修改其属性的对象,第二个对象的属性与第一个对象中要添加或修改的属性一一对应

var book={};
Object.defineProperties(book,{
    _year:{             //定义了一个数据属性
        writable:true,
        value:2004
    },
    year:{               //定义了一个访问器属性
        get:function(){
            return this._year;
        },
        set:function(){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
})

1.3读取属性的特性

Object.getOwnPropertyDescriptor()方法:可以取得给定属性的描述符。

两个参数:属性所在的对象,要读取其描述符的属性名称。

返回值:一个对象。

  • 如果是访问器属性,这个对象的属性有configurable,writable,set,get
  • 如果是数据属性,这个对象的属性有configurable,writable,enumerable,value
var book={};
Object.defineProperties(book,{
    _year:{
        value:2004
    },
    edition:{
        value:1
    },
    year:{
        get:function(){
            return this._year;
        },
        set:function(){
            if(newValue>2004){
                this._year=newValue;
                this.edition+=newValue-2004;
            }
        }
    }
})
var descriptor=Object.getOwnPropertyDescriptor(book,"_year");
alert(descriptor.value);        //2004
alert(descriptor.configurable);       //默认为false
alert(typeof descriptor.get());   //undefined

var descriptor=Object.getOwnPropertyDescriptor(book,"year");
alert(descriptor.value);   //undefined
alert(descriptor.enumerable);    //false
alert(typeof descriptor.get);    //function

二 创建对象

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

2.1工厂模式

  • 工厂模式抽象了创建具体对象的过程。因为在ES中无法创建类,开发人员发明了一种函数,用函数来封装以特定接口创建对象的细节
function createPerson(name,age,job){
    var o=new Object();
    o.name=name;
    o.job=job;
    o.age=age;
    o.sayName=function(){
        alert(this.name);
    }
    return o;
}
var person1=createPerson("Nicholas",29,"software engineer");
var person2=createPerson("greg",27,"doctor");
  • 工厂模式虽然解决了创建多个相似对象的问题,却没有解决对象识别的问题(怎样知道一个对象的类型)。构造函数模式出现了

2.2 构造函数模式

  • ES中的构造函数可用来创建特定类型的对象。如Object,Array这样的原生构造函数。此外也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=function(){
        alert(this.name);
    }
}
var person1=new Person("Nicholas",29,"software engineer");
var person2=new Person("Greg",27,"doctor");
  • Person()函数与createPerson()不同之处:
  1. 没有显示创建对象
  2. 直接将属性和方法赋给了this对象
  3. 没有return语句
  • 创建Person的新实例,必须使用new操作符。这种方法调用构造函数会经历4个步骤:
  1. 创建一个新对象
  2. 将构造函数的作用域赋给新对象(使得this指向新对象)
  3. 执行构造函数中的代码
  4. 返回新对象
  • person1和person2都有一个constructor(构造函数)属性,该属性指向Person
alert(person1.constructor==Person);    //true
alert(person2.constructor==Person);    //true
  • 以上创建的对象即是Object的实例,也是Person的实例
alert(person1 instanceof Object);   //true
alert(person2 instanceof Object);   //true
alert(person1 instanceof Person);   //true
alert(person2 instanceof Person);   //true
  • 创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,这正是胜过工厂模式的地方。
  • 两个对象之所以是Object和Person的实例,是因为所有对象均继承自Object

1.将构造函数当作函数

任何函数,只要通过new操作符来调用,那它就可以作为构造函数。不通过new操作符调用,就是普通函数

//通过构造函数使用
var person=new Person("Nicholas",29,"software engineer");
person.sayName();   //"Nicholas"
//作为普通函数使用
Person("Greg",27,"doctor");    //添加到window
window.sayName();   //"Greg"
//在另一个对象的作用域中调用
var o=new Object();
Person.call(o,"john",25,"nurse");
o.sayName();   //"john"

2.构造函数的问题

主要问题:每个方法都要在每个实例上重新创建一遍

解决方案:把函数定义转移到构造函数外

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.sayName=sayName;
}
function sayName(){
    alert(this.name);
}
var person1=new Person("Nicholas",29,"software engineer");
var person2=new Person("greg",27,"doctor");

解决了这个问题后又出现了一些新问题,新问题可以通过原型模式解决

2.3原型模式

  • 我们创建的每个函数都有一个prototype(原型)属性,这个属性是个指针,指向一个对象。这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。
  • prototype就是通过调用构造函数而创建的那个对象实例的原型对象

使用原型对象的好处:

  • 不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
person1.sayName();   //"Nicholas"

var person2=new Person();
person2.sayName();   //"Nicholas"

alert(person1.sayName==person2.sayName);  //true
  • 与构造函数模式不同的是,新对象的这些属性和方法是由所有实例共享的

1.理解原型对象

  • 只要创建了一个新函数,就会为该函数创建一个prototype属性,这个属性指向函数的原型对象。
  • 默认,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针
  • 创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性。其他方法是从Object继承而来。当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性。[[Prototype]]),指向构造函数的原型对象。

虽然在所有实现中都无法访问到[[prototype]],但可以通过 isPrototypeOf() 方法确定对象之间是否存在这种关系

alert(Person.prototype.isPrototypeOf(person1));  //true
alert(Person.prototype.isPrototypeOf(person2));  //true
//因为他们内部都有一个指向Person.prototype的指针,因此都返回true

Object.getPrototype()方法:返回[[prototype]]的值

alert(Object.getPrototypeOf(person1)==Person.prototype);  //true
alert(Object.getPrototypeOf(person1).name);      //Nicholas
  • 可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值
  • 如果在实例中添加一个属性,该属性名与实例原型中的一个属性名相同,那实例中就会创建该属性,该属性会屏蔽原型中的那个属性
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();

person1.name="Greg";
alert(person1.name);    //"Greg"  来自实例
alert(person2.name);    //"Nicholas"  来自原型
  • 使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();
person1.name="Greg";
alert(person1.name);    //Greg  来自实例
alert(person2.name);    //Nicholas   来自原型
delete person1.name;
alert(person1.name)    //Nicholas    来自原型
hasOwnProperty()方法(从Object继承而来):可以检测一个属性是存在于实例中,还是存在于原型中。只有给定属性存在于对象实例中时,才会返回true
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();
alert(person1.hasOwnProperty("name"));   //false  来自原型

person1.name="Greg";
alert(person1.name);    //Greg  来自实例
alert(person1.hasOwnProperty("name"));    //true

alert(person2.name);    //Nicholas  来自原型
alert(person2.hasOwnProperty("name"));    //false

delete person1.name;
alert(person1.name);   //Nicholas
alert(person1.hasOwnProperty("name"));   //false

2.原型与in操作符

两种方式使用in操作符

  • 单独使用:in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在于实例或原型
function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var person1=new Person();
var person2=new Person();
alert(person1.hasOwnProperty("name"));   //false  来自原型
alert("name" in person1);  //true

person1.name="Greg";
alert(person1.name);    //Greg  来自实例
alert(person1.hasOwnProperty("name"));    //true
alert("name" in person1);  //true

alert(person2.name);    //Nicholas  来自原型
alert(person2.hasOwnProperty("name"));    //false
alert("name" in person2);  //true

delete person1.name;
alert(person1.name);   //Nicholas
alert(person1.hasOwnProperty("name"));   //false
alert("name" in person2);  //true

同时使用hasOwnProperty()方法和in操作符,就可以确定该属性是存在于对象还是原型。

function hasPrototypeProperty(object,name){
    return !object.hasOwnProperty(name)&&(name in object);
}
  • 使用for-in循环。返回所有能够通过对象访问的,可枚举的属性。即包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性的实例属性也会返回
var o={
    toString:function(){      //该方法屏蔽了原型中的toString()方法(不可枚举)
        return "my object"
    }
};
for(var prop in o){
    if(prop=="toString"){
        alert("Found toString");
    }
}

Object.keys()方法:取得对象上所有可枚举的实例属性。接收一个对象为参数,返回一个包含所有可枚举属性的字符串数组

function Person(){
}
Person.prototype.name="Nicholas";
Person.prototype.age=29;
Person.prototype.job="software engineer";
Person.prototype.sayName=function(){
    alert(this.name);
}
var keys=Object.keys(Person.prototype);
alert(keys);      //"name,age,job,sayName"

var p1=new Person();
p1.name="Rob";
p1.age=31;
var p1keys=Object.keys(p1);
alert(p1keys);    //"name,age"

Object.getOwnPropertyNames()方法:获得所有实例属性,无论是否可枚举

var keys=Object.getOwnPropertyNames(Person.prototype);
alert(keys);     //"constructor,name,age,job,sayName"
    //结果包含不可枚举的constructor属性

3.更简单的原型语法

为减少不必要的输入,从视觉上更好的封装原型的功能,可以用一个包含所有属性和方法的对象字面量来重写整个原型对象

function Person(){
}

Person.prototype={
    name:"Nicholas",
    age:29,
    job:"software engineer",
    sayName:function(){
        alert(this.name);
    }
};

最终结果相同。但constructor属性不在指向Person,而是指向Object构造函数。为解决这个问题可以特意将它设置为适当的值

function Person(){
}

Person.prototype={
    constructor:Person,    //重设该属性会导致[[Enumerable]]特性被设置为true
    name:"Nicholas",
    age:29,
    job:"software engineer",
    sayName:function(){
        alert(this.name);
    }
};

4.原型的动态性

由于在原型中查找值得过程是一次搜索,因此我们对原型对象做的任何修改都能够立即从实例上反映出来。即使先创建了实例后修改原型也是如此

var friend=new Person();
Person.prototype.sayHi=function(){
    alert("hi");
}
friend.sayHi();     //"hi"

即使friend实例在添加新方法前创建,但它仍可以访问新方法。

原因:实例和原型间的松散连接关系。调用friend.sayHi()时,首先会在实例中搜索,搜索不到会继续搜索原型。因为实例与原型之间的连接是个指针,不是副本

如果重写整个原型对象,那就不一样惹。

  • 调用构造函数时会为实例添加一个指向最初原型的[[prototype]]指针,而把原型修改为另外一个对象就等于切断构造函数与最初原型之间的联系。 实例中的指针仅指向原型,而不指向构造函数
function Person(){
}
var friend=new Person();
Person.prototype={
    constructor:Person,
    name:"Nicholas",
    age:29,
    job:"software engineer",
    sayName:function(){
        alert(this.name);
    }
};
friend.sayName();    //error

5.原生对象的类型

  • 原型模式的重要性不仅体现在创建自定义类型方面,所有原生的引用类型也都是采用这种模式创建的。
  • 所有原生的引用类型(Object,Array,String)都在其构造函数的原型上定义了方法。如Array.prototype中可以找到sort()方法,而在String.prototype中可以找到substring()方法
alert(typeof Array.prototype.sort);   //"function"
alert(typeof String.prototype.substring)    //"function"
  • 通过原生对象的原型,不仅可以取得所有默认方法的引用,也可以定义新方法
String.prototype.startsWith=function(text){
    return this.indexOf(text)==0;
};
var msg="hello world";
alert(msg.startsWith("hello"));   //true

6.原型对象的问题

对于包含引用类型值的属性,就有些问题

function Person(){
}
Person.prototype={
    name:"Nicholas",
    age:29,
    job:"software engineer",
    friends:["Shelby","Court"],
    sayName:function(){
        alert(this.name);
    }
};
var person1=new Person();
var person2=new Person();

person1.friends.push("Van");

alert(person1.friends);   //"Shelby","Court","Van"
alert(person2.friends);   //"Shelby","Court","Van"
alert(person1.friends===person2.friends);   //true
//    由于friends数组存在于Person.prototype而非person1中,所以修改也会通过person2.friends反应出来(与person1.friends指向同一数组)

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

  1. 创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。
  2. 构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。 结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。
  3. 这种混成模式还支持向构造函数传递参数
function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    this.friends=["Shelby","Court"];
}
Person.prototype={
    constructor:Person,
    sayName:function(){
        alert(this.name);
    }
}

var person1=new Person("Nicholas",29,"software engineer");
var person2=new Person("Greg",27,"doctor");

person1.friends.push("Van");
alert(person1.friends);    //"Shelby","Court","Van"
alert(person2.friends);    //"Shelby","Court"
alert(person1.friends===person2.friends);    //false
alert(person1.sayName===person2.sayName);     //true

2.5 动态原型模式

动态原型模式:把所有信息都封装在构造函数中,而通过在构造函数中初始化原型。又保持了同时使用构造函数和原型的优点

可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型

function Person(name,age,job){
    this.name=name;
    this.age=age;
    this.job=job;
    //方法
    if(typeof this.sayName!="function"){        
//初次调用构造函数会执行。此后,原型以及完成初始化,能够立即在所有实例中得到反映
        Person.prototype.sayName=function(){
            alert(this.name);
        }
    }
}

var friend=new Person("Nicholas",29,"software engineer");
friend.sayName();

2.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("Nicholas",29,"software engineer");
friend.sayName();   //"Nicholas"
//构造函数在不返回值得情况下,默认返回新对象实例。
  • 这个模式可以在特殊情况下为对象创建构造函数
function SpecialArray(){
    var values=new Array();
    values.push.apply(values,arguments);
    //添加方法
    values.toPipedString=function(){
        return this.join("|");
    }
    return values;
}

var colors=new SpecialArray("red","blue","green");
alert(colors.toPipedString());    //"red|blue|green"

注意: 返回的对象与构造函数或与构造函数的原型属性没有关系。因此,不能依赖instanceof操作符来确定对象类型

2.7 稳妥构造函数模式

稳妥对象:所谓稳妥对象,指的是没有公共属性,且其方法也不引用this的对象。

稳妥构造函数遵循与寄生构造函数类似的模式。但有两点不同

  1. 新创建对象的实例方法不引用this
  2. 不使用new操作符调用构造函数
function Person(){
    var o=new Object();
    o.sayName=function(){
        alert(name);
    }
    return o;
}
var friend=Person("Nicholas",29,"software engineer");
friend.sayName();   //"Nicholas"
//变量friend中保存的是个稳妥对象,除了调用sayName()方法外,没有别的方式可以访问其数据成员

三 继承

许多OO语言都支持两种继承方法接口继承实现继承

接口继承只继承方法签名,实现继承则继承实际的方法。由于函数没有签名,因此在ES中无法实现接口继承,只支持实现继承,且其实现继承主要是依靠原型链来实现的

3.1原型链

ES中描述了原型链的概念,并将原型链作为实现继承的主要方法。

基本思想:利用原型让一个引用类型继承另一个引用类型的属性和方法

回顾构造函数,原型和实例的关系:每个构造函数都有一个原型对象,原型对象都包含一个指向构造函数的指针,而实例都包含一个指向原型对象的内部指针。

基本概念: 使原型对象等于另一个类型的实例,原型对象将包含一个指向另一个原型的指针,而另一个原型也包含着一个指向另一个构造函数的指针。假如另一个原型又是另一个类型的实例, 上述关系依然成立,如此层层递进,就构成了实例和原型的链条

//实现原型链的一种基本模式
function SuperType(){
   this.property=true;
}
SuperType.prototype.getSuperValue=function(){
    return this.property
}
function SubType(){
    this.subproperty=false;
}
//继承了SuperType
SubType.prototype=new SubType();

SubType.prototype.getSubValue=function(){
    return this.subproperty;
}
var instance=new SubType();
alert(instance.getSuperValue());   //true
  • 通过实现原型链,本质上扩展了原型搜索机制(当以读取模式访问一个实例属性时,首先会在实例中搜索,如果没找到,则会继续搜索实例的原型)
  • 通过原型链实现继承的情况下,搜索过程就会沿着原型链继续向上。
  • 以调用instance.getSuperValue()为例,会经历三个步骤:
  1. 搜索实例
  2. 搜索SubType.prototype
  3. 搜索SuperType.prototype

1.默认的原型

所有的引用类型默认都继承了Object,这个继承也是通过原型链实现的。

所有函数的默认原型都是Object的实例,因此默认原型都会包含一个内部指针,指向Object.prototype。这也是所有自定义类型都会继承toString(),valueOf() 等默认方法的根本原因

2.确定原型和实例的关系

  • instanceof 操作符
alert(instance instanceof Object);    //true
alert(instance instanceof SuperType);    //true
alert(instance instanceof SubTypr);    //true
  • isPrototypeOf()方法
alert(Object.prototype.isPrototypeOf(instance));//true
alert(SuperType.prototype.isPrototypeOf(instance)); //true alert(SubType.prototype.isPrototypeOf(instance)); //true

3 谨慎地定义方法

  • 给原型添加方法的代码一定要放到替换原型的语句之后
function SuperType(){
    this.property=true;
}
SuperType.prototype.getSuperValue=function(){
    return this.property
}
function SubType(){
    this.subproperty=false;
}
//继承了SuperType
SubType.prototype=new SubType();

SubType.prototype.getSubValue=function(){
    return this.subproperty;
}
//重写超类型中的方法
SubType.prototype.getSuperValue=function(){
    return false;
}
var instance=new SubType();
alert(instance.getSuperValue());   //false
//当SubType的实例调用getSuperValue()时,调用的就是重新定义的方法
//当SuperType的实例调用getSuperValue()时,调用的是原来的方法
//    通过原型链继承时,不能使用对象字面量创建原型方法。这样会重写原型链
function SuperType(){
    this.property=true;
}
SuperType.prototype.getSuperValue=function(){
    return this.property
}
function SubType(){
    this.subproperty=false;
}
//继承了SuperType
SubType.prototype=new SubType();
//使用字面量添加新方法,重写了原型链,会导致上一行代码无效
SubType.prototype={
    getSubValue:function(){
        return this.subproperty;
    },
    someOtherMethod:function(){
        return false;
    }
}
var instance=new SubType();
alert(instance.getSuperValue());   //error

4.原型链的问题

  1. 主要问题在于包含引用类型值得原型。通过原型实现继承时,原型实际上会变成另一个类型的实例。于是,原先的实例属性变成了现在的原型属性
function SuperType(){
    this.colors=["red","blue","green"];
}
function SubType(){
}
//继承SuperType
SubType.prototype=new SuperType();

var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors);   //"red,blue,green,black"

var instance2=new SubType();
alert(instance2.colors);  //"red,blue,green,black"

    2.创建子类型的实例时,不能向超类型的构造函数中传递参数

3.2借用构造函数(伪造对象或经典继承)

  • 基本思想:在子类型构造函数内部调用超类型构造函数
function SuperType(){
    this.colors=["red","blue","green"];
}
function SubType(){
    //继承SuperType
    SuperType.call(this);
}

var instance1=new SubType();
instance1.colors.push("black");
alert(instance1.colors);    //"red,blue,green,black"

var instance2=new SubType();
alert(instance2.colors);   //"red,blue,green"
//SubType的每个实例都有自己的colors属性的副本

1.传递参数

function SuperType(name){
    this.name=name;
}
function SubType(){
    //继承了SuperType,传递了参数
    SuperType.call(this,"Nicholas");
    //实例属性
    this.age=29;
}
var instance=new SubType();
alert(instance.name);    //"Nicholas"
alert(instance.age);    //29

2.借用构造函数的问题

  • 方法都在构造函数中定义,函数复用实现不

3.3组合继承(伪经典继承)

组合继承:将原型链和借用构造函数的技术组合到一起,发挥二者之长的一种继承模式。成为JS最常用的继承模式

思路:使用原型链实现对原型属性和方法的继承,通过借用构造函数实现对实例属性的继承

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;
}
//继承方法
SubType.prototype=new SuperType();
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){
    alert(this.age);
}

var instance1=new SubType("Nicholas",29);
instance1.colors.push("black");
alert(instance1.colors);     //"red,blue,green,black"
instance1.sayName();    //"Nicholas"
instance1.sayAge();    //29

var instance2=new SubType("Greg",27);
alert(instance2.colors);    //"red,blue,green"
instance2.sayAge();      //27
instance2.sayName();    //"Greg"

3.4 原型式继承

  • 借助原型可以基于已有的对象创建新对象,同时还不必因此创建自定义类型
function object(o){   //本质上,object()对传入其中的对象执行了一次浅复制
    function F(){}
    F.prototype=o;
    return new F();
}
var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
}
var anotherPerson=object(person);
anotherPerson.name="Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson=object(person);
yetAnotherPerson.name="Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
  • ES5通过Object.create()方法规范了原型式继承
  • Object.create()方法接收两个参数:用作新对象原型的对象,为新对象定义额外属性的对象(可选)
var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
}
var anotherPerson=Object.create(person)
anotherPerson.name="Greg";
anotherPerson.friends.push("Rob");

var yetAnotherPerson=Object.create(person)
yetAnotherPerson.name="Linda";
yetAnotherPerson.friends.push("Barbie");

alert(person.friends);   //"Shelby,Court,Van,Rob,Barbie"
//    第二个参数指定的属性都会覆盖原型对象上的同名属性。每个属性通过自己的描述符定义
var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
}
var anotherPerson=Object.create(person,{
    name:{
        value:"Greg"
    }
})
alert(anotherPerson.name)   //"Greg"

3.5 寄生式继承

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

function createAnother(original){
    var clone=object(original);    //通过调用函数创建一个新对象
    clone.sayHi=function(){      //以某种方式增强这个对象
    alert("hi")
    }
    return clone;    //返回这个对象
}
var person={
    name:"Nicholas",
    friends:["Shelby","Court","Van"]
}
var anotherPerson=createAnother(person);
anotherPerson.sayHi();  //hi

3.6 寄生组合式继承

组合继承最大问题:无论什么情况下,都会调用两次超类型构造函数:一次是在创建子类型原型的时候,另一次是在子类型构造函数内部

function SuperType(name){
    this.name=name;
    this.colors=["red","blue","green"]
}
SuperType.prototype.sayName=function(){
    alert(this.name);
}
function SubType(name,age){
    SuperType(this,name);        //第二次调用SuperType()
    this.age=age;
}
SubType.prototype=new SuperType();    //第一次调用SuperType()
SubType.prototype.constructor=SubType;
SubType.prototype.sayAge=function(){
    alert(this.age);
}

寄生组合式继承:通过借用构造函数来继承属性,通过原型链的混成形式来继承方法。

基本思路:不必为了指定子类型的原型而调用超类型的构造函数。本质上,就是使用寄生式继承来继承超类型的原型,然后再将结果指定给子类型的原型

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","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)
}

猜你喜欢

转载自blog.csdn.net/weixin_42069386/article/details/83044809