javascript中的对象,原型,原型链和面向对象

1、javascript中的属性、方法   javascript

  1.首先,关于javascript中的函数/“方法”,说明两点: java

  1)若是访问的对象属性是一个函数,有些开发者容易认为该函数属于这个对象,所以把“属性访问”叫作“方法访问”,而实际上,函数永远不会属于一个对象,对象拥有的,只是函数的引用。确实,有些函数体内部使用到了this引用,有时候这些this确实会指向调用位置的对象引用,可是这种用法从本质上并无把一个函数变成一个方法,只是发生了this绑定罢了。所以,若是属性访问返回的是一个函数,那它也并非一个方法,属性访问返回的函数和其余函数并无任何区别,只是有时候会发生隐式的this绑定罢了 c++

  2)javascript中很难肯定“复制”函数究竟意味着什么。事实上,在javascript中,函数没法(用标准、可靠的方法)真正地复制,可以复制的只是函数的引用。算法

  2.对象的原型[[prototype]]链与函数的原型chrome

  原型链:对象[[prototype]]l链是一种机制,是对象中的一个内部连接引用另外一个对象。编程

  原型:函数.prototype指向的就是一个对象,叫作函数的原型对象。设计模式

  A.对象原型链浏览器

  javascript对象有一个特殊的[[prototype]]内置属性,其实就是对于其余对象的引用,几乎全部的对象在建立时[[prototype]]的属性都会被赋予一个非空的值(除了object.create(null)).全部普通的[[prototype]]链最终都会指向内置的object.prototype。里面包含了不少常见的功能,诸如.toString(),.valueOf(),.hasOwnProperty(),.isPrototypeOf();函数式编程

  原型链本质上是属性查找使用的。myObject.a不只仅是在myObject中查找名字为a的属性。在语言规范中,myObject.a其实是执行了[[Get]]操做。对象默认的[[Get]]操做首先在对象中查找是否有名称相同的属性,若是找到就返回这个属性的值。若是没有找到,按照[[Get]]算法的定义会遍历对象的原型链(prototype链),直到找到匹配的属性名或者查找完整条[[prototype]]链,若是是后者的话,[[Get]]操做的返回值是undefined.函数

  在javascript中,给一个对象设置属性如myObject.foo='bar',不只仅是添加一个新属性或者修改已有的属性,,完整的流程以下:

  1)若是对象中包含名为foo的普通属性,不论上层原型链存不存在,这条赋值语句会直接修改myObject已有的属性名,此时会发生屏蔽;

  2)若是对象myObject中不包含名为foo的属性,[[prototype]]链就会遍历,若是原型链上找不到foo,foo就会被直接添加到myObject上。

  3)若是myOjbect中不包含foo这个属性,但foo存在于原型链的上层,赋值语句myObjec.foo的行为会有些不一样,具体以下:

    a.若是[[prototype]]链上层名为foo的普通数据访问属性没有被标记为只读(writable,false),那就会直接在myObject添加一个名为foo的新属性,它是屏蔽属性;

    b.若是[[prototype]]链上层存在foo,且被标记为只读,那么没法修改已有属性或者在myObject上面建立屏蔽属性,在费严格模式下,这条赋值语句会被忽略。

    c.若是[[prototype]]链上层存在foo而且它是一个setter,那么必定会调用这个setter,foo不会被添加到myObject上,也不会从新定义foo这个setter。

  可见只有在myObject中包含foo属性,或者myObject与原型链都不包含foo属性,或者myObject不包含foo属性,原型链的foo属性没有被标记为只读的状况下,才会发生屏蔽。若是在上面b,c两种状况下也但愿屏蔽foo,不能使用=操做符,而是使用object.defineProperty来向myObject添加属性foo。

  B:函数原型

  对象原型链[[prototype]]与函数原型prototype,这两个是大相径庭的事物,虽然两者存在必定的关联。对象的原型链[[prototype]]如上所述,是对象的一个隐藏属性,用于属性查找。在chrome等浏览器的实现中,能够经过_proto_访问到。

  函数做为对象,天然也有内置的隐藏属性[[prototype]],其原型链的终点指向Function.prototype,而Function.prototype又指向Object.prototype.

  此外,任何函数好比function Foo()默认都有一个特殊的显式属性prototype,(String,Number,Object,Function这些所谓的子类型说白了就是一些内置函数,所以都有prototype属性)。函数的prototype属性是显示的,它指向一个对象,这个对象一般称之为函数原型。

  那么函数原型这个对象到底是什么呢?

  最直接的解释就是:这个对象时在调用a=new Foo()时建立的,执行这句话同时令新建立的对象a,其a.[[prototype]]连接到这个Foo.prototype所指向的原型对象。若是屡次调用new Foo(),那么他们的[[prototype]]关联的是同一个对象,都是Foo.prototype所指向的对象。可见:1)函数做为对象,其Foo.[[prototype]]是不连接到Foo.prototype的,而是new 调用建立的对象链接到Foo.prototype指向的原型对象;2)new调用会在新建立的对象和函数原型之间建立关联,这个关联不存在于对象和构造函数之间,只是上述代码会同时为Foo.prototype添加construnctor属性,该属性指向“构造函数“Foo”(再次强调,javascript没有构造函数,只有函数的构造调用),对象经过原型链也能访问construnctor属性,不过这除了营造出相似“构造对象”的假象,貌似用处不大!!!

  实际上,new Foo()这个函数调用实际上并无直接建立关联,这个关联只是一个意外的反作用。new Foo()只是间接完成了咱们的目的:一个关联到其余对象的新对象。那么有没有更直接的方法作到这一点呢?固然,那就是Object.create(...)!

  var bar=Object.create(foo)会建立一个新对象(bar),并把其[[prototype]]关联到指定对象(foo)。这样就能够充分发挥[[prototype]]委托的威力而避免没必要要的麻烦(好比使用new 构造函数会生成.prototype和.constuctor的引用)

  Object.create(null),会建立一个空[[prototype]]链的对象,这个对象没法进行委托。所以不会受到原型链的干扰,很是适合用于存储数据。

  在ES5以前的Object.create()的polyfill的代码:   

  

