一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Javascript 原型与原型链深入详解

时间:2015-11-16 编辑:简简单单 来源:一聚教程网

一. 普通对象与函数对象

JavaScript 中,万物皆对象!但对象也是有区别的。分为普通对象和函数对象,Object ,Function 是JS自带的函数对象。下面举例说明

 function f1(){};
 var f2 = function(){};
 var f3 = new Function('str','console.log(str)');

 var o3 = new f1();
 var o1 = {};
 var o2 =new Object();

 console.log(typeof Object); //function
 console.log(typeof Function); //function
 console.log(typeof o1); //object
 console.log(typeof o2); //object
 console.log(typeof o3); //object
 console.log(typeof f1); //function
 console.log(typeof f2); //function
 console.log(typeof f3); //function
    
  在上面的例子中 o1 o2 o3 为普通对象,f1 f2 f3 为函数对象。怎么区分,其实很简单,凡是通过 new Function() 创建的对象都是函数对象,其他的都是普通对象。f1,f2,归根结底都是通过 new Function()的方式进行创建的。Function Object 也都是通过 New Function()创建的。

二. 原型对象


   在JavaScript 中,每当定义一个对象(函数)时候,对象中都会包含一些预定义的属性。其中函数对象的一个属性就是原型对象 prototype。注:普通对象没有prototype,但有__proto__属性。

  原型对象其实就是普通对象(Function.prototype除外,它是函数对象,但它很特殊,他没有prototype属性(前面说道函数对象都有prototype属性))。看下面的例子:
 function f1(){};
 console.log(f1.prototype) //f1{}
 console.log(typeof f1. prototype) //Object
 console.log(typeof Function.prototype) // Function,这个特殊
 console.log(typeof Object.prototype) // Object
 console.log(typeof Function.prototype.prototype) //undefined

 从这句console.log(f1.prototype) //f1 {} 的输出就结果可以看出,f1.prototype就是f1的一个实例对象。就是在f1创建的时候,创建了一个它的实例对象并赋值给它的prototype,基本过程如下:
 var temp = new f1();
 f1. prototype = temp;

  所以,Function.prototype为什么是函数对象就迎刃而解了,上文提到凡是new Function ()产生的对象都是函数对象,所以temp1是函数对象。
 var temp1 = new Function ();
 Function.prototype = temp1;

那原型对象是用来做什么的呢?主要作用是用于继承。举了例子:
  var person = function(name){
   this.name = name
  };
  person.prototype.getName = function(){
     return this.name;
  }
  var zjh = new person(‘zhangjiahao’);
  zjh.getName(); //zhangjiahao

   从这个例子可以看出,通过给person.prototype设置了一个函数对象的属性,那有person实例(例中:zjh)出来的普通对象就继承了这个属性。具体是怎么实现的继承,就要讲到下面的原型链了。

三.原型链


JS在创建对象(不论是普通对象还是函数对象)的时候,都有一个叫做__proto__的内置属性,用于指向创建它的函数对象的原型对象prototype。以上面的例子为例:

  console.log(zjh.__proto__ === person.prototype) //true

同样,person.prototype对象也有__proto__属性,它指向创建它的函数对象(Object)的prototype

  console.log(person.prototype.__proto__ === Object.prototype) //true

继续,Object.prototype对象也有__proto__属性,但它比较特殊,为null

  console.log(Object.prototype.__proto__) //null

我们把这个有__proto__串起来的直到Object.prototype.__proto__为null的链叫做原型链。如下图:


四.内存结构图


为了更加深入和直观的进行理解,下面我们画一下上面的内存结构图:

画图约定:

疑点解释:
1.Object.__proto__ === Function.prototype // true
  Object是函数对象,是通过new Function()创建,所以Object.__proto__指向Function.prototype。

2.Function.__proto__ === Function.prototype // true
  Function 也是对象函数,也是通过new Function()创建,所以Function.__proto__指向Function.prototype。

自己是由自己创建的,好像不符合逻辑,但仔细想想,现实世界也有些类似,你是怎么来的,你妈生的,你妈怎么来的,你姥姥生的,……类人猿进化来的,那类人猿从哪来,一直追溯下去……,就是无,(NULL生万物)
正如《道德经》里所说“无,名天地之始”。

