引言

  本文先从介绍JavaScript中基本的几种设计模式开始,最后引出原型对象,然后对原型对象有一个较全面的介绍。

1、创建对象的几种设计模式

  A、工厂模式

  我们知道在JavaScript中创建对象可以使用Object构造函数或者对象字面量的方式。但是使用这些方式有一个问题,就是调用同一个接口创建对象时会出现大量重复的代码。开发人员通过将创建对象的过程细节封装为一个接口。这样可以解决创建多个相似对象的问题。例子如下:

/**
* 工厂模式
**/
function createPerson(name, age, job) {
var obj = new Object();
obj.name = name;
obj.age = age;
obj.job = job;
obj.sayName = function () {
alert(this.name);
}
return obj;
} var person1 = createPerson("张三", 29, "工程师");
var person2 = createPerson("李四", 33, "销售");

  如以上代码所示:我们在创建相同或相似对象的时候不需要每次都调用Object的构造函数,只需要简单的调用工厂方式来创建对象即可。但是工厂模式没有解决对象识别问题(怎么知道一个对象时什么类型)。因为通过工厂模式创建的对象都是Object类型的,而没法判断是Person类型。后来JavaScript中又引入了构造函数模式。  

  B、构造函数模式

  在JavaScript中通过构造函数可以来创建特定类型的对象,例如:Object、Array等。我们通过构造函数模式来解决对象识别问题。例子如下:

 /**
* 构造函数模式
**/
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.sayName = function () {
alert(this.name);
}
} person1 = new Person("张三", 29, "工程师");
person2 = new Person("李四", 33, "销售");

  在构造函数模式中,当代码以13,14行的方式进行调用时会发生以下几个步骤。(1)、创建一个新对象。(2)、将构造函数的作用域赋给新对象(因此this就指向这个新对象)。(3)、执行构造函数中的代码(为新对象添加属性)。4、返回这个新对象。这样person1和person2都有一个constructor(构造函数)属性,该属性指向Person。

  对象的constructor属性最初是用来判断对象类型的,但是判断引用类型的类型还是使用instanceof更加可靠一些。通过构造函数模式我们就可以辨别出对象的类型了,这事构造函数模式胜过工厂模式的地方。当然person1和person2还是Object类型的对象,因为所有对象均继承自Object类型。

  但是构造函数模式也有缺点。通过构造函数模式创建的对象都有一个相同的方法。即每一个方法都需要在每一个对象上重新创建一遍。如上面的例子所示,sayName方法作为一个对象,在每一个对象上面都会创建一个新的函数对象,这无疑是对内存的消耗。其实所有对象只需共用一个sayName函数即可。有人认为只需把sayName方法放到构造函数外面,这个方法在函数数量较少的时候还可以,但是数量一多,会给维护造成一定的困难。这时我们可以引入原型模式来改进这个问题。

  C、原型模式

  我们创建的每一个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,这个对象包含可以由特定类型的所有实例共享的属性和方法。这个类似于C#,Java中的static定义的属性(字段)和方法。所有实例共享这些属性和方法。例子如下:

 /**
* 原型模式
**/
function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
person1 = new Person();
person2 = new Person();

  理解prototype(原型)对象

  创建新函数时,就会根据一组特定的规则为该函数创建prototype属性。这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向包含prototype属性所在的函数的指针。

  创建自定义构造函数之后,其原型对象只会取得constructor(构造函数)属性,至于其他方法都是从Object集成而来的。上面例子的原型图如下所示:

  该图表示了Person构造函数、Person的原型对象及现有的两个Person实例之间的关系。

  Person构造函数内部存在一个prototype属性指向了Person原型对象,而Person原型对象内部的constructor(构造函数)属性又指向Person构造函数。Person的每一个实例内部都有一个prototype属性指向Person原型对象。Person的每一个实例仅仅与原型对象存在关系与Person构造函数对象不存在任何关系。在图上我们也可以看出了。

  虽然name,age,job等属性都是我们通过Person.prototype.name的方式添加进去的。所以这些属性和方法(sayName)都是在原型对象中的。但是我们还是可以调用person1.age、person1.sayName()。这是为什么呢?这是通过查找对象属性的过程来实现的。详细的查找过程如下:

  每当代码读取某个对象的某个属性的时候,都会执行一次搜索,目标是给定名称的属性。搜索本身从对象的实例开始(图中person1、person2)。如果在实例中找到该属性,就直接返回,不执行下面的操作。如果在实例中没有找到给定名称的属性,就继续搜索实例Prototype指针指向的原型对象,在原型对象中查找指定名称的属性。找到就返回该属性。

  虽然可以通过实例访问原型对象中的值,但不能通过对象实例重写原型的值。如果我们在实例中添加一个属性,该属性与原型中的属性同名,那我们就在实例中创建该属性,该属性会屏蔽原型中的同名属性。请看下面的例子:

 /**
* 原型模式
**/
function Person() { }
Person.prototype.name = "Nicholas";
Person.prototype.age = 29;
Person.prototype.job = "Software Engineer";
Person.prototype.sayName = function () {
alert(this.name);
}
person1 = new Person();
person2 = new Person(); person1.name = "Greg";
alert(person1.name); //Greg来自实例
alert(person2.name); //Nicholas来自原型 delete person1.name;
alert(person1.name); //Nicholas来自原型

  注意:使用hasOwnproperty()方法可以检测一个属性是否存在于实例中(在实例中返回true),还是存在于原型中。person1.hasOwnproperty("name")的形式。详细的分析如图:

  重点:更简洁的原型方法

 /**
* 更加简洁的原型语法
**/
function Person() { }
Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function () {
alert(this.name);
}
}

  重点:原型的动态性

  由于在原型中查找值的过程是一次搜索,因此我们对原型所做的修改都会立即实例上反映出来,即使先创建对象,后修改原型对象也是如此。但是如果是重写整个原型对象,情况可能就不一样了。调用构造函数时会为实例添加一个指向最初原型对象的(prototype)指针。但是重写整个原型对象,等于切断了构造函数和原来原型对象之间的关系。看下面的例子:

