首先 JavaScript是没有类这个概念的
ECMA-262把对象定义为:“无序属性的集合,其属性可以包含基本值,对象或者函数”
创建对象的方法:
var person = new Object(); person.name = "niko"; person.age = 20; person.say = function(){alert(this.name);};
上面的例子用对象字面量语法可以写为:
var Person = { name : "niko"; age : 20; say : function(){alert(this.name);} };
虽然Object构造函数或对象字面量可以用来创建单个对象,但是这些方式有明显的缺点,使用同一个接口创建很多对象,会产生大量重复的代码,为了解决这个问题,人们开始使用工厂模式的一种变体
工厂模式抽象了创建具体对象的过程 实现的例子如下
function createPerson(name,age,job){ var o = new Objcect(); o.name = name; o.age = age; o.job = job; o.sayName = function(){ alert(this.name);}; return o;//返回的就是这个对象 }
构造函数模式 : 按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头
以上的代码使用构造函数模式可以写成这个样子:
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); }; }
构造函数的问题:
使用构造函数的主要问题就是每个方法都要在每个实例上重新创建一遍,因为在ECMAScript中的函数就是对象,因此没定义一个函数,也就是实例化了一个对象
当然我们大可像下面这样把函数定义转移到构造函数外部来解决这个问题
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); }
在这个例子中,我们在构造函数内部,将sayName属性设置成为全局的sayName属性,因为sayName包含的是一个指向函数的指针,因此person1和person2就共享了在全局作用域中定义的同一个sayName()函数 这样做问题又来了:在全局作用域中定义的函数实际上只能被某个对象调用,这让全局作用域有点名不副实,更加让人无法接受的是,如果要定义很多方法,那么就要定义很多个全局函数
原型模式:
我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含由特定类型的所有实例共享的属性和方法,使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法
function Person(){} Person.prototype.name = "niko"; Person.prototype.age = 28; Person.prototype.job = "software engineer"; Person.prototype.sayhi = function(){alert(this.name);};
hasOwnProperty()方法可以检测一个属性是否在对象的实例中
原型与in操作符:单独使用时,in操作符会在通过对象能够访问给定的属性时返回true,无论该属性是存在于实例中还是原型中
要取得对象上所有可以枚举的实例属性,可以使用ECMAScript的Object.keys()方法 这个方法接收一个对象作为参数,返回一个包含所有可以枚举属性的字符串数组
更简单的原型语法:
前边的例子中没添加一个属性和方法都要敲一遍Person.prototype 为减少不需要输入,可以这样做
function Person(){} Person.prototype = { name : "niko"; age : 29; sayName : function(){alert(this.name);} }
原型的动态性:
由于在运行中查找值的过程是一次搜索,因此我们队原型对象所做的任何修改都能够立即从实例上反映出来--即使是先创建了实例后修改原型也照样如此
var friend = new Person(); Person.prototype.sayHi = function(){alert(hi);}; friend.sayHi(); //"hi"
当我们调用friend.sayHi()时 她会首先从实例中搜索名为sayHi的属性 在没有找到的情况下会继续搜索原型,因为实例与原型之间的链接只不过是一个指针,而非一个副本 实例中的指针仅指向原型,而不指向构造函数
function Person(){} var friend = new Person(); Person.prototype = { constructor : Person, name : "niko", age : 20, sayName : function(){alert("this.name");} }; friend.sayName(); //error
这是因为重写原型对象切断了现有原型和任何之前已经存在的对象实例之间的联系,他们引用的任然是最初的原型
原生对象的原型
原型模式的重要性不仅体现在创建自定义类型方面,就连所有原声的引用类型,都是采用这种模式创建的,所有原声引用类型(Object,String,Array等等)都在其构造函数的原型上定义了方法。
原型对象的问题:
原型模式最大的问题是由其共享的本质所导致的,原型中所有的属性是被很多实例共享的,这种共享对于函数来说非常合适,对于那些包含基本值的属性倒也说得过去,但是对于包含引用类型值的属性来说,问题就比较突出了
function Person(){} Person.prototype={constructor:Person,name:"niko",age:"20",friends:["shelby","sansa"],sayName:function(){alert(this.name);}}; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends);//shelby,sansa,Van alert(person2.friends);//shelby,sansa,Van
再次,Person.prototype对象有一个名为friends的属性,该属相包含一个字符串数组,然后,创建了两个Person的实例,接着,修改了person1.friends引用的数组,向数组中添加了一个字符串,由于friends数组存在于Person.prototype而非person1中,所以刚刚提到的修改也会通过person2.friends反映出来 可是,实例一般要有属于自己的全部的属性,所以这正是我们很少有人看到使用原型模式的原因。
组合使用构造函数模式和原型模式
创建自定义类型的最常见的方式,就是组合使用构造函数模式与原型模式,构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性,结果,每一个实例都有有一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存
function Person(name,age,job){ this.name = name; this.age = age; this.job = job; this.friends=["shelly","sansa"]; }//这是用构造函数模式,每个实例都会初始化构造函数模式下的方法 Person.prototype = { constructor : Person, sayName : function(){alert(this.name);} } var person1 = new Person();//这里传入三个参数 var person2 = new Person();//这里同样传入三个参数 person1.friends.push("van"); alert(person1.friends);//shelly sansa van alert(person2.friends);//shelly sansa
继承:ECMAScript只支持实现继承
原型链:原型链的基本思想是利用原型让一个引用类型继承另一个引用类型 实现原型链有一种基本方式 代码大致如下
function SuperType(){this.property=true;} SuperType.prototype.getSuperValue=function(){return this.property}; function SubType(){this.subproperty = false;} SubType.prototype = new SuperType();//继承了SuperType SubType.prototype = new SuperType(); SubType.prototype.getSubValue = function(){return this.subproperty;}; var instance = new SubType(); alert(instance.getSuperValue());//true
原型链的问题:
function SuperType(){ this.colors=["red","green"]; } function SubType(){} SubType.prototype = new SuperType(); var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors);//red.green.black var instance2 = new SubType(); alert(intance2.colors);//red green black
借用构造函数实现继承:这种方式叫做伪继承或者经典继承
function SuperType(){this.colors=["red","blue"];} function SubType(){SuperType.call(this);} var instance1 = new SubType(); instance1.colors.push("black"); alert(instance1.colors);//red blue black var instance2 = new SubType(); alert(instance2.colors);//red blue
组合继承:
function SuperType(name){ this.name = name; this.colors = ["red","blue"]; } 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("niko",20); instance1.colors.push("black"); instance1.sayName();//niko instance1.sayAge();//20 var instance2 = new SubType("Sansa",19); alert(instance2.colors); instance2.sayName();//Sansa instance2.sayAge();//29