1.原型模式
首先我们来谈谈prototype属性,也就是原型属性。每当我们创建一个函数时,函数内部都会自动生成一个指针(既自动生成一个属性就是我们说的prototype),这个指针指向指向原型对象,这个对象的用途是包含可以由它特定类型的所有实例共享的属性和方法,用大白话解释这句话:就是通过用构造函数而创建的它本身这一类的那些对象实例共享原型对象中的属性和方法。注意prototype这个属性在函数中直接看是看不到的,我们可以通过打印函数的prototype来查看。原型对象中会自动生成一个constructor(构造函数)的一个属性:这个属性的指针会指向本函数。有人可能不知道原型对象是什么,其实很好理解,函数的prototype属的值其实就是一个对象(即原型对象)。
下面是原型与实例还有构造函数之间的关系(画的不太好谅解。。)
function Hanshu(){ console.log("hi") } console.log(Hanshu.prototype) console.log(Hanshu.prototype.constructor)
第一个console打印出来的就是原型对象(既prototype对应的值),点进去里面会有函数的一些属性,对象里面有一个constructor的一个属性。当我们再去打印prototype属性里面的constructor属性的时候打印出本函数体。
接着我们来看共享原型对象属性的例子。
function Hanshu(a,b,c){ this.name=a; this.age=b this.job=c } Hanshu.prototype.gongxiang="我是原型对象中的一个属性我被所以实例所共享" var a=new Hanshu("大牛","30","装") var b=new Hanshu("大牛2","300","傻") console.log(a.gongxiang) //我是原型对象中的一个属性我被所以实例所共享 console.log(b.gongxiang) //我是原型对象中的一个属性我被所以实例所共享
我们可以看到当给Hanshu的原型对象添加属性的时候,在Hanshu的所有实例中都可以访问到gongxiang这个属性,上面这段代码解释了函数原型对象中的属性和方法被它的所有实例所共享。
接下来我们探索一下解释器在读代码的时候,是先访问的实例还是原型对象的问题。
function Hanshu(a,b,c){ this.name=a; this.age=b this.job=c this.gongxiang="我是实例里面的属性" //新添加了一个属性 } Hanshu.prototype.gongxiang="我是原型对象中的一个属性我被所以实例所共享" var a=new Hanshu("大牛","30","装") var b=new Hanshu("大牛2","300","傻") console.log(a.gongxiang) //我是实例里面的属性 console.log(b.gongxiang) //我是实例里面的属性
上面这段代码唯一改变的就是我们在函数里面添加了一个与原型对象中属性一致的名字,这个时候我们再去访问实例里面的gongxiang这个属性的时候发现它打印出来的是实例里面的属性。也就是说解释器在读取代码的时候,首先先去实例里面找gongxiang这个属性,然后才回去找原型里面的属性,注意这里函数实例里面的属性屏蔽掉了原型里面的属性。也许这个例子让你感觉不到访问先后顺序,那我们在来改变一下代码位置。
function Hanshu(a,b,c){ this.name=a; this.age=b this.job=c } var a=new Hanshu("大牛","30","装") var b=new Hanshu("大牛2","300","傻") Hanshu.prototype.gongxiang="我是原型对象中的一个属性我被所以实例所共享" //先创建实例在给原型添加属性 console.log(a.gongxiang) //我是原型对象中的一个属性我被所以实例所共享 console.log(b.gongxiang) //我是原型对象中的一个属性我被所以实例所共享
上面这段代码我们又改变的代码顺序,我们先创建里实例,然后才给原型添加的属性,这个时候依然可以访问到gongxiang这个属性,这就是原型模式的动态属性,当解释器去读取代码的时候,先去访问实例中的代码,发现没有,然后就去访问原型对象,发现有这个属性,再把它读出来。
关于原型对象的从写:
我们一遍一遍的重复的写类似obj.prototype.xxx=???这样的语法太麻烦了,既然原型对象是对象,那我们可以直接去定义原型对象,可以一样问题来了constructor本来是原型对象里面自动执行函数本身的,由于从写constructor不再执行原函数。还有书上并没有说从写原型对象以后,原型对象是否还跟属性共享的问题。请看以下代码
function Abc(){ } Abc.prototype={ name:"ffk", age:10, job:"work" } var Hanshu=new Abc() console.log(Hanshu.name)//ffk console.log(Abc.prototype)//
原型从写以后原型对象里面的属性和方法依旧被共享。可是当我们去打印构造函数的原型对象的时候,constuctor不再指向函数本身,而是继承obj。
其实从写原型函数带来的问题还是有一些的,我们继续看一下代码
function Abc(){ } var Hanshu=new Abc() //换了位置放到了前面 Abc.prototype={ name:"ffk", age:10, job:"work" } console.log(Hanshu.name)// undefined
★★★重要
当我们把对象创建放到从写原型对象之前,发现再去访问的时候,已经找不到属性了,虽然之前我们说过解释器在读代码的时候先去实例里面找然后再去原型里面找(动态属性)。
在从写原型对象的情况下,也就是说我们创建对象实例之前,你并没有告诉解释器我的原型对象要换对象了,所以在它创建实例的时候它的实例内部指针还指向的是默认的那个最初的原型对象,等你后面再从写原型对象的时候已经晚了,最初原型对象中只有coustructor属性,没有name,所以打印出来是undefined(书上不是这样说的,我是按我自己的理解来解释的,我感觉这样说比书上更好理解。。大家这样理解就行了没问题的)。