JavaScript OOP(三):prototype原型对象(即构造函数的prototype属性)
通过构造函数生成的实例化对象,无法共享属性或方法(即每个实例化对象上都有构造函数中的属性和方法);造成了一定的资源浪费
1 function Obj(name,age){ 2 this.name=name; 3 this.age=age; 4 this.func=function(){ 5 return 'this is a test function'; 6 }; 7 } 8 var o1=new Obj('小明',10); 9 var o2=new Obj('小白',12); 10 console.log(o1.func===o2.func);
运行结果:
我们可以看出所有实例化对象(即o1,o2)中的func()都相同。但是每个对象都新建了func()方法,显得多余且浪费资源
为了解决上述问题,就要用到JavaScript的prototype对象:起到共享某些相同属性和方法的作用!
JavaScript的对象都继承自"原型"对象(与java、c++中类相似的作用);除了null,null没有自己的原型
JavaScript原型设计机制:原型上面的属性和方法,都能够被子对象共享
1 function Obj1(name,age){ 2 this.name=name; 3 this.age=age; 4 } 5 Obj1.prototype.address='beijing';//原型上的属性被所有实例化对象共享 6 Obj1.prototype.begin=function(){//原型上的方法被所有实例化对象共享 7 return 'this is a begin function'; 8 }; 9 var o3=new Obj1('晓明',100); 10 var o4=new Obj1('你猜',90); 11 console.log(o3,o4); 12 console.log(o3.address,o4.address); 13 console.log(o3.begin(),o4.begin());
运行结果:
但是实例化对象的属性或方法可能覆盖原型对象上的属性或方法
1 o3.address='shanghai'; 2 o3.begin=function(){ 3 return 'this is a begin function for o3'; 4 }; 5 console.log(o3.address,o3.begin());
运行结果:
实际上所有函数都有自己的原型对象;因为函数在广义上可以认为是对象
对象能作为其他对象的原型对象,也能作为原型对象的实例化对象,由此形成了prototype chain原型链
所有的对象的原型对象如果一层层往上“回溯”,最后可以回溯到Object.prototype;而Object.prototype的原型对象是null,null没有自己的原型对象
1 console.log(Object.getPrototypeOf(Object.prototype));
运行结果:
表明Object.prototype的原型对象是null
如果尝试获取null或undefined的原型对象:
1 console.log(Object.getPrototypeOf(null));//报错 Cannot convert undefined or null to object
注意:读取对象的某个属性时,js引擎会先在对象本身属性上寻找,如果找不到,那么去原型对象上找,一层一层往上"回溯",直至null.所以如果找寻某个不存在的属性,会将完整的原型链遍历一变,对性能影响较大。
constructor属性:prototype原型对象有一个constructor属性,默认指向prototype所在的构造函数
1 var O5=function(){}; 2 O5.prototype=new Array();//O5的prototype属性等于new Array();那么O5的实例化对象共享着Array对象的所有属性和方法,并且O5.prototype的constructor也与Array.prototype的constructor一样 3 // O5.prototype.constructor=O5; 4 var o6=new O5(); 5 o6.push(1,2,3); 6 console.log(o6); 7 console.log(O5.prototype.constructor===Array.prototype.constructor);//true
运行结果:
因为constructor属性定义在prototype上,所以所有实例对象都能访问
1 var a1=new O6(); 2 var a2=new O6(); 3 console.log(a1.constructor===a2.constructor && a1.constructor===O6.prototype.constructor);
运行结果:
所以使用constructor属性,能确定对象使用的构造函数;同时我们可以根据constructor属性,间接调用构造函数
1 var a3=new a2.constructor(); 2 console.log(a3.constructor===a1.constructor);//true
注意原型对象被覆盖可能出现的问题:
1 function Abc(){}; 2 Abc.prototype.constructor=Abc; 3 Abc.prototype.f=function(){ 4 console.log('hello'); 5 }; 6 var a4=new Abc(); 7 console.log(a4.constructor===Abc.prototype.constructor); 8 Abc.prototype={ 9 f1:function(){ 10 console.log('hi'); 11 } 12 };//此时,定义Abc.prototype为一个新对象,原先定义在prototype上面的属性和方法均失效 13 //所以调用原先prototype的f方法,报错 14 var a5=new Abc(); 15 //a5.f();//a5.f is not a function 16 //但是能调用新定义的属性或方法 17 a5.f1();//'hi'
运行结果:
name属性,可以查看构造函数的名字
1 function Obj2(){}; 2 var o7=new Obj2(); 3 console.log(o7.constructor.name);//Obj2
运行结果:
总结起来:1. 构造函数生成对象;构造函数的原型(prototype)属性上面定义的方法或属性被所有实例化对象共享;构造函数的原型属性是实例对象的原型对象。
2. constructor属性时定义在构造函数的prototype属性(原型对象),被所有实例化对象共享;所以实例化的对象能够直接调用constructor属性
3. 原型对象=构造函数的prototype属性
instanceof 关键字:判断对象是否为构造函数的实例
1 function Obj3(){}; 2 var o8=new Obj3(); 3 console.log(o8 instanceof Obj3); 4 console.log(o8 instanceof Object); 5 //instanceof对整个原型链上对象均有效 6 var n1=new Number(10);//var n1=new Object(10); 7 console.log(n1 instanceof Number,n1 instanceof Object);//true true 8 9 // o8 instanceof Obj3等同于Obj3.prototype.isPrototypeOf 10 console.log(Obj3.prototype.isPrototypeOf(o8));//true 11 //可以翻译成Obj3构造函数的prototype(原型)属性是o8的原型对象 12 13 //null,undefined instanceof Object均为false
运行结果:
Object.getPrototypeOf():获取一个对象的原型对象
1 //console.log(Object.getPrototypeOf(null),Object.getPrototypeOf(undefined));//Cannot convert undefined or null to object
不能获取null或者undefined的原型对象
function test1(){} console.log(Object.getPrototypeOf(test1)); console.log(Object.getPrototypeOf(test1)===Function.prototype) function Test(){} var t1=new Test(); console.log(Object.getPrototypeOf(t1)); console.log(Object.getPrototypeOf(t1)===Test.prototype);
运行结果:
Object.setPrototypeOf():第一个参数是现有对象,第二个是原型对象;返回一个新对象
1 var a={ 2 name:'apple', 3 f:function(){ 4 console.log('this is a test function'); 5 } 6 }; 7 var o9=Object.setPrototypeOf({},a); 8 //o9本身是空对象,它的原型对象是a;o9能调用a的属性和方法 9 console.log(o9); 10 console.log(o9.name);//apple 11 o9.f();
运行结果:
所以我们通过构造函数生成实例化对象,本质其实就是将构造函数的property属性赋值给实例对象的原型对象
1 function F(){}; 2 var f1=new F(); 3 console.log(f1); 4 /** 5 * 相当于 6 * var f1=Object.setPrototypeOf({},F.prototype); 7 * F.call(f1);//调用F函数,this指向f1 8 */
Object.create()使用场景:
有时候,我们不能拿到对象的构造函数,只能取到实例对象;比如如下生成的:
1 var o10={ 2 name:'chrome', 3 speed:'fast' 4 };
o10是一个实例对象,但是并不是很容易找到它的构造函数
那么如何以o10为原型,另外生成一个实例对象?使用Object.create()
1 var o11=Object.create(o10); 2 console.log(o11); 3 console.log(o11.name,o11.speed); 4 console.log(Object.getPrototypeOf(o11)===o10);//true;说明o11的原型对象等于o10
运行结果:
Object.prototype.isPrototypeOf:判断是否是某个对象的原型对象
1 console.log(o10.isPrototypeOf(o11));//true;o10是o11的原型对象 2 console.log(o11.isPrototypeOf(o10)); 3 console.log(Object.prototype.isPrototypeOf(null));//除了null或者Object.create(null)生成对象,其它对象的原型对象往原型链回溯一定可以回溯到Object.prototype 4 console.log(Object.prototype.isPrototypeOf(Array));
运行结果:
__proto__:内部属性,不应该对使用者显露;尽量减少使用这个属性
__pro__:设置对象的原型对象;其实一般都可以用Object.getPrototypeOf()和Object.setPrototypeOf()替代
1 var o12={ 2 name:'apple', 3 age:100 4 }; 5 var o13={ 6 sex:'male', 7 email:'qq@com' 8 }; 9 o12.__proto__=o13; 10 console.log(Object.getPrototypeOf(o12)===o13); 11 //即__pro__是指定对象的原型对象,也就是构造函数的prototype属性
运行结果:
总结:
- JavaScript的继承机制主要是基于prototype的。
- 构造函数生成实例化对象;构造函数的prototype属性就是实例化对象的原型对象;原型对象上的属性和方法被所有实例化对象所共享!
- 原型链一般往上回溯可以回溯到Object.prototype;Object.prototype的原型对象是null;而null的原型对象不存在!
- 原型对象上有construtor属性,等于构造函数名;因为是定义在原型对象上,所以被所有实例对象共享(由此我们也可以间接调用构造函数生成实例对象)!
- Object.getPrototypeOf():获取某个对象的原型;不能获取null或者undefined的原型
- Object.setPrototypeOf():第一个参数是现有对象,第二个是原型对象;返回一个新对象
- Object.create():以参数为原型对象生成新对象
- __proto__属性:设置对象的原型对象;尽量减少使用该属性
- instanceof:判断对象是否是某构造函数的实例对象
自己用Excel画的一张图:
当时光不再,莫空留遗恨!