原型模式和基于原型继承的javascript系统

导言:

在以类为中心的面向对象编程语言中,类和对象的关系可以想象成铸模和铸件的关系,对象总是总类中创建而来。而在原型编程思想中,类并不是必须的,对象也不必要从类中创建而来,一个对象是克隆另外一个对象创建而来的,原型模式不仅仅是一种设计模式,也是一种编程范式

1.使用克隆的原型模式

如果要使用原型模式,那么我们只需要负责调用克隆的方法,便能完成克隆对象的功能。原型模式的实现关键是语言本身是否提供了克隆方法,ECMAscript5提供了一个方法可以用来克隆对象这个方法是Object.creat().代码如下:

var Plane = function () {

      this.blood = 100;

      this.attracklevel=1;

      this.defenselevel=0.5

}

var plane = new Plane()

plane.blood = 500;

plane.attracklevel=10

var clonePlane = Object.create(plane)  // 克隆对象

clonePlane.blood //  500

以上代码是通过使用Object.cteate()来克隆一个对象的副本,但是有些浏览器并不支持Object.create()方法,那么这是我们为了做兼容可以进行这样的处理,代码如下:

Object.create = Object.create || function (obj) { // obj是要被克隆的对象

    // 通过构造函数的方式来克隆

    var F = function () {}

    F.prototype = obj;

    return new F()

}

在浏览器控制台运行这段代码:

var a = {name: 'steve'}

var b = Object.create(a) // 得到对象a的一个副本,但是对象a和对象b的地址引用肯定是不相等的

console.log(b)  // 得到的是steve

2.克隆是创建对象的手段

    上面的代码中我们知道了如何通过原型模式来克隆出一个对象,但是原型模式的真正目的并不是创建出一个一模一样的对象,而是提供了便捷的方式去创建某个类型的对象,克隆只是创建对象的过程和手段。

    我们在进行编程的时候有一个非常重要的原则,那就是依赖导致原则,那么什么是依赖倒置原则了?所谓的依赖倒置就是说创建对象的时候避免依赖具体的类型,就像java等静态类型的语言进行编程的时候,类型之间的解耦是非常重要的,通过new XXX的方式创建对象显得很僵硬,因此就有了工厂方法和抽象工厂模式方法来帮助我们解决这个问题,但是这两种方法带来的结果是最终会出现很多平行的工厂类,这就会导致额外的代码,如有一个创建狗的工厂:

function dogFactory (name,age) {

    var obj = new Object();

    obj.name = name;

    obj.age = age

    return obj

}

有一个创建猫的工厂:

function catFactory (name,age) {

var obj = new Object();

obj.name = name;

obj.age = age

return obj

}

很明显创建狗的工厂和创建猫的工厂的内部的代码是一模一样的,只是工厂名字变了,由此可见这两个平行的工厂额外的增加了代码量,这显然是不期望的。在js中,原型模式为我们提供了另外一种创建对象的方式,就是通过克隆对象,而不是像上面一样从一个工厂类中创建对象。通过克隆对象我们就不在关心对象的具体类型是什么了,因此通过原型模式创建对象就会显得非常容易,也不存在类型耦合的问题。其实javascript的对象系统就是通过原型模式来搭建的,所有的对象都是从某个对象上克隆而来的。既然整个对象系统是基于原型搭建的,那么这种原型模式也一定遵循一些编程规范,接下来了解一下原型编程规范的基本规则

3原型编程规范基本规则

1.所有的数据都是对象,例如前面的var F = function(){},这个函数不仅仅是一个函数,他也是一个对象,其实函数在js中就是一等公民,也就是说函数就是对象,具体一点的解释如下:

javascript在设计的时候,模仿java引入了两套类型机制,基本类型和引用类型,基本类型包括

undefined,number,bool,string,function,object,但是这并不是一个好的想法,按照javascript设计者本意的想法是出来undefined,其余的都是对象,为了实现这一目标,number,bool,string这几种基本类型数据可以通过包装类型的方式变成对象类型数据来处理。什么意思了?有点不明白,function ,object是对象是可以接受的,但是number,bool,string 不就是一个数字,一个布尔值,一个字符串吗,怎么能成为对象了,这显然很矛盾,但时当我们知道了包装类型的时候问题就会立马得到解决,所谓的包装类型就是javascript提供了Numer,Bool,String这样的构造函数,通过将上面几个基本类型的首字母换成大写字母就得到了对应的包装类型,如通过var s = String('abc')可以将字符创abc转换为对象类型来处理,

事实上,我们并不能说在javascript中所有的数据都是对象,但可以说绝大部分是对象,那么我们相信在js中一定会有一个根对象存在,其实这个根对象就是Object.prototype(是Object构造器的原型),这个对象是一个空的对象,其实我们遇到的每一个对象最终都是从Object.prototype克隆而来的。如:

创建两个对象obj1和obj2,

var obj1 = {} ;var obj2 = new Object();利用es5提供的getPrototypeOf()来查看这两个对象的原型

console.log(Object.getPrototypeOf(obj1) === Object.prototype) // true

console.log(Object.getPrototypeOf(obj2) === Object.prototype)  // true

2.要得到一个对象不是通过实例化类,而是找到一个对象作为原型并且克隆他。

如:找到一个对象a = {name: 'steve'},将啊作为F的原型来克隆a

var creatObj = function () {

    var F = function () {}

    F.prototype = a

    return new F() // 得到一个a的副本,到这一步我们应该很困惑,为什么没有类的概念,而在这里却用了new,其实这里的F比不是类,而是一个构造函数,这里使用new运算符来创建兑现的过程实际上也只是先克隆Object.prototype对象,然后在进行一些其他额外的操作

}

可已通过下面的代码来理解new运算的过程

// 创建一个构造函数Person

function Person (name) {

        this.name = name

}

// 在构造函数的原型对象上挂在一个方法

Person.prototype.getName = function () {

  return this.name

}

// 创建一个克隆兑现的工厂

var objFactory = function () {

    // 创建一个对象,将其加工处理后最终返回出去

    var obj  =  new Object()

    Consructor = [].shift.call(arguments) // 拿到第一个参数,这里是一个构造函数,shift会改变原数组

    obj._proto_ = Constructor.prototype  // 让obj指向正确的原型

    var ret = Constructor.apply(obj,arguments) // 借用外部传入的构造器给obj设置属性,也就是我前面说的对obj进行加工处理

    return  typeof ret === 'object' ? ret : obj // 既然是对象工厂,那么最终一定要返回出去一个对象

}

var a = objFactory(Person,'steve')

console.log(a.name) // steve

console.log(a.getName()) // steve

console.log(Object.getPrototypeOf(a) === Person.prototype)// true, 很明显得到兑现的原型就是对应构造函数的原型

3.对象会记住他的原型: 所有的对象都有一个_proto_属性,对象就是通过这个属性来记住他的原型的

4.如果对象无法响应某个请求,那么就会把请求委托给他的原型

你可能感兴趣的:(原型模式和基于原型继承的javascript系统)