머리말
"js 언어에서는 모든 것이 객체이다"라는 속담처럼 객체를 생성하는 방법에는 여러 가지가 있으므로 오늘은 가장 간단한 방법을 정리하겠습니다.
객체 리터럴 형식
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
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");
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`과 동일하지 않습니다. 이는 무엇을 의미하나요? '생성자 패턴'에는 '공용'이라는 개념이 전혀 없다는 점을 설명합니다. 생성된 각 객체 인스턴스에는 고유한 속성과 메서드 집합이 있다는 것을 이해할 수 있지만 메서드를 수행해야 합니다. 한 세트면 좀 불필요해요
명백한 단점: 위에서 설명한 대로 이 문제를 해결하기 위해 새로운 모드인 '프로토타입 모드'가 등장했습니다. 이 모드는 단순히 스테이지 점프라는 점을 살펴보겠습니다. 아래에서 '프로토타입 패턴'을 살펴보겠습니다.프로토타입 패턴
여기서 기억해야 할 한 가지가 있습니다. 생성자의 속성과 메서드는 각 개체 인스턴스 간에 공유되지 않으며 모두 별도로 설정됩니다. 공유를 달성하려면 생성자의 프로토타입에 속성과 메서드를 저장해야 합니다. 이 문장은 무엇을 의미하나요? 아래에서 자세히 설명하겠습니다
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 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 중국어 웹사이트의 기타 관련 기사를 참조하세요!