原型链的从对象创建到原理详解

来源:互联网 发布:怎么查看淘宝积分 编辑:程序博客网 时间:2024/06/06 02:23

js原型与原型链终极详解

一、在javascript中没有所谓的空对象,即使最简单的{}也具有从Object.prototype继承的方法和属性。

二、创建的对象的俩种等价的方法:使用字面量方法创建对象:var oschina = {goes:”far”}; 使用内置构造函数创建对象:var oschina = new Object(); car.gose = “far”。我们应该尽量使用字面量发创建对象,其显著优点在于它仅仅输入更短的字符。但是选择它还有以下几个更重要的原因:

1、选择字面量法创建对象强调该对象仅是一个可变的hash映射,而不是从对象中提取的属性或方法。

2、对使用Object构造函数相对,使用字面量在于它并没有作用域解析。因为可能以同样的名字创建一个局部函数,解释器需要从调用Object(0的位置开始一直向上查询作用域链,知道发现全局Object构造函数。

3、构造函数Object可以仅接受一个参数,并且还依赖传递的值,该OBject()可能会委派另一个内置函数来创建对象,并且返回了一个并非期望的不同对象。如将数字、字符串、布尔值当做参数传递给Object构造函数,其结果是获得了以不同构造函数所创建的对象,例如:var oschina = new Object(1);ochina.constructor为Number;传递的值是动态的,直到运行时才确定其类型,这种行为会导致意想不到的结果。

三、构造函数仍然是函数,但它需要用new操作符调用,若忘记用new操作符会导致构造函数中的this指向全局对象(浏览器中指向window),如下面代码:

function  abc(){ this.after = "def";}//定义对象var oschina = new abc();alert(typeof oschina); //'object'alert(oschina.after);//'def';var oschina = abc();alert(typeof oschina); //'undefined'alert(window.after);//'def';

四、检查数组性质。当数组作为操作数并使用typeof操作符时,其结果会返回”object”,因为数组也是对象。但是这对于排除错误没有什么帮助,通常我们需要知道某个值是否为一个数组。有时候检查代码可以用是否存在length属性或一些数组方法,但是这些检查并非万能,我们无法确定一个非数组对象就不能拥有这些属性和方法。还可以使用instanceof Array进行检查,但是这种检查存在兼容性问题。

ECMAScript 定义了Array.isArray(),方法,接受参数为数组时返回true。但是有些环境不支持这种方法,最好的检查数组性质的方法是Object.prototype.toString.call方法,若是数组会返回[object Array],若是对象则为[object object]。因此要用如下方法检测:

if(typeof Array.isArray === "undefined"){  Array.isArray = function(arg){     return Object.prototype.toString.call(arg) === "[object Array]";  };}

//调用Array.isArray()
五、在一般情况下,除了Date()构造函数以外,很少需要用其他内置构造函数。


  • 之前了解了字面量创建对象

    var zhangSan ={name:"张三",age:14}
  • 工厂法创建对象

    function createPeople(name,food){    var people = new object();    people.name=name;    people.eat=function(food){     alert(food);     }    return people;}var zhangSan=createPeople("zhangSan","豆腐");zhangSan.eat();//这时会弹出豆腐.        
  • 以及构造函数创建对象(创建类)

     function createPeople(name,age,food){this.name=name;this.age=age;this.food=food;this.eat=function(){ alert(this.name+"爱吃"+this.food;}}var zhangSan = new createPeople("张三",13,"豆腐");zhangSan.eat();//这是会弹出张三爱吃豆腐这句话

第九行的那句话可以分解为两句话来理解:

1 var zhangSan={};//第一步首先创建一个空对象,2 createPeople.apply(zhangSan arguments);//这一步将函数中的this绑定到zhangSan上,然后就是执行函数中的代码

好了,采用构造函数创建对象,我们可以将这个函数理解成一个类,由这个类可以创造属于它的对像,而不是像上边的工厂方法那样每个对象都没有明确的分类.

  • 在Javascript中,你每次创建一个对象,堆中就要为这个对象的方法和属性分配一个内存空间,但是对于很多对象来说,它们的属性大都不尽相同,但是很多时候它们的很多方法都是相同的,不同的只是传入的参数的不同而已,这样子是很浪费我们的内存的,这时候,我们就先去了解一下对象的原型链:

    每个函数内都有一个prototype属性,这个prototype属性执行它们 的prototype对象,而每一个函数的protocoltype对象中都有一个constructor属性,这个属性指向这个函数本身,

    所以问题就来,这是一个很绕口的问题,那么我们就来张图理解一下:

原型链理解图

那你说这跟我创建的每一个对象上的方法有神马关系呢?当然有关系了,当我们使用new一个构造方法来创建一个对象的时候,我们创建的那个对象中,也会有一个_ proto_属性,这个属性指向构造函数的原型对象,也就是说,在我们对象实例和构造函数之间,它们都指向对象原型,我们每一个对象中都可以继承构造函数原型对象中的属性和方法.那么这就引出了我们要说的内容,就是将我们每个对象都需要的方法放到我们的prototype对象中:

 function createPeo(userName,hobby){     this.userName=userName;     this.hobby=hobby;     createPeo.prototype.eat=function (){//在对象原型上定义函数,来节省内存;         alert(this.userName+"喜欢吃"+this.hobby);     } } var shang  = new createPeo("张珊","炒面"); var liSi   = new createPeo("李四","撸串");//这样就可以极大的节省了我们的内存,而且每一个对象都能调用到我们的方法 .

好了,现在我们已经成功的创建了一个类,并且由这个类创建了我么所需要的对象,那么我们都知道,有了类,有了对象,我们总是会下意识的去背了起来,累的三大特征:封装,继承,多态,那么在JavaScript中,类的封装是怎么来实现的呢?这个在明天讲述闭包的时候我们再进行讨论,今天我们先来讨论一下类中都有的继承,在我们JavaScript中,类的继承并不像Java中的那样用一个class…extend就行了,在JavaScript中,我们并没有一个class继承的概念,我们用的是很随意的一个原型链的执行继承,就是通过prototype属性来指向想要继承的对象的原型对象,那么我们就开始想,那么我直接用prototype属性指向不就行了吗?

 function Dog(name,age){     this.name=name;     this.age=age; } function LittleDog(){} LittleDog.prototype=Dog.prototype;//你以为这样就完成了原型继承,只不过,这样子直接指向原型链的话,你在修改子类的时候,相应的就会修改父类.并且,既然他两个都指向一个原型,那你直接用Dog来创建不就完了了吗,这就失去了继承的意义.

所以,直接将原型链指向父类是不对的,说到这里,我们就可以用一个空函数来作为中转完成原型链的继承:

function Dog(name,age){    this.name=name;    this.age=age;    Dog.prototype.eat=function(){alert("我今年"+this.age+"岁"); }}function LittleDog(name,age){    this.name=name;    this.age=age;}function F(){}F.prototype=Dog.prototype;LittleDog.prototype=new F();LittleDog.prototype.constructor=LittleDog; var xiaoGou=new LittleDog("旺财",13);xiaoGou.eat();//这时用小狗调用父类中的方法,返回一个弹框,说明继承成功.

通过这种中间函数来完成类的继承.它并没有改变原有Dog中的原型链条,同时也完成了继承,如果想要在LitteDog中添加原型方法,就可以在new F()中创建方法和属性,当然,我们也可以将这种继承方法封装成一个函数,这样,我们程序的执行效率和美观程度就大大的提升了:

 function inherits(child,parent){      var f = function(){}      f.prototype = parent.prototype;      child.prototype = new f();      child.prototype.constructor=child; }//这是一个封装函数,再进行类的继承时,可以用这个函数来套用.

在类的继承中,除了我们上边说的这种原型继承,还有类继承,以及类继承与原型继承的混用.


一. 普通对象与函数对象
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

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

画图约定:
3

疑点解释:
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

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

有两点需要注意:
(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

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

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

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

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

1 0
原创粉丝点击