JavaScript로 객체를 생성하는 7가지 패턴

黄舟
풀어 주다: 2017-02-07 14:19:32
원래의
828명이 탐색했습니다.

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 객체는 이전 객체와 동일하며 속성과 메서드도 동일합니다.


객체 생성자 또는 객체 리터럴 메서드를 사용하여 단일 객체를 생성할 수 있지만 이러한 메서드에는 분명한 단점이 있습니다. 동일한 인터페이스를 사용하여 생성된다는 점입니다. 객체는 반복되는 코드를 많이 생성합니다. 이 문제를 해결하기 위해 사람들은 공장 패턴의 변형을 사용하기 시작했습니다.


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()의 차이점을 알 수 있습니다. is:

  • 개체를 명시적으로 생성하지 않았습니다.

  • 이 개체에 속성과 메서드를 직접 할당합니다.

  • No return 문

또한 함수 이름 Person이 대문자 P를 사용한다는 점에도 유의해야 합니다. 관례적으로 생성자는 항상 대문자로 시작해야 하고, 생성자가 아닌 경우는 항상 소문자로 시작해야 합니다. 이 접근 방식은 주로 ECMAScript의 다른 기능과 구별하기 위해 다른 OO 언어에서 차용되었습니다. 생성자 자체도 함수이기 때문에 객체를 생성할 수 있습니다.

Person 인스턴스를 생성하려면 new 연산자를 사용해야 합니다. 위의 방법은 다음 네 단계를 거칩니다.

1. 새 개체 만들기

2. 생성자의 범위를 새 개체에 할당합니다(따라서 이 새 개체를 가리킵니다).

3. 생성자에서 코드를 실행합니다.

4. 새 개체를 반환합니다.

다른 인스턴스입니다. 두 객체 모두 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
로그인 후 복사

객체의 생성자 속성은 처음에 객체 유형을 식별하는 데 사용됩니다. 그러나 객체 유형을 감지하는 경우에는 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)를 참고해주세요!


원천:php.cn
본 웹사이트의 성명
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
인기 튜토리얼
더>
최신 다운로드
더>
웹 효과
웹사이트 소스 코드
웹사이트 자료
프론트엔드 템플릿
회사 소개 부인 성명 Sitemap
PHP 중국어 웹사이트:공공복지 온라인 PHP 교육,PHP 학습자의 빠른 성장을 도와주세요!