/**
* 更加简洁的原型语法
**/
function Person() { } var friend = new Person(); Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
sayName: function () {
alert(this.name);
}
} //var friend = new Person();
friend.sayName(); //error

  我们看到这时调用sayName方法会报错。但是如果将实例化语句放到注释处,程序运行正常。下图展示了该例子的相关内幕:

  通过该图我们知道重写整个原型对象会带来的一些问题。讲了这么多的原型模式,大家应该对这个模式也比较清楚了。但是原型模式并不是完美的。原型模式中的属性对于所有实例都是共享的。这对于函数没有问题。但是对于包含引用类型的属性来说就比较突出了。看下面的例子:

 function Person() { }

     Person.prototype = {
constructor: Person,
name: "Nicholas",
age: 29,
job: "Software Engineer",
firends: ["Mark","Nicy"],
sayName: function () {
alert(this.name);
}
} person1 = new Person();
person2 = new Person(); person1.firends.push("Nolos");
alert(person1.firends); //"Mark","Nicy","Nolos"
alert(person2.firends); //"Mark","Nicy","Nolos"
alert(person1.firends === person2.firends); //true

  以上的代码我们看到,我们将friends属性设置为Array类型。作为一个引用类型我们将其放在原型对象中。但是我们给person1的friedns属性添加了一个值。但是friends也影响到了。这就是原型模式存在的问题。

  D、组合使用构造函数模式和原型模式

  创建自定义对象的常见方式,就是组合使用构造函数模式和原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。每一个实例都有属于它自己的实例属性的一份副本,同时又引用着原型对象中的共享属性和方法,这样最大限度的节约了内存。看下面的的例子:

 /**
* 组合使用构造函数模式和原型模式
**/
function Person(name, age, job) {
this.name = name;
this.age = age;
this.job = job;
this.friends = ["Mark", "Nicy"]
} Person.prototype = {
constructor: Person,
sayName: function () {
alert(this.name);
}
} var person1 = new Person("Nicolas", 29, "Software Engineer");
var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Nolos"); alert(person1.friends); //"Mark","Nicy","Nolos"
alert(person2.friends); //"Mark","Nicy" alert(person1.friends === person2.friends); //false
alert(person1.sayName === person2.sayName); //true

