史上最通俗易懂的关于JavaScript 的 prototype、原型继承、this指针的讲解

来源:互联网 发布:大数据探索性分析 编辑:程序博客网 时间:2024/05/01 20:35

起这个题目不是为了装*,只是在网上看了太多文章,都是写的云里雾里,一怒之下决定自己写一个。由于这篇文章属于原创,本人也是刚刚学习Javascript,对于很多细节的理解如果有写的不对的地方欢迎指正。

首先讲两个概念面向对象和基于对象。所谓面向对象,应该好理解,目前最主流的程序开发语言都是面向对象的。像Java、C++这些先定义类,然后创建该类的对象的编程方法就是面向对象编程了。 那么什么是基于对象编程呢,Javascript就属于基于对象编程的语言,Javascript里面其实没有类这个概念,也就是说你不能定义一个类,语言的一切特性都是建立在对象的基础之上的,可能不好理解,下面我会慢慢展开这个概念。

想来想去还是决定先从new开始说吧。当Javascript执行new命令时它会做如下几件事(假如执行的是new Foo()):

1.创建一个空对象。

2.把这个对象传递给Foo()构造函数。

3.返回Foo()构造函数修改过的对象。

这其实不是new命令执行的全部动作,还有一些动作我们稍后再提,这些动作能完成new的最基本的功能。看下面代码:

function Foo(){this.foo = 'I am foo';return 'I am a function';}var foo = new Foo();console.log(foo.foo);
这段代码会打印‘I am foo'字符串,看上去,代码的意思是创建了一个Foo类型的对象,而这个对象有一个foo属性在Foo()构造函数里被赋值为"I am Foo"。 习惯面向对象编程的人喜欢这样去理解这段代码。但其实,Javascript是一种简单的脚本语言,并没有类。上面的代码本质上是这样的,创建了一个空对象(注意这里的对象是不属于某个类型的对象,但是它里面可以存储属性和方法),把这个对象传递给Foo()函数,Foo()函数的内部通过this变量来访问这个对象,并可以对其进行赋值操作,然后把这个修改后的对象返回并赋值给foo变量。OK,这看上去是不是就很像面向对象编程了。我们可以利用this给对象添加方法,也可以给对象设置属性,看看下面代码:

function Foo(){this.foo = 'I am Foo';this.sayHello = function(){alert('hello!');}return 'I am a function';}var foo = new Foo();console.log(foo.foo);foo.sayHello(); 

是不是很刁,觉得已经可以做面向对象编程了,可是这里其实有一个问题,就是对象之间不能共享属性,每个对象都保持自己各自的属性。代码如下:

function Foo(){this.foo = 'I am Foo';this.sayHello = function(){alert('hello!');}return 'I am a function';}var foo = new Foo();var boo = new Foo();boo.foo = 'I don want to be Foo'console.log(foo.foo);console.log(boo.foo);

这里有个问题,foo属性我其实是想让它有类属性的特性,当它被改变是,我希望所有Foo构造的对象都受到影响,可是Javascript目前的特性来讲,无法实现,于是Javascript的发明者Brendan Eich决定为构造函数设置一个prototype属性,即原型对象。然后另Javascript中所有对象都有一个__proto__属性,每一个对象被创建时,Javascript会把这个对象的__proto__属性赋值为这个对象所基于的原型对象,比如new Foo()语句生成的对象的__proto__属性会被赋值为Foo.prototype。当从一个对象中获取一个属性失败时,JS会自动尝试去其__proto__链接指向的对象中去查找,如果还找不到,继续在它的__proto__指向对象中找,直到找到Object.prototype为止。


前面说过Javascript中所有变量都是对象,除了两个例外null和undefined。现在我们来讨论函数,其实函数也是个对象这里我先把对象分成两类,普通对象和函数对象。之所以把函数单独分类为函数对象,因为函数有一个prototype属性(原型属性)。在定义一个函数的时候,Javascript会执行几个动作:

1.为该函数对象添加一个原型属性(即prototype对象)。

