下面小编就为大家带来一篇浅析在 javascript 中创建对象的各种模式。小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
Javascript 是一种由 Netscape 的 LiveScript 发展而来的原型化继承的基于对象的动态类型的区分大小写的客户端脚本语言,主要目的是为了解决服务器端语言,比如 Perl,遗留的速度问题,为客户提供更流畅的浏览效果。
最近在看《javascript 高级程序设计》(第二版)
javascript 中对象的创建
• 工厂模式
• 构造函数模式
• 原型模式
• 结合构造函数和原型模式
• 原型动态模式
面向对象的语言大都有一个类的概念,通过类可以创建多个具有相同方法和属性的对象。虽然从技术上讲,javascript 是一门面向对象的语言,但是 javascript 没有类的概念,一切都是对象。任意一个对象都是某种引用类型的实例,都是通过已有的引用类型创建;引用类型可以是原生的,也可以是自定义的。原生的引用类型有:Object、Array、Data、RegExp、Function。 !引用类型就是一种数据结构,将数据和功能组织在一起,通常被称为类。 缺乏类概念的 javascript 中,需要解决的问题就是如何高效的创建对象。
1.1.0. 创建对象的一般方法
- var person = {}; //对象字面量表示,等同于var person = new Objcect();
- person.name = 'evansdiy';
- person.age = '22';
- person.friends = ['ajiao','tiantian','pangzi'];
- person.logName = function() {
- console.log(this.name);
- }
基于 Object 引用类型,创建了一个对象,该对象包含四个属性,其中一个为方法。如果需要很多类似 person 的实例,那就会有许多重复的代码。
1.1.1. 工厂模式 [top]
通过一个可以包含了对象细节的函数来创建对象,然后返回这个对象。
- function person(name,age,friends) {
- var o = {
- name: name,
- age: age,
- friends: friends,
- logName: function() {
- console.log(this.name);
- }
- };
- return o;
- }
- var person1 = person('Evansdiy','22',['ajiao','tiantian','pangzi']);
每次调用 person 函数,都会通过该函数内部的对象 o 创建新的对象,然后返回,除此之外,这个为了创建新对象而存在的内部对象 o 没有其他的用途。另外,无法判断工厂模式创建的对象的类型。
1.1.2. 构造函数模式 [top]
- function Person(name, age, job) {
- this.name = name;
- this.age = age;
- this.job = job;
- this.logName = function() {
- console.log(this.name);
- }
- }
- //通过new操作符创建Person的实例
- var person1 = new Person('boy-a', '22', 'worker');
- var person2 = new Person('girl-b', '23', 'teacher');
- person1.logName(); //boy-a
- person2.logName(); //girl-a
对比工厂模式,可以发现,这里并不需要创建中间对象,没有 return。另外,可以将构造函数的实例标识为一种特定的类型,这就解决了对象识别的问题(通过检查实例的 constructor 属性,或利用 instanceof 操作符检查该实例是否通过某个构造函数创建)。
console.log(person1.constructor == Person);//constructor 位于构造函数原型中,并指向构造函数,结果为 true
console.log(person1 instanceof Person);// 通过 instanceof 操作符,判断 person1 是否为构造函数 Person 的实例但构造函数模式也有自己的问题,实际上,logName 方法在每个实例上都会被重新创建一次,需要注意的是,通过实例化创建的方法且并不相等,以下代码将会得到 false:
console.log(person1.logName == person2.logName);//false 我们可以将方法移到构造器外部(变为全局函数)来解决这个问题:
- function logName() {
- console.log(this.name);
- }
- function logAge() {
- console.log(this.age);
- }
但是,在全局下创建的全局函数实际上只能被经由 Person 创建的实例调用,这就有点名不副实了;如果方法很多,还需要逐一定义,缺少封装性。
1.1.3. 原型模式 [top]
javascript 中的每一个函数都包含一个指向 prototype 属性的指针(大部分浏览器可以通过内部属性__proto__访问),prototype 属性是一个对象,其中包含了由某种引用类型创建的所有实例共享的属性和方法。
- function Person() {}
- Person.name = 'evansdiy';
- Person.prototype.friends = ['ajiao', 'jianjian', 'pangzi'];
- Person.prototype.logName = function() {
- console.log(this.name);
- }
- var person1 = new Person();
- person1.logName(); //'evansdiy'
以上代码做了这几件事情:
1. 定义了一个构造函数 Person,Person 函数自动获得一个 prototype 属性,该属性默认只包含一个指向 Person 的 constructor 属性;
2. 通过 Person.prototype 添加三个属性,其中一个作为方法;
3. 创建一个 Person 的实例,随后在实例上调用了 logName() 方法。
这里需要注意的是 logName() 方法的调用过程:
1. 在 person1 实例上查找 logName() 方法,发现没有这个方法,于是追溯到 person1 的原型
2. 在 person1 的原型上查找 logame() 方法,有这个方法,于是调用该方法 基于这样一个查找过程,我们可以通过在实例上定义原型中的同名属性,来阻止该实例访问原型上的同名属性,需要注意的是,这样做并不会删除原型上的同名属性,仅仅是阻止实例访问。
var person2 = new Person();
person2.name = 'laocai'; 如果我们不再需要实例上的属性时,可以通过 delete 操作符删除。
delete person2.name; 利用 for-in 循环枚举出实例可以访问到的所有属性(不论该属性存在于实例或是原型中):
- for(i in person1) {
- console.log(i);
- }
同时,也可以利用 hasOwnProperty() 方法判断某个属性到底存在于实例上,还是存在于原型中,只有当属性存在于实例中,才会返回 true:
console.log(person1.hasOwnProperty('name'));//true!hasOwnProperty 来自 Object 的原型,是 javascript 中唯一一个在处理属性时不查找原型链的方法。[via javascript 秘密花园] 另外,也可以通过同时使用 in 操作符和 hasOwnProperty() 方法来判断某个属性存在于实例中还是存在于原型中:
console.log(('friends' in person1) && !person1.hasOwnProperty('friends')); 先判断 person1 是否可以访问到 friends 属性,如果可以,再判断这个属性是否存在于实例当中(注意前面的!),如果不存在于实例中,就说明这个属性存在于原型中。 前面提到,原型也是对象,所以我们可以用对象字面量表示法书写原型,之前为原型添加代码的写法可以修改为:
- Person.prototype = {
- name: 'evansdiy',
- friends: ['ajiao','jianjian','pangzi'],
- logName: function() {
- console.log(this.name);
- }
- }
由于对象字面量语法重写了整个 prototype 原型,原先创建构造函数时默认取得的 constructor 属性会指向 Object 构造函数:
// 对象字面量重写原型之后
console.log(person1.constructor);//Object 不过,instanceof 操作符仍会返回希望的结果:
// 对象字面量重写原型之后
console.log(person1 instanceof Person);//true 当然,可以在原型中手动设置 constructor 的值来解决这个问题。
- Person.prototype = {
- constructor: Person,
- ......
- }
如果在创建对象实例之后修改原型对象,那么对原型的修改会立即在所有对象实例中反映出来:
- function Person() {};
- var person1 = new Person();
- Person.prototype.name = 'evansdiy';
- console.log(person1.name); //'evansdiy'
实例和原型之间的连接仅仅是一个指针,而不是一个原型的拷贝,在原型实际上是一次搜索过程,对原型对象的所做的任何修改都会在所有对象实例中反映出来,就算在创建实例之后修改原型,也是如此。 如果在创建对象实例之后重写原型对象,情况又会如何?
- function Person() {};
- var person1 = new Person1();//创建的实例引用的是最初的原型
- //重写了原型
- Person.prototype = {
- friends: ['ajiao','jianjian','pangzi']
- }
- var person2 = new Person();//这个实例引用新的原型
- console.log(person2.friends);
- console.log(person1.friends);
以上代码在执行到最后一行时会出现未定义错误,如果我们用 for-in 循环枚举 person1 中的可访问属性时,会发现,里头空无一物,但是 person2 却可以访问到原型上的 friends 属性。 !重写原型切断了现有原型与之前创建的所有对象实例的联系,之前创建的对象实例的原型还在,只不过是旧的。
// 创建 person1 时,原型对象还未被重写,因此,原型对象中的 constructor 还是默认的 Person()
console.log(person1.constructor);//Person()
// 但是 person2 的 constructor 指向 Object()
console.log(person2.constructor);//Object() 需要注意的是,原型模式忽略了为构造函数传递参数的过程,所有的实例都取得相同的属性值。同时,原型模式还存在着一个很大的问题,就是原型对象中的引用类型值会被所有实例共享,对引用类型值的修改,也会反映到所有对象实例当中。
- function Person() {};
- Person.prototype = {
- friends: ['ajiao', 'tiantian', 'pangzi']
- }
- var person1 = new Person();
- var person2 = new Person();
- person1.friends.push('laocai');
- console.log(person2.friends); //['ajiao','tiantian','pangzi','laocai']
修改 person1 的引用类型值 friends,意味着 person2 中的 friends 也会发生变化,实际上,原型中保存的 friends 实际上只是一个指向堆中 friends 值的指针(这个指针的长度是固定的,保存在栈中),实例通过原型访问引用类型值时,也是按指针访问,而不是访问各自实例上的副本(这样的副本并不存在)。
1.1.4. 结合构造函数和原型模式创建对象 [top]
结合构造函数和原型模式的优点,弥补各自的不足,利用构造函数传递初始化参数,在其中定义实例属性,利用原型定义公用方法和公共属性,该模式应用最为广泛。
- function Person(name, age) {
- this.name = name;
- this.age = age;
- this.friends = ['ajiao', 'jianjian', 'pangzi'];
- }
- Person.prototype = {
- constructor: Person,
- logName: function() {
- console.log(this.name);
- }
- }
- var person1 = new Person('evansdiy', '22');
- var person2 = new Person('amy', '21');
- person1.logName(); //'evansdiy'
- person1.friends.push('haixao');
- console.log(person2.friends.length); //3
1.1.5. 原型动态模式 [top]
原型动态模式将需要的所有信息都封装到构造函数中,通过 if 语句判断原型中的某个属性是否存在,若不存在(在第一次调用这个构造函数的时候),执行 if 语句内部的原型初始化代码。
- function Person(name, age) {
- this.name = name;
- this.age = age;
- if (typeof this.logName != 'function') {
- Person.prototype.logName = function() {
- console.log(this.name);
- };
- Person.prototype.logAge = function() {
- console.log(this.age);
- };
- };
- }
- var person1 = new Person('evansdiy', '22'); //初次调用构造函数,此时修改了原型
- var person2 = new Person('amy', '21'); //此时logName()方法已经存在,不会再修改原型
需要注意的是,该模式不能使用对象字面量语法书写原型对象(这样会重写原型对象)。若重写原型,那么通过构造函数创建的第一实例可以访问的原型对象不会包含 if 语句中的原型对象属性。
- function Person(name, age) {
- this.name = name;
- this.age = age;
- if (typeof this.logName != 'function') {
- Person.prototype = {
- logName: function() {
- console.log(this.name);
- },
- logAge: function() {
- console.log(this.Age);
- }
- }
- };
- }
- var person1 = new Person('evansdiy', '22');
- var person2 = new Person('amy', '21');
- person2.logName(); //'amy'
- person1.logName(); //logName()方法不存在
需要说明的是,各模式都有自己的应用场景,无所谓优劣。
以上这篇浅析在 javascript 中创建对象的各种模式就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持 phperz。
来源: http://www.phperz.com/article/17/0223/265962.html