浅谈JavaScript原型对象与相关设计模式的更多相关文章

  1. 浅谈JavaScript原型

    在JavaScript中,所有函数都会拥有一个叫做prototype的属性,默认初始值为“空”对象(没有自身属性的对象). 1.原型属性 如下所示,简单地定义一个函数: function foo(a, ...

  2. 浅谈JavaScript原型图与内存模型

    js原型详解 1.内存模型: 1.原型是js中非常特殊一个对象,当一个函数(Person)创建之后,会随之就产生一个原型对象 2. 当通过这个函数的构造函数创建了一个具体的对象(p1)之后,在这个具体 ...

  3. 浅谈JavaScript原型与原型链

    对于很多前端开发者而言,JavaScript的原型实在是很让人头疼,所以我这边就整理了一下自己对应原型的一点理解,分享给大家,供交流使用 原型 说起原型,那就不得不说prototype.__proto ...

  4. 浅谈javascript的原型及原型链

    浅谈javascript的原型及原型链 这里,我们列出原型的几个概念,如下: prototype属性 [[prototype]] __proto__ prototype属性 只要创建了一个函数,就会为 ...

  5. 浅谈 JavaScript 编程语言的编码规范

    对于熟悉 C/C++ 或 Java 语言的工程师来说,JavaScript 显得灵活,简单易懂,对代码的格式的要求也相对松散.很容易学习,并运用到自己的代码中.也正因为这样,JavaScript 的编 ...

  6. 浅谈JavaScript中的闭包

    浅谈JavaScript中的闭包 在JavaScript中,闭包是指这样一个函数:它有权访问另一个函数作用域中的变量. 创建一个闭包的常用的方式:在一个函数内部创建另一个函数. 比如: functio ...

  7. 浅谈JavaScript浮点数及其运算

    原文:浅谈JavaScript浮点数及其运算     JavaScript 只有一种数字类型 Number,而且在Javascript中所有的数字都是以IEEE-754标准格式表示的.浮点数的精度问题 ...

  8. C#核心基础--浅谈类和对象的概念

    浅谈类和对象的概念 一.什么是类?什么是对象? 学习一门面向对象编程语言,我们必须得知道什么是类?什么是对象? 类(Class)实际上是对某种类型的对象定义变量和方法的原型.它表示对现实生活中一类具有 ...

  9. javaScript系列 [03]-javaScript原型对象

    [03]-javaScript原型对象 引用: javaScript是一门基于原型的语言,它允许对象通过原型链引用另一个对象来构建对象中的复杂性,JavaScript使用原型链这种机制来实现动态代理. ...

随机推荐

  1. python第一天

    python 解释器执行代码有两种 一种在解释器: win+R==>cmd 打开终端进行 输入python 加 路径 另一种在文件里写完再到解释器执行:win+R==>cmd 打开终端进行 ...

  2. 使用oracle utl_http包需要注意的事项

    总结下几次使用utl_http包遇到的几个问题 关于utl_http包功能还是很强大的 可以通过他来捕捉网站页面的内容 或者调用一个url的接口完成某项功能 Eg: declare   req  UT ...

  3. Windows 8及以上系统安装好SQL Server 2008之后找不到SQL Server配置管理器的问题

    直接的方法: 打开[运行]->输入[C:\Windows\SysWOW64\mmc.exe /32 C:\Windows\SysWOW64\SQLServerManager10.msc]即可. ...

  4. Win10中解决SYSTEM权限获取,删Windows old

    一.[Windows.old]文件夹[右键]->[属性] 二.[安全]->[高级] 三.[更改] 四.添加[Everyone],点击[确定] 五.如下图,勾选两个选项,再[确定] 六.一路 ...

  5. JavaWeb学习总结-05 Servlet 与页面的交互(02)

    一 模拟请求数据 为了测试方便,把请求 json,txt, xml,html格式的文件放到了公网上面,可以通过以下地址请求: http://wx.glab.cn/xpxiaowu4java/json/ ...

  6. 捉襟见肘之UIScrollView 【一】

    参考地址:http://segmentfault.com/a/1190000002412930 另一个优秀的UIScrollView实践文章地址:http://tech.glowing.com/cn/ ...

  7. POJ3468A Simple Problem with Integers(区间加数求和 + 线段树)

    题目链接 题意:两种操作:一是指定区间的数全都加上一个数,二是统计指定区间的和 参考斌神的代码 #include <iostream> #include <cstring> # ...

  8. RequestDemo01

    package com.etc.requestdemo; import java.io.IOException;import java.io.PrintWriter; import javax.ser ...

  9. java编程思想-java中的并发(四)

    五. 新类库中的构件 Java SE5的java.util.concurrent引入了大量设计用来解决并发问题的新类.学习使用它们将有助于编写出更加简单而强壮的并发程序. 1. CountDownLa ...

  10. sql 编写横竖表转换

    将横表转为竖表,基本思想是: 1)将横表的多条数据,"压"成一条.相当于将这么多条分组,每组"压"成一条数据.利用group by 2) 再对竖表中的列,由特定 ...