2.另prototype属性指向这个函数的原型对象。(比如定义Foo()函数,那么Foo.protopye就指向Foo()函数的原型对象,即Foo{}对象,这里一定要注意函数对象和函数的原型对象可不是一个对象)。

3.为prototype对象添加一个constructor属性,constructor属性指向prototype原型对象的构造函数。(Foo()是一个函数对象而原型对象Foo{}只是一个普通对象。)


在Javascript里每一个函数都可以结合new来创建一个对象(模拟面向对象编程),所以Javascript里的任何函数都是构造函数。

上代码!

function Foo(){<span style="white-space:pre"></span>this.foo = 'I am Foo';<span style="white-space:pre"></span>return 'I am a function';}//Function是一个函数对象,它的prototype指向"function()"原型对象,按理Function的原型对象应该是Function{}//但是此处给出结果是function()对象,那我们这里先特殊记忆一下只有Function的原型对象为"function()"。//其他构造函数的原型对象为“对象名{}”。//据一遍技术文档里说function()对象是一个空函数(我更愿意理解为//function()对象是一个普通对象,因为它没有prototype属性)console.log(Function.prototype) //function()console.log(Function.__proto__); //function()//个人这里有一个比较难理解的地方,所有函数对象的__proto__属性都指向Function.prototype,应为所有函数都是有//Function函数构造的,包括Function对象自己,所以 Function.__proto__ 等于 Function自身的prototype。console.log(Function.prototype === Function.__proto__);console.log(Function.prototype.constructor); //Function()console.log(Function.constructor); //Function()console.log(Function.__proto__.prototype); //undefinedconsole.log(Function.prototype.__proto__); //Object {}console.log(Object.prototype); //Object {}console.log(Object.__proto__); //function()console.log(typeof(Foo)); <span style="white-space:pre"></span>//functionconsole.log(Foo.prototype);  //Foo{}//Foo本身是一个函数对象,它是由Function构造函数创建的,Foo.__proto__指向Function.prototype即function对象console.log(Foo.__proto__);  //function()console.log(typeof(Foo.prototype)); //objectconsole.log(typeof(Foo.__proto__)); //function//下面这句等价于Function.prototype.__proto__,因为function()是一个对象,它的构造函数自然是Object{},所以//等于Object.prototype,即Object {}原型对象。console.log(Foo.__proto__.__proto__); //Object {}console.log(Object.prototype); //Object {}console.log(Foo.prototype.constructor); //Foo()//Foo.constructor实际等于Foo.__proto__.constructor(Function.prototype.constructor)console.log(Foo.constructor); //Function()var foo = new Foo();var st = "I'm a string";var Class = function(){};//Class是一个函数对象,console.log(Class.__proto__); //function()console.log(Class.prototype); //Object {}console.log(String.__proto__); //function()console.log(String.prototype); //String {}console.log(st.__proto__);<span style="white-space:pre"></span>//String {}console.log(typeof(foo)); <span style="white-space:pre"></span>//objectconsole.log(foo.__proto__);  //Foo {}console.log(foo.prototype);  //undefinedconsole.log(foo.constructor); //Foo()

为什么Foo.constructor等于Foo.__proto__.constructor呢?解释一下,这个就涉及到所谓的原型链,Foo对象自然是没有constructor属性,所以JS到Foo.__proto__中去找constructor属性。Foo.__proto__实际上指向Function.prototype,应为Foo是基于Function构造函数创建的对象。

OK写到这里感觉稍微有些清晰了,大家可以用上面学习到的Javascript的特性,自己用Javascript模拟一下面向对象编程。自己看看效果,可以多试试。

上面已经提到了this,可以做基本的理解,理解了上面的概念之后再学习this就比较简单了,可以参考下面的网址学习this的更多细节:

http://jiangzhengjun.javaeye.com/blog/486450

写了这么些,终于理解为什么网上那么多关于prototype的技术博客都些的云里雾里了,看来用“史上最通俗易懂”做标题确实有点装*。prototype、__proto__确实有点抽象,个人觉得这个算是生硬加入到Javascript中的特性,应该算是Javascript的一个缺点,这里面的条理说实话不是很清晰。





0 0
原创粉丝点击