javascript--prototype机制

  在Javascript对象有一个特殊的prototype内置属性,它其实就是对于其他对象的引用

  当我们试图访问一个对象下的某个属性的时候,会在JS引擎触发一个GET的操作,首先会查找这个对象是否存在这个属性,如果没有找的话,则继续在prototype关联的对象上查找,以此类推。如果在后者上也没有找到的话,继续查找的prototype,这一系列的链接就被称为原型链。

  在javascript中对象都是可以使用tostring(),valueOf()方法,函数都是可以使用call(),apply(),因为普通对象都是通过prototype链最终指向了内置的Object.prototype,而函数都是指向了Function.prototype。

 

基于prototype机制实现"继承"

  在javascipt中实现继承背后是依靠prototype机制的,javascript与其他面向类的语言不同,它是没有类作为对象的抽象模式去实现继承(在ES6中是有class这个关键字的,也是prototype机制的一种语法糖),但是我们可以在javascript中写出模仿类的代码,这里来看看常见的构造函数模式+原型模式:

function Animal(name,color){
        this.name = name;
        this.color = color;
    }
    Animal.prototype.type = "动物";
    Animal.prototype.msg = function(){
        alert( "I am "+this.name+" my color is "+this.color);
    }
    var cat = new Animal("cat","black");
    console.log(cat.name);              //cat
    console.log(cat.type);                 //动物
    console.log(cat.constructor === Animal);        //true

  

  函数Animal就可以看做是一个'类',里面定义了name和color属性,主要看下划线的代码,cat是通过new关键字将Animal实现“构造函数调用”产生的新对象,并传入两个参数。

  一般我们通过new来调用函数,会发生以下的操作:

  1、创建一个全新的对象;

  2、这个新对象会被执行prototyp链接;

  3、这个新对象会被绑定函数调用的this上;

  4、如果函数没有返回对象,那么new表达式中的函数调用会自动返回这个新对象

 

  可以看到,使用getPrototypeOf获取cat对象的原型是全等于Animal.prototype的,cat也如预期那样继承了Animal的name、color属性,当然也继承了type属性和msg方法了,这段代码展示了两个技巧:

  ·this.name = name给每个对象都添加了name属性,有点像类实例封装的数据值;

  ·Animal.prototype.type和Animal.prototype.msg会给Animal.prototype对象添加一个属性和方法,现在cat.type和cat.msg()是可以正常工作的,背后是依靠prototype机制,因为在创建cat对象的时候,内部的prototype都会关联到Animal.prototype上了,当cat中无法找到type(或者msg)的时候,就会到Animal.prototype上找到。

 

  它是函数,为什么会被我们认为是"类"?

  在javascript中有个约定俗成的惯例,就是“构造函数”(或者叫类函数)首字母需要大写,其实这个Animal函数和其他普通的函数是没有任何区别的,只是我们在调用这个函数的时候加上一个new的关键字,把这个函数调用变成一个“构造函数调用”。

  大概就是这个函数都是要通过new来调用了吧。

   其实还有个更重要的原因,来看一段代码:

function Animal(name,color){
        this.name = name;
        this.color = color;
    }
    Animal.prototype.type = "动物";
    Animal.prototype.msg = function(){
        alert( "I am "+this.name+" my color is "+this.color);
    }
    var cat = new Animal("cat","black");
    console.log(cat.constructor === Animal);    //true

  新创建的对象cat里面有个constructor属性,它是全等于函数Animal,看起来就像是cat对象是由Animal函数构造出来的

  但事实是cat.consructor引用同样被关联到Animal.prototype上,Animal.prototype.constructor默认指向了Animal,来看一段代码验证一下这个观点

  

function Foo(){
            name : 'x'
    }
