JavaScript语言对原型对象的理解

  

function Person() {

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

var person1 = new Person();
person1.sayName();

var person2 = new Person();
person2.sayName();
View Code

 

  无论什么时候,只要创建了一个新函数,就会根据一组特定的规则为该函数创建一个prototype属性,这个属性指向函数的原型对象(图6.1中的序号1)。在默认情况下,所有原型对象都会自动获得一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针(图6.1中的序号2)。

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

  虽然在所有实现中都无法访问到[[Prototype]].但可以通过isPrototypeOf()方法来确定对象之间是否存在这种关系,从本质上讲,如果[[Prototype]].指向调用isPrototypeOf()方法的对象(Person.prototype),那么这个方法就返回true。

  

alert(Person.prototype.isPrototypeOf(person1));   //true

  ECMAScript5增加了一个新方法,叫Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。

alert(Object.getPrototypeOf(person1) == Person.prototype); //true

   虽然可以通过对象实例访问保存在原型中的值,但却不能通过对象实例重写原型中的值。如果我们在实例中添加了一个属性,而该属性与实例原型中的一个属性同名,那我们就在实例中创建该属性,该属性将会屏蔽原型中的那个属性。

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();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name);  //"Greg" --来自实例
alert(person2.name);    //"Nicholas" --来自原型
View Code

  使用delete操作符则可以完全删除实例属性,从而让我们能够重新访问原型中的属性。

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();
var person2 = new Person();

person1.name = "Greg";
alert(person1.name);  //"Greg" --来自实例
alert(person2.name);    //"Nicholas" --来自原型

delete person1.name;
alert(person1.name);   //"Nicholas"  --来自原型
View Code

  使用hasOwnProperty()方法可以检测一个属性是存在于实例中,还是存在于原型中。这个方法只在给定属性存在于对象实例中时,才会返回true。

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();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));  //false
person1.name = "Greg";
alert(person1.hasOwnProperty("name"));  //true
View Code

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

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();
var person2 = new Person();

alert(person1.hasOwnProperty("name"));  //false
alert("name" in person1);  //true
View Code

   hasPrototypeProperty()方法,当一个属性先是存在于原型中,因此hasPrototypeProperty()返回true.当在实例中重写name属性后,该属性就存在于实例中了,因此hasPrototypeProperty()返回false.即使原型中仍然有name属性,但 由于现在实例中也有了这个属性,因此原型中的name属性就用不到了(该方法没有找到,是否弃用?)。

  在使用for-in循环时,返回的是所有能够通过对象访问的、可枚举的属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。屏蔽了原型中不可枚举属性(即将[[Enumerable]]标记为false的属性)的实例属性也会在for-in循环中返回。

  更简单的原型语法

function Person() {

}
Person.prototype ={
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName :function () {
        alert(this.name);
    }
};
View Code

  在上面的代码中,我们将Person.prototype设置为等于一个以对象字面量形式创建的新对象。最终的结果相同,但有一个例外:constructor属性不再指向Person了。前面曾经介绍过,每创建一个函数,就会同时创建它的prototype对象,这和对象也会自动获得constructor属性。而我们在这里使用的语法,本质上完全重写了默认的prototype对象,因此constructor属性也就变成了新对象的constructor属性(指向Object构造函数),不再指向Person函数。此时,尽管instanceof操作符还能返回正确的结果,但通过constructor已经无法确定对象的类型了。

function Person() {

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

var friend = new Person();

alert(friend instanceof Object);  // true
alert(friend instanceof Person);  // true
alert(friend.constructor == Person); //false
alert(friend.constructor == Object);  //true
View Code

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

function Person() {

}
Person.prototype ={
    constructor:Person,
    name:"Nicholas",
    age:29,
    job:"Software Engineer",
    sayName :function () {
        alert(this.name);
    }
};
View Code

  原型的动态性

  由于在原型中查找值的过程是一次搜索,因此我对原型对象所做的任何修改都能够立即从实例上反映出来-即使是先创建了实例后修改原型也照样如此(当调用friend.sayHi()时,首先会在实例中搜索名为sayHi的属性,在没找到的情况下,会继续搜索原型)。

function Person() {

}
Person.prototype ={
    constructor:Person,
    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"
View Code

  尽管可以随时为原型添加属性和方法,并且修改能够立即在所有对象实例中反映出来,但如果是重写整个原型对象,那么情况就不一样了。我们知道,调用构造函数时回味实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。

  

  原型对象的问题

  原型对象所有属性是被很多实例共享的,这种共享对于函数非常合适。对于那些包含基本值的属性倒也说的过去。然而,对于包含引用类型值的属性来说,问题就比较突出了。

function Person() {

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

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

person1.friend.push("Van");

alert(person1.friend);  //"Shelby,Court,Van"
alert(person2.friend);  //"Shelby,Court,Van"
alert(person1.friend == person2.friend);   //true
View Code

 

 

posted @ 2019-12-04 16:46  给自己一点正能量!  阅读(238)  评论(0)    收藏  举报