[关闭]
@greenfavo 2015-10-14T11:22:01.000000Z 字数 6749 阅读 686

js学习笔记-原型和原型链

js


一,私有变量和函数

在函数内部定义的变量和函数,如果不对外提供接口,外部是无法访问到的,也就是该函数的私有变量和函数。

  1. function Box(){
  2. var color="green";//私有变量
  3. var fn=function(){//私有函数
  4. }
  5. }

这样在函数对象Box外部无法访问变量color和fn,他们是私有的:

  1. var obj=new Box();
  2. console.log(obj.color);//undefined
  3. console.log(obj.fn);//undefined

二,静态变量和函数

当定义一个函数后通过点号"."为其添加的属性和函数,通过对象本身仍然可以访问到,但其实例却访问不到,这样的变量和函数分别被称为静态变量和静态函数。

  1. function Obj(){};
  2. Obj.num = 72;//静态变量
  3. Obj.fn = function() //静态函数
  4. {
  5. }
  6. alert(Obj.num);//72
  7. alert(typeof Obj.fn)//function
  8. var t = new Obj();
  9. alert(t.name);//undefined
  10. alert(typeof t.fn);//undefined

三,实例变量和函数

在面向对象编程中除了一些库函数我们还是希望在对象定义时定义一些属性和方法,实例化后可以访问,js也能做到这样。

  1. function Box(){
  2. this.a=[];//实例变量
  3. this.fn=function(){};//实例方法
  4. }
  5. console.log(typeof Box.a);//undefined
  6. console.log(typeof Box.fn);//undefined
  7. var box=new Box();
  8. console.log(typeof box.a);//object
  9. console.log(typeof box.fn);//function

为实例变量和方法添加新的方法和属性

  1. var box1=new Box();
  2. box1.a.push(1);
  3. box1.fn={};
  4. console.log(box1.a);//[1]
  5. console.log(typeof box1.fn);//object
  6. var box2=new Box();
  7. console.log(box2.a);//[]
  8. console.log(typeof box2.fn);//function

在box1中修改了a和fn,而在box2中没有改变,由于数组和函数都是对象,是引用类型,这就说明box1中的属性和方法虽然同名但却不是一个引用,而是对Box对象定义的属性和方法的一个复制。

这对属性来说没什么问题,但对于方法来说问题就很大了,因为方法都是在做完全一样的功能,但是却要复制2份,如果一个函数对象有上千个实例方法,那么它的每个实例都要保持一份上千个方法的复制,这显然是不科学的。因此有了prototype。

四,基本概念

我们创建的每个函数都有一个prototype属性,这个属性是一个指针,指向一个对象,这个对象的用途是包含可以由特定类型的所有实例共享的属性和方法。那么,prototype就是通过调用构造函数而创建的那个对象实例的原型对象。

使用原型的好处是可以让对象实例共享它所包含的属性和方法。也就是说,不必在构造函数中添加定义对象信息,而是可以直接将这些信息添加到原型中。使用构造函数的主要问题就是每个方法都要在每个实例中创建一遍。

在js中,一共有两种类型的值,原始值和对象值。每个对象都有一个内部属性prototype,我们通常称之为原型。原型的值可以是一个对象,也可以是null。如果它的值是一个对象,则这个对象一定有自己的原型,这样就形成了一条线性的链,称之为原型链。

含义

函数可以作为构造函数使用。另外只有函数才有prototype属性并且可以访问到,但是对象实例不具有该属性,只有一个内部的不可访问的proto属性.proto是对象中一个指向相关原型的神秘链接。按照标准,proto是不对外公开的,也就是说是个私有属性,但是Firefox的引擎将他暴露了出来成为了一个共有的属性,我们可以对外访问和设置。

  1. var Browser = function(){};
  2. Browser.prototype.run = function(){
  3. alert("I'm Gecko,a kernel of firefox");
  4. }
  5. var Bro = new Browser();
  6. Bro.run();

当我们调用Bro.run()方法时,由于Bro中没有这个方法,所以,他就会去他的proto中去找,也就是Browser.prototype,所以最终执行了该run()方法。(在这里,函数首字母大写的都代表构造函数,以用来区分普通函数)。

当调用构造函数创建一个实例时,实例内部将包含一个内部指针proto指向构造函数的prototype,这个连接存在于实例和构造函数的prototype之间,而不是实例与构造函数之间。

  1. function Person(name){ //构造函数
  2. this.name=name;
  3. }
  4. Person.prototype.printName=function() //原型对象
  5. {
  6. console.log(this.name);
  7. }
  8. var person1=new Person('Byron');//实例化对象
  9. console.log(person1.__proto__);//Person{}
  10. console.log(person1.constructor);//function Person (name)
  11. console.log(Person.prototype);//指向原型对象Person