Foo.prototype = { } var foo = new Foo(); console.log(foo.constructor === Foo);    //false console.log(foo.constructor === Object); //true

  上面代码定义了一个函数Foo同时修改了原型对象prototype,foo是通过new调用Foo产生的新对象,但是foo.constructor并不等于Foo,原因是foo上并不存在constructor属性,所以它会委托原型链到Foo.prototpe上查找,但是这个对象上也没有constructor属性,所以会继续在原型链找,直到原型链的终点--Object.prototype,这个对象有constructor属性,并指向了内置的Object()函数。

  foo是由Foo构造出来这个观点是错误的。

 

  原型继承和类继承的区别?

  在面向类的语言中,类可以被复制多次,就像模具制作东西一样,实例一个类的时候就会将类的行为复制一份到物理对象中,但是在javascript中,是不存在这种复制机制的,在创建实例对象时,它们的prototype会被关联到“构造函数”原型对象上。拿之前的栗子来说就是cat对象的prototype被关联到Animal.prototype上,当我们想访问cat.type时,就会在原型链上去查找,而不是另外复制一份出来保存到新对象上

  来看一段代码:

function Foo(){
                name : 'x'
        }
        Foo.prototype = {
                friends : ['y','z','c']
        }
        var foo = new Foo();   
        console.log(foo.friends);                 //["y", "z", "c"]
        Foo.prototype.friends.push('a');          //向Foo.prototype.friends添加一个a
        console.log(foo.friends);                 //["y", "z", "c", "a"]     

 

  继承的打开方式

  在javascript中有许许多多的继承方式:原型继承、借用构造函数、寄生继承等等。

  假设我们现在有两个“类”,SuperType和SubType,我们想要SubType去继承SuperType,就要修改Subtype的原型了,常见的写法有:

  ·SubType.prototype = SuperType.protype

  ·Subtype.protype = new SuperType()

   安利另一种写法,来看代码:

function Foo(name){
                this.name = name;
        }
        Foo.prototype.sayName = function(){
                console.log(this.name);
        }
        function Bar(name,label){
                Foo.call(this,name);            //借用Foo函数
                this.label = label;
        }
        Bar.prototype = Object.create(Foo.prototype);       //创建Bar.prototype对象并关联到Foo.prototype上
        //在Bar.prototype添加一个方法
        Bar.prototype.sayLabel = function(){
                console.log(this.label);
        }
    
        var a = new Bar('x','person');
        a.sayName();      //x
        a.sayLabel();       //person

  这段代码的核心语句就是下划线的Bar.prototype = Object.create(Foo.prototype),调用Object.create会凭空创建一个新对象并把新对象内部的prototype关联到你指定的对象上。

  为什么要安利这种写法呢?

  ·Bar.prototype = Foo.prototype并不会创建一个关联到Bar.prototype的新对象,这样只是让Bar.prototype直接引用Foo.prototype对象,当我们试图在Bar.prototype添加(或者修改)属性或者方法时,就相当于修改了Foo.prototype对象本身了,这会影响到后面创建的后代,显然不是我们想要的

  ·Bar.prototype = new Foo()的确会创建一个关联到Bar.prototype的新对象,这样写法会调用Foo函数,假如Foo里面有一些讨厌的操作:比如向this添加属性、写日志、修改状态等等也会影响到后代,这也不是我们想看到的

  因此,需要创建一个合适的关联对象,通过使用 Object.create

  在ES6新增了一个辅助函数Object.setPrototypeOf,它可以修改对象的prototype关联,用法如下:

  Object.setPrototypeOf(Bar.prototype,Foo.prototype);

 

  结束语:这篇博文酝酿了挺久,关于原型链可以展开来细讲的点很多,也很凌乱,一直想把这些点串起来,也是自己对于这些知识点理解不够透彻、掌握不熟练,如果文中出现错误的地方,欢迎大家指正,如果这篇博文有些许帮助,点下右下角的推荐哈:)

 

参考资料:《你不知道的javascript》

  

 

  

posted @ 2016-03-28 21:23  weapon-x  阅读(936)  评论(0编辑  收藏  举报