起这个题目不是为了装*,只是在网上看了太多文章,都是写的云里雾里,一怒之下决定自己写一个。由于这篇文章属于原创,本人也是刚刚学习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); //undefined
console.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>//function
console.log(Foo.prototype); //Foo{}
//Foo本身是一个函数对象,它是由Function构造函数创建的,Foo.__proto__指向Function.prototype即function对象
console.log(Foo.__proto__); //function()
console.log(typeof(Foo.prototype)); //object
console.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>//object
console.log(foo.__proto__); //Foo {}
console.log(foo.prototype); //undefined
console.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的更多细节:
写了这么些,终于理解为什么网上那么多关于prototype的技术博客都些的云里雾里了,看来用“史上最通俗易懂”做标题确实有点装*。prototype、__proto__确实有点抽象,个人觉得这个算是生硬加入到Javascript中的特性,应该算是Javascript的一个缺点,这里面的条理说实话不是很清晰。