在我看来,函数是就是一个工厂,我们向其加入原材料(参数),工厂进行操作与产出(return)
我们先来探讨一下手工工厂的特点。
接下来我们看一下构造函数:
function ManualRobot(){ this.name=name; this.age=age; this.sayHello=function(){ console.log('Hello') } }
构造函数是一个手工工厂。
为什么这样说呢?
也就是说构造函数会造成内存的浪费,我们能不能把sayHello写在一个空间里,让这200多个对象指向它呢?
当然是可以的,那就是原型对象。(将函数写成全局函数再调用,存在封装不好的缺点,这里不进行讨论。)
众所周知,随着构造函数的产生,会伴随生成一个原型对象,什么是原型对象呢?
举个简单的例子,我有一个全自动化工厂,它会由固定的模板生产出完全相同的机器人。
这个固定的模板是这样的:
function AutoRobot(){} AutoRobot.prototype.name='小机器人' AutoRobot.prototype.age=1 AutoRobot.prototype.sayHello=function(){ console.log('Hello') }
现在,有一个人从我这里买了一个机器人
那么他这个购买的行为也就是:
var r1=new AutoRobot ( )
这个时候可以输出这个机器人的属性
console.log(r1.name) //小机器人 console.log(r1.age) //1 r1.sayHello() //Hello
然后,我的顾客嫌弃机器人太小了,不够成熟
她首先改了这个机器人的名字
r1.name='大机器人'
然后改了机器人的年龄
r1.age=18
这个时候实例r1的名字和年龄就改变了
也就是说:
当我们需要读取对象的某个属性时,都会执行一次搜索。首先在该对象中查找该属性,若找到,返回该属性值;否则,到[[prototype]]指向的原型对象中继续查找。
由此我们也可以看出另外一层意思:如果对象实例中包含和原型对象中同名的属性或方法,则对象实例中的该同名属性或方法会屏蔽原型对象中的同名属性或方法。原因就是“首先在该对象中查找该属性,若找到,返回该属性值;”
以上呢就是利用原型对象创造实例对象,就是原型对象是实例对象的模板。
现在大家对原型对象有了一个基本的认识,接下来我们看一下构造函数,原型对象,实例对象的关系
function AutoRobot(){} AutoRobot.prototype.name='小机器人' AutoRobot.prototype.age=1 AutoRobot.prototype.sayHello=function(){ console.log('Hello') }
在刚开始构造函数的protype属性指向原型对象,原型对象的constructor属性指向构造函数,并且我向原型对象中添加了一些东西。为下图:
然后我们new了一个实例,实例r1的[[Prototype]]属性指向原型对象
var r1=new AutoRobot ( ) console.log(r1.name) //小机器人 console.log(r1.age) //1 r1.sayHello() //Hello
接着我们对实例的属性进行修改
r1.name='大机器人' r1.age=18 console.log(r1.name) //大机器人 console.log(r1.age) //18
这是因为当我们需要读取对象的某个属性时,都会执行一次搜索。首先在该对象中查找该属性,若找到,返回该属性值;否则,到[[prototype]]指向的原型对象中继续查找。
现在我们来总结一下构造函数和原型对象的优缺点
构造函数:
原型对象
构造函数的优点是个性化,原型对象的优点是共享属性。
但是我们在实例化对象的时候呢,往往希望他们的属性是个性的,但方法是共享的。
比如小明叫小明,小红叫小红,郭越叫郭越,但是我们都会吃饭学习记笔记。
这个时候我们可不可以将构造函数和原型对象结合使用呢?
只要我们将属性写在构造函数中,将方法写在原型对象中:
function Student(name,age){ this.name = name this.age = age } Student.prototype.sayHello=function(){ console.log('Hello') } Student.prototype.study=function(){ console.log('我要学习了') } var xiaoming = new Student('小明',18) var xiaohong = new Student('小红',18) var guoyue = new Student('郭越',18)
但这样封装性并不好,有没有更好的方法?
我们在构造函数中加入一个判断,判断某个方法的属性是不是函数。
function Student(name, age) { this.name = name; this.age = age; if(typeof this.studay !== "function"){ Student.prototype.study = function(){ console.log('我要学习了') } } } var guoyue = new Student("郭越", 18) guoyue.studay() //我要学习了
或者直接用ES6中的class关键字创造类
在class类中
class Robot{ constructor(name,age){ this.name = name this.age = age this.myName = function(){ console.log('Hello,i am '+this.name) } } sayBye(){ console.log('Bye!') } } Robot.prototype.factory = '郭越机器厂' //实例化 var r1 = new Robot('机器人1号',18) console.log(r1.factory) Robot.prototype.sayBye() Robot.prototype.myName()
执行结果:
可见sayBye是原型方法,myName不是