精华内容
下载资源
问答
  • 家庭网络概念  家庭网络(Home Network):指是融合家庭控制网络和多媒体信息网络于一体家庭信息化平台,是在家庭范围内实现信息设备、通信设备、娱乐设备、家用电器、自动化设备、照明设备、保安(监控)装置...
  • 家庭网络概念  家庭网络(Home Network):指是融合家庭控制网络和多媒体信息网络于一体家庭信息化平台,是在家庭范围内实现信息设备、通信设备、娱乐设备、家用电器、自动化设备、照明设备、保安(监控)装置...
  • 什么是双向绑定 如图: ...双向绑定.jpg ...双向绑定机制维护了页面(View)与数据...双向绑定,也是Angular2核心概念之一,Angular2的双向绑定是这样: data=>view:数据绑定,模板语法是 [] view=>...

    什么是双向绑定

    如图:

     
    双向绑定.jpg

    双向绑定机制维护了页面(View)与数据(Data)的一致性。如今,MVVM已经是前段流行框架必不可少的一部分。

    Angular2中的双向绑定

    双向绑定,也是Angular2的核心概念之一,Angular2的双向绑定是这样的:

    • data=>view:数据绑定,模板语法是 []
    • view=>data:事件绑定,模板语法是 ()
    • Angular其实并没有一个双向绑定的实现,他的双向绑定就是数据绑定+事件绑定,模板语法是 [()] 。

    Angular2官方给的例子:

    <!--value是数据绑定,input是事件绑定-->
    <input [value]="currentHero.name"   
           (input)="currentHero.name=$event.target.value"
           >
    <!--等价-->
    <input [(ngModel)]="currentHero.name">
    

    上面是input空间的双向绑定语法,很清楚的说明了双向绑定与两个单向绑定的关系。这里没有使用ngModule语法,ngModule语法内部实现与这个差不多。

    事件绑定

    1. 用户操作出发DOM事件通知
    2. Angular监听到了通知,然后执行模板语法,上面的例子就是将input控件的输入值赋给了currentHero.name

    数据绑定

    由于js语言并没有属性变化通知的机制,所以angular也不知道谁发生了变化,在什么时候变了。Angular的变化机制是:

     
    image.png

    上面的例子中input的数据绑定过程如下:

    1. 代码修改了currentHero.name的值。
    2. 触发整个组件树的变化检查。
    3. input显示了修改后的值。
    数据何时变化

    主要入下集中情况可能改变数据:

    • 用户输入操作,比如点击,提交等。
    • 请求服务端数据。
    • 定时事件,比如setTimeoutsetInterval

    这几点有个共同点,就是他们都是异步的。也就是说,所有的异步操作是可能导致数据变化的根源因素。

    如何通知变化

    在Angularjs中是由代码$scope.$apply()或者$scope.$digest触发,而Angular2接入了ZoneJS,由它监听了Angular所有的异步事件。ZoneJS重写了所有的异步API(所谓的猴子补丁,MonkeyPath)。ZoneJS会通知Angular可能有数据发生变化,需要检测更新。

    变化检测原理 -- 脏检查

    所谓脏检查就是存储所有变量的值,每当可能有变量发生变化需要检查时,就将所有变量的旧值跟新值进行比较,不相等就说明检测到变化,需要更新对应的视图。

    AngularJS与Angular2变化检测的区别

    Angularjs的变化检测机制也是脏检查,而Angular2的变化检测性能比Angularjs提升了很多。

    Angular2

    Angular的核心是组件化,组件的嵌套会使得最终形成一棵组件树。Angular的变化检测可以分组件进行,每个组件都有对应的变化检测器ChangeDetector。可想而知,这些变化检测器也会构成一棵树。

    另外,Angular的数据流是自顶而下的,从父组件到子组件单向流动。单向数据流向保证了高效、可预测的变化检测,尽管检查了负组件之后,自组件可能会改变父组件的数据使得父组件需要再次被检查,这是不被推荐的数据处理方式。在开发模式下,Angular会进行二次检查,如果出现上述情况,二次检查就会报错:ExpressionChangedAfterItHasBeenCheckedError(关于这个问题的答案,可以在参考资料中找到)。而在生产环境中,脏检查只会执行一次。

    Angularjs

    相比之下,Angularjs采用的是双向数据流,错综复杂的数据流使得他不得不多次检查,使得数据最终趋向稳定。理论上,数据永远不可能稳定,Angularjs的策略是,脏检查超过10次就认定程序有问题。

     
    angular2-change-detection-moscowjs-31-9-638.jpg

    变化检测优化

    优化策略

    有2个思路:

    1. OnPush策略:我知道我没变,别查我。
    2. 手动控制刷新:我变了,只查我。

    变化检测策略 OnPush

    Angular还让开发者拥有制定变化策略的能力。

    export enum ChangeDetectionStrategy { 
      OnPush, // 表示变化检测对象的状态为`CheckOnce` 
      Default, // 表示变化检测对象的状态为`CheckAlways`
    }
    

    ChangeDetectionStrategy可以看到,Angular有两种变化检测策略。Default是Angular默认的变化检测策略,也就是脏检查(只要有值发生变化,就全部检查)。开发者可以根据场景来设置更加高效的变化检测方式:OnPushOnPush策略,就是只有当输入数据的引用发生变化或者有事件触发时,组件进行变化检测。

    @Component({
      template: `
        <h2>{{vData.name}}</h2>
        <span>{{vData.email}}</span>
      `,
      // 设置该组件的变化检测策略为onPush
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    class VCardCmp {
      @Input() vData;
    }
    

    比如上面这个例子,当vData的属性值发生变化的时候,这个组件不会发生变化检测,只有当vData重新赋值的时候才会。一般,只接受输入的木偶子组件(dumb components)比较适合采用onPush策略。

    那什么时候只要对象的属性值发生变化,整个对象的引用就变了呢?不可变对象(Immutable Object)。当组件中的输入对象是不变量时,可采用onPush变化检测策略,减少变化检测的频率。换个角度来说,为了更加智能地执行变化检测,可以在只接受输入的子组件中采用onPush策略。

    手动控制变化检测

    Angular不仅可以让开发者设置变化检测策略,还可以让开发者获取变化检测对象引用ChangeDetectorRef,手动去操作变化检测。变化检测对象引用给开发者提供的方法有以下几种:

    • markForCheck():将检查组件的所有父组件所有子组件,即使设置了变化检测策略为onPush
    • detach():将变化检测对象脱离检测对象树,不再进行变化检查;结合detectChanges可实现局部变化检测。(采用onPush策略之后的组件detach()无效)
    • detectChanges():将检测该组件及其子组件,结合detach可实现局部检测。
    • checkNoChanges(): 检测该组件及其子组件,如果有变化存在则报错,用于开发阶段二次验证变化已经完成。
    • reattach():将脱离的变化检测对象重新链接到变化检测树上。

    那么,如果是Observable的话,它会订阅所有的变量变化,只要在订阅回调函数中手动触发变化检测即可实现最小成本的检测(仍采用onPush变化检测策略)。举个例子:

    @Component({
      template: '{{counter}}',
      changeDetection: ChangeDetectionStrategy.OnPush
    })
    class CartBadgeCmp {
    
      @Input() addItemStream:Observable<any>;
      counter = 0;
    
      constructor(private cd: ChangeDetectorRef) {}
    
      ngOnInit() {
        this.addItemStream.subscribe(() => {
          this.counter++;        // 数据模型发生变化
          this.cd.markForCheck(); // 手动触发检测
        })
      }
    }
    

    另外,当数据模型变化太过频繁,我们可自定义变化检测的时机。举个例子:

    @Component({
      template: `{{counter}}
      <input type="check" (click)="toggle()">`, 
    })
    class CartBadgeCmp { 
      counter = 0;
      detectEnabled = false;
     
      constructor(private cd: ChangeDetectorRef) {}
    
      ngOnInit() {
        // 每10毫秒增加1
        setInterval(()=>{this.counter++}, 10);
      }
      
      toggle(){
          if( this.detectEnabled ){
              this.cd.reattach();  // 链接上变化检测树
          }
          else{
              this.cd.detach(); // 脱离变化检测树
          }
      }
    }
    

    总结

    Angular与Angularjs都采用变化检测机制,前者优于后者主要体现在:

    • 单项数据流动
    • 以组件为单位维度独立进行检测
    • 生产环境只进行一次检查
    • 可自定义的变化检测策略:DefaultonPush
    • 可自定义的变化检测操作:markForcheck()detectChanges()detach()reattach()checkNoChanges()
    • 代码实现上的优化,据说采用了VM friendly的代码。




    链接:https://www.jianshu.com/p/cee44e8831c9

    转载于:https://www.cnblogs.com/ckAng/p/11057712.html

    展开全文
  • 计算机体系结构 程序员所见到的计算机系统系统的属性,概念性的结构与功能特性。 计算机组成:实现计算机体系结构所体现的属性。 总线:总线是连接各个部件的信息传输线,是各个部件共享的传输介质。 面向CPU的双...
    1. 计算机组成:实现计算机体系结构所体现的属性。 总线:总线是连接各个部件的信息传输线,是各个部件共享的传输介质。
    2. 面向CPU的双总线结构:I/O设备和主存交换信息时仍要占用CPU。 单总线结构图:必须设置总线判优秀逻辑,影响工作速度。
    3. 以存储器为中心:提高了传输效率,减轻了系统总线的负担,且保留了i/o设备与主存交换信息不经过CPU的特点。
    4. 总线的分类:片内总线(芯片内部);系统总线(各部件之间)-数据总线(双向),地址总线(单向),控制总线。;通信总线:用于计算机系统之间或计算机系统与其他系统之间的通信。
    5. 总线特性:机械特性(尺寸,形状),电气特性(传输方向和有效的电平范围),功能特性(每根传输线的功能),时间特性:信号的时序关系。
    6. 总线的性能指标:总线宽度,总线带宽,时钟同步/异步,总线复用,信号线数,总线控制方式,其他指标。
    7. 总线控制:集中式:链式查询:设备的优先权与总线控制器的距离有关。计数器定时查询:优先权由计数值决定,计数值为0时同链式查询方式。独立请求方式:中央仲裁器的内部排队逻辑决定;分布式。
    8. 总线通信控制:目的:解决通信双方如何获知传输开始和结束,以及通信双方协调和配合问题。 总线传输周期:申请分配,寻址,传数,结束。
    9. 总线通信:同步通信,异步通信,半同步通信,分离式通信。
    1个时钟周期为1/100MHz=0.01us,总线宽度为32位=4B,数据传输率为4B/0.04us=100MBps.
    奇偶检验码:信息为+1位奇偶检验位。奇检验:使信息位和检验位中“1”的个数共计为奇数;偶检验:~1的个数为偶数。
    
    1. 异步串行通信单位:波特率:单位时间内传送二进制数据的位数,单位为bps(位/秒),记为波特。 比特率:单位时间内传送二进制数据位的位数。
    总线按其所在的位置,分为片内总线、系统总线、通信总线。
    
    1. 存储器:按存储介质分类:半导体存储器(易失),磁表面存储器,磁芯存储器,光盘存储器;按存取方式分类:随机访问(存取时间与物理地址无关):随机存储器(RAM),只读存储器(ROM)。串行访问(存取时间与物理地址有关):顺序存取存储器,直接存取存储器。按在计算机中的作用分类:主存储器,寄存器,告诉缓冲存储器,辅助存储器。
    存储器的层次结构:缓存-主存层次和主存-辅存层次。 虚地址(逻辑地址):用户编程的地址。实地址(物理地址):实际的主存单元地址。
    
    1. MDR:主存数据寄存器(数据总线),MAR:主存地址寄存器(地址总线)

    主存中存储单元地址的分配:地址线24根,按字节寻址范围为2的24次方=16M;若字长32位,则一个字有4个字节,所以要留2根地址线指出该字中的哪个字节[00,01,10,11],即寻址范围为 2的(24-2)次方=4M;若字长16位,则一个字有2个字节,所以要留1根地址线指出该字中的哪个字节[0,1],即寻址范围为
    2的(24-1)次方=8M; 某机字长16位,存储容量为64KB,若按字编址,它的寻址范围是32K。
    SRAM静态随机存储器,DRAM动态 存储器的扩展:位扩展、字扩展和字位同时扩展。 汉明码:增添 2k ≥ n + k +1位检测位,2i ( i = 0,1,2 ,3 , )、 C2 检测的 g2 小组包含第 2,3,6,7,10,11,···gi和 gj 小组共同占第 2i-1 + 2j-1 位

    输入输出系统的发展概况 :分散连接 (CPU 和 I/O设备 串行 工作 程序查询方式)
    总线连接(CPU 和 I/O设备 并行工作 中断方式 DMA 方式 ) 输入输出系统的组成 :I/O 软件;I/O 硬件 I/O 设备与主机的联系方式 :I/O

    1. 设备编址;设备选址 联络方式 :立即响应;异步工作采用应答信号 ;同步工作采用同步时标 连接方式:辐射式连接,总线连接。
    2. i/o设备与主机信息传送的控制方式:程序查询方式,程序中断方式,DMA方式

    数据线:根数等于存储字长的位数或字符的位数。命令线:传输CPU想设备发出的命令信号,其根数与命令信号多少有关。状态线:将i/o设备状态报告主机。设备选择先(地址线):传送设备码,根数取决i、o指令中设备码的位数。

    1. 传送数据功能:数据缓冲寄存器暂存准备交换的信息,与数据线项链;选址功能:当设备选择线的设备码与本设备码相符时,发出设备选中信号SEL;反映i/o设备工作状态的功能,用于触发器D和工作触发器B标志设备状态;传送命令功能:命令寄存器存放i/o指令中的命令码,只有SEL信号有效,才接受命令线上的命令码。

    中断处理过程是由硬件和软件结合来完成的。
    为什么要使用中断?解决速度问题,使CPU和I/O并行工作;对意外情况(如磁盘损坏、运算溢出等)能够及时处理。是实时控制领域中,及时响应外来信号的请求。

    1. 中断服务程序的流程:保护现场;中断服务;恢复现场;中断返回。
    2. DMA接口功能:向CPU申请DMA传送;处理总线控制权的转角;管理系统总线、控制数据传送;确定数据传送的首地址和长度,修正传送过程中的数据地址和长度;DMA传送结束时,给出操作完成信号。
    3. DMA组成:主存地址寄存器(AR)和字计数器(WC)、数据缓冲寄存器(BR)、控制逻辑、中断机构、设备地址寄存器(DAR)
    原码:正数时前面补0,负数补1;注意整数时变换符号位,小数时变换个位;;x=+1110 [x]原=0,1110;x=-1110 
    [x]原=2的4次方+1110=1,1110 ;  x=+0.1101  [x]原 = 0.1101  ;x = -0.1101  
    [x]原=1-(-0.1101) = 1.1101;  特殊:[+0]原=0,0000   [-0]原=1,0000
    补码:正数的补码为原码本身,负数补码为原码除符号位外取反加一(当真值为负数时,原码是补码除符号位外取反加一)。x = +1010 
    [x]补 = 0,1010;x=1011000 [x]补 =2的(7+1)次方 +(1011000 ) =1,0101000  
    特殊:[+0]补=0,0000=[-0]补 反码:正数的反码为原码本身,负数的反码为原码除符号位外每位取反。 x =   -0.1010
    [x]反 =1.0101   ,特殊:[+0]反= 0,0000  [-0]反= 1,1111
    总结:最高位为符号位,“,”(逗号整数);“.”(小数点小数);对于正数,原码 = 补码 = 反码;对于负数 ,符号位为 1,其数值部分 原码除符号位外每位取反末位加 1得到补码,原码除符号位外每位取反得到反码。
    
    1. 指令:使计算机执行某种操作的命令。从层次结构看,分成:微指令+机器指令。 指令系统:一台计算机中所有机器指令的集合。
    2. 指令格式:操作码字段op(操作特性与功能),地址码字段(操作数的地址)。操作码字段为8位,则指令系统中的指令数目为28=256条。
    3. 机器字长:运算器一次能处理的二进制数的位数。
    4. 指令字长:一个指令字中包含二进制代码的位数;指令字长由操作码长度、操作码地址长度和个数共同决定。
    指令系统可分为固定字长指令、可变字长指令。 指令有半字长、单字长、双字长、多字长等不同的长度类型。 陷阱:意外事故的中断
    
    1. 寻址方式:确定本条指令的操作数地址,吓一跳欲执行指令的指令地址。有指令寻址+数据寻址两种方式。 指令寻址:分为 顺序寻址和跳跃寻址。
    2. 偏移寻址:直接寻址和寄存器间接寻址方式的结合。包括基址寻址,变址寻址,相对寻址。 CISC:复杂 RISC:简化
    展开全文
  • 首先引入了竞价价值的概念,提出了一种基于贪心策略的启发式算法确定竞拍成功的用户集合,然后提出一种基于临界价格的定价算法,确保用户的竞价反映了其真实估价。理论分析及实验结果证明所提任务分配机制在保证激励...
  • 1. 双向链表的简介&概念单链表在很多时候已经可以...图:单链表示意图对此在单链表的基础上,产生了双向链表的概念,即: 在单链表的基础上,对于每一个结点设计一个前驱结点,前驱结点与前一个结点相互连接,...

    1.  双向链表的简介&概念

    单链表在很多时候已经可以胜任很多优秀的操作了,但是,单链表任然存在不足,所谓‘单链表’,是指结点中只有一个指向其后继的指针,具有单向性,有时需要搜索大量数据的时候,就必须要多次进行从头开始的遍历,这样的搜索不是很便利。

    图:单链表示意图

    对此在单链表的基础上,产生了双向链表的概念,即: 在单链表的基础上,对于每一个结点设计一个前驱结点,前驱结点与前一个结点相互连接,构成一个链表。

    双向链表可以简称为双链表,是链表的一种,它的每个数据结点中都有两个指针,分别指向直接后继和直接前驱。所以,从双向链表中的任意一个结点开始,都可以很方便地访问它的前驱结点和后继结点。

    图:双向链表示意图

    一个完整的双向链表应该是头结点的pre指针指为空,尾结点的next指针指向空,其余结点前后相链。

    2. 双向链表的结点设计

    对于每一个结点而言,有:

    其中,DATA表示数据,其可以是简单的类型(如int,double等等),也可以是复杂的结构体(struct类型);

    pre代表的是前驱指针,它永远指向当前结点的前一个结点,注意,如果当前结点是头结点,则pre指针为空;

    next代表的是后继指针,它永远指向当前结点的下一个结点,注意,如果当前结点是尾结点,则next指针为空

    其代码设计可以为:typedef struct line{

    int data;           //data

    struct line *pre;   //pre node

    struct line *next;  //next node

    }line,*a;

    //分别表示该结点的前驱(pre),后继(next),以及当前数据(data)

    3. 双链表的创建

    对于创建双向链表,我们需要先创建头结点再逐步的进行添加,请注意,双向链表的头结点是有数据元素的,也就是头结点的data域中是存有数据的,这与一般的单链表是不同的。

    对于逐步添加数据,我们采取的做法是,开辟一段新的内存空间作为新的结点,为这个结点进行的data进行赋值,然后将已成链表的上一个结点的next指针指向自身,自身的pre指针指向上一个结点。

    其代码可以设计为://创建双链表

    line* initLine(line * head){

    int number,pos=1,input_data;

    //三个变量分别代表结点数量,当前位置,输入的数据

    printf("请输入创建结点的大小\n");

    scanf("%d",&number);

    if(number<1){return NULL;} //输入非法直接结束

    //头结点创建///

    head=(line*)malloc(sizeof(line));

    head->pre=NULL;

    head->next=NULL;

    printf("输入第%d个数据\n",pos++);

    scanf("%d",&input_data);

    head->data=input_data;

    line * list=head;

    while (pos<=number) {

    line * body=(line*)malloc(sizeof(line));

    body->pre=NULL;

    body->next=NULL;

    printf("输入第%d个数据\n",pos++);

    scanf("%d",&input_data);

    body->data=input_data;

    list->next=body;

    body->pre=list;

    list=list->next;

    }

    return head;

    }

    初步看起来双向链表似乎比单链表的两种创建方法要复杂,其实将过程逐步拆解为 创建头结点----创建一个新的结点----将头结点和新结点相互链接----再度创建新结点……这样的过程去思考,再反过头多看几遍代码会有助于理解。

    展开全文
  • 学过集合论的同学一定知道子集的概念,使用ES6 class写过继承的同学一定知道子类的概念,而使用过TypeScript的同学,也许知道子类型的概念。 但是你知道协变 (Covariant)、逆变 (Contravariant)、双向协变 ...

    前言

    学过集合论的同学一定知道子集的概念,使用ES6 class写过继承的同学一定知道子类的概念,而使用过TypeScript的同学,也许知道子类型的概念。

    但是你知道协变 (Covariant)、逆变 (Contravariant)、双向协变 (Bivariant) 和不变 (Invariant) 这些概念吗?你知道像TypeScript这种强大的静态类型检查的编程语言,是怎么做类型兼容的吗?我们今天来聊聊。

    关于Subtyping

    子类型是编程语言中一个有趣的概念,源自于数学中子集的概念:

    如果集合A的任意一个元素都是集合B的元素,那么集合A称为集合B的子集。

    而子类型则是面向对象设计语言里常提到的一个概念,是继承机制的一个产物,以下概念来源百度:

    在编程语言理论中,子类型是一种类型多态的形式。这种形式下,子类型可以替换另一种相关的数据类型(超类型,英语:supertype)。

    子类型与面向对象语言中(类或对象)的继承是两个概念。子类型反映了类型(即面向对象中的接口)之间的关系;而继承反映了一类对象可以从另一类对象创造出来,是语言特性的实现。因此,子类型也称接口继承;继承称作实现继承。

    我们可以理解子类就是实现继承,子类型就是接口继承,下面这幅图更精确的定义了这个概念,很多同学应该知道这个例子:

    这幅图中,猫是一种动物,所以我们说猫是动物的子集,猫是动物的子类,或者说猫这种类型是动物这种类型的子类型。

    Co…, Contra…, Bi…, Invariant?

    一下提到四个陌生的单词,很多同学肯定一下就懵了。React开发者应该对HOC (High Order Component) 不陌生,它就是使用一个基础组件作为参数,返回一个高阶组件的函数。React的基础是组件 (Component),在TypeScript里是类型 (Type),因此我们用HOT (High Order Type) 来表示一个复杂类型,这个复杂类型接收一个泛型参数,返回一个复合类型。

    下面我用一个例子来阐述这四个概念,你可以将它使用TypeScript Playground运行,查看静态错误提示,进行更深刻理解:

    interface SuperType {
        base: string;
    }
    interface SubType extends SuperType {
        addition: string;
    };
    
    // subtype compatibility
    let superType: SuperType = { base: 'base' };
    let subType: SubType = { base: 'myBase', addition: 'myAddition' };
    superType = subType;
    
    // Covariant
    type Covariant<T> = T[];
    let coSuperType: Covariant<SuperType> = [];
    let coSubType: Covariant<SubType> = [];
    coSuperType = coSubType;
    
    // Contravariant --strictFunctionTypes true
    type Contravariant<T> = (p: T) => void;
    let contraSuperType: Contravariant<SuperType> = function(p) {}
    let contraSubType: Contravariant<SubType> = function(p) {}
    contraSubType = contraSuperType;
    
    // Bivariant --strictFunctionTypes false
    type Bivariant<T> = (p: T) => void;
    let biSuperType: Bivariant<SuperType> = function(p) {}
    let biSubType: Bivariant<SubType> = function(p) {}
    // both are ok
    biSubType = biSuperType;
    biSuperType = biSubType;
    
    // Invariant --strictFunctionTypes true
    type Invariant<T> = { a: Covariant<T>, b: Contravariant<T> };
    let inSuperType: Invariant<SuperType> = { a: coSuperType, b: contraSuperType }
    let inSubType: Invariant<SubType> = { a: coSubType, b: contraSubType }
    // both are not ok
    inSubType = inSuperType;
    inSuperType = inSubType;
    

    我们将基础类型叫做T,复合类型叫做Comp<T>

    • 协变 (Covariant):协变表示Comp<T>类型兼容和T的一致。
    • 逆变 (Contravariant):逆变表示Comp<T>类型兼容和T相反。
    • 双向协变 (Covariant):双向协变表示Comp<T>类型双向兼容。
    • 不变 (Bivariant):不变表示Comp<T>双向都不兼容。

    TS类型系统

    在一些其他编程语言里面,使用的是名义类型 Nominal type,比如我们在Java中定义了一个class Parent,在语言运行时就是有这个Parent的类型。因此如果有一个继承自ParentChild类型,则Child类型和Parent就是类型兼容的。但是如果两个不同的class,即使他们内部结构完全一样,他俩也是完全不同的两个类型。

    但是我们知道JavaScript的复杂数据类型Object,是一种结构化的类型。哪怕使用了ES6的class语法糖,创建的类型本质上还是Object,因此TypeScript使用的也是一种结构化的类型检查系统 structural typing

    TypeScript uses structural typing. This system is different than the type system employed by some other popular languages you may have used (e.g. Java, C#, etc.)

    The idea behind structural typing is that two types are compatible if their members are compatible.

    因此在TypeScript中,判断两个类型是否兼容,只需要判断他们的“结构”是否一致,也就是说结构属性名和类型是否一致。而不需要关心他们的“名字”是否相同。

    基于上面这点,我们可以来看看TypeScript中那些“奇怪”的疑问:

    为什么TS中的函数类型是双向协变的?

    首先我们需要知道,函数这一类型是逆变的。

    对于协变,我们很好理解,比如DogAnimal,那Array<Dog>自然也是Array<Animal>。但是对于某种复合类型,比如函数。(p: Dog) => void却不是(p: Animal) => void,反过来却成立。这该怎么理解?我这里提供两种思路:

    假设(p: Dog) => voidAction<Dog>(p: Animal) => voidAction<Animal>

    1. 基于函数的本质

      我们知道,函数就是接收参数,然后做一些处理,最后返回结果。函数就是一系列操作的集合,而对于一个具体的类型Dog作为参数,函数不仅仅可以把它当成Animal,来执行一些操作;还可以访问其作为Dog独有的一些属性和方法,来执行另一部分操作。因此Action<Dog>的操作肯定比Action<Animal>要多,因此后者是前者的子集,兼容性是相反的,是逆变。

    2. 基于第三方函数对该函数调用

      假设有一个函数F,其参数为Action<Animal>,也就是type F = (fp: Action<Animal>) => void。我们假设Action<Dog>Action<Animal>兼容,此时我们如果传递Action<Dog>来调用函数F,会不会有问题呢?

      答案是肯定的,因为在函数F的内部,会对其参数fp也就是(p: Animal) => void进行调用,此时F也可以使用Cat这一Animal对其进行调用。而此时我们传递的参数fp(p: Dog) => voidfp被调用时使用的是Cat这一参数。这显然会使程序崩溃!

      因此对于函数这一特殊类型,兼容性需要和其参数的兼容性相反,是逆变。

    其次我们再来看看为什么TS里的函数还同时支持协变,也就是双向协变的?

    前面提到,TS使用的是结构化类型。因此如果Array<Dog>Array<Animal>兼容,我们可以推断:

    • Array<Dog>.pushArray<Animal>.push兼容
      • 也就是(item: Dog) => number(item: Animal) => number兼容
        • ((item: Dog) => number).arguments((item: Animal) => number).arguments兼容
          • DogAnimal兼容

    为了维持结构化类型的兼容性,TypeScript团队做了一个权衡 (trade-off)。保持了函数类型的双向协变性。但是我们可以通过设置编译选项--strictFunctionTypes true来保持函数的逆变性而关闭协变性。

    为什么参数少的函数可以和参数多的函数兼容?

    这个问题其实和函数类型逆变兼容一个道理,也可以用上述的两种思路理解,Dog相当于多个参数,Animal相当于较少的参数。

    为什么返回值不是void的函数可以和返回值是void的函数兼容?

    从第三方函数调用的角度,如果参数是一个非void的函数。则表明其不关心这个函数参数执行后的返回结果,因此哪怕给一个有返回值的函数参数,第三方的调用函数也不关系,是类型安全的,可以兼容。

    怎么构造像Java那样的名义类型?

    通常情况下,我们不需要构造名义类型。但是一定要实现的话,也有一些trick:

    名义字符串:

    // Strings here are arbitrary, but must be distinct
    type SomeUrl = string & {'this is a url': {}};
    type FirstName = string & {'person name': {}};
    
    // Add type assertions
    let x = <SomeUrl>'';
    let y = <FirstName>'bob';
    x = y; // Error
    
    // OK
    let xs: string = x;
    let ys: string = y;
    xs = ys;
    

    名义结构体:

    interface ScreenCoordinate {
      _screenCoordBrand: any;
      x: number;
      y: number;
    }
    interface PrintCoordinate {
      _printCoordBrand: any;
      x: number;
      y: number;
    }
    
    function sendToPrinter(pt: PrintCoordinate) {
      // ...
    }
    function getCursorPos(): ScreenCoordinate {
      // Not a real implementation
      return { x: 0, y: 0 };
    }
    
    // Error
    sendToPrinter(getCursorPos());
    

    如何在运行时检测变量的“名义”类型?

    TypeScript的类型检测只是一种编译时的转译,编译后类型是擦除的,无法使用JavaScript的instanceof关键字实现类型检验:

    interface SomeInterface {
      name: string;
      length: number;
    }
    interface SomeOtherInterface {
      questions: string[];
    }
    
    function f(x: SomeInterface|SomeOtherInterface) {
      // Can't use instanceof on interface, help?
      if (x instanceof SomeInterface) {
        // ...
      }
    }
    

    如果要实现检测,需要我们自己实现函数判断类型内部的结构:

    function isSomeInterface(x: any): x is SomeInterface {
      return typeof x.name === 'string' && typeof x.length === 'number';
    
    function f(x: SomeInterface|SomeOtherInterface) {
      if (isSomeInterface(x)) {
        console.log(x.name); // Cool!
      }
    }
    

    还有更多“奇怪”的疑问,可以参考TypeScript Wiki FAQs

    类型安全和不变性

    最后来聊一下不变性 (Invariant) 的应用。上面我们提到Array<T>这一复合类型是协变。但是对于可变数组,协变并不安全。同样,逆变也不安全(不过一般逆变不存在于数组)。

    下面这个例子中运行便会报错:

    class Animal { }
    
    class Cat extends Animal {
        meow() {
            console.log('cat meow');
        }
    }
    
    class Dog extends Animal {
        wow() {
            console.log('dog wow');
        }
    }
    
    let catList: Cat[] = [new Cat()];
    let animalList: Animal[] = [new Animal()];
    let dog = new Dog();
    
    // covariance is not type safe
    animalList = catList;
    animalList.push(dog);
    catList.forEach(cat => cat.meow()); // cat.meow is not a function
    
    // contravariance is also not type safe, if it exist here
    catList = animalList;
    animalList.push(dog);
    catList.forEach(cat => cat.meow());
    

    因此,我们使用可变数组时应该避免出现这样的错误,在做类型兼容的时候尽量保持数组的不可变性 (immutable)。而对于可变数组,类型本应该做到不变性。但是编程语言中很难实现,在Java中数组类型也都是可变而且协变的

    参考

    1. What are covariance and contravariance?
    2. Covariance, contravariance and a little bit of TypeScript
    3. TypeScript Deep Dive
    4. Type System Behavior
    展开全文
  • 提出了一种利用光纤光栅(FGs)双向反射率紧凑型高频真实时间延迟(TTD)波束形成器。 FG可以是一组光纤布拉格光栅(FBG)或chi光纤光栅(CFG)。 与以前系统相比,大大... 概念验证实验结果证明了该方案可行
  • 为了克服单向这一缺点,设计出了双向链表(double linked list)是在单链表每个结点中,在设置一个指向其前驱结点指针域。 所以在双链表中结点都有两个指针域,一个指向直接后继,另一个指向直接前驱。 //...
  • 关于这种行为的一个非常吸引人的解释是粒子的概念,它位于波动函数的某些有限部分中。 本文对粒子的概念提出了挑战。 在Tan,Walls和Collett实验的一个变体的基础上,证明了这一概念导致一种情况,即粒子必须同时在...
  • 这种灵活扩展到任何需要以这种形式进行通信用例。 单向沟通 通信一种方式涉及将请求发送到客户端并接收响应,或者侦听请求并对其进行响应。 这些API提供与HTTP请求/响应类似体验。 使用两种方法是listen...
  • 为了保障大数据计算的安全,结合大数据安全的安全研究和可信云的概念,提出一个大数据计算下SAT的双向防御系统模型。该模型通过动态安全因子对用户数据证据进行规范;逐步确定各行为证据的安全权限,实现行为的可靠...
  • 针对属性值为广义灰数时已有灰靶决策方法的不足问题,提出了广义灰数灰靶决策方法,首先,通过定义广义灰数,将区间灰数的概念进行了进一步的拓展;其次,基于范数概念...
  • 单链表在很多时候已经可以胜任很多优秀操作了,http://groups.tianya.cn/list-208292-1.shtml但是,单链表任然存在不足,所谓‘单链表’,是指结点中只有一个指向其后继指针,具有单向,有时需要搜索大量数据...
  • 针对协同设计任务分配中忽略设计人员自我发展需求的问题,在对设计人员知识作业过程进行分析的基础上,提出显性知识的学习模型和知识集合的概念,并构建了一个能实现任务与设计人员之间双向选择的优化模型,从而实现...
  • 于是我们引入了“满意度”的概念。 在对已知数据分析后,可以过滤掉一些不符合要求的数据。然后我们使用了匈牙利算法对总满意度进行分析,解决了签约(配对)成功率最高和可能最大的两个问题。使用了纳什均衡求解...
  • C++STLlist容器的概念以及用法 list基本概念 功能:将数据进行链式存储 链表(list)是一种物理存储单元上非连续性的储存结构,数据元素的逻辑顺序是通过链表中的指针链接实现的 链表的组成: 链表由一系列节点组成 ...
  • https双向认证

    千次阅读 2017-09-06 17:43:58
    https协议简单来说就是http协议基础上增加了SSL协议 ,从而来保证数据传输安全。 SSL协议: SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:SSL记录协议(SSL ...
  • 上篇教程给大家分享了单链表的概念,以及如何用 Java 实现一个单链表的结构。单链表是最基础的一种链表结构,在实际开发中的使用有一些局限,比如只能单向往后查找节点,如果需要找到某元素的前驱节点,单链表是...
  • 双向链表和单向链表

    千次阅读 2020-02-04 12:59:16
    概念: 双向链表也叫双链表,是链表一种,是在操作系统中常用数据结构,它每个数据结点中都有两个指针,分别指向...由于双向链表具有对称,能方便地完成各种插入、删除等操作,但需要注意前后方向操作 理...
  • 针对这种情况提出了一种新基于动态ε支配多目标遗传算法(DEMOEA),它不需要手动设定ε值,并且引入了动态网格概念来改善边界解丢失现象。通过与其他两个经典多目标进化算法NSAGA-II和SPEA-2对比实验...
  • 推理基本概念推理推理方式:从推出途径来划分演绎推理归纳推理默认推理从推理时用到知识确定来划分确定推理不确定推理按进展来划分单调推理非单调推理按推理中是否用到与推理有关启发知识来划分...
  • iOS双向签名原理

    2020-04-01 15:55:08
    为了保证系统安全,Apple公司采用双向签名机制。 一、RSA加密 在理解iOS整个开发者开发流程之前需要知道加密算法。 RSA也是网络安全应运而生算法,也是目前使用最广泛加密算法。它生成公钥和私钥。公钥是...
  • 对MP4一些概念的理解

    2019-09-28 13:24:15
    首先,对视频一些基本概念的理解: I帧:i帧又称为内编码帧,是一种自带全部信息独立帧,可独立解码,可理解为一张静态图片,视频序列中第一个帧始终是i帧,因为它是关键帧。 P帧:P帧又称为帧间预测编码帧,...
  • 文章目录线索二叉树的概念线索二叉树的结构二叉树的线索化使用线索二叉树进行遍历双向线索二叉树的概念双向线索二叉树的实现过程双向线索二叉树的遍历 线索二叉树的概念 当我们对普通的二叉树进行遍历时需要使用栈...
  • angular2双向绑定与变化检测很久很久以前,微软公司出了一套桌面应用框架WPF,其中,有一个全新模式:...双向绑定,也是angular2核心概念之一,angular2的双向绑定是这样:data=>view : 数据绑定.模板语法 []view=
  • 它两个版本都是强主张的,如果你用它,必须接受以下东西:- 必须使用它的模块机制- 必须使用它的依赖注入- 必须使用它的特殊形式定义组件(这一点每个视图框架都有,难以避免)所以Angular是带有比较强的排它性的,...
  • 提高免疫的概念是:增强机体抵抗外部感染的能力,识别和清除抗原异物的能力,防治恶性肿瘤发生的能力以及抗衰老的能力。这是一个单向调节的概念,是基于机体免疫机制低下需要提升的角度提出的。任何事物都需要和谐...
  • https协议简单来说就是http协议基础上增加了SSL协议 ,从而来保证数据传输安全。 SSL协议: SSL协议位于TCP/IP协议与各种应用层协议之间,为数据通讯提供安全支持。SSL协议可分为两层:SSL记录协议(SSL ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 586
精华内容 234
关键字:

双向性的概念