3.Function.prototype.__proto__ === Object.prototype //true
其实这一点我也有点困惑,不过也可以试着解释一下。
Function.prototype是个函数对象,理论上他的__proto__应该指向 Function.prototype,就是他自己,自己指向自己,没有意义。
JS一直强调万物皆对象,函数对象也是对象,给他认个祖宗,指向Object.prototype。Object.prototype.__proto__ === null,保证原型链能够正常结束。

五.constructor


  原型对象prototype中都有个预定义的constructor属性,用来引用它的函数对象。这是一种循环引用
  person.prototype.constructor === person //true
  Function.prototype.constructor === Function //true
  Object.prototype.constructor === Object //true

完善下上面的内存结构图:


有两点需要注意:
(1)注意Object.constructor===Function;//true 本身Object就是Function函数构造出来的
(2)如何查找一个对象的constructor,就是在该对象的原型链上寻找碰到的第一个constructor属性所指向的对象

六.总结


1.原型和原型链是JS实现继承的一种模型。
2.原型链的形成是真正是靠__proto__ 而非prototype

要深入理解这句话,我们再举个例子,看看前面你真的理解了吗?
  var animal = function(){};
  var dog = function(){};

  animal.price = 2000;//
  dog.prototype = animal;
  var tidy = new dog();


  console.log(dog.price) //undefined
  console.log(tidy.price) // 2000

为什么呢?画一下内存图:


这说明什么问题呢,执行dog.price的时候,发现没有price这个属性,虽然prototype指向的animal有这个属性,但它并没有去沿着这个“链”去寻找。同样,执行tidy.price的时候,也没有这个属性,但是__proto__指向了animal,它会沿着这个链去寻找,animal中有price属性,所以tidy.price输出2000。由此得出,原型链的真正形成是靠的__proro__,而不是prototype。
因此,如果在这样指定dog.__proto__ = animal。那dog.price = 2000。

最后打个比喻,虽然不是很确切,但可能对原型的理解有些帮助。


父亲(函数对象),先生了一个大儿子(prototype),也就是你大哥,父亲给你大哥买了好多的玩具,当你出生的时候,你们之间的亲情纽带(__proto__)会让你自然而然的拥有了你大哥的玩具。同样,你也先生个大儿子,又给他买了好多的玩具,当你再生儿子的时候,你的小儿子会自然拥有你大儿子的所有玩具。至于他们会不会打架,这不是我们的事了。
所以说,你是从你大哥那继承的,印证了那句“长兄如父”啊!


js深入原型链

前面把js作用域和词法分析都说了下,今天把原型链说下,写这个文章费了点时间,因为这个东西有点抽象,想用语言表达出来不是很容易,我想写的文章不是简单的是官方的API的copy,而是对自己的知识探索和总结的过程,以及在这个过程碰到的问题都一一写出来,我想大多数人应该也有这个疑惑,然后带着疑惑去找答案,当你把这个疑惑解决之后,才觉得很有成就感。下面不多说了,开始说说什么是原型链。要想了解原型链,还是要从简单的开始,什么是原型?首先看下代码:

function funcA() {
    this.show = function() {}
}
console.log(funcA.prototype);



由这个图可以看出funcA的原型是一个对象,他的名字也是funcA,有一个constructor属性指向funcA,同时有一个__proto__指向Object.prototype(这个是怎么得到?可以看下上面的__proto__下面的constructor属性指向是Object,说明他是Object的原型).funcA原型大概写成:


funcA.prototype={

constructor:funcA,

__proto__:Object.prototype


}



PS:这里的__proto__从哪里冒出来的?在js中,每个对象都有一个名为__proto__的内部隐藏属性,指向于它所对应的原型对象(chrome、firefox中名称为__proto__,并且可以被访问到),而普通的对象没有prototype属性,只有__proto__属性。由上面的结果可以得到下面的这张图


现在我们对原型有了一个大概的了解,反正他就是这么一个对象,有几个不同寻常的属性,继续深入,当我们利用funcA创建对象的时候,这个又会发生什么?


function funcA() {
    this.show = function() {}
}
console.log(funcA.prototype);
var a = new funcA();
console.log(a);
console.log(a.__proto__);
console.log(a.__proto__ === funcA.prototype);
console.log(a.__proto__.constructor === funcA);

运行结果如下:

