美文网首页
多态、封装、继承

多态、封装、继承

作者: YHuang | 来源:发表于2016-12-06 23:47 被阅读0次

    多态

    同一个操作作用于不同的对象,得到不同的结果。
    重点分离:做什么 和 谁去做。
    鸭子辩型:如果它走起路来像鸭子,叫起来也是鸭子,那么它就是鸭子。

    // 做什么
    function sayName( people ) {
        people.sayName()
    }
    
    // 谁去做
    const xiaoming = {
        name: '小明',
        sayName() {
            console.log( this.name )
        }
    }
    
    const lihong = {
        name: '李红',
        sayName() {
            console.log( this.name )
        }
    }
    
    // 同一个操作作用在不同的对象
    sayName( xiaoming )  // 小明
    sayName( lihong )  // 李红
    

    封装

    只让别人知道你想让别人知道的,不让别人知道你不想让别人知道的。
    封装的目的是将信息隐藏,数据、类型、实现、变化都能封装。

    • 封装数据

    Javascript 中要达到封装的目的,只有通过创建作用域来达到隐藏的目的。

    const xiaoming = ( function() {
    // 封装私有数据,变量命名前缀加_下划线是潜规则,表示是私有的
     const _name = '小明',
         _age = 22
    // 向外界提供公共方法
      return {
        getName() { return _name },
        getAge() { return _age }
      }
    })()
    
    xiaoming.getName()  // '小明'
    xiaoming.getAge()  // 22
    xiaoming._name  // undefined
    xiaoming._age  // undefined
    
    • 封装类型

    Javascript是弱类型语言,封装类型没多大意义。(完)

    • 封装实现

    对象只需要对外提供一个固定的接口,而内部如何实现这个接口,对外界来说应该是透明的,外界不用关心这个对象内部如何去实现这个接口,外界只需要调用这个接口就行了。只要这个接口不变,内部的实现如何去改变都不会影响外界。只要你打一个电话给我,你都会得到我送给你的一颗同品牌同口味同款式的棒棒糖,你不用管我怎么得到这颗棒棒糖,你想吃的时候只需要打一个电话给我就好了嘿嘿嘿。

    /**
     * 有一个 obj对象,这个对象有一个接口
     * 外界只要调用这个接口,就会返回一个数字 666
     * 接口的实现对于外界来说是透明的,外界只需调用接口
     */
    const obj = ( function() {
      let _num = 666
      return {
        generate666() { return _num }
      }
    })()
    
    // 外界只需要调用这个对象的generate666接口就行了
    obj.generate666()  // 666
    
    /**
     * 某天这个对象的内部实现改了,但是接口没有改
     * 依旧是对外返回一个数字 666
     */
    const obj = ( function() {
      let _num = 600 + 60 + 6
      return {
        generate666() { return _num }
      }
    })()
    
    // 外界依旧只是调用这个对象的generate666接口就行了
    obj.generate666()  // 666
    
    • 封装变化:

    考虑你的设计中哪些地方可能变化,这种方式与关注会导致重新设计的原因相反。它不是考虑什么时候会迫使你的设计改变,而是考虑你怎样才能够在不重新设计的情况下进行改变。这里的关键在于封装发生变化的概念,这是许多设计模式的主题。
    ----《设计模式》

    找到变化的东西,封装起来,和不变的分开。这样就能够容易地把变化的东西替换掉。

    继承

    顺藤摸瓜图

    Javascript 是基于原型的语言,没有类的概念。但是你可以用类的概念去理解,实际上在 Javascript 中,“类”也是一个对象。
    原型模式是一种设计模式,也是一种编程泛型。
    在诸如 JAVA 或者 Python 这种以类为中心的面向对象的语言来说,类和对象的关系就像是设计图和成品的关系。要创造成品(对象),就先设计图纸(类),然后按照图纸(类)制造出(new)成品(对象)。
    而在 Javascript 中,虽然有 new 这个关键字,但是他做的事情和 JAVA 或者 Python 做的事情不一样。
    Javascript 中 new 做的事情是通过找到一个对象的原型进行克隆,获得新对象。
    Javascript 中绝大部分数据都是对象,根对象是 Object.prototype,所有对象都克隆这个根对象。

    // 所有对象都克隆 Object.prototype
    const o1 = {}
    const o2 = new Object()
    
    Object.getPrototypeOf( o1 ) === Object.prototype  // true
    Object.getPrototypeOf( o2 ) === Object.prototype  // true
    
    // 如果在 Object.prototype 上增加一个属性,那么所有对象都会增加这个属性
    Object.prototype.six = 666
    
    const o3 = {}
    const o4 = new Object()
    const o5 = new Date()
    const o6 = new Number( 66 )
    
    o3.six  // 666
    o4.six  // 666
    o5.six  // 666
    o6.six  // 666
    

    要创建一个新对象,不是通过实例化类,而是找到一个原型对象进行克隆,得到新对象。
    而在现代浏览器中,都会向外暴露一个 __proto__ 属性(在ECMA规范中的描述是 [[ Prototype ]]),这个属性指向这个对象的原型对象。原型对象会有一个 constructor 属性来存储它的构造函数名。
    这样在访问一个对象的时候,如果本身有属性,就返回本身的属性,如果没有,对象会去它的原型对象上找这个属性,如果原型对象上还没有,又会去原型对象的原型对象找,一层一层找,如果你愿意一层一层一层地拨开原型对象的属性,直到尽头 null。如果找不到了,你会发现,你会流泪,就返回 undefined。这不就是原型链的原理吗嘿嘿嘿。
    这个克隆的过程是引擎帮忙实现的。但是我们也可以实现一下 new 的过程。

    function A() {
      this.ownNum = 666
      this.ownFn = function() { console.log(123) }
    }
    A.prototype.protoNum = 888
    A.prototype.protoFn =  function() { console.log(456) }
    A.prototype.constructor = A  // 隐含属性,无需设置
    
    const a = new A()
    a.constructor === A  // true
    a.__proto__ === A.prototype  // true
    
    /** new 的过程 **/
    // 克隆 Object.prototype
    const o1 = new Object()
    
    // 让 o1 的 [[ Prototype ]] 指针指向一个原型对象
    o1.__proto__ = A.prototype
    
    // A.call( o1 ):给 o1 设置新的属性,o1可能被改变(取决于 A构造函数 中是否使用了 this)
    // o2 = A.call( o1 ):A 构造函数可能直接 return 一个新对象,而不是默认 return 构造出来的 o1 对象
    // 比如 function A() { ... return {b: 9} },那么在 new A() 之后返回的是 { b: 9 },而不是默认的 o1
    const o2 = A.call( o1 )
    
    // 总是返回一个这个新建的对象
    // 无论是 o1 还是 { b: 9 },如果有 { b: 9 },那么这个的优先级是最高的
    return typeof o2 === 'object' ? o2 : o1
    

    而这上述所有过程涉及到的继承是这样的:(每一个函数都有 prototype 属性)

    /*-------------------< 代码的层面 >---------------------*/
    // 引擎实现的代码 [native code]
    function Object() {}
    Object.prototype.xx = xx
    
    // 引擎实现的代码 [native code]
    function Function() {}
    Function.prototype.yy = yy
    
    // 用户编写的代码
    function A() {}
    A.prototype.zz = zz
    
    // new一个对象
    const a = new A()
    /*-------------------< 代码的层面 />---------------------*/
    
    /*-------------------< 背后的逻辑 >---------------------*/
    a.__proto__ => A.prototype
    
    A.__proto__ => Function.prototype  // typeof A === 'function'
    A.prototype.constructor => A 
    A.prototype.__proto__ => Object.prototype  // typeof A.prototype === 'object'
    
    Function.__proto__ => Object.prototype
    Function.prototype.constructor => Function
    Function.prototype.__proto__ => Object.prototype  // typeof Function.prototype === 'function'
    
    Object.__proto__ => Function.prototype  // typeof Object === 'function'
    Object.prototype.constructor => Object
    Object.prototype.__proto__ => null
    /*-------------------< 背后的逻辑 />---------------------*/
    

    在访问 a 的时候,如果本身有想要的属性,就会返回本身的属性,此时用 a.hasOwnProperty( 'a自有属性' ) 进行属性检测是返回 true 。如果 a 本身的自有属性没有,此时就展现原型链的作用了。a会通过 a.__proto__ 访问到 A.prototype ,在 A.prototype 找到了就返回,如果还没找到,就继续一直往下找,直到终点 null ,还没找到,就返回 undefined

    可以说,最初的时候,每一个对象都是一样的,都是从 Object.prototype 上克隆而来,都是空白的。但是 Javascript 的世界是缤纷多彩的,大家都一样就没什么意思了。
    于是有些对象被人为(引擎)增加了 length 长度的数据属性,有了 pushshift 等方法属性,再用语法层面的 [] 符号表示,就成为了定义中的数组,即Array

    function Array() {
      this.length = xxx
    }
    Array.prototype.push = function() {}
    Array.prototype.shift = function() {}
    
    var arr = []
    // 等价
    var arr = new Array()
    

    是不是和 function A() {} 的套路很像?是的。
    同样道理,Date 对象也是如此,因为这个对象有了 GMT 时间的数据属性等,又有了 getDaygetMonth 等方法,就成了定义中的日期对象。
    Javascript 的继承的另一种理解,更像是“借用”。类似 Object.prototype.toString.call( ... )
    继承的本质是改变这个对象的 __proto__ 指向。
    一个对象有两种属性,自有属性和继承属性。自有属性就是本身自己身上有的东西,访问之后直接就给你返回的,继承属性其实算是借用的属性,是通过访问自身的 __proto__ 的指向对象属性,这时候已经是在访问其他对象了,不是自己的属性。
    就好比如两个对象其实是不认识,不相关的,但是你把 A对象 的 proto 属性改为 B对象,这两个对象就产生了关系,这时候你也可以称为继承。
    这种理解和其他基于 CLASS 类与对象的继承有很大的区别。
    此外,继承也是代码重用的手段,Javascript 中的继承不是通过严格意义上的拓展父类来实现的,而是通过原型实现的。
    形如 Photoshop,一般都不会直接操作原始图层,都会复制(克隆)一份图层,在复制的图层上进行编辑。

    相关文章

      网友评论

          本文标题:多态、封装、继承

          本文链接:https://www.haomeiwen.com/subject/exqdmttx.html