• JavaScript之美读书笔记一


    前两天去图书馆借了一本《JavaScript之美》,在书架上无意中看到的,想着那就看看吧。

    第一章

    原型

    有些JavaScript爱好者宣称JavaScript是一种基于原型而不是面向对象的语言,任何带有“类”字样的方法根本不适用于JavaScript。但“原型”的含义是什么?原型和类有着怎样的区别?

    用通用的编程术语来讲,原型是指为其他对象提供基本行为的对象。其他对象也可在此基础上扩展基本行为,加入个性化行为。该过程也称为有差异的继承,有别于类继承的是它不需要明确指定类型(静态或动态),从形式上而言,它也不是在一种类型的基础上定义其他类型。类继承的目的是复用,而原型继承则不一定。

    JavaScript的每个对象均指向一个原型对象并继承其属性。JavaScript的原型是实现复用的好工具。原型还可以继承自其他原型,从而形成原型链。

    JavaScript将prototype属性绑定到构造器,其结果是多个层级的对象继承通常需要链接构造器和原型来实现。构造器-原型链句法,不但不优雅,缺点还体现在需要预先规划。ES6的class关键字只是将现有实现方式形式化。

    ES5标准引入了Object.create,以增加原型继承的灵活度,扩展应用场景。该方法允许将原型直接赋给对象,JavaScript原型不再受限于构造器和类型的限制。

    var circle = Object.create({
      area: function() {
        return Math.PI * this.radius * this.radius;
      },
      grow: function() {
        this.radius++;
      },
      shrink: function() {
        this.radius--;
      }
    });
    

    Object.create方法的第2个参数可选,表示继承自哪个对象。不幸的是第2个参数不是对象自身,而是一个完整的 meta 属性定义:

    var circle = Object.create({
      area: function() {
        return Math.PI * this.radius * this.radius;
      },
      grow: function() {
        this.radius++;
      },
      shrink: function() {
        this.radius--;
      }
    }, {
      radius: {
        writable: true, configurable: true, value: 7
      }
    });
    

    或者可以手动将属性赋给它。

    即便如此,Object.create方法也只是允许对象继承某个原型的属性,但真实应用场景,往往需要从多个原型对象获得行为。

    mixin方法

    函数复用的最基本方法是手动委托,任何公共函数都可以直接用call或apply方法调用。

    mixin基础

    从传统意义上讲,mixin是一个类,它定义了一组原本要用实体定义的函数。然而,mixin类被视作是抽象的,因为它不是由自己来完成实例化。相反,具体的类通过复制(或借)mixin类的函数,继承mixin的行为,而不必跟行为的提供者产生正式的关系。mixin类可以是常规对象,原型或函数等。

    应用场景

    1:类形式的mixin
    先定义一个圆形mixin

    var circleFns = {
      area: function() {
        return Math.PI * this.radius * this.radius;
      },
      grow: function() {
        this.radius++;
      },
      shrink: function() {
        this.radius--;
      }
    };
    

    再看一个定义按钮行为的mixin

    var clickableFns = {
      hover: function() {
        console.log('hovering');
      },
      press: function() {
        console.log('button pressed');
      },
      fire: function() {
        return this.action();
      }
    };
    

    如何将mixin对象整合到你的对象之中?你需要借助extend函数。

    function extend(destination, source) {
      for(var key in source) {
        if(source.hasOwnProperty(key)) {
          destination[key] = source[key];
        }
      }
      return destination;
    }
    

    用刚才创建的两个mixin对象,扩展新对象RoundButton的基础原型RoundButton.prototype:

    var RoundButton = function(radius, label, action) {
      this.radius = radius;
      this.label = label;
      this.action = action;
    };
    
    extend(RoundButton.prototype, circleFns);
    extend(RoundButton.prototype, clickableFns);
    
    var roundButton = new RoundButton(3, 'send', function(){return 'send';});
    
    roundButton.area();
    roundButton.fire();
    

    函数形式的mixin
    下面我们将圆形和按钮mixin改写为函数。

    var circleFns = function() {
      this.area = function() {
        return Math.PI * this.radius * this.radius;
      };
      this.grow = function() {
        this.radius++;
      };
      this.shrink = function() {
        this.radius--;
      };
    };
    
    var clickableFns = function() {
      this.hover = function() {
        console.log('hovering');
      };
      this.press = function() {
        console.log('button pressed');
      };
      this.fire = function() {
        return this.action();
      };
    };
    

    现在原型对象通过Function.prototype.call就能将自己注入到目标对象中去

    circleFns.call(RoundButton.prototype);
    clickableFns.call(RoundButton.prototype);
    

    这种方法给人的感觉是很贴切,编码风格自然简洁,this总是指向接收者而不是我们不需要的抽象对象,并且,我们不必提防无意中复制了被继承的属性。
    带options参数
    函数形式的mixin方法还支持通过options参数将行为参数化,以掺杂使用各种行为。看下述示例:

    var withOval = function(options) {
      this.area = function() {
        return Math.PI * this.longRadius * this.shortRadius;
      };
      this.ratio = function() {
        return this.longRadius / this.shortRadius;
      };
      this.grow = function() {
        this.shortRadius += (options.growBy / this.ratio());
        this.longRadius += (options.growBy);
      };
      this.shrink = function() {
        this.shortRadius -= (options.shinkBy / this.ratio());
        this.longRadius -= (options.shinkBy);
      };
    };
    
    var OvalButton = function(longRadius, shortRadius, label, action) {
      this.longRadius = longRadius;
      this.shortRadius = shortRadius;
      this.label = label;
      this.action = action;
    };
    
    withOval.call(OvalButton.prototype, {growBy: 2, shinkBy: 2});
    
    button.area();
    button.grow();
    button.area();
    

    添加缓存
    函数形式的mixin还能做进一步优化,对mixin构造闭包,我们能缓存第一次定义时的结果,由此所带来的性能上的提升非常显著。下面是增加缓存机制的withRectangle mixin:

    var withRectangle = (function() {
      function area() {
        return this.length * this.width;
      };
      function grow() {
        this.length++, this.width++;
      };
      function shrink() {
        this.length--, this.width--;
      };
      return function() {
        this.area = area;
        this.grow = grow;
        this.shrink = shrink;
        return this;
      };
    })();
    
    var RectangleButton = function(length, width) {
      this.length = length;
      this.width = width;
    };
    withRectangle.call(RectangleButton.prototype);
    
    var button = new RectangleButton(4, 2);
    button.area();
    

    小结

    类继承重复用一个对象定义另一个对象,由此形成了一系列紧密的耦合关系,将不同的层级粘结在一起,对象之间的依赖关系非常复杂。相反,mixin极其敏捷,你几乎不需要调整代码库,只要发现了一组通用的,可共享的行为,就可以根据需要创建mixin,而其他所有对象不管在整个模型中扮演什么角色,都可以访问mixin的功能。mixin和对象之间的关系非常自由:mixin的任意组合可应用于任意对象,对象对应用于它的mixin数量也没有限制。这正是原型继承赋予我们的根据机会复用代码的能力。

  • 相关阅读:
    ( ) 与 { } 差在哪?-- Shell十三问<第七问>
    exec 跟 source 差在哪?-- Shell十三问<第六问>
    var=value?export前后差在哪?-- Shell十三问<第五问>
    " "( 双引号) 与 ' '( 单引号) 差在哪?-- Shell十三问<第四问>
    别人 echo 、你也 echo ,是问 echo 知多少?-- Shell十三问<第三问>
    Shell prompt(PS1) 与 Carriage Return(CR) 的关系?-- Shell十三问<第二问>
    Shell十三问更新总结版 -- 什么叫做 Shell?-- Shell十三问<第一问>
    公司项目部运维应急预案-强烈建议收藏!
    抗DDOS应急预案实践-生产环境总结-建议必看
    Ceph日常运维管理和排错 -- <7>
  • 原文地址:https://www.cnblogs.com/sunshine21/p/10155665.html
Copyright © 2020-2023  润新知