JS:构造函数继承原型链
js:构造函数 继承 原型链
创建构造函数
//1.定义一个构造函数
function obj(a) {
this.a = a || 'a';
}
console.dir(obj.prototype.constructor);// obj(函数自身)
/*-------构造函数的prototype.constructor应指向函数自身-------*/
//2.在该构造函数的原型中 定义该构造函数创建的所有对象都会继承的公共方法
obj.prototype.geta = function() {
return this.a;
}
var o1 = new obj('1');
console.log(o1.geta()) // '1'
继承内部属性和方法
function obj(a, b) {
//通过改变父类构造函数的执行上下文,继承父类构造函数中定义的属性
obj.call(this, a);
this.b = b;
}
var o2 = new obj('2', ['a','b']);
console.log(o2.geta()); // 报错
由于geta方法被定义在obj.prototype中,call方法只能使obj调用obj内部定义的方法,故obj.prototype中定义的方法没有被obj继承。要解决此问题可以将geta定义在obj对象内部,但是这样会在每一个实例中创建一个geta方法,对于公用函数来说这样做显然会造成资源浪费,将公共方法定义在构造函数的原型中来继承则更加适合。
构造函数结合原型继承
继承prototype中属性和方法
//通过将prototype指向父级创建的实例,继承父类构造函数prototype中定义的方法
obj.prototype = new obj(); //此时obj.prototype.constructor 为 obj
obj.prototype.constructor = obj; //修正constructor指向为obj函数自身,避免修改原型链时造成的constructor丢失
var o3 = new obj('3', ['a','b']);
console.log(o2.geta()); //'3'
注意 obj.prototype = new obj()与obj.prototype = obj.prototype的区别:
前者将obj的prototype指向了obj创建的一个实例,该实例的__proto__指向obj.prototype(通过new创建的对象,其__proto__总是指向其构造函数的prototype)
后者将obj和obj的prototype指向同一个原型对象,如果定义子构造函数的prototype中的属性或方法将影响到父级。
构造函数在实例化对象时,每一个子类都将调用一次父类构造函数。
上面代码中
obj.prototype = new obj();
可写作
obj.prototype = (function() {
function o() {}
o.prototype = obj.prototype;
return new o();
})();
封装成继承函数
//继承函数
function extend(son, father) {
function f() {}
f.prototype = father.prototype;
son.prototype = new f();
son.prototype.constructor = son;
}
这样在创建obj实例时,减少了对父类的构造函数的调用次数,在继承层级较多时,可以减少内存占用。
使用这种继承方式时要尽量减少继承层级,参考下面例子,创建一个e对象实例共调用5次父级构造函数。
//a类
function a() {
console.log('a()');
}
a.prototype.print = function() {
console.log('msg');
}
//b类
function b() {
a.call(this);
console.log('b()');
}
extend(b, a);
//c类
function c() {
b.call(this);
console.log('c()');
}
extend(c, b);
//d类
function d() {
c.call(this);
console.log('d()');
}
extend(d, c);
//e类
function e() {
d.call(this);
console.log('e()');
}
extend(e, d);
//创建一个e的实例
var e = new e(); //a() b() c() d() e()
e.print(); //msg.
构造函数继承对象
在前面的继承中,是将子构造函数的prototype指向父构造函数的实例,下面的代码会将一个构造函数的prototype指向一个对象,并用该构造函数创建实例。
//继承函数
function clone(object) {
function f() {}
f.prototype = object;//指定一个对象
return new f();
}
//父级
var obj1 = {
a: 'a',
geta: function() {
return this.a;
}
}
//子级
var obj2 = clone(obj1);
obj2.b = ['b'];
obj2.getb = function() {
return this.b.join(',');
}
//创建实例1
var obj1 = clone(obj2);
obj1.b.push('b1');
//创建实例2
var obj2 = clone(obj2);
obj2.b.push('b2');
console.log(obj1);//b,b1,b2
console.log(obj2);//b,b1,b2
console.log(obj2);//b,b1,b2
使用这种继承方式时,创建的所有实例虽然会返回一个新对象,但其引用和父级指向同一个内存地址,只有重新赋值时才会分配新的内存地址,如果直接操作其修改会影响到父级。
//创建实例1
var obj1 = clone(obj2);
obj1.b = [];
obj1.b.push('b1');
//创建实例2
var obj2 = clone(obj2);
obj2.b = [];
obj2.b.push('b2');
console.log(obj2);//b
console.log(obj1);//b1
console.log(obj2);//b2
原型继承和原型链
new关键字只能对(构造)函数使用,如果要继承的目标是一个对象需要通过设置prototype来实现,而且在存在操作实例时影响父级的风险。使用object.create()方法可以以一个对象为目标来创建一个新对象,并实现继承。
var obj = {
name:'name',
printname:function(){
console.log(this.name)
}
}
//var obj2 = new obj1();//报错,
//new关键字只能用function(构造函数)来new对象,不能直接用对象来new对象
var obj1 = object.create(obj);
obj1.name1 = 'name1';
console.dir(obj1);//{name1:'name1'},obj1内部没有name属性,只有name1属性
obj1.printname();//name,obj继承并能够调用printname方法和读取name属性
console.dir(obj2.prototype);//undefined
//所有索引对象(array object function)都有__proto__,只有function对象才有prototype
console.dir(obj2.__proto__);//obj对象{name:'name',printname:function(){console.log(this.name)}}
//object.create()方法创建的对象其原型指向创建的目标对象
使用object.create()方法可以实现多重继承。
var obj = {
name:'name',
print:function(key){
console.log(this[key])
}
}
var obj1 = object.create(obj);
obj1.name1 = 'name1';
var obj2 = object.create(obj1);
obj2.name2 = 'name2';
obj2.print('name');//name
obj2.print('name1');//name1
obj2.print('name2');//name2
console.dir(obj2);//{name2:'name2'}
//obj2内部没有name属性,只有name2属性,但继承了obj1和obj的属性和方法
console.dir(obj2.__proto__);//obj1对象
console.dir(obj2.__proto__.__proto__);//obj对象
console.dir(obj2.__proto__.__proto__.__proto__);//object对象
console.dir(obj2.__proto__.__proto__.__proto__.__proto__);//null
//所有对象的原型最终都会指向object对象,object对象原型为null
由于object.create()创建的对象原型直接指向父级,所以父对象修改会影响子对象,而子对象修改无法影响父对象。
注意object.create方法不管参数是对象还是构造函数,都只能创建出对象。
如果创建的目标是函数,则创建结果是__proto__指向目标函数的空对象({ } ,不会报错)
在前面已经多次涉及原型链知识,再提一下
var func0 = function(){
this.name0 = 'func0';
}
func0.prototype.print0 = function(){
console.log(this.name0);
}
console.dir(func0.prototype); //{print0:function(){console.log(this.name0)}}原型对象
console.dir(func0.prototype.constructor);//func0
//函数的prototype.constructor默认指向该函数自身
var func1 = function (){
this.name1 = 'func1'
func0.call(this);
}
func1.prototype = new func0();
func1.prototype.print1 = function(){
console.log(this.name1);
}
console.dir(func1.prototype); //func0实例对象
console.dir(func1.prototype.constructor);//func0 由于将原型指向了func0实例,导致构造器指向func0
func1.prototype.constructor = func1;
//手动将构造器指向修正回函数自身,可以确保由fun1创建的实例instanceof为fun1,避免造成instanceof失真
console.dir(func1.prototype.__proto__);//{print0:function(){console.log(this.name0)}}原型对象
//由于func1.prototype为func0实例,故func1.prototype.__proto__指向func0.prototype
//通过new创建的对象,其__proto__总是指向其构造函数的prototype
console.dir(func1.__proto__);//function对象
//func1是通过new function()创建实例,function构造函数对象的prototype是function对象
console.dir(func1.__proto__.__proto__);//object对象
//所有引用类型数据原型都是原自object对象
//function对象的构造函数的prototype是object对象
console.dir(func1.__proto__.__proto__.__proto__);//null
//已经查找到原型链终点,即object对象的__proto__为null