JS 原型的 理解
1、理解原型对象
我们创建 函数时,函数有一个 prototype(原型)属性,指向一个原型对象。在默认情况下,所有原型对象都会自动获得一个 constructor(构造函数)属性,这个属性包含一个指向 prototype 属性所在函数的指针。
function Person(){ }
就拿前面的例子来说,Person.prototype. constructor 指向 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(); //"Nicholas"
创建了自定义的构造函数之后,其原型对象默认只会取得 constructor 属性;至于其他方法,则都是从 Object 继承而来的。
当调用构造函数创建一个新实例后,实例有一个属性__proto__(图片中用{[prototype]});指向构造函数的原型对象。
Person 的每个实例——person1 和 person2 都包含一个内部属性,该属性仅仅指向了 Person.prototype;换句话说,它们与构造函数没有直接的关系。
可以通过 isPrototypeOf()方法来确定对象之间是否存在这种关系。
用原型对象的 isPrototypeOf()方法测试了 person1 和 person2。因为它们内部都有一个指向 Person.prototype 的指针,因此都返回了 true。
alert(Person.prototype.isPrototypeOf(person1)); //true
alert(Person.prototype.isPrototypeOf(person2)); //true
Object.getPrototypeOf(),在所有支持的实现中,这个方法返回[[Prototype]]的值。例如:
alert(Object.getPrototypeOf(person1) == Person.prototype); //true
alert(Object.getPrototypeOf(person1).name); //"Nicholas"
当读取某个对象的某个属性时,都会执行一次搜索,搜索首先从对象实例本身开始。
如果在实例中找到属性,则返回该属性的值;如果没有找到,则继续搜索指针指向的原型对象,在原型对象中查找属性。
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"—— 来自原型
下面是内存 的 展示:
通过使用 hasOwnProperty()方法,什么时候访问的是实例属性,什么时候访问的是原型属性就一清二楚了。
调用 person1.hasOwnProperty( "name")时,只有当 person1 重写 name 属性后才会返回 true,因为只有这时候 name 才是一个实例属性,而非原型属性。
2、原型与in 操作符
在单独使用时, in 操作符会在通过对象能够访问给定属性时返回 true,无论该属性存在于实例中还是原型中。
var person1 = new Person(); var person2 = new Person(); alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true person1.name = "Greg"; alert(person1.name); //"Greg" —— 来自实例 alert(person1.hasOwnProperty("name")); //true alert("name" in person1); //true alert(person2.name); //"Nicholas" —— 来自原型 alert(person2.hasOwnProperty("name")); //false alert("name" in person2); //true delete person1.name; alert(person1.name); //"Nicholas" —— 来自原型 alert(person1.hasOwnProperty("name")); //false alert("name" in person1); //true
以上代码执行的整个过程中, name 属性要么是直接在对象上访问到的,要么是通过原型访问到的。因此,调用"name" in person1 始终都返回 true,无论该属性存在于实例中还是存在于原型中。
同时使用 hasOwnProperty()方法和 in 操作符,就可以确定该属性到底是存在于对象中,还是存在于原型中
在使用 for-in 循环时,返回的是所有能够通过对象访问的、可枚举的(enumerated)属性,其中既包括存在于实例中的属性,也包括存在于原型中的属性。
3、更简单的原型写法(原型的重写)
前面例子中每添加一个属性和方法就要敲一遍 Person.prototype。为减少不必要的输入,也为了从视觉上更好地封装原型的功能,更常见的做法是用一个包含所有属性和方法的
对象字面量来重写整个原型对象,
function Person(){ } Person.prototype = { name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
但有一个例外: constructor 属性不再指向 Person 了。这里本质上完全重写了prototype 对象,因此 constructor 属性不再指向 Person 函数。
constructor 属性则等于 Object 而不等于 Person 了。
Person.prototype = { constructor : Person, name : "Nicholas", age : 29, job: "Software Engineer", sayName : function () { alert(this.name); } };
4、原型的动态性
在原型中查找值的过程是一次搜索,因此我们对原型对象所做的任何修改都能够立即从实例上反映出来——即使是先创建了实例后修改原型也照样如此。
var friend = new Person(); Person.prototype.sayHi = function(){ alert("hi"); }; friend.sayHi(); //"hi"(没有问题!)
即使 person 实例是在添加新方法之前创建的,但它仍然可以访问这个新方法。
我们调用 person.sayHi()时,首先会在实例中搜索名为 sayHi 的属性,在没找到的情况下,会继续搜索原型。
调用构造函数时会为实例添加一个指向最初原型的[[Prototype]]指针,而把原型修改为另外一个对象就等于切断了构造函数与最初原型之间的联系。
但是如果重写了 原型,就会出问题:
先创建了 Person 的一个实例,然后又重写了其原型对象。然后在调用friend.sayName()时发生了错误,因为 friend 指向的原型中不包含以该名字命名的属性。
function Person(){ } var friend = new Person(); Person.prototype = { constructor: Person, name : "Nicholas", age : 29, job : "Software Engineer", sayName : function () { alert(this.name); } }; friend.sayName(); //error
如下:
6、原型对象的问题
原型模式的最大问题是由其共享的本性所导致的。
当创建的对象有引用类型是,就会出现问题
当p1的friends增加时,p2的也变了,这就是问题所在,共享的属性(引用类型)会被修改
这个问题正是我们很少看到有人单独使用原型模式的原因所在,所以一般用
组合使用 构造函数和原型 模式:
属性放到构造函数中,方法放到原型中去