【javascript】 原型对象和原型链的相关讲解

首先,javascript是一种弱类型的语言,也就是javascript中的变量都不需要像C或者java那样明确定义它的类型。相比于java等的语言来说,javascript在使用上更加灵活,没有那么多限制。

为了方便后续的讲解,先来说一说在函数中存在的三种类型的变量,结合代码来看:

function Fun () {
	varOne = 10;
	var varTwo = 20;
	this.varThree = 30;

	console.log(window);
}
var fun1 = new Fun;

注:javascript中的函数,既是类,也是对象,函数本身是“以函数为类”的构造函数,在new对象时被自动执行

如上所示:
(1)varOne = 10:
没有任何前缀定义的变量varOne 实际上默认有一个隐藏的前缀window就相当于是window.varOne = 10,如果直接写变量名,不加任何前缀的话,就相当于是在window中增加了一个以该变量名为键的键值对(在javascript中的所有变量都是以键值对的形式存在的);
在这里插入图片描述
(2)var varTwo = 20:
以var为“命令”定义的变量varTwo 是函数Fun()的一个局部变量,当该函数运行时,给局部变量申请一段系统堆栈空间,当函数运行结束时,局部变量占用的系统堆栈空间随着释放,该局部变量也就不存在了
(3)this.varThree = 30:
以this为前缀的变量,在该函数被当成构造函数执行时,this就表示该类实例化产生的对象,而这个变量就相当于该类的成员,这类成员是public修饰的,或者说是模拟public的,调用的方法是对象.成员

下面思考一个问题:能否利用javascript模拟类和对象的思想,产生一个“静态”的成员,起始准确来说应该是“模拟静态”的成员?这就涉及到了原型的概念,来看:

先来简单举例,看一下什么是原型(原型对象)和原型链:
定义一个函数:

function Fun (real,vir) {
	real = real || 0.0;
	vir = vir || 0.0;
	this.real = real;
	this.vir = vir;
}

有关上面提到的逻辑或||可以查看javascript的逻辑与和逻辑或
实例化两个上述类的对象

var fun1 = new Fun(1.2,3.4);
var fun2 = new Fun(6.7);

输出Fun()函数以及两个对象来看看:
在这里插入图片描述
可以看到js的类(也是函数)有一个prototype(原型,或者原型对象)和(原型链),而js的对象不存在prototype(原型),只存在(原型链);前面说到,类是对象,也是函数,

那么要区分一个对象既是对象,也是类,还是仅仅只是对象,可以看是否存在prototype成员,如果存在的话,它既是类,也是对象,否则就只是对象。

还有一点注意:本人使用的是火狐浏览器,上面对原型链的表示是这种形式,但其实早一点的时候,原型链表示为__proto__,现在被更高级的火狐系统内部更改成了,可以输出看看:

console.log("fun1.__proto__  : ",fun1.__proto__ );

在这里插入图片描述
而我们知道,fun1和fun2对象是从属于Fun类的,即fun1 instanceof Fun的输出结果为true,那么这个从属关系是从何得来的?

任何一个对象都有原型链,指向它所属的类的原型对象

即判断一个对象是否属于某个类,可以看该对象的原型链是否指向这个类的原型对象。

原型链除了可以表示一个对象的从属类之外,还可以更改对象的从属类,这在java中是觉得不可能实现的,
function ABC () {
}
fun2.__proto__ = ABC.prototype;

console.log("fun2 instanceof Fun : " , fun2 instanceof Fun);
console.log("fun2 instanceof ABC : " , fun2 instanceof ABC);

fun2.proto = ABC.prototype这句代码的意思是,把fun2对象的原型链指向了类ABC的原型对象,这样做就将fun2的从属类变成了ABC,输出结果是:
在这里插入图片描述
上面提到的利用js模拟类和对象的思想,产生一个“模拟静态”的成员,做法是给类Fun的原型对象中增加一个成员count,那么这个成员就可以被Fun类的两个对象都可以使用,如下:

Fun.prototype.count = 1;
console.log("fun1.count:", fun1.count);
console.log("fun2.count:", fun2.count);

结果都是1,
在这里插入图片描述
这个count就相当于是java中的静态成员了,因为不管是fun1对象还是fun2对象,调用的count成员都是Fun类中的,而它的表示方法也和java中类似,为类.prototype.成员。

接着来看,如果执行了fun1.count = "ac";这条语句的话,就相当于给fun1新增加了一个count为键,字符串ac为值的键值对,并不是对Fun.prototype.count = 1产生的count值做更改,来看结果:
在这里插入图片描述

注:这里涉及到了“左值”和“非左值”的概念;
左值,是指等号=左侧的变量,其它场合的变量都是非左值

js对于左值的处理原则:

1、查看该左值是否存在;
2、若不存在,则,创建;
3、完成赋值操作。

