JavaScript:创建对象

标签: 面向对象原型模式JavaScript对象对象创建JavaScript对象创建
329人阅读 评论(0) 收藏 举报
分类:

  使用Object构造函数或对象字面量都可以创建对象,但缺点是创建多个对象时,会产生大量的重复代码,因此下面介绍可解决这个问题的创建对象的方法

1、工厂模式

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.getName = function () {
        return this.name;
    }
    return o;//使用return返回生成的对象实例
}
var person1= createPerson('Jack', 19, 'SoftWare Engineer');
var person2= createPerson('Greg', 29, 'Doctor');
alert(person1.age);//19
alert(person1.getName());//'Jack'
alert(person2.age);//29
alert(person2.getName());//'Greg'

  可以无数次地调用这个函数,而每次它都会返回一个包含三个属性一个方法的对象,工厂模式虽然解决了创建多个相似对象的问题,但是却没有解决对象识别的问题(即怎样知道一个对象的类型),因为创建对象都是使用Object的原生构造函数来完成的。


2、构造函数模式

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.getName = function () {
        return this.name;
    }
}
var person1 = new Person('Jack', 19, 'SoftWare Engineer');
var person2 = new Person('Liye', 23, 'Mechanical Engineer');
alert(person1.age);//19
alert(person1.getName());//'Jack'
alert(person2.age);//23
alert(person2.getName());//'Liye'

使用自定义的构造函数(与普通函数一样,只是用它来创建对象),定义对象类型(如:Person)的属性和方法。它与工厂方法区别在于:

a、没有显式地创建对象

b、直接将属性和方法赋值给this对象;

c、没有return语句;

    此外,要创建Person的实例,必须使用new关键字,以Person函数为构造函数,传递参数完成对象创建;实际创建经过以下4个过程:

a、创建一个对象

b、将函数的作用域赋给新对象(因此this指向这个新对象,如:person1)

c、执行构造函数的代码

d、返回该对象

   上述由Person构造函数生成的两个对象person1与person2都是Person的实例,因此可以使用instanceof判断,并且因为所有对象都继承Object,因此person1 instanceof Object也返回真:

alert(person1 instanceof Person);//true;
alert(person1 instanceof Object);//true;
alert(person2 instanceof Person);//true;
alert(person2 instanceof Object);//true;
alert(person1.constructor =Person);//ture;
alert(person2.constructor =Person);//ture;
alert(person1.constructor === person2.constructor);//ture;

   构造函数与其它函数的唯一区别,就在于调用它们的方式不同。任何函数,只要通过new操作符来调用,那么它就可以作为构造函数;而任何函数,如果不通过new操作符来调用,那它跟普通函数也不会有什么两样。

//当作构造函数使用
var person = new Person('Jack', 19, 'SoftWare Engineer');
person.getName();//Jack
//作为普通函数调用
Person('Jack', 19, 'SoftWare Engineer');
window.getName();//Jack
//在另一个对象的作用域中调用
var o=new Object();
Person.call(o,'Jack', 19, 'SoftWare Engineer');
o.getName();//Jack

  虽然构造函数方式比较不错,但也存在缺点,那就是在创建对象时,特别针对对象的属性指向函数时,会重复的创建函数实例,以上述代码为基础,可以改写为:

function Person(name,age,job){
    this.name = name;
    this.age = age;
this.job = job;
//改写后效果与原代码相同,不过是为了方便理解
this.getName = new Function () {      
  return this.name;
    }
}
var person1 = new Person('Jack', 19, 'SoftWare Engineer');
var person2 = new Person('Liye', 23, 'Mechanical Engineer');
alert(person1.getName === person2.getName);//false,

  上述代码,创建多个实例时,会重复调用new Function();创建多个函数实例,这些函数实例还不是一个作用域中,当然这一般不会有错,但这会造成内存浪费。当然,可以在函数中定义一个getName = getName的引用,而getName函数在Person外定义,这样可以解决重复创建函数实例问题,但在效果上并没有起到封装的效果,如下所示:

function Person(name,age,job){
    this.name = name;
    this.age = age;
    this.job = job;
    this.getName = getName;
}
function getName() {
        return this.name;
}

如果对象需要定义很多方法,那么就要定义很多个全局函数,于是我们这个自定义的引用类型就丝毫没有封装性可言了。


3、原型模式

  JS每个函数都有一个prototype(原型)属性,这个属性是一个指针,指向一个对象,它是所有通过new操作符使用函数创建的实例的原型对象。原型对象最大特点是,所有对象实例共享它所包含的属性和方法,也就是说,所有在原型对象中创建的属性或方法都直接被所有对象实例共享。换句话说,不必在构造函数中定义对象实例的信息,而是可以将这些信息直接添加到原型对象中

