JavaScript原型和面向对象

原型(prototype)的来源:

  虽然JavaScript和Java并没什么关系,但是JavaScript在开始创建的时候想要模仿Java 通过new 操作符实现对一些方法和属性的共享。但是在es6 之前JavaScript并没有真正类 (class)的概念,所以JavaScript借鉴了其他语言(self , io, lua等)的原型机制。 简单点说就是在JavaScript中每个函数都可以是一个构造器,每个函数被创建的时候都会有一个prototype ,而函数通过new 操作符可以产生自己的实例对象,这个实例对象会继承父类prototype 上的所有方法 。

原型的构成:

  原型的本质其实是个对象字面量,主要由三部分构成 : constructor (构造器) , 原型方法 ,_ proto_ (原型链)

这里写图片描述

  上图是以数组为例,因为所有的数组都是Array的实例产出,所以所有的数组都能用上边那些原型方法。

  constructor :构造器,指向的是当前拥有这个原型的那个类(函数)。

  _ proto_ : 原型链索引,指向自己的父类,也就是说指向的是当前这个原型的爸爸。 一个被new 出来的实例对象到底能使用什么方法不只是取决是它的爸爸,还有他的爷爷,太爷爷….. , 也就是说如果只要是老祖宗给它继承下来的东西它都是能用的 。_ proto_这个东西就是用来翻族谱的,儿子的 _ proto_会指向爸爸,爸爸的_ proto_会指向爷爷,以此类推。下边来看个例子。如果实例对象某个自定义的方法或属性跟prototype 上重名的话,会以这个实例对象自定义的方法或属性返回。所以这也是一种非常灵活的改写。

这里写图片描述

  在页面创建了一个div dom实例对象

  看一下这个实例对象的 _ proto_ ,找到了它的爸爸 HTMLDivElement

这里写图片描述

展开HTMLDivElement这个对象的_ proto_,找到了它的爷爷 HTMLElement ,

这里写图片描述

往下翻翻发现它爷爷居然也有个 _ proto_ 指向了Element

这里写图片描述

  接着往下找下去你会发现还有 Node 、 EventTarget 不过最后指向的是Object 到这里才算结束了。 这个原型上的方法我们的 box 实例对象都能使用,因为是继承关系。咱们这个往上级找爸爸的过程形成了一条链路就是原型链。总结来说,当访问一个对象的属性的时候,JavaScript会从对象本身开始往上遍历整个原型链,如果到达了原型链的顶部,也就是Object.prototype仍然未发现需要查找的属性,那么JavaScript就会返回undefined .

JavaScript 面向对象:

  不管是任何语言中面向对象有三个要素: 封装、继承 、多态。 作为一个以函数作为一等对象的语言,封装在JavaScript中随处可见,看一个封装的例子,顺便了解几个概念:

var Book = function(id,name){
  // 私有属性
 var num=1;
//私有方法
function()  checkId{
}
// 特权方法
this.setName = fucntion(name){}
//特权属性
this.type = "book"
}

Book.method2 = function(){}   // 类方法 
Book.name  = "hello"  //类属性

  上边的私有属性和方法就是一个简单的封装,只有内部可以访问到。
多态 :就是同一种方法多种调用方式,简单的例子是根据参数不同执行不同代码,或者根据this类型不同,执行不同代码,不展开了。
继承是大家问到比较多的地方,这里要敲一下黑板的:
上边已经说过了JavaScript开始创建的时候就考虑到了一些方法和属性共享的情景,并且创建了原型。所以我们实现继承的方式必然和原型脱不开联系。

  现在有两个函数 A 和B ,如果B想继承A的原型方法应该怎么做?

情景一 :简单暴力的
function A(){}
A.prototype = {
  aaa:1
}
function B(){}
B.prototype = A.prototype ;
var  b = new B();
b.aaa  // 1
A.prototype.bbb = 2;
b.bbb  // 2

这个例子暴露出来最大的问题是不灵活,因为上边说过prototype是个对象字面量,直接赋值会传递引用地址。好比两个人共用了一个u盘,其中一个人对里边的文件进行了操作另一个使用时也会发现变化。所以这是不可取的

情景二 : 模仿一下

  既然直接赋值原型不行。那我们只把别人原型上的方法和属性拷贝过来不就可以了吗?就是说我们再拿一个u盘,把里边的文件拷贝一份出来,大家自己用自己的这样就能互不干扰了。

function A(){}  
A.prototype= {aaa:1 }
fucntion B(){}
for (var property in A.prototype){
  B.prototype[property] = A.prototype[property]
}
var b = new B();
A.prototype.aaa = 2;
b.aaa;     // 1

  这样看起来似乎就比较灵活了,但是这样似乎只继承了A的原型方法,假如A拥有特权方法和属性怎么办? 例如下面这种的

function A (name){
   this.name =name;
   this.sayHey =fucntion(){
        console.log("hello");
}
}

  所以还得把特权方法和特权属性继承过来; 这就需要用到两个方法 call 和apply。
这两个方法功能相似,调用的对象都是个函数,第一个参数都代表执行调用函数的对象,后边传入的参数代表调用函数的参数;区别主要在于call 方法参数是单个传入,apply 方法可以直接传入一个数组 。
要在B函数内执行A函数里的代码。

function  A(name,age){
   this.name= name;
   this.age = age;
}
function B(name,age){
   A.call(this,name,age)   或  A.apply(this,arguments)
// 执行上边这行代码相当于
// this.name = name;
// this.age = age;
}

  所以要想实现特权属性、方法和原型属性、方法都继承就得结合一下


function B(){
 A.apply(this,arguments);
}
for (var property in A.prototype){
  B.prototype[property] = A.prototype[property]
}
情景三 :并不完美

  似乎一切看起来都妥了,我们说过原型是由三部分构成的,其中有个比较关键的部分叫constructor (构造器), 它会指向当前这个原型的所属函数。还有个_ proto_会指向自己的父类原型。 采用直接复制的方式会把constructor和_ proto_属性也复制过来,虽然并不影响功能使用,但是会使得_ proto_在追溯自己的爸爸的时候迷失方向的,有可能会认贼作父。所以采用上边那种方式,在复制完后还需要将constructor和_ protp_重新调整一下。

  好在有个牛人(道格拉斯. 克罗克福)发明了更简单的方法- 原型链继承 (上边那种叫拷贝继承)

// 父类
function Parent (name,age){
this.name = name;
this.age = age;
}
//子类
fuction Children(){}
//中间类
function F(){}
F.prototype = Parent.prototype ;
Children.prototype = new F();

  在进行new 操作的时候实际是将 Children的 _ proto_指向F.prototype,而F.prototype又和Parent.prototype相同。所以Children的实例在使用方法和属性的时候能使用到Parent类的方法和属性。 你可能会疑问为什么不直接使用 Children.prototype =new Parent() 呢? 这是因为在 调用new Parent()的时候会产生一个Parent类的实例,这可能并不是我们想要的。而F作为中间传递者,没有任何的私有方法、属性或特权方法、属性。所以在实例化的时候产生的负面影响几乎没有。
当然,最后不能忘了我们还有特权方法和属性要继承,所有完美的继承方式应该是这样的:

// 父类
function Parent (name,age){
 // 特权属性
this.name = name;
this.age = age;
this.show =fucntion(){ }
}
//子类
fuction Children(name,age){
   Parent.apply(this,arguments)
}
//中间类
function F(){}
F.prototype = Parent.prototype ;
Children.prototype = new F();
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值