1.工厂模式
我们可以把JavaScript中的对象看作成散列表,无非就是一组键值对,其中值可以是数据或函数。每个对象都是基于引用类型创建的。Object构造函数或对象字面量都可以用来创建单个对象,但是这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。我们可以使用工厂模式,用函数来封装以特定接口创建对象的细节,如:
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("wuyu", 20, "enginneer"); var person1 = createPerson("zhangsan", 29, "tearcher");
根据接受的参数通过createPerson()函数构建包含必要信息的Person对象,可以无数次的调用这个函数并且返回包含三个属性和一个方法的对象。
工厂模式虽然很好的解决的创建多个相似对象的问题,但却没有解决对象识别的问题,即怎么知道一个对象的类型。这里我们可以用构造函数模式来解决。
2.构造函数模式
ECMAScript中的构造函数可用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。如:
function Person(name, age, job) { this.name = name; this.age = age; this.job = job; this.sayName = function(){ alert(this.name); } } var person1 = new Person("wuyu", 20, "enginneer"); var person2 = new Person("zhangsan", 29, "tearcher");
这段代码中,Person()方法(当作构造函数使用时,首字母通常大写)替代了createPerson()函数,除此之外,还存在以下不同之处:
●没有显式地创建对象
●直接将属性和方法赋给了this对象
●没有return 语句
要创建Person的新实例,必须使用new操作符。以这种方法调用构造函数实际上会经历以下4个步骤:
●创建一个新对象
●将构造构造函数的作用域赋给新对象(因此this就是指向了这个新对象)
●执行构造函数中的代码(为这个新对象添加属性)
●返回新对象
上面的示例,person1和person2分别保存着Person的一个不同的实例,这两个对象都有一个constructor(构造函数)属性,该属性指向Person,如:
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true对象的consctructor属性最初是用来标识对象类型的,但是检测对象类型还是使用instanceof检测对象类型比较可靠些。这例子中创建的所有对象既是Object的实例,同时也是Person的实例.如下:
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Object); //true
创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型,而这就是构造函数模式胜过工厂模
式的地地方。
构造函数也是函数,区别构造函数与其他普通函数,主要就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,那它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName=function(){ alert(this.name); }; } //当作构造函数使用 var person = new Person("wuyan",20,"enginner"); person.sayName(); //wuyan //作为普通函数使用 Person("zhangsan",27,"Doctor"); window.sayName(); //zhangsan //在另一个对象的作用域中调用(将Person()放入o作用域中调用) var o = new Object(); Person.call(o,"Kristen",25,"Nuese"); o.sayName(); //Kristen构造函数模式虽然好用,但也并非没有缺点。最大的问题就是每个方法都要在每个实例上重新创建一遍。比如上面的person1和person2都有一个sayName()的方法,但那两个方法不是同一个Function的实例。ECMAScript中的函数是对象,因此每定义一个函数,也就是实例化了一个对象。创建两个实现的功能完全相同的Function实例真没必要,大可通过把函数定义转移到构造函数外部来解决这个问题。如下:
function Person(name,age,job){ this.name=name; this.age=age; this.job=job; this.sayName= sayName; } functin sayName = function(){ alert(this.name); }; var person1 = new Person("wuyu", 20, "enginneer"); var person2 = new Person("zhangsan", 29, "tearcher");将sayName转移到构造函数外部,而在构造函数内部我们将sayName属性设置成了全局的sayName函数。这样一来,sayName包含的是一个指向函数的指针,因此person1 和person2就共享了在全局作用域中的同一个sayName()函数。可是,对象需要定义很多方法,那么就要定义很多全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。这些问题我们可以使用原型模式来解决。
3.原型模式
3.1什么是原型
我们创建的每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,而这个对 象的用途是包含可以由特定类型的所有实例共享的属性和方法。prototype就是通过调用构造函数创建的那个 对象实例的原型对象。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。如下:
function Person() { } Person.prototype.name = "wuyan"; Person.prototype.age = 20; Person.prototype.job = "engineer"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); person.sayName(); //wuyan var person2 = new Person(); person2.sayName(); //wuyan alert(person1.sayName == person2.sayName); //true
我们将所有属性和sayName()方法直接添加到了Person的prototype属性中,在创建新对象时,这些属性和方法是由所有实例共享的。
3.2理解原型对象
无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型都会自动获得一个constructor(构造函数)属性,这个属性一个指向prototype属性所在函数的指针。
当调用构造函数创建一个新实例后,该实例的内部将包含一个指针(内部属性),指向构造函数的原型对象。这个内部属性叫做_proto_,可以在浏览调度中看见,但对于脚本来说则是完全不可见的。我们只要记住,就是这个连接存在于实例与构造函数的原型属性之间,而不是存在于实例与构造函数之间。
Person.prototype指向了原型对象,而Person.prototype.constructor又指回了Person。Person.prototype的对象中除了包含constructor属性之外,还包括后来添加的其他属性。Person的每个实例person1和person2都包含一个内部属性,该属性仅仅指向了Person.prototype,它们与构造函数没有直接的关系。
我们可以使用isPrototypeOf()方法来确定对象之间是否存在在这种关系,当对象的_proto_属性指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就会返回true。如:
alert(Person.prototype.isPrototypeOf(person1)); //true alert(Person.prototype.isPrototypeOf(person2)); //true虽然可以通过对象实例访问保存在原型中的值,但去不能通过对象实例重写原型中的值。如下:
function Person() { } Person.prototype.name = "wuyan"; Person.prototype.age = 20; Person.prototype.job = "engineer"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = "poxiao"; alert(person1.name); //poxiao--->来自实例,在搜索name属性时,在对象实例本身就可以 //找到,就不 必再搜索原型了 alert(person2.name); //wuyan--->来自原型 //当然,我们可以使用delete操作符删除实例属性,这样就可以重新访问到原型中的属性了。如下: function Person() { } Person.prototype.name = "wuyan"; Person.prototype.age = 20; Person.prototype.job = "engineer"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); person1.name = "poxiao"; alert(person1.name); //poxiao--->来自实例 alert(person2.name); //wuyan--->来自原型 delete person1.name; alert(person1.name); //wuyan--->来自原型使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(它是从Object继承来的)只在给定属性存在于对象实例中时,才会返回true。如下:
function Person() { } Person.prototype.name = "wuyan"; Person.prototype.age = 20; Person.prototype.job = "engineer"; Person.prototype.sayName = function(){ alert(this.name); } var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false person1.name = "poxiao"; alert(person1.name); //poxiao-->来自实例 alert(person1.hasOwnProperty("name")); //true alert(person2.name); //wuyan--->来自原型 alert(person2.hasOwnProperty("name")); //false delete person1.name; alert(person1.name); //wuyan--->来自原型 alert(person1.hasOwnProperty("name")); //false
3.3原型与in操作符
用两种方式使用in操作符:一、单独使用;二、在for-in中使用。
单独使用时,in操作符会在通过对象能够访问属性时返回true,无论该属性存在于实例中还是原型中。如下:
function Person(){} Person.prototype.name="wuyan"; Person.prototype.age=20; Person.prototype.job="engineer"; Person.sayName=function(){ alert(this.name); } var person1=new Person(); var person2=new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name="poxiao"; alert(person1.name); //poxiao来自实例 alert(person1.hasOwnProperty("name")); //true alert("name" in person1); //true alert(person2.name); //wuyan来自原型 alert(person2.hasOwnProperty("name")); //false alert("name" in person2); //true delete person1.name; alert(person1.name); //wuyan来自原型 alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true alert(person1.hasOwnProperty("qqqq")); //false alert("qqqq" in person1); //false //同时使用hasOwnProperty()方法和in操作符,就可以确定该属性到底是存在于对象中, //还是存在于原型中。如下: function hasPrototypeProperty(object, name){ return !object.hasOwnProperty(name) && (name in object) ; }在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。根据规定,所有开始人员定义的属性都是可枚举的(除ie8及更早版本)。如:
var o={ toString:function(){ return "My Object"; } } for (var prop in o){ if(prop == "toString"){ alert("Found toString"); //在IE中不会显示 } }
3.4更简单的原型
用一个包含所有属性和方法的对象字面量来重写整个原型对象。如下:
function Person() { } Person.prototype = { name : "wuyan", age : 20, job : "engineer", sayName : function() { alert(this.name); } }
结果与先前的相同,但有一个不相同的:constructor属性不再指向Person了。我们这样写,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。
当然,我们可以特意将它设置回适当的值。如下:
function Person() { } Person.prototype = { constructor : Person, name : "wuyan", age : 20, job : "engineer", sayName : function() { alert(this.name); } }
3.5原型的动态性
如果重写了整个原型对象,就相当于把原型修改为另外一个对象了(因为调用构造函数是会为实例添加一个指向最初原型的_proto_指针),就等于切断了构造函数与最初原型之间原联系。如下:
function Person() { } var friend = new Person(); Person.prototype = { constructor : Person, name : "wuyan", age : 20, job : "enginner", sayName : function() { alert(this.name); } }; friend.sayName(); //error
3.6原生对象的原型
原型模式的重要性不公体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建,如Object、Array、String等。
3.7原型对象的问题
原型模式也是有缺点的。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。原型模式最大的问题是由其共享的本性所导致的,尤其对引用类型属性。如下:
function Person() { } Person.prototype = { constructor : Person, name : "wuyan", age : 20, job : "engineer", friends : ["poxiao", "wyl"], sayName : function() { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("wx"); alert(person1.friends); //poxiao,wyl,wx alert(person2.friends); //poxiao,wyl,wx alert(person1.friends === person2.friends); //true 问题出来了,person1结 //交了新朋友意味着person2也必须结交这个朋友
4.组合使用构造函数模式和原型模式
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内在。这种混合模式还支持向构造函数传递参数。如下:
function Person(name ,age ,job){ this.name = name; this.age = age; this.job = job; this.friends = ["poxiao", "wyl"]; } Person.prototype = { constructor : Person, sayName: function() { this.name; } } var person1 = new Person("wuyan", 20, "engineer"); var person2 = new Person("shaobo", 26, "teacher"); person1.friends.push("wyl"); alert(person1.friends); //poxiao,wyl,wyl alert(person2.friends); //poxiao,wyl alert(person1.friends === person2.friends); //false alert(person1.sayName=== person2.sayName); //true这种构造函数与原型混合的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。
5.动态原型模式
在其他OO语言的开始人员在看到独立的构造函数和原型时,很可能会感到非常困惑。动态原型模式正是致力于解决这个问题的一个方案,把它所有信息都封装在了构造函数中,而通过在构造函数中初始化原型,又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。如下:
function Person(name , age ,job) { //属性 this.name = name; this.age = age; this.job = job; //方法 if(typeof this.sayName != "function") { Person.prototype.sayName = function(){ alert(this.name); }; } } var friends = new Person("wuyan", 20, "engineer"); friends.sayName();
使用动态原型模式时,不能使用对象字面量重写原型。如果在已经创建了实例的情况下重写原型,那么就会切断现有实例与新原型之间的联系。
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 friend = new Person("wuyan", 20, "engineer"); friend.sayName(); //wuyan
除了使用new操作符并把使用的包装函数叫做构造函数外,这个模式跟工厂模式一模一样。
关于寄生构造函数模式,有一点要需要说明:首先,返回的对象与构造函数或者与构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没有什么不同。为此,不能依赖instanceof操作符来确定对象类型。由于存在上述问题,我们建议在可以使用其他模式的情况下,不要使用这种模式。
7.稳妥构造函数模式
所谓稳妥对象,批的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者在防止数据被其他应用程序改动时使用。稳妥构造函数与寄生构造函数有两点不同:一是新创建对象的实例方法不引用this;二是不使用new操作符调用构造函数。如下:
function Person(name, age, job) { //创建要返回的对象 var o = new Object(); //可以在这定义私有变量和函数 //添加方法 o.sayName = function() { alert(name); } return o; } var friend = Person("wuyan", 20, "engineer"); friend.sayName(); //wuyan
注意:在以这种模式创建的对象中,除了使用sayName()方法之外,没有其他办法访问name的值。这样,变量friend 中保存的就是一个稳妥对象。安全性高。
和寄生构造函数模式一样,返回的对象与构造函数或者与构造函数的原型属性之间没一点关系。
相关推荐
这篇博客“javascript创建对象的方式(二)”可能详细介绍了在JavaScript中除了最基础的字面量语法之外的其他创建对象的方法。这里我们将深入探讨几种常见的创建对象的方式。 1. **构造函数**: JavaScript中的...
JavaScript创建对象的方法主要有以下几种: 一、直接创建 直接创建是通过new Object()来创建一个新的空对象,然后逐步给这个对象添加属性和方法。例如,创建一个名为person1的对象,并添加name、gender属性,以及一...
工厂模式是最早期的对象创建方式之一,它通过一个函数来创建对象,并返回新创建的对象。这种方法的核心是利用函数封装创建对象的细节,然后通过返回值来创建对象实例。工厂模式的优点是解决了创建多个具有相同属性和...
如下所示: 代码如下: var person...上面的例子创建了一个名为person的对象,并为它添加了三个属性(name、age和job)和一个方法(sayName())。其中,sayName()方法用于显示this.name()的值。早期的JavaScript开发人员
首先,让我们从最基础的创建对象的方式开始。在JavaScript中,可以使用字面量语法来创建一个简单的对象: ```javascript var obj = { name: 'John', age: 30, sayHello: function() { console.log('Hello, ' + ...
在JavaScript这门动态类型的编程语言中,创建对象是其核心特性之一。本文将详细探讨三种主要的创建JavaScript对象的方法,并结合“源码”与“工具”的概念,来深入理解这些方式在实际开发中的应用。 一、字面量...
本文将深入探讨JavaScript创建对象的8种常见方式,帮助你更好地理解和掌握这门动态类型的编程语言。 1. **字面量(Literal)方式** 这是最简单直接的创建对象的方式,通过大括号{}来定义一个对象,然后在内部用...
javascript对象创建方法总结,通过这些方法的总结,可以对对象有了更深一步的了解,也加深了对对象的巩固认识。
这篇博文主要探讨了JavaScript创建对象的几种常见方式,这对于理解和掌握JavaScript面向对象编程至关重要。在实际开发中,了解并灵活运用这些方法能够提高代码的可读性和可维护性。下面,我们将详细讲解标题中提到的...
在JavaScript中,创建对象是编程的基本操作之一。它主要用于构建复杂的数据结构,实现面向对象编程。JavaScript提供了多种创建对象的方法,包括字面量语法、构造函数、对象原型、工厂函数、模块模式以及近年来引入的...
在JavaScript中,对象是核心概念之一,它们是无序属性的集合,允许我们存储和操作数据。对象的属性可以是基本值(如字符串、数字、布尔值)或更复杂的对象和函数,使得JavaScript具备强大的数据结构和面向对象编程...
### JavaScript 动态创建对象属性详解 #### 一、引言 在JavaScript中,对象是其核心特性之一,能够帮助开发者高效地管理数据和逻辑。本文将深入探讨如何在JavaScript中动态创建对象属性,这对于构建灵活的应用程序...
在提供的资源中,《代码之美》PDF文件可能包含了关于编程实践和代码风格的指导,而《Javascript面向对象编程》PPT可能更具体地阐述了JavaScript OOP的细节和示例。学习这些材料将有助于深入理解JavaScript的面向对象...
### JavaScript面向对象创建对象的方式小结 #### 一、引言 在JavaScript中,面向对象编程(OOP)是一种非常重要的编程范式。它通过创建和操作对象来组织代码,提高代码的复用性和可维护性。本文将详细介绍...
2. **对象创建与原型Prototype**: JavaScript使用`new`关键字创建对象实例,原型链允许对象间共享属性和方法,`__proto__`或`prototype`属性是理解这一机制的关键。 3. **函数对象Function**: 在JavaScript中,函数...
浅析在javascript中创建对象的各种模式.doc
- **第三章:JavaScript中的类和对象**:详细介绍如何使用JavaScript创建类和对象。 - **第四章:继承**:探讨JavaScript中实现继承的不同方式。 - **第五章:封装和私有性**:讲解如何在JavaScript中实现封装以及...
在JavaScript中,创建对象是面向对象编程的基础。本文主要解析了JavaScript中几种常见的创建对象的模式,包括工厂模式、构造函数模式、原型模式以及它们的组合和动态原型模式。 首先,JavaScript是一门基于原型的...
在JavaScript中,创建对象是面向对象编程的基础。虽然JavaScript没有像其他面向对象语言那样的"类"概念,但它提供了多种创建对象的模式,以满足不同场景的需求。这些模式包括工厂模式、构造函数模式、原型模式以及...
不使用`new`调用函数也会创建对象,只是不会链接到正确的原型。 4. **动态类型**: JavaScript是动态类型的,这意味着变量的类型可以在运行时改变。这使得在创建对象时更加灵活,但也可能导致一些潜在的类型错误。...