2.3原型模式
实际上,我们创建的每个函数都有一个prototype(原型)属性。这个属性是一个指针,它指向一个对象。而这个对象的用途是 包含可以由特定类型的所有实例共享的属性和方法。
按照字面意思来理解,prototype 就是 通过 构造函数 ,而创建的对象实例的 原型对象。基本可以类比于java中类的概念。
使用原型对象的好处是可以让所有对象实例分享它包含的属性和方法,而不必在构造函数中定义对象实例的信息。而是直接将他们添加在原型对象中,这与类的概念已经非常非常相似了。
可以发现,page1和page2的方法相同,原因是二者的方法都取自原型对象的方法,而不是自己new一个出来
换句话说,page1和page2访问了同一组属性和同一个alert1函数。
1.理解原型的对象
任何时候,只要创建了新的函数,就会根据一组特定的规则创建一个prototype属性。这个属性指向函数的原型对象。在默认情况下,所有原型对象都会自动获取一个constructor(构造函数)属性,这个属性包含一个指向prototype属性所在函数的指针。
如同上图的例子:
Page是一个新函数 == >
page有一个prototype属性 == >
Page.prototype指向原型对象 == >
原型对象包含一个指向prototype属性所在函数(Page)的指针 == >
结论:Page.prototype.constructor = Page
创建一个自定义的构造函数后,其原型对象只会拥有一个constructor属性,其他有关方法,都是从Object继承而来。当使用构造函数创建实例后,实例的内部会包含一个指针,它指向构造函数的原型对象,ECMA-262中管这个指针叫做[[Prototype]],访问时可以使用__proto__来访问。
这个指针是用来连接实例与原型对象的。
① 原型对象的constructor属性指向构造函数
② 构造函数的prototype属性指向原型对象
③ 实例的[[prototype]]指向原型对象
实际上我们可以发现,两个实例person1和person2中都不包含具体的方法sayName(),但是我们却可以调用person1.sayName(),这是通过查找对象属性的过程来实现的。
检查一个实现与原型对象是否存在关系可以使用原型对象.isPrototype()
当一个实例的[[Prototype]]指向调用isPrototype()的原型对象时,这个函数就会返回true:
//p1 是 实例
//Person函数 是 构造函数
//Person.prototype 是 原型对象
原型对象的isPrototypeOf(实例) = true
Person.prototype.isPrototypeOf(p1) = true
另外有一个新方法使得我们可以通过实例获取原型对象:Object.getPrototypeOf(实例)
获取到原型对象之后,我们便可以访问原型对象中自定义的属性的值,也可以更改原型对象中自定义的属性的值。
每当代码读取某个实例的某个属性时,都会执行一次搜索,搜索目标时给定名字的属性。首先从实例本身开始,如果在实例中找到具有给定名称的属性,则返回;如果没有找到,则搜索指针指向实例的原型对象,如果在原型对象中找到具有给定名称的属性,则返回;否则就会报错:并没有定义该属性。
说白了,当我们访问一个属性时,它会先问:
实例有这个属性吗?有-返回,没有-继续请问实例的原型对象。
实例的原型对象有这个属性吗? 有-返回,没有-报错。
因此,如果在原型对象和实例中定义了命名相同的值,那么访问实例的时候,只会返回实例中定义的值。这并不是说你通过为实例的属性赋值而更改了原型对象中的某个属性值,而是在搜索时第一次提问便拿到了结果,所以根本就没有执行第二次提问。
如果我们想重新访问实例对应的原型对象中的某个属性的话,就可以i使用delete 实例.属性,这样就能清除实例中的属性值,进而访问原型对象中的属性了。