面向对象语言有一个标志,那就是它们都有类的概念,经过类能够建立任意多个具备相同属性和方法的对象。javascript
ECMAScript没有类的概念,它的对象也与基于类的语言中的对象有所不一样。ECMAScript把对象定义为:java
无序属性的集合,其属性能够包含基本值、对象或函数。安全
每一个对象实例都是基于一个引用类型建立的,这个引用类型能够是ECMAScript原生类型,也能够是开发者定义的类型。函数
咱们能够经过Object构造函数或对象字面量来建立单个对象,但这些方式有个明显的缺点:使用同一个接口建立不少对象,会产生大量的重复代码。this
为解决上述问题,可使用工厂模式建立对象。工厂模式抽象了建立具体对象的过程。prototype
因为ECMAScript没有类,能够定义一种函数,用函数来封装以特定接口建立对象的细节。例如:3d
function createStudent(name,age) { var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ alert(obj.name); }; return obj; } var Bob = createStudent("Bob", 24); var Tom = createStudent("Tom", 28);
工厂模式虽然解决了建立多个类似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。上面代码,咱们的本意是建立一个Student类,Bob和Tom是Student类型,但实际上根本不存在Student类型,而Bob和Tom是Object类型。指针
构造函数可用来建立特定类型的对象,这意味着能够将构造函数的实例标识为一种特定的类型。code
构造函数与与工厂模式的不一样之处在于:对象
使用new操做符调用构造函数时,会经历如下4个步骤:
function Student(name,age) { this.name = name; this.age = age; this.sayName = function(){ alert(this.name); }; } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // false
因为ECMAScript中的函数是对象,所以只要定义一个函数,就会实例化一个对象。这会致使使用构造函数模式建立对象时,构造函数中的每一个方法都要在每一个实例上从新建立一遍。这样作浪费内存,下降执行效率。
咱们能够把方法定义从构造函数内部移到外部,如:
function Student(name,age) { this.name = name; this.age = age; this.sayName = sayName; } function sayName(){ alert(this.name); } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
这样会致使新的问题:sayName本应是Student的私有方法,如今却能够被任意调用,这破坏类的封装特性。
当咱们建立一个函数时,就会同时建立它的prototype对象,这个prototype对象也会自动得到constructor属性,这个属性指向构造函数对象。
当咱们经过构造函数实例化一个对象时,实例对象内部将包含一个指针(内部属性),它指向构造函数的原型对象。这个内部属性称为[[Prototype]]
,在脚本中没有标准的方式访问该内部属性。
原型中方法和属性被其所有实例所共享。
function Student() { } Student.prototype.name = "xiaohong"; Student.prototype.age = 24; Student.prototype.sayName = function() { alert(this.name); }; var Bob = new Student(); var Tom = new Student(); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
前面的代码中每添加一个属性和方法就要敲一遍Student.prototype
,为了减小没必要要的输入,也为了从视觉上更好地封装原型的功能,能够用对象字面量重写整个原型。
function Student() { } Student.prototype = { name = "xiaohong", age = 24, sayName = function() { alert(this.name); } }; var Bob = new Student(); var Tom = new Student(); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true alert(Student.prototype.constructor == Student); // false alert(Student.prototype.constructor == Object); // true
重写原型后,现有原型的constructor属性再也不指向构造函数对象,而是指向对象字面量的构造函数Object。
因为在原型中查找值的过程是一次搜索,所以咱们对原型对象所作的任何修改都可以当即从实例上反映出来——即便是先建立了实例后修改原型也照样如此。
可是,若是建立实例后重写原型,实例会因为没法查找到属性或方法报错。这是因为实例对象会经过内部属性[[Prototype]]
链接到原型,在原型中查找属性,而重写原型切断了实例对象与原型对象之间的联系。
构造函数模式用于定义实例属性,而原型模式用于定义方法和共享属性。
这种组合方式的优势是:
function Student(name,age) { this.name = name; this.age = age; } Student.prototype = { sayName = function() { alert(this.name); } }; var Bob = new Student("Bob",24); var Tom = new Student("Tom",28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
原型模式中构造函数和原型是分离,为了把全部信息都封装在构造函数中,可使用动态原型模式。
构造函数模式的主要缺点是同个方法要在不一样实例对象中重复建立,浪费内存,因此引入了原型模式。动态原型模式经过检查某个应该存在的方法是否有效,若是无效则在构造函数中初始化原型,这样就解决方法对象重复建立的问题,而封装性更好。
function Student(name,age) { this.name = name; this.age = age; // sayName不存在,则初始化原型 if (typeof this.sayName != "function") { this.sayName = function() { alert(this.name); }; } } var Bob = new Student("Bob",24); var Tom = new Student("Tom",28); alert(Bob instanceof Student); // true alert(Tom instanceof Student); // true alert(Bob.sayName == Tom.sayName); // true
寄生构造函数模式就是用new操做符调用工厂模式。因为重写了返回值,返回对象和构造函数及其原型没有任何联系,没法对象类型识别。
function Student(name,age) { var obj = new Object(); obj.name = name; obj.age = age; obj.sayName = function(){ alert(this.name); }; return obj; } var Bob = new Student("Bob", 24); var Tom = new Student("Tom", 28);
稳妥对象指没有公共属性,并且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中或者防止数据被其余应用程序改动时使用
稳妥构造函数与寄生构造函数相似,但有两点不一样:
function Student(name,age) { var obj = new Object(); obj.sayName = function(){ alert(name); }; return obj; } var Bob = Student("Bob", 24); Bob.sayName();