面向对象

面向对象的语言有一个标志,那就是它们都有类的概念,而经过类能够建立任意多个具备相同属性和方法的对象编程

建立对象

虽然Object构造函数或者对象字面量均可以用来建立单个对象,但这些方式有个明显的缺点:使用同一个接口建立不少对象,会产生大量重复代码(为何或产生大量重复的代码?)。app

工厂模式

function createPerson(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 person1 = createPerson("Nicholas", 29, "Software Engineer"); 
var person2 = createPerson("Greg", 27, "Doctor");

工厂模式的特色:解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎么知道一个对象的类型);函数

构造函数模式

构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个作法借鉴自其余 OO 语言,主要是为了区别于 ECMAScript中的其余函数;由于构造函数自己也是函数,只不过能够用来建立对象而已。性能

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 的新实例,必须使用 new 操做符。这中方式调用构造函数实际上回会经历4个步骤this

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

缺点: 每一个方法都要在每一个实例上从新建立一遍。spa

原型模式

每个函数都有一个 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

新对象的这些属性和方法是由全部实例共享的。换句话说,person1 和 person2 访问的都是同一组属性和同一个 sayName()函数指针

理解原型对象

构造函数、原型和实例的关系:每一个构造函数都有一个原型对象(prototype属性),原型对象都包含一个指向构造函数的指针(constructor(构造函数)属性),而实例有包含一个指向原型对象的内部指针([[Prototype]]、Firefox、Safari 和 Chrome 在每一个对象上都支持一个属性__proto__)code

图片描述

更简单的原型语法

function Person(){ 
} 
Person.prototype = { 
    constructor : Person,   //
    name : "Nicholas", 
    age : 29, 
    job: "Software Engineer", 
    sayName : function () { 
        alert(this.name); 
    } 
};

本质上彻底重写了默认的 prototype 对象,所以 constructor 属性也就变成了新对象的 constructor 属性(指向 Object 构造函数),再也不指向 Person 函数。因此须要手动设置 constructor 属性,并将它的值设置为 Person,确保经过该属性可以访问到适当的值。对象

原型与in操做符

原型的动态性

原生对象的原型

原型模式的重要性不只体如今建立自定义类型方面,就连全部原生的引用类型,都是采用这种模式建立的。全部原生引用类型(Object、Array、String等)都在期构造函数的原型定义了方法。例如,在Array.prototype中能够找到sort()方法,而在String.prototype中能够找到substring()方法。

alert(typeof Array.prototype.sort); //"function" 
alert(typeof String.prototype.substring); //"function"

经过原生对象的原型,不只能够取得全部默认方法的引用,并且也能够定义新方法。能够像修改自定义对象的原型同样修改原型对象的原型,所以能够随时添加方法。下面的代码就给基本包装类型String添加了一个名为 startsWith()的方法。

String.prototype.startsWith = function (text) { 
    return this.indexOf(text) == 0; 
}; 
var msg = "Hello world!"; 
alert(msg.startsWith("Hello")); //true

原型对象的问题

缺点:

  1. 由于省略了为构造函数传递初始化参数这一环节,结果全部在默认状况下都将取得相同的属性值
  2. 最大问题是由其共享的本性所致使的。

原型中全部属性是被不少实例共享的,这种共享对于函数很是合适。对于那些包含基本值的属性倒也说的过去,毕竟(如前面的例子所示),经过在实例上添加同一个同名属性,能够隐藏在原型中的对应属性。然而,对于包含引用类型值的属性来讲,问题就比较突出了。

