Javascript-10-面向对象-2

创建对象

虽然Object构造函数或对象字面量都可以用来创建单个对象,但这些方式有个明显的缺点:使用同一个接口创建很多对象,会产生大量的重复代码。为解决这一问题,人们开始使用工厂模式的一种变体。

工厂模式

工厂模式抽象了创建具体对象的过程,用函数来封装以特定接口创建对象的细节,例如:

function createPerson(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

var person1 = createPerson('Nicholas', 29, 'Software Engineer');
var person2 = createPerson('greg', 27, 'Doctor');

工厂模式虽然解决了创建多个相似对象的问题,但却没有解决对象识别的问题(即怎样知道一个对象的类型)。

构造函数模式

JS中的构造函数可以用来创建特定类型的对象。像Object和Array这样的原生构造函数,在运行时会自动出现在执行环境中。此外,也可以创建自定义的构造函数,从而定义自定义对象类型的属性和方法。例如,可以用构造函数重构前面的例子:

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.sayName = function() {
        alert(this.name);
    }
}

var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');

与工厂模式的不同之处:

  • 没有显式地创建对象。
  • 直接将属性和方法赋给了this对象。
  • 没有return语句。

按照惯例,构造函数始终都应该以一个大写字母开头,而非构造函数则应该以一个小写字母开头。因为构造函数本身也是函数函数,只不过可以用来创建对象而已。

要创建Person的新实例,必须使用new操作符。以这种方式调用构造函数实际上会经历以下4步:

  • 创建一个新对象。
  • 将构造函数的作用域赋给新对象(因此this就指向了这个新对象)。
  • 执行构造函数中的代码(为这个新对象添加属性)。
  • 返回新对象。

对象的constructor属性最初是用来标识对象类型的。但是检测对象类型,还是instanceof操作符更可靠一些。

创建自定义的构造函数意味着将来可以将它的实例标识为一种特定的类型;而这正是构造函数模式胜过工厂模式爹地方。

将构造函数当作函数

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

构造函数的问题

使用构造函数的主要问题,就是每个方法都要在每个实例上重新创建一遍。以这种方式创建函数,会导致不同的作用域链和标识符解析,但创建Function新实例的机制仍然是相同的。因此,不同实例上的同名函数是不相等的。

原型模式

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,而这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。使用原型对象的好处是可以让所有对象实例共享它所包含的属性和方法。例如:

function Person() {
    Person.prototype.name = 'Nicholas';
    Person.prototype.age = 29;
    Person.prototype.job = 'Software Engineer';
    Person.prototype.sayName = function() {
        alert(this.name);
    }

    var person1 = new Person();
    person1.sayName(); // 'Nicolas'

    var person2 = new Person();
    person2.sayName(); // 'Nicholas'

    alert(person1.sayName == person2.sayName); // true
}
理解原型对象

无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性是一个指向prototype属性所在函数的指针。通过这个构造函数,我们还可继续为原型对象添加其他属性和方法。

创建了自定义的构造函数之后,其原型对象默认只会取得constructor属性;至于其他方法,则都是从Object继承来的。当调用构造函数创建一个新实例后,该实例内部将包含一个指针(内部属性),指向构造函数的原型对象–[[Prototype]]。虽然在脚本中没有标准的方式访问[[Prototype]],但Firefox、Safari、Chrome在每个对象上都支持一个属性__proto__;而在其他实现中,这个属性对脚本则是完全不可见的。不过,要明确的是,这个连接存在于实例与构造函数的原型对象之间,而不是存在于实例与构造函数之间。

每当代码读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值。如果没有找到。则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

当为对象实例添加一个属性时,这个属性就会屏蔽原型对象中保存的同名属性,即添加这个属性只会阻止我们访问原型中的那个属性,但不会修改那个属性。即使将这个属性设置为null,也只会在实例中设置这个属性,而不会回复其指向原型的连接。使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法(不要忘了它是从Object继承来的)只在给定属性存在于对象实例中时,才会返回true。

原型与in操作符

有两种方式使用in操作符:单独使用和在for-in循环中使用。在单独使用时,in操作符会在通过对象能够访问给定属性时返回true,无论该属性存在与实例中还是原型中。

由于in操作符只要通过对象能够访问代属性就返回true,hasOwnProperty()只在属性存在于实例中时才返回true,因此,只要in操作符返回true而hasOwnProperty()返回false,就可以确定属性是原型中的属性。

