首页 web前端 js教程 JavaScript中创建对象的7种模式

JavaScript中创建对象的7种模式

Feb 07, 2017 pm 02:19 PM

ECMA-262把对象定义为:”无需属性的集合,其属性可以包含基本值、对象或者函数。”严格来讲,这就相当于说明对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。正因为这样,我们可以把ECMAScript的对象想象成散列表:无非就是一组名对值,其中值可以是数据或函数。 


创建自定义对象最简单的方式就是创建一个Object的实例,然后再为他添加属性和方法,如下所示:

var person = new Object();
person.name = "liubei";
person.age = 29;
person.job = "shayemuyou";

person.sayName = function(){
    alert(this.name);
}
登录后复制

上面的例子创建了一个名为person的对象,并为他添加了三个属性和一个方法。其中sayName()方法用于显示name属性,this.name将被解析为person.name,早期的开发人员经常使用这个模式来创建对象,后来对象字面量的方法成了创建对象的首选模式,上面的例子用对象字面量的语法可以写成如下这样:

var person = {    name:"liubei",    age:29,    job:"shayemuyou",    sayName:function(){        alert(this.name);    }}
登录后复制

这个例子中的person对象和前面的对象是一样的,都有相同的属性和方法。


虽然Object构造函数或者对象字面量的方法都可以用来创建单个对象,但是这些方法有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为了解决这个方法,人们开始使用工厂模式的一种变体。


1 工厂模式

工厂模式是软件工程领域一种广为人知的设计模式,这种模式抽象了创建具体对象的过程。考虑到ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节,如下所示:

function createPerson(name, age, job){    
var o = new Object();    
o.name = name;    
o.age = age;    
o.job = job;    
o.sayName = function(){        
alert(this.name);    
}    
return o;}
var person1 = createPerson("wei",25,"software");
var person2 = createPerson("bu",25,"software");
登录后复制

函数createPerson()能够根据接受的参数来构建一个包含所有必要信息的Person对象。可以多次调用这个函数,每次都会返回一个包含三个属性一个方法的对象。工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题,即怎么样知道这是哪个对象类型。


2 构造函数模式

像Array、Object这样的原生构造函数,在运行时会自动出现在执行环境中。此外,我们可以创建自定义个构造函数,从而定义自定义类型的属性和方法。例如,我们可以使用构造函数重写上个例子:

function Person(name, age, job){    
this.name = name;    
this.age = age;    
this.job = job;    
this.sayName = function(){        
alert(this.name);    
}}
var person1 = new Person("wei",25,"software");
var person2 = new Person("bu",25,"software");
登录后复制

在这个例子中,Person()函数取代了createPerson()函数,我们注意到Person()与createPerson()的不同之处在于:

  • 没有显式的创建对象

  • 直接将属性和方法赋值给this对象

  • 没有return语句

此外,还应该注意到函数名Person使用的是大写字母P。按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。这个做法借鉴了其他OO语言,主要是为了区别于ECMAScript中的其他函数。因为构造函数本身也是函数,只不过可以创建对象而已。

要创建一个Person实例,必须使用new操作符。以上这种方式会经过以下四个步骤:

1.创建一个新对象

2.将构造函数的作用域赋给新对象(因此this指向这个新对象)

3.执行构造函数中的代码

4.返回新对象

在前面例子的最后,person1和person2分别保存着Person的一个不同的实例。这两个对象都有一个constructor(构造函数)属性,该属性指向Person。如下:

console.log(person1.constructor == Person);     //true
console.log(person2.constructor == Person);     //true
console.log(person1.constructor == Person);     //true
console.log(person2.constructor == Person);     //true
登录后复制

对象的constructor属性最初是用来标识对象类型的。但是,提到检测对象类型,还是instanceof操作符比较可靠一些。我们在这个例子中创建的对象都是Object对象的实例,也是Person对象的实例,这一点通过instanceof操作符可以验证。

console.log(person1 instanceof Object);     //true
console.log(person1 instanceof Person);     //true
console.log(person2 instanceof Object);     //true
console.log(person2 instanceof Person);     //true
登录后复制

创建自定义的构造函数意味着将来可以将他的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式的地方。在这个例子中,person1和person2之所以同是Object的实例,是因为所有的对象都继承自Object。


构造函数的主要问题,就是每个方法都要在实例上重新创建一遍,造成内存浪费。在前面的例子中,person1和person2都有一个名为sayName()的方法,但是两个方法不是同一Function的实例。不要忘了ECMAScript中的函数也是对象,因此每定义一个函数,也就是实例化了一个对象,从逻辑角度讲,此时的构造函数可以这样定义:

