JavaScript创建对象之理解原型对象(笔记4)

上次已经详细分析了Person构造函数、Person构造函数的原型对象以及Person的实例对象之间的关系,现在我们来探讨它们的作用域链之间的关系。
示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
unction Person(name,age,job)
{
this.name = name;
this.age = age;
this.job = job;
this.sayNationality = function(){
alert(this.nationality);
};
}
Person.prototype.name = "Ken";
Person.prototype.age = 18;
Person.prototype.job = "Student";
Person.prototype.nationality = "China";
Person.prototype.sayName = function () {
alert(this.name);
}
var person1 = new Person('Nicholas',29,'Software Engineer');
person1.sayName();//Nicholas
person1.sayNationality();//China
var person2 = new Person('Nicholas',29,'Software Engineer');
person2.sayName();//Nicholas

每当代码
person1.sayName();//Ken
person1.sayNationality();//China
person2.sayName();//Ken
读取某个对象的某个属性时,都会执行一次搜索,目标是具有给定名字的属性。搜索首先从对象实例本身开始。如果在实例中找到了具有给定名字的属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找具有给定名字的属性。如果在原型对象中找到了这个属性,则返回该属性的值。也就是说,在调用person1.sayName()的时候,会先后执行两次搜索。首先,解析器会问:“实例person1有sayName属性吗?”答:“没有。”然后,它继续搜索,再问:“person1的原型有sayName属性吗?”答:“有。”于是,它就读取那个保存在原型对象中的函数。同理,当调用person1.sayNationality方法的时候,会先在当前实例对象中搜索有没有sayNationality方法,有就调用这个方法,在执行sayNationality方法的时候,实例对象并没有this.nationality属性成员,它就会通过实例对象的prototype属性的作用域链搜索nationality标识符,在person1的原型对象上有nationality属性,于是就可以正常读取它保存的值。当我们调用person2.sayName()时,将会重现相同的搜索过程,得到相同的结果。而这正是多个对象实例共享原型所保存的属性和方法的基本原理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
person1.constructor //它指向Person构造函数
//从Person构造函数指向原型对象,这样就可以访问原型对象的成员
person1.constructor.prototype
person1.constructor.prototype.name //Ken;
/*underfined,在所有对象实例中都是无法访问到[[Prototype]]
*/
person1.prototype;//underfined
/*
怎样可以不通过person1.constructor来访问prototype呢?可以利用原型对象上的isPrototypeOf()方法来确定person1与原型对象之间是否存在这种关系,若存在就通过Object.getPrototypeOf(person1).name来访问person1的原型对象的成员name属性。
*/
if(Person.prototype.isPrototypeOf(person1))
{
alert(Object.getPrototypeOf(person1).name)//Ken
}

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

1
2
3
4
5
6
7
8
9
10
11
12
function Person(){}
Person.prototype.name = "Ken";
Person.prototype.age = 18;
Person.prototype.job = "Student";
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);//"Ken" 来自原型对象的成员

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function Person(){}
Person.prototype.name = "Ken";
Person.prototype.age = 18;
Person.prototype.job = "Student";
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);//"Ken" 来自原型对象的成员
delete person1.name;
alert(person1.name);//"Ken" 来自原型对象的成员

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

1
2
//在上面代码的最后一行加上这行代码
alert(person1.hasOwnProperty("name"));//false

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

1
2
//在上面代码的最后一行加上这行代码
alert("name" in person1); //true

文章目录