if(!Object.create){
    Object.create=function(obj){
        function Foo(){};
        Foo.prototype=obj;
        return new Foo();
    };   
}

 

1、javascript中所谓“类”

  类说白了只是一种设计模式(模板模式),在编程尤为是函数式编程中,类并非必须的编程基础,只是一种可选的代码抽象。只不过在有些语言如java中,并不给你选择的机会,在c/c++中,会提供过程化和面向类这两种语法。类自己仅仅是一种抽象的表示,须要实例化才能对它进行操做。类经过复制操做被实例化为对象形式,继承操做也相似,子类会包含父类行为的复制后的原始副本,所以子类能够重写全部的继承行为或者新行为而不会影响到父类。可见,在面向类的语言中,类的继承,类的实例化本质上是复制。

  javascript属于哪种呢?事实上,javascript拥有一些近似类的语法,但在近似类的表象之下,javascript的机制和类彻底不一样。在类的继承以及实例化时,javascript的对象机制并不会自动执行复制行为,而是经过原型链关联到实际的父类属性,看起来彷佛其余语言“继承”的是方法的签名,而javascript继承的是实际的方法。

  记住:javascript中只有对象,并不存在能够实例化的“类”,一个对象并不会被复制到其余对象中,他们只会被“关联”起来

  一样,在面向类的语言中,继承、实例化着复制操做。就实例化来讲,javascript不存在将类实例化为对象的说法,javascipt中原本就只有对象;就继承来讲,javascript只会在两个对象之间建立一个关联,这样一个对象就能够经过委托访问另外一个对象的属性和函数。“委托”这个术语比“继承”更准确地描述Javascipt中对象的关联机制!

  不过,为了使得javascript表现出和其余语言相似的复制行为(不论继承仍是实例化,本质都是复制),javscript开发者想出了不少方法来模拟类的复制行为:如混入,寄生继承和基于原型的继承。  

2、在javascript中模拟类的实现

  1.混入

  包括在jQuery源代码中,模拟其余语言的类复制行为,这种方法就是“混入”(mixin)。

  手动实现的mixin(在不少库中也叫作extends)代码以下:

function mixin(sourceObj,targetObj){
    for(var key in sourceObj){
        if(!(key in targetObj)){
            targetObj[key]=sourceObj[key];
        }
    }
}

  

  1)这不可以解决javascript中的显式伪多态问题,在javascript中调用相似父类中的同名方法没有super这样的用法(相对多态),只能使用绝对引用父类名.方法名.call,(显式伪多态)。这样的后果是,在支持多态的面向类的语言中,子类和父类的关系只须要在类定义的开头建立便可,所以只须要在这一个地方维护两个类的联系。然而javascript的显示伪多态会在全部须要使用多态的地方引入一个函数关联,于是增长了代码的维护成本。

  2)混入也没法彻底模拟类的复制行为,由于对象(和函数)只能复制引用,没法复制被引用的对象或函数自己。

  2.寄生式继承

  寄生式继承的主要推广者是Doulgas Crockford.寄生式继承的主要思路是建立一个用于封装继承过程的函数,该函数在内部以某种方式加强对象,最后返回这个对象。  

 

function SuperType(name){
    this.name=name;
}
SuperType.prototype.sayName=function(){
    return this.name;
};

//寄生式继承
function SubType(name,age){
    var instance=new SuperType(name);//实际上寄生式继承中,这里不必定非得是new,凡是返回一个对象的操做均可以
    var sayName=instance.sayName;
    instance.age=age;
    //方法重写
    instance.sayName=function(){    
        var name=sayName.call(this);
        console.log('welcome '+name);        
    };
    instance.sayAge=function(){
        console.log(this.age);
    }
    return instance;
}
 
//测试
//事实上,使用new时候会建立一个对象,可是因为咱们返回了Instance这个对象,new建立的这个对象会被忽略,所以下面的代码加不加new实质上是同样的
var subInstance=new SubType('bobo',28);
subInstance.sayName();//输出welcome bobo
subInstance.sayAge();//输出28

 

  寄生式继承的主要问题之一是不能复用函数,在上面的例子中,若是调用两次SubType()[或者调用new SubType()达到的效果是一致的],那么返回的两个SubType“实例”各自拥有本身的一套函数。此外,引用父类的方法,须要首先将方法的引用赋值给对应变量,如上面的代码所示。

 

  3.基于原型的继承

   基于原型的继承,其思路是使用原型链实现对原型属性和方法的继承,借用构造函数实现对实例属性的继承,这也是被广泛使用的一种方法。下面是一个案例:  

//基于原型的继承
function SuperType(name){
    this.name=name;
}
SuperType.prototype.getName=function(){
    return this.name;
};

function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}
SubType.prototype=Object.create(SuperType.prototype);
SubType.prototype.sayName=function(){
    //调用this的方法
    var name=this.getName();
    console.log('welcome '+name);
}
SubType.prototype.sayAge=function(){
    console.log(this.age);
}

//测试
var subInstance=new SubType('bobo',28);
subInstance.sayName();//输出welcome bobo
subInstance.sayAge();//输出28

  

上面代码最核心的部分就是:

  SubType.prototype=Object.create(SuperType.prototype).这句话调用会建立一个新对象SubType.prototype,并将其内部的[[prototype]]关联到指定对象,本例中是SuperType.prototype.

  注意,有两种常见的错误,实际上他们都有一些问题:  

//达不到想要的效果
SubType.prototype=Foo.prototype;
//基本达到需求,但存在一些反作用
SubType.prototype=new SuperType();

 

 

第一种,SubType.prototype=SuperType.prototype并不会建立一个关联到Foo.prototype的新对象,而只是让SubType.prototype直接引用SuperType.prototype,所以执行相似SubType.prototype.xxx的赋值语句的时候,将直接修改Foo.prototype对象自己。实际上这样不是你想要的效果,由于此时根本不须要SubType对象,直接使用SuperType就能够了,代码还能够更简单。

第二种:SubType.prototype=new SuperType()的确会建立关联到SuperType.protoType的新对象。但使用了SuperType的构造函数调用,若是函数SuperType有一些反作用(好比写日志,注册到其余对象等等),就会影响到SubType()的后代,形成不可预知的后果。其次是调用了两次SuperType这个函数。第一次是在子类构造函数的内部,经过调用父类的SuperType为子类的对象添加name属性;第二次是在设置子类原型链prototype的地方,对SuperType实行了构造调用,这会致使子类的原型链中也存在一个name属性(而且值为undefined),只不过因为属性屏蔽,子类实例对象中的name属性屏蔽了其原型链中的name属性b罢了。