function Person(){ 
} 
Person.prototype = { 
    constructor: Person, 
    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

继承

原型链

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

function SuperType(){ 
    this.property = true; 
}   
SuperType.prototype.getSuperValue = function(){ 
    return this.property; 
}; 
function SubType(){ 
    this.subproperty = false; 
} 
//继承了 SuperType 
SubType.prototype = new SuperType(); 
SubType.prototype.getSubValue = function (){ 
    return this.subproperty; 
}; 
var instance = new SubType(); 
alert(instance.getSuperValue()); //true

图片描述

原型的搜索机制:当以读取模式方式访问一个实例属性时,首先会在实例中搜索该属性。若是没有找到改属性,则会继续搜索实例的原型。在经过原型链实现继承的状况,搜索过程就得以沿着原型链继续向上。在找不到属性或方法的状况下,搜索过程是要一环一环地前行到原型链末端才会停下来。

默认的原型

咱们引用类型默认都继承了Object,而这个继承也是经过原型链实现的。你们要记住,全部函数的默认原型都是Object的实例,所以默认原型都会包含一个内部指针,指向Object.prototype。这也正式全部自定义类型都会继承toString()、valueOf()等默认方法的根本缘由。
完整的原型链:
图片描述

原型链的问题

主要问题来自于包含引用类型的原型(引用类型值的原型属性会被全部实例共享)。在经过原型来实现继承时,原型实际上回变成另外一个类型的实例。因而,原先的实例属性也就瓜熟蒂落地变成了如今的原型属性了。

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"

原型链的第二个问题是:在建立子类型的实例时,不能向超类型的构造函数中传递参数。实际上,应该说是没有办法在不影响全部对象实例的状况下,给超类型的构造函数传递参数。有鉴于此,在加上前面刚刚讨论过的因为原型中包含引用类型值所带来的问题,实践中不多会单独使用原型链。

借用构造函数

基本思想:在子类型构造函数的内部调用超类型构造函数。经过apply()和call()方法也能够在(未来)新建立的对子那个上执行构造函数

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"

优势

传递参数.借用构造函数能够在子类型构造函数中向超类型构造函数传递参数

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

问题

构造函数存在的问题——方法都在构造函数定义,所以函数就没法复用。并且在超类型的原型中定义方法,对子类型而言也是不可见的,结果全部类型都只能在使用构造函数模式

组合继承

组合继承:有时候也叫伪经典继承,指的是将原型链和借用构造函数的技术组合到一块,从而发挥两者之长的一种继承模式。
基本思想: 使用原型链实现对原型属性和方法的继承,而经过借用构造函数来实现对实例属性的继承。这样,即经过在原型上定义方法实现了函数复用,又可以保证每一个实例都有它本身的属性。

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.sayName(); //"Greg"; 
instance2.sayAge(); //27

instanceof 和 isPrototypeOf()也可以用于识别基于组合继承建立的对象

小结

ECMAScript 支持面向对象(OO)编程,但不使用类或者接口。对象能够在代码执行过程当中建立和加强,所以具备动态性而非严格定义的实体。在没有类的状况下,能够采用下列模式建立对象。

  • 工厂模式,使用简单的函数建立对象,为对象添加属性和方法,而后返回对象。这个模式后来被构造函数模式所取代。
  • 构造函数模式,能够建立自定义引用类型,能够像建立内置对象实例同样使用new操做符。不过,构造函数模式也有缺点,即它的每一个成员都没法获得复用,包括函数。因为函数能够不局限于任何对象(即与对象具备松散耦合的特色),所以没有理由在多个对象间共享函数。
  • 原型模式,使用构造函数的prototype属性来指定那些应该共享的属性和方法。组合使用构造函数模式和原型模式时,使用构造函数定义实例属性,而使用原型定义共享的属性和方法。

JavaScript主要经过原型链实现继承。原型链的构建是经过讲一个类型的实例赋值给另外一个构造函数的原型实现的。这样,子类型就可以访问超类型的全部属性和方法,这一点与基于类的继承很类似。原型链的问题是对象实例共享全部继承的属性和方法,所以不适宜单独使用。解决这个问题的技术是借用构造函数,即在子类型构造函数的内部调用超类型构造函数。这样就能够作到每一个实例都具备本身的属性,同时还能保只使用构造函数模式来定义类型。使用最多的继承模式是组合继承,这种模式使用原型链继承共享的属性和方法,而经过借用构造函数继承实例属性。
此外,还存在下列可供选择的继承模式。

  • 原型式继承,能够在没必要预先定义构造函数的状况下实现继承,其本质是执行对给定对象的浅复制。而复制获得的副本还能够获得进一步改造。
  • 寄生式继承,与原型式继承很是类似,也是基于对象或某些写信建立一个对象,而后加强对象。最后返回对象。为了解决组合继承模式因为屡次调用超类型构造函数而致使的低效率问题,能够将这个模式与组合继承一块儿使用。
  • 寄生组合式继承,集寄生式继承和组合继承的有点与一身,是实现基于类型继承的最有效方式
相关文章
相关标签/搜索