在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。

要取得对象上所有可枚举的实例属性,可以使用Object.keys()方法。这个方法接收一个对象作为参数,返回一个包含所有可枚举属性的字符串数组。

更简单的原型语法

为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的对象字面量来重写整个原型对象,例如:

function Person() {

}

Person.prototype = {
    name: 'Nicholas',
    age: 29,
    job: 'Software Engineer',
    sayName: function() {
        alert(this.name)
    }
}
原型的动态性

由于在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来–即使是先创建了实例后修改原型也照样如此,例如:

var friend = new Person();

Person.prototype.sayHi = function() {
    alert('Hi');
}

friend.sayHi(); // 'Hi'

重写原型对象切断了现有原型与任何之前已经存在的对象实例之间的练习,它们引用的仍然是最初的原型。

原生对象的原型

原型模式的重要性不仅体现在创建自定义类型方面,就连所有原生的引用类型,都是采用这种模式创建的。所有原生引用类型(Object、Array、String等等)都在其构造函数的原型上定义了方法。

通过原生对象的原型,不仅可以取得所有默认方法的引用,而且可以定义新方法。

原型对象的问题

原型模式省略了为构造函数传递初始化参数,所有实例子啊默认情况下都将取得相同的属性值。原型模式的最大问题是由其共享的本性导致的。

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

创造自定义类型的最常见方式就是组合使用构造函数与原型模式。构造函数用于定义实例属性,而原型模式用于定义方法和共享的属性。结果,每个实例都会有自己的一份实例属性的副本,但同时又共享着对方法的引用,最大限度的节省了内存。另外,这种混成模式还支持向构造函数传递参数。

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.firends = ['Sgelby', 'Court']
}

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

var person1 = new Person('Nicholas', 29, 'Software Engineer');
var person2 = new Person('Greg', 27, 'Doctor');

person1.firends.push('Van');
alert(person1.firends); // 'Shelby, Court, Van'
alert(person2.friends); // 'Shelby, Court'
alert(person1.firends === person2.friends); // false
alert(person1.sayName === person2.sayName); // true
动态原型模式

动态原型模式吧所有的信息都封装在了构造函数中,而通过在构造函数中初始化原型(仅在必要的情况下),又保持了同时使用构造函数和原型的优点。换句话说,可以通过检查某个应该存在的方法是否有效,来决定是否需要初始化原型。例如:

function Person(name, age, job) {
    // 属性
    this.name = name;
    this.age = age;
    this.job = job;

    // 方法
    if (typeof this.sayName != function) {
        Person.prototype.sayName = function() {
            alert(this.name);
        }
    }
}

var friend = new Person('Nicholas', 29, 'Software Engineer');
friend.sayName(); 
寄生构造函数模式

通常,在前述的几种模式都不适用的情况下,可以使用寄生(parasitic)构造函数模式。这种模式的基本思想是创建一个函数,该函数的作用仅仅是封装创建对象的代码,然后再返回新创建的对象。例如:

function Person(name, age, job) {
    var o = new Object();
    o.name = name;
    o.age = age;
    o.job = job;
    o.sayName = function() {
        alert(this.name);
    }
    return o;
}

var friend = new Person('Nicholas', 29, 'Software Engineer');
friend.sayName(); // 'Nicholas'

关于寄生构造函数模式,有一点需要说明;首先,返回的对象与构造函数或者构造函数的原型属性之间没有关系;也就是说,构造函数返回的对象与在构造函数外部创建的对象没什么不同。为此,不能依赖instanceof操作符来确定对象类型。所以在可以使用其他模式的情况下,不要使用这种模式。

稳妥构造函数模式

稳妥对象,指的是没有公共属性,而且其方法也不引用this的对象。稳妥对象最适合在一些安全的环境中(这些环境中会禁止使用this和new),或者防止数据被其他应用程序改动时使用。稳妥构造函数遵循与寄生构造函数类似的模式,但有两点不同:一是新创建的对象的实例方法不引用this;二是不使用new操作符调用构造函数。按照稳妥构造函数的要求,可以将前面的Person构造函数重写如下:

function Person(name, age, job) {
    //  创建要返回的对象
    var o = new Object();

    // 可以在这里定义私有变量和函数

    // 添加方法
    o.sayName = function() {
        alert(name);
    }

    // 返回对象
    return o;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值