JavaScript 原型链学习(三)原型对象存在的问题 与 组合使用构造函数和原型

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

function Person() {}

Person.prototype={
    constructor: Person,
    name : "Nicholas" ,
    age : 29,
    job : "Software Engineer",
    friends : ["Shelby", "Court"] ,
    sayName : function (){
        alert(this.name ) ;
    }
};

var person1 = new Person();
var person2 = new Person();

person1.friends.push("Van");

alert(person1.friends);    //”Shelby, Court, Van"
alert(person2.friends);    //”Shelby, Court, Van"
alert(person1.friends === person2.friends);     //true    

在此,Person.prototype对象有一个名为frends的属性,该属性包含一个字符串数组。然后,创建了Person的两个实例。接着修改了person1.friends引用的数组,向数组中添加了一个字符串。由于friends数组存在于Person.prototype而非person1中,所以刚刚的修改也会通过person2.friends(与person1.friends指向同一个数组)反应出来。假如我们的初衷就是像这样在所有实例中共享一个数组,那就没什么好说的。可是,实例一般都是要有属于自己的全部属性的。而这个问题正是我们很少看到有人单独使用原型的原因。

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

function Person(name, age, job) {
    this.name = name;
    this.age = age;
    this.job = job;
    this.friends = ["Shelby","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.friends.push ("Van");
alert(person1.friends);    //"Shelby, Count ,Van"
alert(person2.friends);    //"She1 by,Count"
alert(person1.friends === person2.friends);    //false
alert(person1.sayName === person2.sayName);    //true

在这个例子中,实例属性都是在构造函数中定义的,而由所有实例共享的属性constructor和方法sayName()则是在原型中定义的。而修改了person1.friends (向其中添加一个新字符串),并不会影响到person2.friends,因为它们分别引用了不同的数组。
这种构造函数与原型混成的模式,是目前在ECMAScript中使用最广泛、认同度最高的一种创建自定义类型的方法。可以说,这是用来定义引用类型的一种默认模式。

posted @ 2018-08-04 08:56  魏亚林  阅读(288)  评论(0编辑  收藏  举报