[转]JavaScript构造函数及原型对象

JavaScript中没有类的概念,因此其在对象建立方面与面向对象语言有所不一样。数组

 JS中对象能够定义为”无序属性的集合”。其属性能够包含基本值,对象以及函数。对象实质上就是一组没有特定顺序的值,对象中每一个属性、方法都有一个名字,每一个名字都映射到了一个值,所以咱们能够将对象想象称为一个散列表。

JS是一种基于对象的语言,对象的概念在JS体系中十分的重要,所以有必要清楚地了解一下JS中对象建立的经常使用方法及各自的局限性。app

使用Object或对象字面量建立对象

在说工厂模式建立对象以前,咱们不妨回顾一下JS中最基本的建立对象的方法,好比说我想建立一个student对象怎么办?最简单地,new一个Object:函数

var student = new Object();
student.name = "easy";
student.age = "20";

这样,一个student对象就建立完毕,拥有2个属性name以及age,分别赋值为"easy"20ui

若是你嫌这种方法有一种封装性不良的感受,咱们也可使用对象字面量的方式来建立student对象:this

var sutdent = {
  name : "easy",
  age : 20
};

这样看起来彷佛就完美了。可是立刻咱们就会发现一个十分尖锐的问题:当咱们要建立同类的student1,student2,…,studentn时,咱们不得不将以上的代码重复n次。spa

var sutdent1 = {
  name : "easy1",
  age : 20
};

var sutdent2 = {
  name : "easy2",
  age : 20
};

...

var sutdentn = {
  name : "easyn",
  age : 20
};

能不能像工厂车间那样,有一个车床就不断生产出对象呢?咱们看”工厂模式”。.net

工厂模式建立对象

JS中没有类的概念,那么咱们不妨就使用一种函数将以上对象建立过程封装起来以便于重复调用,同时能够给出特定接口来初始化对象:prototype

function createStudent(name, age) {
  var obj = new Object();
  obj.name = name;
  obj.age = age;
  return obj;
}

var student1 = createStudent("easy1", 20);
var student2 = createStudent("easy2", 20);
...
var studentn = createStudent("easyn", 20);

这样一来咱们就能够经过createStudent函数源源不断地”生产”对象了。看起来已经高枕无忧了,但贪婪的人类总有不知足于现状的天性:咱们不只但愿”产品”的生产能够像工厂车间通常源源不断,咱们还想知道生产的产品到底是哪种类型的。指针

好比说,咱们同时又定义了”生产”水果对象的createFruit()函数:code

function createFruit(name, color) {
  var obj = new Object();
  obj.name = name;
  obj.color = color;
  return obj;
}

var v1 = createStudent("easy1", 20);
var v2 = createFruit("apple", "green");

对于以上代码建立的对象v一、v2,咱们用instanceof操做符去检测,他们通通都是Object类型。咱们的固然不知足于此,咱们但愿v1是Student类型的,而v2是Fruit类型的。为了实现这个目标,咱们能够用自定义构造函数的方法来建立对象。

构造函数模式建立对象

在上面建立Object这样的原生对象的时候,咱们就使用过其构造函数:

var obj = new Object();

在建立原生数组Array类型对象时也使用过其构造函数:

var arr = new Array(10);  //构造一个初始长度为10的数组对象

在进行自定义构造函数建立对象以前,咱们首先了解一下构造函数普通函数有什么区别

其一,实际上并不存在建立构造函数的特殊语法,其与普通函数惟一的区别在于调用方法。对于任意函数,使用new操做符调用,那么它就是构造函数;不使用new操做符调用,那么它就是普通函数。

其二,按照惯例,咱们约定构造函数名以大写字母开头,普通函数以小写字母开头,这样有利于显性区分两者。例如上面的new Array(),new Object()。

其三,使用new操做符调用构造函数时,会经历(1)建立一个新对象;(2)将构造函数做用域赋给新对象(使this指向该新对象);(3)执行构造函数代码;(4)返回新对象;4个阶段。

了解了构造函数普通函数的区别以后,咱们使用构造函数将工厂模式的函数重写,并添加一个方法属性:

function Student(name, age) {
  this.name = name;
  this.age = age;
  this.alertName = function(){
    alert(this.name)
  };
}

function Fruit(name, color) {
  this.name = name;
  this.color = color;
  this.alertName = function(){
    alert(this.name)
  };
}

这样咱们再分别建立Student和Fruit的对象:

var v1 = new Student("easy", 20);
var v2 = new Fruit("apple", "green");

这时咱们再来用instanceof操做符来检测以上对象类型就能够区分出Student以及Fruit了:

alert(v1 instanceof Student);  //true
alert(v2 instanceof Student);  //false
alert(v1 instanceof Fruit);  //false
alert(v2 instanceof Fruit);  //true

alert(v1 instanceof Object);  //true 任何对象均继承自Object
alert(v2 instanceof Object);  //true 任何对象均继承自Object

这样咱们就解决了工厂模式没法区分对象类型的尴尬。那么使用构造方法来建立对象是否已经完美了呢?

咱们知道在JS中,函数是对象。那么,当咱们实例化不止一个Student对象的时候:

var v1 = new Student("easy1", 20);
var v2 = new Student("easy2", 20);
...
var vn = new Student("easyn", 20);

其中共同的alertName()函数也被实例化了n次,咱们能够用如下方法来检测不一样的Student对象并不共用alertName()函数:

alert(v1.alertName == v2.alertName);  //flase

这无疑是一种内存的浪费。咱们知道,this对象是在运行时基于函数的执行环境进行绑定的。在全局函数中,this对象等同于window;在对象方法中,this指向该对象。在上面的构造函数中:

this.alertName = function(){
    alert(this.name)
  };

咱们在建立对象(执行alertName函数以前)时,就将alertName()函数绑定在了该对象上。咱们彻底能够在执行该函数的时候再这样作,办法是将对象方法移到构造函数外部:

function Student(name, age) {
  this.name = name;
  this.age = age;
  this.alertName = alertName;
}

function alertName() {
  alert(this.name);
}

var stu1 = new Student("easy1", 20);
var stu2 = new Student("easy2", 20);

在调用stu1.alert()时,this对象才被绑定到stu1上。

咱们经过将alertName()函数定义为全局函数,这样对象中的alertName属性则被设置为指向该全局函数的指针。由此stu1和stu2共享了该全局函数,解决了内存浪费的问题。

可是,经过全局函数的方式解决对象内部共享的问题,终究不像一个好的解决方法。若是这样定义的全局函数多了,咱们想要将自定义对象封装的初衷便几乎没法实现了。更好的方案是经过原型对象模式来解决。

原型模式建立对象

函数的原型对象

在了解如何使用原型模式建立对象以前,有必要先搞清楚什么是原型对象。

咱们建立的每个函数都有一个prototype属性,该属性是一个指针,该指针指向了一个对象。对于咱们建立的构造函数,该对象中包含能够由全部实例共享的属性和方法。以下如所示:

原型对象

在默认状况下,全部原型对象会自动包含一个constructor属性,该属性也是一个指针,指向prototype所在的函数:

原型对象-constructor

对象实例和原型对象的关联

在调用构造函数建立新的实例时,该实例的内部会自动包含一个[[Prototype]]指针属性,该指针指便指向构造函数的原型对象。注意,这个指针关联的是实例与构造函数的原型对象而不是实例与构造函数

实例与原型对象

使用原型模型建立对象

直接在原型对象中添加属性和方法

了解了原型对象以后,咱们即可以经过在构造函数原型对象中添加属性和方法来实现对象间数据的共享了。例如:

function Student() {
}

Student.prototype.name = "easy";
Student.prototype.age = 20;
Student.prototype.alertName = function(){
                                alert(this.name);
                              };

var stu1 = new Student();
var stu2 = new Student();

stu1.alertName();  //easy
stu2.alertName();  //easy

alert(stu1.alertName == stu2.alertName);  //true 两者共享同一函数

 

以上代码,咱们在Student的protptype对象中添加了name、age属性以及alertName()方法。但建立的stu1和stu2中并不包含name、age属性以及alertName()方法,而只包含一个[[prototype]]指针属性。当咱们调用stu1.namestu1.alertName()时,是如何找到对应的属性和方法的呢?

当咱们须要读取对象的某个属性时,都会执行一次搜索。首先在该对象中查找该属性,若找到,返回该属性值;不然,到[[prototype]]指向的原型对象中继续查找。

由此咱们也能够看出另一层意思:若是对象实例中包含和原型对象中同名的属性或方法,则对象实例中的该同名属性或方法会屏蔽原型对象中的同名属性或方法。缘由就是“首先在该对象中查找该属性,若找到,返回该属性值;”

拥有同名实例属性或方法的示意图:

上图中,咱们在访问stu1.name是会获得”EasySir”:

alert(stu1.name);  //EasySir

经过对象字面量重写原型对象

不少时候,咱们为了书写的方便以及直观上的”封装性”,咱们每每采用对象字面量直接重写整个原型对象:

function Student() {
}

Student.prototype = {
  constructor : Student,
  name : "easy",
  age : 20,
  alertName : function() {
    alert(this.name);
  }
};

要特别注意,咱们这里至关于用对象字面量从新建立了一个Object对象,而后使Student的prototype指针指向该对象。该对象在建立的过程当中,自动得到了新的constructor属性,该属性指向Object的构造函数。所以,咱们在以上代码中,增长了constructor : Student使其从新指回Student构造函数。

原型模型建立对象的局限性

原型模型在对象实例共享数据方面给咱们带来了很大的便利,但一般状况下不一样的实例会但愿拥有属于本身单独的属性。咱们将构造函数模型和原型模型结合使用便可兼得数据共享和”不共享”。

构造与原型混合模式建立对象

咱们结合原型模式在共享方法属性以及构造函数模式在实例方法属性方面的优点,使用如下的方法建立对象:

//咱们但愿每一个stu拥有属于本身的name和age属性
function Student(name, age) {
  this.name = name;
  this.age = age;
}

//全部的stu应该共享一个alertName()方法
Student.prototype = {
  constructor : Student,
  alertName : function() {
                alert(this.name);
              }
}

var stu1 = new Student("Jim", 20);
var stu2 = new Student("Tom", 21);

stu1.alertName();  //Jim  实例属性
stu2.alertName();  //Tom  实例属性

alert(stu1.alertName == stu2.alertName);  //true  共享函数

以上,在构造函数中定义实例属性,在原型中定义共享属性的模式,是目前使用最普遍的方式。一般状况下,咱们都会默认使用这种方式来定义引用类型变量。

 

引用自:http://blog.csdn.net/a153375250/article/details/51083245

相关文章
相关标签/搜索