欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

JS:构造函数继承原型链

程序员文章站 2022-08-21 17:36:39
js:构造函数 继承 原型链 创建构造函数 //1.定义一个构造函数 function obj(a) { this.a = a || 'a'; } co...

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