3、javascipt中的“类”关系(称为内省或者反射)

假设有对象a,如何寻找a委托的对象呢?

1)站在“类”的角度来判断:

   a instanceof Foo;

instanceof回答的问题是,在a的整条[[prototype]]链中是否有指向Foo.prototype的对象?其左操做符是一个对象,右操做符是一个函数。不能两个对象(好比a和b)是否经过[[prototype]]相互关联。

2)更简洁的判断[[prototype]]反射的方法

  Foo.prototype.isPrototypeof(a)

isPrototypeof一样可以回答上述问题:在a的整条[[prototype]]链中,是否出现过Foo.prototype?

一样的问题,一样的答案,但在第二种方法中并不须要访问函数Foo,只须要两个对象就能够判断他们之间的关系,

 如:b.isPrototypeOf(c)

4、面向委托的设计

记住!javascript中只有对象,并不存在能够实例化的“类”,上述任何模拟类的方法都显得不三不四。对象直接定义本身的行为便可!!!

咱们不须要类来建立两个对象的关系,只须要经过委托来关联对象就足够了。从如今开始,尽可能抛弃全部的function ,new (),.prototype的写法!!

出于各类缘由,以“父类”,“子类”,“继承”,“多态”结束的属于(包括原型继承)和其余面向对象的术语都没法帮助理解javascript的真实机制!相比之下,“委托”是一个更合适的描述,委托行为意味着某些对象在找不到属性或者方法引用时会把这个请求委托给另外一个对象,javascript对象之间的关系是委托而不是复制!

 

下面以具体案例为例,对比基于类的写法和基于委托的写法两者的不一样:

1.基于类的写法

 

//基于类的写法
function SuperType(name){
    this.name=name;
}
SuperType.prototype.getName=function(){
    return this.name;
}

function SubType(name,age){
    SuperType.call(this,name);
    this.age=age;
}
SubType.prototype=Object.create(SuperType.prototype);
SubType.prototype.getAge=function(){
    return this.age;
};
SubType.prototype.sayHello=function(){
    var name=this.getName();
    return 'hello '+name;
};


//测试
var instance=new SubType('bobo',28);
console.log(instance.sayHello());//输出hello bobo
console.log(instance.getAge());//输出28

 

2.基于委托的写法

//采用基于委托的写法
var superObj={
    init:function(name){
        this.name=name;
    },
    getName:function(){
        return this.name;
    }
};
var subObj=Object.create(superObj);
//不能像下面这么写了
//对象字面量的写法会建立一个新对象赋值给subObj,原来的关联就不存在了
// subObj={
//     setup:function(name,age){
//         this.init(name);
//         this.age=age;
//     },
//     sayHello:function(){
//         var name=this.getName();
//         return 'hello '+name;
//     },
//     getAge:function(){
//         return this.age;
//     }

// };
 
//只能这么写
subObj.setup=function(name,age){
    this.init(name);
    this.age=age;
};
subObj.sayHello=function(){
    return 'hello '+this.name;
};
subObj.getAge=function(){
    return this.age;
};
var b1=Object.create(subObj);
b1.setup('bobo',28);
console.log(b1.sayHello()); //hello bobo
console.log(b1.getAge()); //28

var b2=Object.create(subObj);
b2.setup('leishao',27);
console.log(b2.sayHello()); //hello leishao
console.log(b2.getAge()); //27

对比两种写法:

1)基于委托的写法中不会出现function构造函数,new,prototype,有的只是对象和对象之间的关联;

2)基于类的写法,子类一般和父类取相同的方法名来实现重写的效果,而在基于委托的写法中,则须要尽可能避免这种方法,避免重名在原型链查找中引发不可预测的后果。

3)基于类的写法中,属性通常在构造函数中声明,建立对象和对象初始化是在一次构造函数的调用中一次性完成的(var instance=new SuperType('bobo',28));然而在基于委托的写法中,建立对象和对象初始化分开,分红了两次(var b1=Object.crate(subObj);b1.setup('bobo',28)),属性通常也在初始化函数中定义。这样当然多谢了代码,但同时也增长了灵活性,能够根据须要让他们出如今不一样的地方。

相关文章
相关标签/搜索