function Person(){
}
Person.prototype.name = 'Jack';//使用原型来添加属性
Person.prototype.age = 29;
Person.prototype.getName = function(){
    return this.name;
}
var person1 = new Person();
alert(person1.getName());//Jack
var person2 = new Person();
alert(person1.getName === person2.getName);//true;共享一个原型对象的方法

  原型是指向原型对象的,这个原型对象与构造函数没有太大关系,唯一的关系是函数的prototype是指向这个原型对象!而基于构造函数创建的对象实例也包含一个内部指针为:[[prototype]]指向原型对象。

  实例属性或方法的访问过程是一次搜索过程:首先从对象实例本身开始,如果找到属性就直接返回该属性值;如果实例本身不存在要查找属性,就继续搜索指针指向的原型对象,在其中查找给定名字的属性,如果有就返回;

  基于以上分析,原型模式创建的对象实例,其属性是共享原型对象的;但也可以自己实例中再进行定义,在查找时,就不从原型对象获取,而是根据搜索原则,得到本实例的返回;简单来说,就是实例中属性会屏蔽原型对象中的属性;

(1)、原型与in操作符

  一句话:无论原型中属性,还是对象实例的属性,都可以使用in操作符访问到;要想判断是否是实例本身的属性可以使用object.hasOwnProperty(‘attr’)来判断;在for-in循环中,返回的是所有能够通过对象访问的、可枚举的属性。

var cat = new Object;
cat.legs = 4;
cat.name = "Kitty";
'legs' in cat; // true
'abc' in cat; // false
"toString" in cat; // true, inherited property!!!
cat.hasOwnProperty('legs'); // true
cat.hasOwnProperty('toString'); // false
cat.propertyIsEnumerable('legs'); // true
cat.propertyIsEnumerable('toString'); // false

(2)、更简单的原型方法

   为了更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,例如:

function Person(){
}
Person.prototype={
       name:'Jack',
       age:29,
       getName:function(){
          return this.name;
       }
};
var friend=new Person();
alert(friend.constructor==Person);//false
alert(friend.constructor==Object);//true

如果constructor的值真的很重要,可以像下面这样特意将它设置回适当的值;

function Person(){
}
Person.prototype={
       constructor:Person,
       name:'Jack',
       age:29,
       getName:function(){
          return this.name;
       }
};

(3)、原生对象中原型

    原生对象中原型与普通对象的原型一样,可以添加/修改属性或方法,如以下代码为所有字符串对象添加去左右空白原型方法:

String.prototype.trim = function(){
    return this.replace(/^\s+/,'').replace(/\s+$/,'');
}
var str = '   word space   ';
alert('!'+str.trim()+'!');//!word space!

(4)、原型对象的问题

   它省略了为构造函数传递初始化参数,这在一定程序带来不便;另外,最主要是当对象的属性是引用类型时,它的值是不变的,总是引用同一个外部对象,所有实例对该对象的操作都会其它实例:

function Person() {
}
Person.prototype.name = 'Jack';
Person.prototype.lessons = ['Math','Physics'];
var person1 = new Person();
person1.lessons.push('Biology');
var person2 = new Person();
alert(person2.lessons);//Math,Physics,Biology,person1修改影响了person2


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

  目前最为常用的定义类型方式,是组合构造函数模式与原型模式。构造函数模式用于定义实例的属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方方法的引用,最大限度的节约内存。此外,组合模式还支持向构造函数传递参数,可谓是集两家之所长。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.lessons = ['Math', 'Physics'];
}
Person.prototype = {
    constructor: Person,//原型字面量方式会将对象的constructor变为Object,此外强制指回Person
    getName: function () {
        return this.name;
    }
}
var person1 = new Person('Jack', 19, 'SoftWare Engneer');
person1.lessons.push('Biology');
var person2 = new Person('Lily', 39, 'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定义方法

在所接触的JS库中,jQuery类型的封装就是使用组合模式来实例的!!!


5、动态原型模式

  组合模式中实例属性与共享方法(由原型定义)是分离的,这与纯面向对象语言不太一致;动态原型模式将所有构造信息都封装在构造函数中,又保持了组合的优点。其原理就是通过判断构造函数的原型中是否已经定义了共享的方法或属性,如果没有则定义,否则不再执行定义过程。该方式原型上的方法或属性只定义一次,且将所有构造过程都封装在构造函数中,对原型所做的修改能立即体现所有实例中:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.lessons = ['Math', 'Physics'];
    if (typeof this.getName != 'function') {//通过判断实例封装
       Person.prototype.getName=function () {
          return this.name;
      };
    }
}
var person1 = new Person('Jack', 19, 'SoftWare Engneer');
person1.lessons.push('Biology');
var person2 = new Person('Lily', 39, 'Mechanical Engneer');
alert(person1.lessons);//Math,Physics,Biology
alert(person2.lessons);//Math,Physics
alert(person1.getName === person2.getName);//true,//共享原型中定义方法

注意:使用动态原型模式时,不能使用对象字面量重写原型。

0
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:676450次
    • 积分:8844
    • 等级:
    • 排名:第2296名
    • 原创:303篇
    • 转载:25篇
    • 译文:0篇
    • 评论:187条
    文章分类
    最新评论