>  기사  >  웹 프론트엔드  >  JS 객체를 생성하는 일반적인 방법은 무엇입니까?

JS 객체를 생성하는 일반적인 방법은 무엇입니까?

零下一度
零下一度원래의
2017-06-28 09:55:261216검색

머리말

"js 언어에서는 모든 것이 객체이다"라는 속담처럼 객체를 생성하는 방법에는 여러 가지가 있으므로 오늘은 가장 간단한 방법을 정리하겠습니다.

JS에서 객체를 생성하는 가장 간단한 방법은 다음과 같습니다. JavaScript 예: 객체 리터럴 형식 또는 객체 생성자 사용

객체 리터럴 형식

1 var person = new Object();2   person.name = "jack";3   person.sayName = function () {4   alert(this.name)5 }

객체 생성자 사용

1 var person = {2   name: "jack";3   sayName: function () {4     alert(this.name)5   }6 }

분명한 단점: 여러 객체를 생성할 때 코드 중복이 발생하므로 '팩토리 패턴'이 생겨났습니다.

Factory Pattern

팩토리 패턴을 간단하게 이해하자면, Factory : "객체를 생성하고, 생성 과정 전체를 책임지지만, 작업이 완료된 후에는 아무런 관련이 없습니다. O ( ∩_∩)오하하~”

 1 function createPerson (name) { 2   var o = new Object(); 3   o.name = name; 4   o.sayName = function () { 5     alert(this.name) 6   } 7   return o 8 } 9 10 var p1 = new createPerson("jack");
분명한 단점: 모든 객체 인스턴스는 `Object` 유형이므로 유형 구분이 거의 없습니다! 유형을 구분하지 못한다면 구분할 수 없습니다. 그럼 코드를 살펴보겠습니다:

1 var p1 = new createPerson("jack");2 var p2 = new createPerson("lucy");3 4 console.log(p1 instanceof Object); //true5 console.log(p2 instanceof Object); //true
이런가요? 그래서 이 문제를 해결하기 위해 우리는 '생성자 패턴'

생성자 패턴을 사용합니다. 나는 특정 유형의 객체 인스턴스만 생성하고 다른 것은 신경 쓰지 않습니다. (여기에 이미 유형 개념이 있다는 것을 눈치채셨나요? 작은 그룹처럼 느껴집니다.)

 1 function Person (name) { 2   this.name = name; 3   this.sayName = function () { 4     alert(this.name) 5   } 6 } 7  8 function Animal (name) { 9   this.name = name;10   this.sayName = function () {11     alert(this.name)12   }13 }14 15 var p1 = new Person("jack")16 p1.sayName() //"jack"17 18 var a1 = new Animal("doudou")19 a1.sayName() //"doudou"20 21 console.log(p1 instanceof Person) //true22 console.log(a1 instanceof Animal) //true23 console.log(p1 instanceof Animal) //false(p1显然不是Animal类型,所以是false)24 console.log(a1 instanceof Person) //false(a1也显然不是Person类型,所以同样是false)

위 코드는 다음을 증명합니다. 생성자 패턴은 실제로 객체 유형을 구별할 수 있습니다. 그렇다면 이 모델은 완벽하지 않습니까? 다음 코드를 살펴보겠습니다.

1 //接着上面的代码2 console.log(p1.sayName === a1.sayName) //false

문제를 찾았나요? `p1`의 `sayName`은 `a1`의 `sayName`과 동일하지 않습니다. 이는 무엇을 의미하나요? '생성자 패턴'에는 '공용'이라는 개념이 전혀 없다는 점을 설명합니다. 생성된 각 객체 인스턴스에는 고유한 속성과 메서드 집합이 있다는 것을 이해할 수 있지만 메서드를 수행해야 합니다. 한 세트면 좀 불필요해요

명백한 단점: 위에서 설명한 대로 이 문제를 해결하기 위해 새로운 모드인 '프로토타입 모드'가 등장했습니다. 이 모드는 단순히 스테이지 점프라는 점을 살펴보겠습니다. 아래에서 '프로토타입 패턴'을 살펴보겠습니다.
프로토타입 패턴


