JS prototype与__proto__的联系与区别

写在前面

这篇文章我的目的是试图讲清楚prototype与__proto__。很多人也许和我一样,很困扰于这两个东西究竟是干啥的,网上的各种资料也是讲的云里雾里,傻傻分不清楚。今天我就来尝试把它们说清楚。

JavaScript里没有类的概念

首先必须明确这一点,如果你学过Java之类的面向对象语言,你肯定熟悉类的概念,在面向对象语言的逻辑里,我们要先设计类(Class),然后再把类实例化成对象(Object),就像是图纸和产品的关系,我们必须先设计“图纸”,然后才能打造“产品”。

而在JavaScript里,根本没有类的概念,它的所有东西都是对象,其实,这并没有什么问题,不是所有语言都必须像Java一样必须依赖类这个概念设计,比如C语言里也没有类的概念,一样活得很好。不过,我们必须承认,类的思想会给工程问题带来很多方便,所以,在JavaScript的不断发展中,一些需求也催生了JavaScript想模仿类这个概念。比如说,如果你想构建一个有一定规模的项目,那你必然会涉及到一些继承问题,而这种继承问题,在Java中就是用类来解决的,那在JavaScript中,我们该怎么办?当然有办法,那就是模仿,我们没有类,但我们可以模仿类,而且随着时代的发展,我们有了更好的方法。prototype与__proto__都是在这个过程中催生的产物,我们一会儿马上讨论,在这之前,我们先明确一下类型(type)的概念。

我们没有类(Class),但我们有类型(Type)

上面说过,JavaScript没有类的概念,它里面的所有东西都是对象,但他的对象是有类型的,英文是type。

关于类型的总结,基本上每本教科书上都会列举的很清楚,分两种,原始类型(Primitive Types)与引用类型(Reference Types)。

原始类型(5种):Boolean、Number、String、Null、Underfined

引用类型(6种):Array、Date、Error、Function、Object、RegExp (其实也不止这些)

在javascript中,可以用typeof操作符判断一个变量的类型,关于typeof也要说明一点,5中原始类型都可以用它判断出来(除了Null,Null类型返回object),但对于引用类型,只有两种返回值:function和object,这也就说明了一点,其实引用类型根本上只有两种,那就是Function和Object,其他的引用类型都是Object衍生出来的(它们都返回Object),下面代码可以验证:


//代码1

var boo = true;
var num = 123;
var str = "hello world!"
var nu = null;var und;
var fun = new Function();
var arr = new Array();
var dat = new Date();
var err = new Error();
var obj = new Object();
var reg = new RegExp();
console.log(typeof boo);	//boolean
console.log(typeof num);	//number
console.log(typeof str);	//string
console.log(typeof nu);		//object
console.log(typeof und);	//undefined
console.log(typeof fun);	//function
console.log(typeof arr);	//object
console.log(typeof dat);	//object
console.log(typeof err);	//object
console.log(typeof obj);	//object
console.log(typeof reg);	//object

//证明Array等类型是Object与Function的下级
console.log(Array instanceof Object)	//true
console.log(Array instanceof Function)	//true
console.log(Object instanceof Array)	//false
console.log(Function instanceof Array)	//false


为什么说了这么多关于类型的东西呢,因为我们要研究prototype与__prototype,重点就要放在Function和Object两个类型上。下面我们就来揭开神秘面纱。

Function比Object更强大

上面强调过,javascript中的一切东西都是对象,而对象都可以有属性和方法,所以Function也是一种对象,它也可以有自己的属性和方法,它只是和java里的函数长得比较像而已,地位完全不同(一定要明确在javascript中,Function与Object时平级的,而不是Object的附属品)。而且在javascript中,Function的能力要强于Object,可以说,Object能做的事情Function都能做,但Function能做的事情Object并不都能做。

Function能做但Object做不了的事情:

第一,Fuction可以被执行。这是函数最基本的特征,不必多说。

第二,Fuction可以被当做Object的构造函数。当我们使用new操作符后面跟着一个Function类型的变量时,这个Function变量会被当成构造函数返回一个Object对象,具体见下面代码:

//代码2
function Foo () {    console.log("我是个Function");}
foo = new Foo();console.log(typeof Foo);	//function
console.log(typeof foo);	//object


第三,Function有内置的prototype属性,而Object没有。其实这一点与上一点有着很大的关系,正是因为有了把Function当做构造函数的功能,我们才需要prototype属性。下面会详细讲到,这里只要记住一点,prototype只有Function才有:

//接上面代码
console.log(Foo.prototype); //{}
console.log(foo.prototype);  //undefined


明确了Object和Function的关系,我们下面来讨论javascript中的继承问题。

基于原型链的继承

我想很多人和我一样,多少学过一些Java这种面向对象的语言,关于继承的概念,最初也是在Java世界里形成的,所以形成了一种思维定式,但考虑javascript的继承的时候,我们需要打破这种思维定式。在Java中,继承的概念是通过类与类之间实现的,但javascript根本没有类,都是对象,所以真正的继承,其实直接存在于对象与对象之间。