Person的实例person1中包含了name属性,同时自动生成一个proto属性,该属性指向Person的prototype,可以访问到prototype内定义的printName方法.

每个js函数都有prototype属性,这个属性引用了一个对象,这个对象就是原型。原型对象初始化的时候是空的,我们可以在这里面定义任何属性和方法,这些方法和属性都将被该构造函数所创建的对象继承。

五,构造函数,实例和原型对象的区别

实例就是通过构造函数创建的。实例一创造出来就具有constructor属性(指向构造函数)和proto属性(指向原型对象).

构造函数中有一个prototype属性,这个属性是一个指针,指向它的原型对象。

原型对象内部也有一个指针(constructor属性)指向构造函数:

  1. Person.prototype.constructor = Person;

实例可以访问原型对象上定义的属性和方法。
这里的perosn1就是实例,prototype是它的原型对象。

  1. function Animal(name) //积累构造函数
  2. {
  3. this.name = name;//设置对象属性
  4. }
  5. Animal.prototype.behavior = function() //给基类构造函数的prototype添加behavior方法
  6. {
  7. alert("this is a "+this.name);
  8. }
  9. var Dog = new Animal("dog");//创建Dog对象
  10. var Cat = new Animal("cat");//创建Cat对象
  11. Dog.behavior();//通过Dog对象直接调用behavior方法
  12. Cat.behavior();//output "this is a cat"
  13. alert(Dog.behavior==Cat.behavior);//output true;

六,函数对象实例

  1. function Base(){
  2. this.id="base";
  3. }
  4. var obj=new Base();

new 操作符具体干了什么呢?其实就干了三件事。

  1. var obj={};
  2. obj._proto_=Base.prototype;
  3. Base.call(obj);

七,原型链

原型链:当从一个对象那里调取属性或方法时,如果该对象自身不存在这样的属性或方法,就会去自己关联的prototype对象那里找。如果prototype没有,就会去prototype关联的前辈prototype那里找,如果再没有则继续查找Prototype.Prototype引用的对象,以此类推,直到prototype为undefined(Object的Prototype就是undefined)从而形成了所谓的原型链。

  1. function Shape(){
  2. this.name = "shape";
  3. this.toString = function(){
  4. return this.name;
  5. }
  6. }
  7. function TwoShape(){
  8. this.name = "2 shape";
  9. }
  10. function Triangle(side,height){
  11. this.name = "Triangle";
  12. this.side = side;
  13. this.height = height;
  14. this.getArea = function(){
  15. return this.side*this.height/2;
  16. }
  17. }
  18. TwoShape.prototype = new Shape();
  19. Triangle.prototype = new TwoShape();
  20. TwoShape.prototype.constructor = TwoShape;
  21. Triangle.prototype.constructor = Triangle;
  22. var my = new Triangle(5,10);
  23. my.getArea();
  24. my.toString();//Triangle
  25. my.constructor;//Triangle(side,height)

八,原型继承

在原型链的末端,就是Object构造函数prototype属性指向的那个原型对象。这个原型对象是所有对象的祖先,这个老祖宗实现了诸如toString()等所有对象天生就具有的方法。其他内置构造函数,如Function,Boolean,String,Date和RegExp等的prototype都是从这个老祖宗传承出来的,但他们各自又定义了自身的属性和方法,从而他们的子孙就表现出各自宗族的特征。

在ECMAScript中,实现继承的方法就是依靠原型链实现的。

  1. function Father(){ //被继承的函数叫做超类型(父类,基类)
  2. this.name = "Jack";
  3. }
  4. function Son(){ //继承的函数叫做子类型(子类,派生类)
  5. this.age = 10;
  6. }
  7. //通过原型链继承,赋值给子类型的原型属性
  8. //new Father()会将father构造里的信息和原型里的信息都交给Son
  9. Son.prototype = new Father();//Son继承了Father,通过原型,形成链条
  10. var son = new Son();
  11. alert(son.name);//弹出 Jack

原型链的问题

原型链虽然很强大,可以用来实现继承,但也存在一些问题。其中最主要的问题来自包含引用类型的值原型。包含引用类型的原型属性会被所有实例共享。而这也是为什么要在构造函数中而不是在原型对象中定义属性的原因。在通过原型实现继承时,原型实际上变回另一个类型的实例。于是,原先的实例属性也就变成了原型的属性。