对上面的这个结果进行分析,a是funcA创建出来的对象,他有funcA里面的方法,同时他也有一个__proto__,可以用代码表示成:


var a={

show:function(){..…},

__proto__:funcA.prototype

}



同理,我们可以得到下面这张图:


总结下上面的过程,从表象上看,funcA在创建对象的时候,这个对象a,把funcA里面的方法“复制过来”,然后产生了一个__proto__属性指向它的原型。

其实new创建对象,分为三步

1、 创建了一个空对象a={};
2、 将这个空对象的__proto__成员指向了funcA函数对象prototype成员对象
3、将funcA函数对象的this指针替换成a,然后再调用funcA函数,于是我们就给a对象赋值了一个新 的方法成员。

具体的实现代码可以表示如下:


var a ={};
a.__proto__=funcA.prototype;
funcA.call(a);

下面我们用代码来验证:
function funcA() {
    this.show = function() {}
}
console.log(funcA.prototype);

var b=new funcA();//new 来构建

var a ={};//define 手动定义
a.__proto__=funcA.prototype;
funcA.call(a);
console.log(a);
console.log(b);



实现结果如下 ,可以看到a和b的内部方法完全一个,唯一的区别是a来自于Object的prototype,b来自于funcA的prototype(导致输出的名字是不一样)。


现在这个可以对上面的几个过程做一个总结,什么是原型? Js所有的函数都有一个prototype,其实质就是对一个对象的引用,称之为原型(还是有点难理解,但先作为js的一种机制去理解,至于为什么,后面再往深入去探讨)。在构造新的对象的普通对象的时候会产生一个__proto__属性指向它。

继续向下挖:

好了,现在把js的原型和对象的产生理解清楚了,下面的事情就简单了,下面揭开上面图形中的Object的神秘面纱:funcA.prototype.__proto__指向的Object是一个什么东西?它的来源又是什么?我们在控制台上面输出当前的这个值结果如下:


根据上面的经验,我们可以推测出__proto__指向的应该是一个函数的原型,也就是说xxx.prototype=funcA.prototype.__proto__,这个xxx同时应该是当前这个Object的constrcutor,好了下面看看猜想的对不对。


上面的红色框框的是输出的结果,由这个我们可以说我们说的是对的。同理既然Object是一个对象,那他的prototype和__proto__又是什么呢?


由上面的内容可以看出,最终的原型是一个null,即说明 产生Object.prototype这个对象的“母体”的原型是null,总结上面的结果,可以总结到下面这张图上面:


好了,对于原型算是讨论差不多了,下面再说说比较特殊的Function,上点代码

var foo=new Function("var str='c';console.log(str);");
foo();
console.log(foo);
console.log(foo.prototype);
console.log(foo.__proto__);
//

运行结果如下:


由上面的结果可知,由Function产生的对象还是一个函数对象,foo.__proto__是一个空函数对象,foo.prototype是一个Object的实例对象。由上面可以推断出来,foo.__proto__===Function.prototype;为true推出Function.prototype=function(){}(是一个空函数),既然Fuction,prototype是一个函数对象,那它的原型是什么呢?即查看Function.prototype.prototype=?这个值是多少呢?

由此可见,这个function(){}没有原型,他应该是原型的终结了,即全部函数的__proto__就都是function(){},即是Function.prototype,即所有的函数都是Function的一个实例对象,这也就解释了,js中一切皆对象的这句话。

再思考:我们已经找到函数的根了,那Function.prototype对象的根在哪里?Function.prototype已经是到了原型的终结了,那它又是怎么来的呢?我们再看看Function.prototype.__proto__=?这个到底是什么


从这个结果我们看到,Function.prototype.__proto__是一个Object的实例对象,从上面分析原型的时候我们都已经知道,这个“object”最终的__proto__的指向是null。到目前为止我们心里虽然有很多的疑问,但觉得一切变得明朗起来,函数对象和普通对象是相通的,最终的指向都是一个null.总结一下,如下图:



上图是我们分析后所有的元素的关系,这个看起来清楚了许多,所有的对象的__proto__元素的终点都是null,如果撇开null元素,即所有的普通对象的基对象都是Object(是Object函数的原型),包括Object的类型的普通对象;同理,所有的函数的__proto__都是function(){}(Function.prototype),当然也包含Function本身。看完这些是不是有一种从无中生一(null对象),一生二,二生万物的感觉?

