类 Class 类的概念应该是面向对象语言的一个特点,可是JavaScript并不像Java,C++等高级语言那样拥有正式的类,而是多数经过构造器以及原型方式来仿造实现。在讨论构造器和原型方法前,我能够看看一种叫作工厂方式的仿造方法。html
<script> function material() { //工厂模式 alert('steel'); } function createCar(name, color) { var car = {}; car.name = name; car.color = color; car.material = material; return car; //相似调用函数,必需要有返回值 } var car1 = createCar('BMW', '白色'); var car2 = createCar('Benz', '黑色'); console.log(car1); console.log(car2); </script>
这种方式显然能够实现class的功能,可是外形上怎么也没法说它是个class以及class实例的建立过程。所以,出现了“构造函数方式”,它的关键在于构造器(Constructor)概念的引入。函数
构造器(Constructor)this
<script> function material() { //公共部分 alert('stell'); } //constructor 实例化时,此函数将会做为实例化后对象的构造函数 //**构造函数名首字母最好大写,以区分和普通函数** function CreateCar(name, color) { this.name = name; this.color = color; this.material = material; } var car1 = new CreateCar('BMW', '白色'); var car2 = new CreateCar('BenZ', '黑色'); console.log(car1); console.log(car2); console.log("\n"); </script>
这个看起来有点类的样子了吧(先不提那个难看的外置function)?咱们发现,那个constructor
其实就是一个简单的function,它与“工厂方式”中的createCar()区别就在于:
一、方法名大写
二、没有了空对象的建立和返回
三、使用this作引用。
那原来的那个空对象的建立以及返回的步骤去哪了呢?这两个步骤,如今都由建立实例时的“new”实现了。“new”这个操做符负责建立一个空对象,而后将那个叫作构造器的function添加到实例对象中并触发它,这样这个function实际上就是这个对象的一个method,function中的this指向的即是这个对象,最后将这个对象返回。根据如上分析,咱们能够把这个过程简单分解为以下代码:spa
<script> var obj = {}; obj.constructor = CreateCar; obj.constructor("BMW", "白色"); //”this“ refers to obj return obj; </script>
构造函数方式
虽然与高级面向对象语言中的类建立方式已经很接近(使用new
建立),可是貌似那个游离在类以外的function material()
其实倒是个至关有碍观瞻的瑕疵。咱们应该想一种办法让这个方法与类挂钩,让它成为类的一个属性,不是全局的。因而,这就产生了“构造函数+原型法”的类构造方法。prototype
从上面的构造函数模式建立对象的例子上能够看到,每建立一个对象实例,每一个对象中都有material()
这个成员方法,这样看起来是否是会浪费内存空间,下降执行效率。因此JavaScript中提供了原型的方法能够解决这个问题。code
原型:proto
在JavaScript中每一个函数都有一个原型属性,即prototype
,当调用构造函数进行建立对象的时候,全部该构造函数原型的属性在建立的对象上均可用。按照这样的想法多个CreateCar均可以共享一个原型material
.htm
在构造函数+原型法中,咱们对于类的method期待获得的效果是:
1. 仅是类的method而不是全局的。
2. 只在类被定义时建立一个method实例,而后被全部类的实例共用。
由这两个目标,咱们很容易想到高级面向对象语言Java的private static变量的特色。JavaScript没有为咱们提供这么简单的符号来实现这个复杂功能,可是却有一个属性能够帮咱们仿造出这种效果:prototype
。咱们先来看几段prototype的使用代码。对象
> function Car(){ } > > Car.prototype.material = "steel"; > > var car1 = new Car(); var car2 = new Car(); > > document.write(car1.material); //prints "steel" > document.write(car2.material); //prints "steel" > > //car1.prototype.material = "iron" //compile error:car1.prototype is > undefined car1.material = "iron"; > > document.write(car1.material); //prints "iron" > document.write(car2.material); //prints "steel" > document.write(Car.prototype.material); //prints "steel" > > Car.prototype.material = "wood"; var car3 = new Car(); > document.write(car1.material); //prints "iron" > document.write(car2.material ); //prints "wood" > document.write(car3.material ); //prints "wood" > document.write(Car.prototype.material); //prints "wood"
分析该段代码前,须要明确两个概念:对象的直属属性和继承属性。直接在构造函数中经过this.someproperty = xxx
这种形式定义的someproperty属性叫作对象的直属属性,而经过如上第4行代码那样Car.prototype.material = "steel";
这种形式定义的material属性叫作继承属性。由上面这段代码,咱们能够总结出prototype属性的以下特色:blog
PS:对象实例在读取某属性时,若是在自己的直属属性中没有查找到该属性,那么就会去查找function下的
prototype
的属性。继承
Tip:咱们能够经过hasOwnProperty方法来判断某属性是直属于对象仍是继承自它的prototype属性 car1.hasOwnProperty("material"); // true car2.hasOwnProperty("material"); // false "material" in car2;// true
构造函数+原型方式代码以下
<script> function CreateCar(name, color) { this.name = name; this.color = color; } CreateCar.prototype.material = function() { //绑定material到函数原型链上,当实例化时这个函数会做为每一个对象的构造函数 alert(this.name); } var car1 = new CreateCar('BMW', '白色'); var car2 = new CreateCar('Benz', '黑色'); console.log(car1); console.log(car2); console.log(car1.hasOwnProperty("material"));//false 由于material是原型链上的,不是对象自己的,因此false </script>
这个跟高级面向对象语言中的class的样子更~加相似了吧?上述写法只是在“语义”上达到了对类属性和方法的封装,不少面向对象思想的完美主义者但愿在“视觉”上也达到封装,所以就产生了“动态原型法”,请看下面的代码:
function Car(color, title){ this.color = color; this.title = title; if (typeof Car._initialized == "undefined") { Car.prototype.start = function(){ alert("Bang!!!"); }; Car.prototype.material = "steel"; Car._initialized = true; } }
咱们看,其实Car.prototype
的属性定义是能够被放进Car function的定义之中的,这样就达到了“视觉”封装。可是咱们没有单纯的move,咱们须要加一个条件,让这些赋值操做只执行一次,而不是每次建立对象实例的时候都执行,形成内存空间的浪费。添加_initialized
属性的目的就在于此。
对于全部用字面量建立的对象而言,其prototype对象均为Object.prototype(做为一个特殊对象,Object.prototype没有原型对象):
var x = {a:18, b:28}; console.log(x.__proto__);//Object {}
而对于全部用new操做符建立的对象而言,其prototype对象均为constructor函数的prototype属性:
var x = {a:18, b:28}; function Test(c){ this.c = c; } Test.prototype = x; var t = new Test(38); console.log(t);//Object {c=38, a=18, b=28} console.log(t.__proto__);//Object {a=18, b=28} console.log(t.__proto__.__proto__);//Object {}
总结出来一句话就是:用构造函数方式定义对象的全部非函数属性,用原型方式定义对象的函数属性。
整理于:
http://dbear.iteye.com/blog/613745
http://www.cnblogs.com/tomxu/archive/2012/02/21/2352994.html