在创建子类型的实例时,不能向超类型的构造函数中传递参数。实际上,没有办法在不影响所有对象实例的情况下,给超类型的构造函数传递参数。再加上刚才说的由于原型中包含引用类型值所带来的问题,实践中很少会单独使用原型链。

  1. function Person(name){
  2. this.name = name;//设置对象属性
  3. };
  4. Person.prototype.company = "Microsoft";//设置原型的属性
  5. Person.prototype.SayHello = function(){ //原型的方法
  6. alert("Hello,I'm "+ this.name+ " of " + this.company);
  7. };
  8. var BillGates = new Person("BillGates");//创建person对象
  9. BillGates.SayHello();//继承了原型的内容,输出"Hello,I'm BillGates of Microsoft"
  10. var Jobs = new Person("Jobs");
  11. Jobs.company = "Apple";//设置自己的company属性,掩盖了原型的company属性
  12. Jobs.SayHello = function()
  13. {
  14. alert("Hi,"+this.name + " like " + this.company);
  15. };
  16. Jobs.SayHello();//自己覆盖的属性和方法,输出"Hi,Jobs like Apple"
  17. BillGates.SayHello();//Jobs的覆盖没有影响原型,BillGates还是照样输出

proto属性

proto属性(IE浏览器不支持)是实例指向原型对象的一个指针,它的作用就是指向构造函数的原型属性constructor,通过这个属性,就可以访问原型里的属性和方法了。

js中的对象实例本质上是由一系列的属性组成的,在这些属性中,有一个内部的不可见的特殊属性proto,该属性的值指向该对象实例的原型,一个对象实例只拥有一个唯一的原型。

proto属性和prototype属性的区别

prototype是function对象中专有的属性。
proto是普通对象的隐式属性,在new的时候,会指向prototype所指的对象。
proto实际上是某个实体对象的属性,而prototype则是属于构造函数的属性。proto只能在学习或调式的环境下使用。

九,原型模式的执行流程

1,先查找构造函数实例里的属性或方法,如果有,就立即返回。
2,如果构造函数的实例没有,就去它的原型对象里找,如果有,就立即返回。

  1. function Person(){};
  2. Person.prototype.name = "trigkit4";
  3. Person.prototype.say = function(){
  4. alert("Hi");
  5. }
  6. var p1 = new Person();//prototype是p1和p2的原型对象
  7. var p2 = new Person();//p2为实例化对象,其内部有一个__proto__属性,指向Person的prototype
  8. console.log(p1.prototype);//undefined,这个属性是一个对象,访问不到
  9. console.log(Person.prototype);//Person
  10. console.log(Person.prototype.constructor);//原型对象内部也有一个指针(constructor属性)指向构造函数
  11. console.log(p1.__proto__);//这个属性是一个指针指向prototype原型对象
  12. p1.say();//实例可以访问到在原型对象上定义的属性和方法
  1. 构造函数.prototype=原型对象
  2. 原型对象.constructor=构造函数(模板)
  3. 原型对象.isPrototypeof(实例对象) //判断实例对象的原型是不是当前对象

十,工厂模式

  1. function createObject(name,age){
  2. var obj=new Object();
  3. obj.name=name;
  4. obj.age=age;
  5. return obj;
  6. }

工厂模式解决了实例化对象大量重复的问题,但还有一个问题,那就是根本无法搞清楚他们到底是哪个对象的实例。
使用构造函数的方法,既解决了重复实例化的问题,又解决了对象的识别问题。

使用构造函数的方法和工厂模式的不同之处在于:
1,构造函数方法没有显式地创建对象(new Object());
2,直接将属性和方法赋给this对象;
3,没有return语句

当使用了构造函数,并且new 构造函数(),那么在后台就执行了 new Object();
函数体内的this代表了new Object()出来的对象。

  1. 1,判断属性是在构造函数的实例里还是在原型里,可以使用hasOwnProperty()函数
  2. 2,字面量创建的方式使用constructor属性不会指向实例,而会指向Object,为什么指向Object?因为Box.prototype = {};这种写法其实就是创建了一个新对象。构造函数创建的方式则相反。
  3. 而每创建一个函数,就会同时创建它的prototype,这个对象也会自动获取constructor属性。
  4. 3,如果是实例方法,不同的实例化他们的方法地址不同,是唯一的。
  5. 4,如果是原型方法,那么他们的地址是共享的。
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注