可能有人也注意到了右正解的红色的Function,他也是他自己产生的,先不要急, 为了更好的说明上面的这个图我们再看下面的代码说明下:

function funcA() {}
function funcB() {}
console.log(funcA.__proto__);
console.log(funcB.__proto__);
console.log(funcA.__proto__ === funcB.__proto__);
console.log(funcA.__proto__ === funcB.__proto__ && funcB.__proto__ === Number.__proto__ && Number.__proto__ === Function.__proto__);
console.log(Function.prototype == Function.__proto__);

运行结果如下:



上面的运行结果也证明我的们想法,Function是一个特殊的对象,他的实例来源也来自于他自己,因为他的原型是一切函数的源头,他本身也是一个函数,所以就不奇怪Function.prototype===Function.__proto__是成立的。同理,一切函数对象的根源也都是一样的,所以下面的等式都成立了

function funcA() {}
function funcB() {}

funcA.__proto__ === funcB.__proto__ ;
funcB.__proto__ === Number.__proto__ ;
Number.__proto__ === Function.__proto__;
Function.__proto__===Function.prototype
Number.__proto__ === Function.prototype;  // true
Boolean.__proto__ === Function.prototype; // true
String.__proto__ === Function.prototype ; // true
Object.__proto__ === Function.prototype;  // true
Function.__proto__ === Function.prototype; // true
Array.__proto__ === Function.prototype ;  // true
RegExp.__proto__ === Function.prototype;  // true
Error.__proto__ === Function.prototype;   // true
Date.__proto__ === Function.prototype;    // true


说了这么多还没有讲到原型链,呵呵,上面如果都理解了,那么原型链这个东西就so easy了,不多说还是用代码说话:


 var a={};
 a.__proto__.value="a";
 a.show=function(){
     console.log(this.value);
 }
 a.show();


这段代码运行的结果是输出一个字母”a”,但是我们看到a的对象中没有value的这个字段,但同样也能输出,因为在它的__proto__上面有一个value属性,js在寻找属性的时候会沿着__proto__的去寻找,这个就是我们要说的原型链。原理很简单:如果在当前对象中找不到当前属性或者方法(value),那么就会沿着原型链开始找,一直遍历完整个的原型链,一旦找到,就返回第一个找到的属性获得方法,如果没有,就返回undefined。

说了这么多,原型链到底有什么作用?也许很多人会说,可以用来继承,不过没错,但是我们还是要了解,为什么他可以继承,为什么 funcA.prototype=new funcB();这个就可以实现了所谓的继承。下面还是看代码:


function funcA() {
    this.show = function(str) {
        console.log(str);
    }
}
function funcB() {
    this.read = function() {
    }
}
var a = new funcA();
funcB.prototype = a;
a.show("a");
var b=new funcB();
b.show("b");
 

运行的结果如下:


这个是很常用的写法,可以看到b是可以调用funcA的方法的,这个是为什么呢,下面我们画一个原型的示意图


看到上面的图应该就明白了,因为funcB的原型是a,所以导致b.__proto__=a,所以b就能够沿着原型链访问a中的方法,这样就实现了继承----原型链继承,又称为原型继承。

当前这种继承的方法可以不用这么写,原理清楚了,那就改下也没有什么,看一段简单粗暴的原型继承:


function funcA() {
    this.show = function(str) {
        console.log(str);
    }
}
function funcB() {
    this.read = function() {}
}
var a = new funcA();
var b = new funcB();
b.__proto__ = a;
a.show("a");
b.show("b");


这样和上面的输出的结果是一个的,但是这样的话只有对象b继承了a中的方法,但是再产生的funcB的新对象,却没有a中的方法:

如:


var c=new funcB();
c.show("c");//报错


当然实现的方法很多,下讲会讲说下js的三种继承方式,如果篇幅不大,会连着js的this的一起写出来。下面在写一个个实现方法,供参考,原因在上面:


function funcA() {
    this.show = function(str) {
        console.log(str);
    }
}
function funcB() {
    this.read = function() {}
}
var a = new funcA();
var b = new funcB();
 funcA.call(b);//use call
a.show("a");
b.show("b");

热门栏目