说到原型链、原型对象,首先需要讲到构造函数。创建实例可以通过构造函数来创建。但创建实例不止通过构造函数这一种方法,还有字面量创建对象、Object.create()方法。
创建实例对象的几种主要的方法:
1、字面量
var obj={name: "Jakey"};
2、构造函数
var people=function(){
var name="Tom";
}
var obj2=new people();
3、Object.create()
var person={age: 21};
var obj3=Object.create(person);
什么是实例?
通过上面的代码,相信也就明白了实例指的就是实例对象,而实例对象可以通过构造函数创建。实例对象本身就有着__proto__属性,实例对象的__proto__属性指向原型对象。
什么是构造函数?
构造函数与一般函数的区别在于构造函数是用于创建实例对象来使用的,所以构造函数一般都是带有new运算符的函数。构造函数有着所有函数都有的属性:prototype。构造函数的prototype属性指向原型对象。
什么是原型对象?
原型对象是由构造函数的prototype属性和这个构造函数创建的实例对象的__proto__属性共同指向的一个原型链上的对象。如果要判断一个构造函数与实例对象是否有着共同指向的原型对象,可以使用instanceof 来判断,具体用法是 实例对象 instanceof 构造函数。比如引用上面构造函数创建实例对象的例子:obj2 instanceof people,结果返回true。
什么是原型链?
原型对象顾名思义它也是一个对象,所以它也有对象的__proto__属性,那原型对象的__proto__属性也同样地会指向它上一层的原型对象,顺着下去,原型对象的原型对象可能还有它的上一层原型对象,这样一直到Object.prototype这个原型对象为止,这就是整个原型链。
instanceof可以准确判断实例对象属于哪个构造函数吗?
instanceof不仅仅判断实例对象与构造函数是否有着同样指向,实际上,但凡在这个实例对象的原型链上的构造函数与对象之间,使用instanceof来判断都会返回true,所以如果要找到实例对象是直接由哪个构造函数创建的,使用instanceof不可行,这可以使用constructor来替代。比如 obj2 constructor people 就是返回true。
构造函数的new运算符干了什么?
new运算符用来创建实例,底层原理:
var new = function(people){
var obj=Object.create(people.prototype);
var key=people.call(this);
if(typeof key == "object") {
return key;
} else {
return obj;
}
}
很明显,obj是在people函数的原型对象上创建的,这也能解释得通,使用Object.create()来创建对象是可行的。回到上面开始的时候使用Object.create()创建对象的例子,var obj3=Object.create(person),那么可以得出:obj3.__proto__=person
使用new运算符创建对象的原理实现继承
回顾之前所学,继承的方式主要有构造函数实现继承、原型继承、构造函数于原型组合继承,那么如今学习了new运算符的底层原理,我们知道了构造函数创建对象是通过Object.create()实现的,那么对于原型继承是否可以做个优化呢?
下面来分析:
(1)构造函数继承
// 构造函数 var parent=function(){ this.play='Dancing'; }; parent.prototype.say=function () { console.log('hello'); }; var child=function(){ parent.call(this); this.name='Tank'; }; var obj=new child(); console.log(obj.play); // Dancing console.log(obj.name); // Tank console.log(obj.say); // undefined
缺点:父类的原型上属性、方法,子类无法继承
(2)原型继承
为了解决父类的原型属性和方法无法继承
// 原型 var parent2=function(){ this.arr=[1,2,3]; }; var child2=function(){ this.name='Tom'; }; child2.prototype=new parent2(); var obj21=new child2(); var obj22=new child2(); obj21.arr.push(7); console.log(obj21.arr); // 1237 console.log(obj22.arr); // 1237
缺点:原型上继承的方法、属性都一样,改变一个实例对象继承过来的原型上的值,其他实例对象也会改变。
(3)构造函数、原型组合继承
// 组合方法实现继承 var parent3=function(){ this.age=22; }; parent3.prototype.say='i am 32 years old'; var child3=function(){ parent3.call(this); this.name="Jako"; }; child3.prototype=new parent3(); var obj31=new child3(); var obj32=new child3(); obj31.age=32; obj32.say='i am 22 years old'; console.log(obj31.age);//32 // 证明修改继承过来的父类的属性,并不影响其他实例对象 console.log(obj32.age);//22 console.log(obj31.say);// i am 32 years old // 证明可以继承父类的原型并可以做修改,且不影响其他实例对象 console.log(obj32.say);// i am 22 years old
(4)组和方法的优化
组合方法的原理是把构造函数的继承方法与原型继承的方法一起揉合在一起,这就造成了冗余问题。本来父类里面的属性和方法由构造函数通过parent3.call(this)就可以实现继承了,在原型继承child3.prototype=new parent3()上又实现了一次对父类的继承,所以原型继承法又实现一次父类本身的方法和属性显得多余。而回到原型继承的出发点,我们的目的是改善构造函数不能继承父类原型属性和方法的弊端,所以在构造函数继承的基础上,原型继承能不能只是继承父类的原型呢?
能。
在原型继承的关键步骤那里:child3.prototype=new parent3();
我们只需要用new parent3()的原型对象替代new parent3()就可以解决。
根据对原型相关知识的理解,new parent3()是一个实例对象,它的原型是parent3.prototype,也是[new parent3()].__proto__,但后者只是在浏览器控制台里的对象属性,在实际代码中不存在,所以就用前者替代:child3.prototype=parent3.prototype;
// 组合继承方法的优化 var parent4=function(){ this.age=22; }; parent4.prototype.say="i am 32 years old"; var child4=function(){ parent4.call(this); this.name="Jako"; }; child4.prototype=parent4.prototype; var obj41=new child4(); var obj42=new child4(); obj41.age=32; obj42.say='i am 22 years old'; console.log(obj41.age); //32 console.log(obj42.age); //22 console.log(obj41.say); //i am 32 years old console.log(obj42.say); //i am 22 years old console.log(obj41.constructor); // parent4 console.log(obj42.constructor); // parent4
但是,有一个问题是,子类继承父类后,子类对象的直接构造函数不是子类,而是父类,如果要把子类对象的构造函数手动改为子类,那么父类对象的构造函数也会变成子类,因为child4.prototype=parent4.prototype;这句代码已经把子类与父类的原型赋了等值,为了解决这个问题,原型继承还可以继续优化,也就是用另一个方法替代parent4.prototype,可以用Object.create(parent4.prototype);
回顾new 运算符的原理,它的关键部分就是这句代码:var obj=Object.create(people.prototype);也就是说new people()与Object.create(people.prototype)是同样用作创建对象的方法,只是Object.create(people.prototype)是一个中间桥梁的作用
在这里,用的是new parent4();换为Object.create()的表示方法就是Object.create(parent4.prototype)
// 组合继承方法的优化2 var parent4=function(){ this.age=22; }; parent4.prototype.say="i am 32 years old"; var child4=function(){ parent4.call(this); this.name="Jako"; }; child4.prototype=Object.create(parent4.prototype); var obj41=new child4(); var obj42=new child4(); obj41.age=32; obj42.say='i am 22 years old'; console.log(obj41.age); console.log(obj42.age); console.log(obj41.say); console.log(obj42.say); obj41.constructor=child4; console.log(obj41.constructor); // child4 console.log(obj42.constructor); // parent4
另外,在es6语法中新增了一种继承方法,简单介绍一下:
class Parent{
constructor(name){this.name="Tank";}
}
class Child extends Parent(){
constructor(){
super();
}
}
end