js创建自定义对象的几种方式

1. 对象字面量方式(通过JSON来创建对象)。

对象字面量方式是创建自定义对象的首选模式,简单方便。

var person = {
    name: '小白开发',
    job: 'html',
    getName: function () {
         alert(this.name);
    }
}

缺点:使用同一个接口创建很多对象,会产生大量的重复代码。比如我想再创建一个person1对象,我就得把上面的代码再重新写一遍,改变不同的属性值。

2.工厂模式。

由于在ECMAScript中无法创建类,开发人员就发明了一种函数,用函数来封装以特定接口创建对象的细节,工厂模式非常直观,将创建对象的过程抽象为一个函数,用函数封装以特定接口创建对象的细节。如下所示:

function createPerson(name, job) {
   var obj = new Object();
   obj.name = name;
   obj.job = job;
   obj.getName = function () {
       alert(this.name);
   }
   return obj;
}
var p1 = createPerson('小白开发1', 'html1');
var p2 = createPerson('小白开发2', 'html2');

通俗地讲,工厂模式就是将创建对象的语句放在一个函数里,通过传入参数来创建特定对象,最后返回创建的对象。函数createPerson()能够根据接受到的参数来构建一个包含所有必要信息的Person对象。可以无数次的调用这个函数,而每次它都会返回一个包含2个属性和一个方法的对象。
缺点:工厂模式虽然可以创建多个相似的对象,但却不能解决对象标识的问题,即怎样知道一个对象的类型。构造函数模式应运而生。

3、构造函数模式

构造函数模式是java语言创建对象的通用方式。两种语言用构造函数创建对象的方式略有不同,注意区别。在JavaScript中没有类的概念,函数即为一等公民,因此,不必显式声明某个类,直接创建构造函数即可,类的方法和属性在构造函数中(或原型对象上)处理。使用构造函数模式将前面的例子重写示例代码如下:

function Person(name, job) {
   this.name = name;
   this.job = job;
   this.getName = function () {
        alert(this.name);
   }
}
var p1 = new Person('小白开发1', 'java');
var p2 = new Person('小白开发2', 'php');

细心的朋友一定发现了构造函数的函数名首字母是大写的,而普通函数首字母则是小写,这是众多OO语言约定俗成的规定,虽然大多数情况下不大写也不会报错,但是为了代码的规范性和可读性,还是应该将构造函数的首字母大写,与普通函数区别开。
与工厂模式相比,用构造模式创建对象有以下几点不同:

  1. 没有显示地创建对象
  2. 直接将属性和方法赋给this对象
  3. 没有return语句

此外,还应注意到要创建Person的实例,必须要使用new操作符,创建的实例对象将有一个constructor(构造器)属性,指向Person构造函数。调用构造函数创建对象经过了以下几个过程:
1.创建一个新对象
2.将构造函数的作用域赋给新对象(因此this就指向了这个新对象)
3.执行构造函数中的代码
4.返回新对象(不需要显式返回)

构造函数虽好用,但也不是没有缺点。使用构造函数的主要问题是:每个方法都要在每个实例上创建一遍。在ECMAScript中,函数即对象,因此每定义一个函数,也就是实例化了一个对象。下面的例子证明了这个缺点。

var p3 = new Person('唐僧','html');
var p4 = new Person('白骨精','hmtl');
p3.getName();
p4.getName();
console.log(p3.getName == p4.getName);

也就是说通过构造函数实例化的多个对象的方法,是多个不同的方法,但它们内部的代码以及实现的功能是相同的,这就造成了一定的资源浪费。
幸运的是,这个问题可以用原型模式来解决。

4、原型模式

js中,每个函数都有一个prototype属性,它是一个指针,指向一个对象,叫做原型对象,原型对象包含了可以由特定类型的所有实例对象共享的属性和方法。此外,这个对象有一个与生自来的属性constructor,指向创建对象的构造方法。
使用原型模式可以让所有的实例共享原型对象中的属性和方法,也就是说,不必再构造函数中定义对象实例的信息。用代码表示如下:

function Person(){  }
Person.prototype.name = 'Claiyre';
Person.prototype.sex = 'female';
Person.prototype.class = 5;
Person.prototype.getName = function (){
      console.log(this.name);
}
var p5 = new Person();                                                         
p5.getName();    //Claiyre
var p6 = new Person();
p6.getName();    //Claiyre

了解过原型后,可以继续在实例对象上增添属性或方法:

p6.name = 'John';                                                     
p6.getName();       //John

当要读取某个对象的属性时,都会执行一次搜索,搜索首先从对象实例本身开始,如果在实例中找到了这个属性,则搜索结束,返回实例属性的值;
若实例上没有找到,则继续向对象的原型对象延伸,搜索对象的原型对象,若在原型对象上找到了,则返回原型上相应属性的值,若没有找到,则返回undefined。
因此,实例对象属性会覆盖原型对象上的同名属性,所以上面第二行代码输出的是John。
Object.getPrototypeOf(object) 方法返回参数对象的原型对象。
Object.keys(object) 方法返回对象上课枚举的实例属性

扫描二维码关注公众号,回复: 6495804 查看本文章

原型模式也不是没有缺点。首先,它省略了为构造函数传递初始化参数这一环节,结果所有实例在默认情况下都将取得相同的属性值。虽然这会在某种程度上带来一些不方便,但还不是原型的最大问题。原型模式的最大问题是由共享的本性所导致的。
原型中所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说的过去,通过在实例上添加一个同名属性,可以隐藏原型中的对应属性。然后,对于包含引用类型的属性来说,问题就比较突出了。

Person.prototype.friends = ['aa','bb'];

console.log('p6的朋友' + p6.friends);
p5.friends.push('cc');
console.log('p5的朋友' + p5.friends);
console.log('p6的朋友' + p6.friends);

问题来了,我们只想改变p5的朋友列表,但由于原型模式的共享本质,p6的朋友列表也随之改变了。
因此,很少单独使用原型模式

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

组合使用构造函数模式和原型模式,是创建自定义类型的最常见方式。构造函数模式用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。

function Person(name,age,job){
    this.name= name;
    this.age = age;
    this.job = job;
    this.friends = ['wang','li'];
}

Person.prototype = {
    constructor:Person,
    getName : function(){
        alert(this.name);
    }
}

var p1 = new Person('zhang',26,'java',);
var p2 = new Person('sun',25,'php');

p1.friends.push('zhao');
alert(p1.friends);  //'wang,li,zhao'
alert(p2.friends); //'wang,li'
alert(p1.friends === p2.friends);  //false

在上面的例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法getName()则是在原型中定义的。而修改了p1.friends(向其中添加一个新字符串),并不会影响到p2.friends,因为他们分别引用了不同的数组。

其他模式

动态原型模式:仅在第一次调用构造函数时,将方法赋给原型对象的相应属性,其他示例的处理方式同构造函数模式
寄生构造函数模式:仅仅封装创建对象的代码,然后再返回新创建的对象,仍使用new操作符调用
稳妥构造函数模式:没有公共属性,只有私有变量和方法,以及一些get/set方法,用以处理私有变量。

结语

每种模式都有各自的优缺点,具体要使用哪种,还需结合实际场景,深入理解,灵活运用。

猜你喜欢

转载自blog.csdn.net/samarket/article/details/89330519