Yiyi가 JavaScript로 객체(클래스)를 생성하는 8가지 방법을 소개합니다. 마음에 드셨으면 좋겠습니다.
1. Object 생성자를 사용하여 객체 생성
다음 코드는 person 객체를 생성하고 두 가지 방법으로 Name 속성 값을 인쇄합니다.
var person = new Object(); person.name="kevin"; person.age=31; alert(person.name); alert(person["name"])
위 작성 방법의 또 다른 형태는 객체 리터럴을 사용하여 객체를 만드는 것입니다. person[“5”]에 놀라지 마세요. 여기서는 허용됩니다. 그렇지 않으면 이것을 사용하십시오. 괄호 안에는 person["my age"]와 같이 필드 사이에 공백이 있을 수 있습니다.
var person = { name:"Kevin", age:31, 5:"Test" }; alert(person.name); alert(person["5"]);
객체 생성자 또는 객체 리터럴이 있더라도 단일 객체를 생성하는 데 사용할 수 있지만 이러한 방법에는 분명한 단점이 있습니다. 동일한 인터페이스를 사용하여 많은 객체를 생성하면 중복 코드가 많이 생성됩니다. 이 문제를 해결하기 위해 사람들은 공장 패턴의 변형을 사용하기 시작했습니다.
2. 팩토리 패턴
팩토리 패턴은 소프트웨어 공학 분야에서 잘 알려진 디자인 패턴이라는 점을 고려하여 만든 패턴입니다. 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("Nicholas", 29, "Software Engineer"); var person2 = createPerson("Greg", 27, "Doctor");
팩토리 패턴은 유사한 객체를 여러 개 생성하는 문제는 해결하지만 객체 인식 문제(즉, 객체의 유형을 아는 방법)는 해결하지 못합니다. . JavaScript
의 개발로 또 다른 새로운 패턴이 등장했습니다.
3. 생성자 패턴
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("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
이 예에서는 Person() 함수가 createPerson() 함수를 대체합니다. createPerson()과 동일한 부분 외에도 Person()의 코드에는 다음과 같은 차이점이 있음을 확인했습니다.
객체가 명시적으로 생성되지 않았습니다.
에는 이 객체에 속성과 메서드를 직접 할당합니다.
에는 return 문이 없습니다.
Person의 새 인스턴스를 생성하려면 new 연산자를 사용해야 합니다. 이런 방식으로 생성자를 호출하면 실제로 다음 4단계를 거칩니다.
(1) 새 객체를 생성합니다.
(2) 범위를 구성합니다. 함수가 새 객체에 할당됩니다(따라서 이는 새 객체를 가리킵니다).
(3)은 생성자에서 코드를 실행합니다(새 객체에 속성을 추가합니다).
(4)는 새 객체를 반환합니다. .
이전 예의 끝에서 person1과 person2는 각각 다른 Person 인스턴스를 보유합니다. 두 객체 모두 아래와 같이 Person을 가리키는 생성자 속성을 가지고 있습니다.
alert(person1.constructor == Person); //true alert(person2.constructor == Person); //true
객체의 생성자 속성은 처음에 객체 유형을 식별하는 데 사용됩니다. 그러나 객체 유형을 감지하는 경우에는 instanceof 연산자가 더 안정적입니다. 이 예제에서 생성한 모든 객체는 Object 인스턴스이자 Person 인스턴스이며, 이는 instanceof 연산자를 통해 확인할 수 있습니다.
alert(person1 instanceof Object); //true alert(person1 instanceof Person); //true alert(person2 instanceof Object); //true alert(person2 instanceof Person); //true
사용자 정의 생성자를 생성한다는 것은 나중에 해당 인스턴스를 특정 유형으로 식별할 수 있다는 것을 의미합니다. 여기서 생성자 패턴이 팩토리 패턴보다 성능이 뛰어납니다. 이 예에서 person1과 person2는 모두 Object의 인스턴스입니다. 모든 개체가 Object에서 상속되기 때문입니다.
생성자 문제
생성자 패턴은 사용하기 쉽지만 단점이 없는 것은 아닙니다. 생성자를 사용할 때의 주요 문제점은 각 인스턴스에서 각 메소드를 다시 작성해야 한다는 것입니다.
ECMAScript의 함수는 객체이므로 함수가 정의될 때마다 객체가 인스턴스화됩니다. 논리적인 관점에서 볼 때 이때의 생성자는 이렇게 정의할 수도 있습니다.
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = new Function("alert(this.name)"); // 与声明函数在逻辑上是等价的 }
이러한 관점에서 생성자를 살펴보면 각 Person 인스턴스에 다른 Function 인스턴스(name 속성을 표시하기 위해)가 포함되어 있다는 본질을 더 쉽게 이해할 수 있습니다. 확실히 말하면, 이런 방식으로 함수를 생성하면 범위 체인과 식별자 확인이 달라지지만, Function의 새 인스턴스를 생성하는 메커니즘은 여전히 동일합니다. 따라서 다음 코드에서 증명할 수 있듯이 서로 다른 인스턴스에서 동일한 이름을 가진 함수는 동일하지 않습니다.
alert(person1.sayName == person2.sayName); //false
그러나 동일한 작업을 완료하기 위해 실제로 두 개의 Function 인스턴스를 생성할 필요는 없습니다. 게다가 이 객체를 사용하면 함수를 특정 객체에 바인딩할 필요도 없습니다. 위의 코드를 실행하기 전에 개체를 삭제하세요. 따라서 다음과 같이 함수 정의를 생성자 외부로 이동하면 이 문제를 해결할 수 있습니다.
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.sayName = sayName; } function sayName(){ alert(this.name); } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor");
객체가 많은 메소드를 정의해야 하는 경우 많은 전역 함수를 정의해야 하므로 사용자 정의 참조 유형에는 캡슐화가 전혀 없습니다. 다행스럽게도 프로토타입 패턴을 사용하면 이러한 문제를 해결할 수 있습니다.
4. 프로토타입 모드
function Person(){ } Person.prototype.name = "Nicholas"; Person.prototype.age = 29; Person.prototype.job = "Software Engineer"; Person.prototype.sayName = function(){ alert(this.name); }; var person1 = new Person(); person1.sayName(); //"Nicholas" var person2 = new Person(); person2.sayName(); //"Nicholas" alert(person1.sayName == person2.sayName); //true
要理解原型对象,可见我的另一篇博客:JavaScript prototype详解
前面例子中每添加一个属性和方法就要敲一遍Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,如下面的例子所示。
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
在上面的代码中,我们将Person.prototype 设置为等于一个以对象字面量形式创建的新对象。最终结果相同,但有一个例外:constructor 属性不再指向Person 了。前面曾经介绍过,每创建一个函数,就会同时创建它的prototype 对象,这个对象也会自动获得constructor 属性。而我们在这里使用的语法,本质上完全重写了默认的prototype 对象,因此constructor 属性也就变成了新对象的constructor 属性(指向Object 构造函数),不再指向Person 函数。此时,尽管instanceof操作符还能返回正确的结果,但通过constructor 已经无法确定对象的类型了,如下所示。
var friend = new Person(); alert(friend instanceof Object); //true alert(friend instanceof Person); //true alert(friend.constructor == Person); //false alert(friend.constructor == Object); //true
在此,用instanceof 操作符测试Object 和Person 仍然返回true,但constructor 属性则等于Object 而不等于Person 了。如果constructor 的值真的很重要,可以像下面这样特意将它设置回适当的值。
function Person(){ } Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
需要注意一点就是:实例中的指针仅指向原型,而不指向构造函数。
原型对象的问题:原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由其共享的本性所导致的。
function Person(){ } Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", friends : ["Shelby", "Court"], sayName : function () { alert(this.name); } }; var person1 = new Person(); var person2 = new Person(); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Court,Van" alert(person2.friends); //"Shelby,Court,Van" alert(person1.friends === person2.friends); //true
5、组合使用构造函数模式和原型模式(最常用)
创建自定义类型的最常见方式,就是组合使用构造函数模式与原型模式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度地节省了内存。另外,这种混成模式还支持向构造函数传递参数;可谓是集两种模式之长。
function Person(name, age, job){ this.name = name; this.age = age; this.job = job; this.friends = ["Shelby", "Court"]; } Person.prototype = { constructor : Person, sayName : function(){ alert(this.name); } } var person1 = new Person("Nicholas", 29, "Software Engineer"); var person2 = new Person("Greg", 27, "Doctor"); person1.friends.push("Van"); alert(person1.friends); //"Shelby,Count,Van" alert(person2.friends); //"Shelby,Count" alert(person1.friends === person2.friends); //false alert(person1.sayName === person2.sayName); //true
6、动态原型模式
有其他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 friend = new Person("Nicholas", 29, "Software Engineer"); friend.sayName();
7、寄生构造函数模式
通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象;但从表面上看,这个函数又很像是典型的构造函数。下面是一个例子。
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("Nicholas", 29, "Software Engineer"); friend.sayName(); //"Nicholas"
在这个例子中,Person 函数创建了一个新对象,并以相应的属性和方法初始化该对象,然后又返回了这个对象。除了使用new 操作符并把使用的包装函数叫做构造函数之外,这个模式跟工厂模式其实是一模一样的。构造函数在不返回值的情况下,默认会返回新对象实例。
8、稳妥构造函数模式
所谓稳妥对象,指的是没有公共属性,而且其方法也不引用this 的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this 和new),或者在防止数据被其他应用程序(如Mashup程序)改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建对象的实例方法不引用this;二是不使用new 操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person 构造函数重写如下。
function Person(name, age, job){ //创建要返回的对象 var o = new Object(); //可以在这里定义私有变量和函数 //添加方法 o.sayName = function(){ alert(name); }; //返回对象 return o; }
JavaScript에서 객체(클래스)를 생성하는 위의 8가지 방법을 배웠습니까? 학습에 도움이 되기를 바랍니다.
【관련 추천 튜토리얼】
1. JavaScript 동영상 튜토리얼
2. JavaScript 온라인 매뉴얼
3. 부트스트랩 튜토리얼