여기서 기억해야 할 한 가지가 있습니다. 생성자의 속성과 메서드는 각 개체 인스턴스 간에 공유되지 않으며 모두 별도로 설정됩니다. 공유를 달성하려면 생성자의 프로토타입에 속성과 메서드를 저장해야 합니다. 이 문장은 무엇을 의미하나요? 아래에서 자세히 설명하겠습니다

생성자가 생성되면(일반 함수도 마찬가지) `prototype`(프로토타입)이 자동으로 생성됩니다. 생성자와 `prototype`은 일대일 관계를 가지며, 이번에 `prototype`에는 `constructor` 속성이 하나만 있습니다(분명히 또 다른 `__proto__`가 있습니다. 여기서는 이에 대해 논의하지 않으며 나중에 설명할 것입니다)


이 `constructor`는 무엇입니까? 포인터와 유사한 참조로 `prototype`의 생성자를 가리키는데 포인터는 기본적으로 존재해야 합니다

1 console.log(Person.prototype.constructor === Person) //true

방금 `prototype`은 `자동 생성`이라고 했는데 실제로는 없습니다. `prototype`을 생성하는 또 다른 수동 방법입니다:

1 function Person (name) {2   this.name = name3 }4 Person.prototype = {5   //constructor: Person,6   age: 307 }8 console.log(Person.prototype) //Object {age: 30}9 console.log(Person.prototype.constructor === Person) //false

팁: `prototype`이 생성자에 대해 수동으로 생성될 수 있음을 증명하기 위해 여기서 `name` 속성이 `prototype`에 추가됩니다.

아마도 다음 코드 줄에 문제가 있음을 발견했을 것입니다.
1 console.log(Person.prototype.constructor === Person) //false


결과가 'false'인 이유는 무엇입니까? 형님, 방금 '프로토타입'이 기본적으로 생성된 후 다른 방법인 수동 설정을 사용했습니다. 수동 설정의 원리를 자세히 분석해 보겠습니다.

1. 생성자의 `prototype`은 실제로 객체입니다.


2. `prototype`을 이렇게 설정하면 원래 `Person.prototype`이 실제로 객체가 됩니다. 그러면 다른 객체가 다시 참조됩니다

3. 이때 생성자는 `prototype`을 찾을 수 있지만 `prototype`은 생성자를 찾을 수 없습니다.

1 Person.prototype = {2   //constructor: Person, // 因为constructor属性,我没声明啊,prototype就是利用它来找到构造函数的,你竟然忘了声明3   age: 304 }

4. 생성자의 수동 설정을 표시합니다. 함수 프로토타입 사이의 연결을 잃지 않고 다음과 같이 합니다.

1 function Person (name) {2   this.name = name3 }4 Person.prototype = {5   constructor: Person, //constructor一定不要忘了!!6   age: 307 }

画外音:“说到这里,你还没有讲原型模式是如何实现属性与方法的共享啊”,不要急,马上开始:

对象实例-构造函数-原型,三者是什么样的关系呢?

看明白这张图的意思吗?
1.当对象实例访问一个属性时(方法依然),如果它自身没有该属性,那么它就会通过`__proto__`这条链去构造函数的`prototype`上寻找
2.构造函数与原型是一对一的关系,与对象实例是一对多的关系,而并不是每创建一个对象实例,就相应的生成一个`prototype`
这就是原型模式的核心所在,结论:在原型上声明属性或方法,可以让对象实例之间共用它们

然后原型模式就是完美的吗?并不是,它有以下两个主要问题:
问题1:如果对象实例有与原型上重名的属性或方法,那么,当访问该属性或方法时,实例上的会屏蔽原型上的

1 function Person (name) {2   this.name = name3 }4 Person.prototype = {5   constructor: Person,6   name: 'lucy'7 }8 var p1 = new Person('jack');9 console.log(p1.name); //jack

问题2:由于实例间是共享原型上的属性和方法的,所以当其中一个对象实例修改原型上的属性(基本值,非引用类型值或方法时,其他实例也会受到影响


原因就是,当实例自身的基本值属性与原型上的重名时,实例就会创建该属性,留着今后自己使用,而原型上的属性不会被修改;但如果属性是引用类型值,如:`Array`、`Object`,当发生重名时,实例是不会拷贝一份新的留给自己使用的,还是坚持实例间共享,所以就会出现上图中的情况

以上两个问题就是原型模式的明显缺点,为了改掉这些缺点,我们一般会采用一种组合模式“组合使用构造函数模式和原型模式”,其实在原型模式这一节,该模式已经有所应用了

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

这种模式可谓是集构造函数模式和原型模式之所长,用构造函数模式来定义对象实例的属性或方法,而共享的属性或方法就交给原型模式

 1 function Person (name) { 2   this.name = name //实例的属性,在构造函数中声明 3 } 4  5 Person.prototype = { 6   constructor: Person, 7   sayName: function () { //共享的方法存在原型中 8     alert(this.name) 9   }10 }

注:此模式目前是ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法

-----------------

下面要介绍的几个模式是针对不同场景的,而不是说`组合使用构造函数模式和原型模式`有什么缺点,又用这几个模式来弥补,不是这样的

动态原型模式

特点:共享的方法是在构造函数中检测并声明的,原型并没有被显示创建

 1 function Person (name) { 2   this.name = name; 3   if (typeof this.sayName !== 'function') { //检查方法是否存在 4     console.log('sayName方法不存在') 5     Person.prototype.sayName = function () { 6       alert(this.name) 7     } 8   } else { 9     console.log('sayName方法已存在')10   }11 }12 13 var p1 = new Person('jack'); //'sayName方法不存在'14 p1.sayName(); //因为sayName不存在,我们来创建它,所以这里输出'jack'15 var p2 = new Person('lucy'); //'sayName方法已存在'16 p2.sayName(); //这时sayName已存在,所以输出'lucy'

当`Person`构造函数第一次被调用时,`Person.prototype`上就会被添加`sayName`方法;《Javascript高级程序设计》一书说到:使用动态原型模式时,不能使用对象字面量重写原型。我们来理解一下:

 

分析:
1.`p1`实例创建,此时原型没有`sayName`方法,那我们就为原型添加一个
2.随后,我们以字面量的形式重写了原型,这时旧的原型并没有被销毁,而且它和`p1`还保持着联系
3.之后的实例,也就是这里的`p2`,都是与新原型保持联系;所以`p1`、`p2`有各自的构造器原型,即使它们的构造器是同一个

所以切记:当我们采用动态原型模式时,千万不要以字面量的形式重写原型

寄生构造函数模式

了解此模式之前,我们先来想一个问题:构造函数为什么要用`new`关键字调用?代码说话:

我们发现什么?如果不是`new`方法调用构造函数,那么就要显式的`return`,否则构造函数就不会有返回值;但如果使用`new`,那就没有这个问题了

下面我们再来看寄生构造函数模式:

 1 function Person (name) { 2   var o = new Object(); 3   o.name = name; 4   o.sayName = function () { 5     alert(this.name) 6   }; 7   return o 8 } 9 10 var p1 = new Person('jack'); //与工厂模式唯一不同之处:使用new调用11 p1.sayName(); //jack

其实new不new都无所谓,因为我们已经显式的return o

那么寄生构造函数模式到底有什么应用场景呢?据《javascript高级程序设计》一书记载,举例:如果我们想创建一个具有额外方法的特殊数组,那么我们可以这样做:

 1 function SpecialArray () { 2   var values = new Array(); 3   Array.prototype.push.apply(values,arguments); 4     values.toPipedString = function () { 5     return this.join('|') 6   } 7   return values 8 } 9 10 var colors = new SpecialArray('red','blue','green');11 alert(colors.toPipedString()) //'red|blue|green'

最后重要的一点:该模式和构造函数和原型无缘,也就是不能区分实例类型,因为该模式生成的实例,它的构造函数都是Object,原型都是Object.prototype

稳妥构造函数模式

该模式与寄生构造函数相比,主要有两点不同:
1.创建对象实例的方法不引用this
2.不使用new操作符调用构造函数
按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

1 function Person (name) {2   var o = new Object();3   o.sayName = function () {4     alert(name) //这里其实涉及到了闭包的知识,因此产生了私有属性的概念5   }6   return o7 }

此模式最适合在一些安全的环境中(这些环境中会禁止使用this和new),同理,此模式与构造函数和原型也无缘

结语

以上就是对js中创建对象的方式的总结,希望对大家有所帮助

위 내용은 JS 객체를 생성하는 일반적인 방법은 무엇입니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.