function Person(name, age, job){    
this.name = name;    
this.age = age;    
this.job = job;    
this.sayName = new Function("alert(this.name);")   //与声明函数在逻辑上是等价的}
登录后复制

从这个角度来看构造函数,更容易看明白每个Person实例都会包含一个不同的Function实例的本质。说明白些,会导致不同的作用域链和标识符解析,但是创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的,以下代码可以证实这一点。

alert(person1.sayName == person2.sayName);  //false
登录后复制

然而,创建两个完成同样任务的Function实例的确没有必要;况且有this对象在,根本不用在执行代码前就把函数绑定到特定的对象上。因此,可以像下面这样,通过把函数定义转移到构造函数外部来解决这个问题。

function Person(name, age, job){    
this.name = name;    
this.age = age;    
this.job = job;    
this.sayName = sayName;}
function sayName(){    
alert(this.name);
登录后复制

这样做解决了多个函数解决相同问题的问题,但是有产生了新的问题,在全局作用域中实际上只被某个对象调用,这让全局对象有点名不副实。更让人无法接受的是:如果对象需要定义很多方法,那么就要定义很多全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。好在这些问题可以使用原型模式来解决。

3 原型模式


我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的实例就是让所有实例共享它所包含的属性和方法。换句话说,不必在构造函数中定义对象的实例信息,而是可以将这些信息直接添加到原型对象中,如下所示:

function Person(){
}
Person.prototype.name = "wei";
Person.prototype.age = 27;
Person.prototype.job = "Software";
Person.prototype.sayName = function(){
    alert(this.name);
}

var person1 = new Person();
person1.sayName();      //"wei"

var person2 = new Person();
person2.sayName();      //"wei"

alert(person1.sayName == person2.sayName);
登录后复制

在此,我们将sayName()方法和所有的属性直接添加在了Person的prototype属性中,构造函数变成了空函数。即便如此,我们仍然可以通过构造函数来创建新对象,而且新对象还会具有相同的属性和方法。但是与构造函数不同的是,新对象的这些属性和方法是由所有实例共享的。换句话说,person1和person2访问的都是同一组属性和同一个sayName()函数。要理解原型模式的工作原理,就必须先理解ECMAScript中原型对象的性质。

原型对象的本性由于篇幅太长将会在下一章节详细分析。上面我们说了原型模式的好处,接下来我们来看一下原型模式的缺点。原型模式省略了为构造函数传递参数的这一环节,结果所有实例在默认情况下都具有相同的属性值。这会在某些程度上带来一种不便,这并不是原型模式最大的问题,因为如果我们想为一个通过原型模式创建的对象添加属性时,添加的这个属性就会屏蔽原型对象的保存的同名属性。换句话说,就是添加的这个属性会阻止我们去访问原型中的属性,但并不会改变原型中的属性。

原型模式最大的问题是由其共享的本质所导致的。原型中所有的属性被很多实例共享,这种共享对函数非常合适,对包含基本值的属性也说的过去,但是对引用类型的属性值来说问题就比较突出了,下面我们来看一个例子:

function Person(){}Person.prototype = {    
constructor:Person,    
name:"wei",    
age:29,    
friends:["乾隆","康熙"],    
sayName:function(){        
alert(this.name);    
}}
var person1 = new Person();
var person2 = new Person();
person1.friends.push("嬴政");
console.log(person1.friends);   //["乾隆","康熙","嬴政"]
console.log(person2.friends);   //["乾隆","康熙","嬴政"]
console.log(person1.friends === person2.friends);   //true
登录后复制

上面的例子中,Person.prototype对象有一个名为friends的属性,该属性包含一个字符串数组。然后创建了两个Person的实例,接着修改person1.friends引用的数组,向数组中添加一个字符串,由于数组存在于Person.prototype中而不是person1中,所以person2.friends也会被修改。但是一般每个对象都是要有属于自己的属性的,所以我们很少看到有人单独使用原型模式来创建对象。

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

创建自定义类型最常见的方式就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。下面的代码重写了前面的例子:

function Person(name, age){    
this.name = name;    
this.age = age;    
this.friends = ["乾隆","康熙"];}
Person.prototype = {    
constructor:Person,    
sayName:function(){        
alert(this.name);    
}}
var person1 = new Person("wei",29);
var person2 = new Person("bu",25);
person1.friends.push("嬴政");
console.log(person1.friends);   //["乾隆", "康熙", "嬴政"]
console.log(person2.friends);   //["乾隆", "康熙"]
console.log(person1.friends === person2.friends);   //false
console.log(person1.sayName === person2.sayName);   //true
登录后复制

在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。所以修改了person1.friends并不会改变person2.friends,因为他们分别引用了不同的数组。

这种构造函数与原型模式混成的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用的一种默认形式。

5 动态原型模式


有其他OO语言经验的开发人员在看到独立的构造函数和原型时,很可能会感到非常的困惑。动态原型模式就是用来解决这个问题的一个方案,它把所有的信息都封装在了构造函数中,而通过构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否要初始化原型。来看一个例子:

function Person(name, age){    
this.name = name;    
this.age = age;    
this.friends = ["乾隆","康熙"];    
//注意if语句    
if(typeof this.sayName!="function"){        
Person.prototype.sayName = function(){            
alert(this.name);        
}    
}}
var person1 = new Person("wei",29);
person1.friends.push("嬴政");
person1.sayName();
登录后复制

注意构造函数代码中的if语句,这里只在sayName()方法不存在的情况下才会将它添加到原型中。这断代码只有在第一次调用构造函数的时候才会被执行。此后,原型已经被初始化,不需要再做什么修改。不过要记住,这里所做的修改能立即在所有实例中得到反映。因此,这种方法可以说确实非常完美。其中if语句检查的是初始化之后应该存在的任何方法和属性–不必再用一大堆if来检查每个属性和方法,只检查其中一个即可。对于采用这样模式创建的对象,还可以使用instanceof操作符来确定他的类型。

注意:使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有的实例与新原型之间的联系。


6 寄生构造函数模式


通常,在上述几种模式都不适合的情况下可以使用寄生构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象,但从表面看,这个函数又很像典型的构造函数。来看一个例子:

function Person(name, age, job){    
var o = new Object();    
o.name = name;    
o.age = age;    
o.job = job;    
o.sayName = function(){        
alert(this.name);    }    
return o;}
var person = new Person("wei",29,"banzhuan");
person.sayName();   //"wei"
登录后复制

在这个例子中,Person函数创建了一个对象,并以相应的属性和方法初始化该对象,然后返回了这个对象。除了使用new操作符把使用的包装函数叫做构造函数之外,这个模式和工厂模式并没有多大的区别。构造函数在不返回值的情况下,会默认返回新对象的实例。而通过在构造函数的末尾添加一个return语句,可以重写调用构造函数时返回的值。

这个模式可以在特殊的情况下来为对象创建构造函数。假设我们想创建一个具有额外方法的特殊数组。由于不能直接修改Array构造函数,因此可以使用这个模式:

function SpecialArray(){
    //创建数组
    var values = new Array();

    //添加值
    values.push.apply(values,arguments);

    //添加方法
    values.toPipedString = function(){
        return this.join("|");
    }

    //返回数组
    return values;
}
var colors = new SpecialArray("red","blue","green");
console.log(colors.toPipedString());    //red|blue|green
登录后复制

在这个例子中,我们创建了一个名为SpecialArray的构造函数。在这个函数的内部,首先创建了一个数组,然后push()方法初始化了数组的值。随后又给数组实例添加了toPipedString()方法,用来返回以竖线分隔的数组值。最后将数组以函数的形式返回。接着,我们调用了SpecialArray构造函数,传入了初始化的值,并调用了toPipedString()方法。


关于寄生构造函数模式,有一点需要声明:首先,返回的对象与构造函数或者构造函数的原型没有任何关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象的类型。由于存在这一的问题,我们建议在可以使用其他模式的情况下不要使用这种模式。


7 稳妥构造函数模式


道格拉斯·克拉克福德发明了JavaScript中的稳妥对象这个概念。所谓稳妥对象,是指没有公共属性,而且其方法也不引用this对象。稳妥对象最适合在一些安全环境中(这些环境会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数遵循的与寄生构造函数类似的模式,但又两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

function Person(name, age, job){
    //创建要返回的新对象
    var o = new Object();

    //可以在这里定义私有变量和函数

    //添加方法
    o.sayName = function(){
        alert(this.name);
    };

    //返回对象
    return o;
}
登录后复制

注意,在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。可以像下面使用稳妥的Person构造函数:

var person =Person("weiqi",22,"banzhuan");
person.sayName();   //weiqi
登录后复制

这样,变量person中保存的是一个稳妥对象,而除了sayName()方法外,没有别的方式可以访问其他数据成员。即使有其他代码会给这个对象添加方法或数据成员,但也不可能有别的办法访问传入到构造函数中的原始数据。稳妥构造函数模式提供的这种安全性,使得他非常适合在某些安全执行环境–例如,ADsafe(www.adsafe.org)提供的环境下使用。 

       

注意:与寄生构造函数模式类似,使用稳妥构造函数模式创建的对象与构造函数之间没有什么关系,因此instanceof操作符对这种对象也没有意义。

以上就是JavaScript中创建对象的7种模式的内容,更多相关内容请关注PHP中文网(www.php.cn)!


本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
3 周前 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25:如何解锁Myrise中的所有内容
4 周前 By 尊渡假赌尊渡假赌尊渡假赌

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

activex部件不能创建对象如何解决 activex部件不能创建对象如何解决 Jan 24, 2024 pm 02:48 PM

解决办法:1、检查拼写和路径;2、添加对组件的引用;3、检查注册表;4、以管理员身份运行;5、更新或修复Office;6、检查安全软件;7、使用其他版本的组件;8、查看错误信息;9、查找其他解决方案。详细介绍:1、检查拼写和路径:确保对象的名称和路径没有拼写错误,且文件确实存在于指定的路径中;2、添加对组件的引用等等。

如何使用WebSocket和JavaScript实现在线语音识别系统 如何使用WebSocket和JavaScript实现在线语音识别系统 Dec 17, 2023 pm 02:54 PM

如何使用WebSocket和JavaScript实现在线语音识别系统引言:随着科技的不断发展,语音识别技术已经成为了人工智能领域的重要组成部分。而基于WebSocket和JavaScript实现的在线语音识别系统,具备了低延迟、实时性和跨平台的特点,成为了一种被广泛应用的解决方案。本文将介绍如何使用WebSocket和JavaScript来实现在线语音识别系

WebSocket与JavaScript:实现实时监控系统的关键技术 WebSocket与JavaScript:实现实时监控系统的关键技术 Dec 17, 2023 pm 05:30 PM

WebSocket与JavaScript:实现实时监控系统的关键技术引言:随着互联网技术的快速发展,实时监控系统在各个领域中得到了广泛的应用。而实现实时监控的关键技术之一就是WebSocket与JavaScript的结合使用。本文将介绍WebSocket与JavaScript在实时监控系统中的应用,并给出代码示例,详细解释其实现原理。一、WebSocket技

如何利用JavaScript和WebSocket实现实时在线点餐系统 如何利用JavaScript和WebSocket实现实时在线点餐系统 Dec 17, 2023 pm 12:09 PM

如何利用JavaScript和WebSocket实现实时在线点餐系统介绍:随着互联网的普及和技术的进步,越来越多的餐厅开始提供在线点餐服务。为了实现实时在线点餐系统,我们可以利用JavaScript和WebSocket技术。WebSocket是一种基于TCP协议的全双工通信协议,可以实现客户端与服务器的实时双向通信。在实时在线点餐系统中,当用户选择菜品并下单

如何使用WebSocket和JavaScript实现在线预约系统 如何使用WebSocket和JavaScript实现在线预约系统 Dec 17, 2023 am 09:39 AM

如何使用WebSocket和JavaScript实现在线预约系统在当今数字化的时代,越来越多的业务和服务都需要提供在线预约功能。而实现一个高效、实时的在线预约系统是至关重要的。本文将介绍如何使用WebSocket和JavaScript来实现一个在线预约系统,并提供具体的代码示例。一、什么是WebSocketWebSocket是一种在单个TCP连接上进行全双工

JavaScript和WebSocket:打造高效的实时天气预报系统 JavaScript和WebSocket:打造高效的实时天气预报系统 Dec 17, 2023 pm 05:13 PM

JavaScript和WebSocket:打造高效的实时天气预报系统引言:如今,天气预报的准确性对于日常生活以及决策制定具有重要意义。随着技术的发展,我们可以通过实时获取天气数据来提供更准确可靠的天气预报。在本文中,我们将学习如何使用JavaScript和WebSocket技术,来构建一个高效的实时天气预报系统。本文将通过具体的代码示例来展示实现的过程。We

javascript中如何使用insertBefore javascript中如何使用insertBefore Nov 24, 2023 am 11:56 AM

用法:在JavaScript中,insertBefore()方法用于在DOM树中插入一个新的节点。这个方法需要两个参数:要插入的新节点和参考节点(即新节点将要被插入的位置的节点)。

简易JavaScript教程:获取HTTP状态码的方法 简易JavaScript教程:获取HTTP状态码的方法 Jan 05, 2024 pm 06:08 PM

JavaScript教程:如何获取HTTP状态码,需要具体代码示例前言:在Web开发中,经常会涉及到与服务器进行数据交互的场景。在与服务器进行通信时,我们经常需要获取返回的HTTP状态码来判断操作是否成功,根据不同的状态码来进行相应的处理。本篇文章将教你如何使用JavaScript获取HTTP状态码,并提供一些实用的代码示例。使用XMLHttpRequest

See all articles