在我们讨论继承的时候,范围只限于引用类型,不包括原始类型,这点需要明确,其实就是Function与Object两个类型。在考虑javascript的继承的时候,不应该去分别Function和Object,我们只需要统一把它们都看做对象即可,那javascript种究竟是通过什么来明确继承关系的呢,那就是__proto__。__proto__不同于prototype,prototype只有在Function中有,而__proto__在Function和Object中都有。

如果用最简单的话来描述javascript中继承的本质:一个对象A的__proto__属性所指向的那个对象B就是它的原型对象(或者叫上级对象、父对象),对象A可以使用对象B中定义的属性和方法,同时也可以使用对象B的原型对象C的属性与方法,以此递归,这也就是所谓的原型链。

下面的代码就描述了上面那段话:

//代码3
var A = {name:"wangyunok"};
var B = {weibo:"http://weibo.com/wangyunok"};
var C = {github:"https://github.com/wangyunok"}

B.__proto__ = C;
A.__proto__ = B;

console.log(A.name);	//wangyunok
console.log(A.weibo);	//http://weibo.com/wangyunok
console.log(A.github);	//https://github.com/wangyunok


这就实现了javascript中最简单的继承,等等,你也许发现了,这不是__proto__一个东西把继承问题就都解决了么,那要prototype做甚?为什么好多教材上都说prototype与继承有关系?是啊,这也就是前几天我一直困扰的问题,不过现在我弄清楚了,其实,prototype这个东西真正发挥作用的时候,是你把一个Function当做构造函数使用时,而所谓的与继承有关系,不过是因为__proto__并非官方标准中定义的属性,所以他们借助prototype这个属性模仿Java中类与类之间继承的模式。下面我们就来重点分析javascript中用Function类型构造对象的过程,当你知道当你使用new的时候做了什么,你就会很清楚prototype的作用了。

当我们使用new时究竟发生了什么

var foo = new Foo();

前面我们已经分析过,上式中,foo的类型是Object,Foo的类型是Function。但我们想想在Java中,类似的语句有什么区别,对,在Java中,如果你这么写,Foo应该是一个类(Class)。矛盾就在这里,javascript并没有类的概念,但我们又想借用很多传统语言的语法形式,就造就了这种奇葩的现象,这个艰巨的任务只能交给Function。下面让我们把上面的代码写完整:

//代码4

//模仿类
function Foo(name,weibo,github)
	{    
		this.name = name;    
		this.weibo = weibo;    
		this.github = github;
	}
	
Foo.prototype.whoami = function(){ console.log("I'm wangyunok, a coder of javascript!") }

//创建对象
var foo = new Foo('wangyunok','http://weibo.com/wangyunok','https://github.com/wangyunok');

console.log(foo.name);		//wangyunok
console.log(foo.weibo);		//http://weibo.com/wangyunok
console.log(foo.github); 	//https://github.com/wangyunok

foo.whoami();				//I'm wangyunok, a coder of javascript!


上面这种写法,就是javascript中最典型的构建构造函数,然后创建对象的整个过程,一般会建议将方法定义到prototype属性中,注意prototype默认的类型是Object。但其实这么说你大概也就清楚该怎么做,并不知道new的过程到底干了啥,下面我就来告诉你。

第一步,Foo函数被执行。Foo函数在foo的作用域下被执行,所以这里this指代的就是foo,这样name、weibo、github三个属性才会被当做foo的属性被创建,如果你在函数Foo中写一个console.log()语句,它也会在结果中打印出来,见上面代码2。

第二步,将foo.__proto__指向Foo.prototype。这才是javascript构造函数的精髓所在,之后的原理和我们在代码3中讲的一样,foo就继承了Foo.prototype中(以及其原型链上)的属性与方法。下面代码可以佐证:

console.log(foo.__proto__ === Foo.prototype)//true

这样一个鲜活的foo就被创建出来了。

便捷的__proto__

我们再看一下上面的代码,我在其中注释里写到,整个上半部分就是模仿Java中的类的过程,也许在__proto__还没有广泛使用之前,javascript的继承还是通过这种模仿类的过程实现的,我们可以让prototype指向一个父类对象,但这显然麻烦的多。

__proto__目前还不是标准中的东西,不过看一些资料上说,也许会被纳入到ECMAScript6中,不管是不是标准,目前这个属性已经被广泛使用,我们可以用直接为其赋值的方式来指定原型,而不再需要通过prototype那种很麻烦的方式,这是一种进步,应该接纳。

用Stack Overflow上答案来总结

最后,我们用Stack Overflow上关于这个问题得票最多答案作为总结,他解释的非常简单,我也是看到这个之后才豁然开朗的。

原网页在这里: __proto__ VS. prototype in JavaScript

下面是答案:

__proto__ is the actual object that is used in the lookup chain to resolve methods, etc. prototype is the object that is used to build __proto__ when you create an object with new:

( new Foo ).__proto__ === Foo.prototype( new Foo ).prototype === undefined

翻译一下:

__proto__是真正用来查找原型链去获取方法的对象。

prototype是在用new创建对象时用来构建__proto__的对象。

还可以参考一下下图:

image


from: http://www.th7.cn/web/js/201503/88712.shtml

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值