对于fun1.count = "ac";这条语句,fun1.count在=号的左侧,属于左值,处理方法是在fun1中查看是否存在count,结果不存在,所以在fun1中创建一个键count;而像类似console.log("fun1.count:", fun1.count); console.log("fun2.count:", fun2.count);的这种语句中的count是非左值,在fun1中找到了count,就直接输出,而在fun2中没有找到count 的存在,就继续通过原型链查找,结果在类的原型对象中找到了,就输出原型对象中count的值。

模拟继承

下面来看看在javascript中是怎么实现继承关系的;
前面说,任何一个对象的原型链都指向它所从属的类的原型对象,

那么要把某个类设置为另一个类的的派生类,就把这个目标子类的原型对象的原型链指向目标父类的原型对象,这样就实现了继承的关系

具体代码是:

var Parent = function() {
}
var Child = function() {
}

Child.prototype.__proto__ = Parent.prototype;

上述代码可以实现Child类和Parent类之间的继承关系,但是需要特别注意的是:

在几乎所有的程序语言中,用下划线开头的变量(例如__proto__),几乎都是被这个语言隐藏的变量,也即是说,在该语言中,及其不建议编程者直接操作该变量。

所以我们不建议用Child.prototype.__proto__ = Parent.prototype;的方法完成模拟继承,下面给出另外的实现方法:

function Extends (superClass,childClass) {

	var superObject = new superClass;
	superObject.constructor = childClass;
	childClass.prototype = superObject;
}

该函数可以作为模拟继承的一个工具来用,首先给父类实例化出一个对象,将该父类对象的构造设置为子类,然后将父类的这个对象当成子类的原型对象,结合图示来看:
在这里插入图片描述

我觉得大概意思就是把父类产生的对象当成了子类的原型对象,但里面的更改constructor的这一步,我其实觉得好像没有必要,因为constructor实际上只算是个构造的标识,并没有什么实质性的作用,由于constructor是可以更改的,就像前面说过的js可以通过更改constructor从而更改对象的从属类,所以来说实例化类的对象也不是用这个constructor构造来产生的,它好像只是用来得到(或更改)某个对象的从属类的。另外,我还了解到constructor属性不影响任何js的内部属性,属于js的历史遗留物,虽然没有什么实质性的作用,但是为了维持编程惯例,就将对象的constructor执行其构造函数。

可以来看一下模拟继承的结果:

var Parent = function() {
}
var Child = function() {
}

Extends(Parent,Child);

var obj = new Child;
console.log("obj1 instanceof Parent : ",obj1 instanceof Parent);
console.log("obj1 instanceof Child : ",obj1 instanceof Child) ;

在这里插入图片描述
可以看出Child类实例化出的对象既是Child的对象,也是Parent类的对象,即Child类成了Parent类的派生类。

还有一点,子类中怎么出现父类中的成员,两个类之间满足了继承关系之后,需要进行一步操作,子类中才能包含父类的成员,

var Parent = function() {
	this.parentMember = 1;
}

var Child = function() {
	Parent.call(this);
	this.childMember = 2;
}

注意,如果直接在Child函数中调用Parent()函数,由于没有任何前缀,默认以window作为前缀,但是现在我们需要的是this应该是Parent类new出的对象,所以使用call()函数执行Parent()函数,call()函数可以更改所执行的函数的this,在这将this由window改成了Parent类产生的对象,具体操作就是Parent.call(this);,这句代码相当于是java中的super()方法,但是super()必须要放在子类构造的第一行代码的位置,而Parent.call(this)不需要遵循一样的原则,可以放在子类构造的任意位置。
看结果:

var obj1 = new Parent;
var obj2 = new Child;

console.log("obj1 : ",obj1);
console.log("obj2 : ",obj2);

在这里插入图片描述

最后再说明一个问题,即prototype和__proto__的相关指向:

我们知道函数既是类也是对象,它包含prototype(原型对象)和__proto__(原型链)成员,那么一个类的原型链指向哪里?答案是指向Function的原型对象,几乎都有的类的原型链都指向Function的原型对象,可以把Function当成是所有类的基类(或者是基函数,但它不用来表示类与类之间的继承关系),需要注意Function类产生的是函数,而Function类的原型链也指向它自己的原型对象,类似下图显示,: function()。
在这里插入图片描述
还有,子类的原型对象的原型链指向的是父类的原型对象,那么父类的原型对象的原型链只想的是哪里?可以这样说,只要没有明确说明一个类的父类,都默认是Object类的派生类,意思是所有类的原型对象的原型链最终都指向其“父类”的原型对象,知道最顶层的原型对象的原型链指向Object类的原型对象,而Object类的原型对象的原型链指向的是null,作为最后的终结,结合图示来看:
在这里插入图片描述

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值