本周的学习总结

RESTful的来龙去脉

  • level 0
  • level 1
  • level 2
  • level 3

level 0 - 面向服务员

假设我们去麦当劳,想去买个汉堡包,首先和服务员说要一个汉堡包,然后等待喊订单号为123456的客户可以取餐了就行:

//input 
{
    "addOrder": {
        "orderName": "hamburger"
    }
}

//output
{
    "orderId": "123456"
}

如果这时候,我们有一张会员卡,我们想先查询一下余额,这时我们应该询问一下:

//input 
{
    "queryBalance": {
        "cardId": "886333"
    }
}

//output
{
    "balance": "0"
}

发现卡里没钱,汉堡没得吃了,于是我们取消订单

{
    "deleteOrder": {
        "orderId": "123456"
    }
}

level 1 - 面向资源

还是上面那个例子,小麦当劳店扩张了,单单靠一个服务员肯定不够了,需要多个人来负责,有的人专门负责订单相关的事情:

/orders

{
    "addOrder": {
        "orderName": "hamburger"
    }
}

{
    "deleteOrder": {
        "orderId": "123456"
    }
}

有的人专门负责钱包相关的事情:

/cards

{
    "queryBalance": {
        "cardId": "886333"
    }
}

Level 2 - 打上标签

服务的流程还可以继续优化,因为负责订单的人每次都需要去看是新增订单还是删除订单,很不方便,于是我们规定:

  • 所有新增资源的请求,都在请求上面写上大大的POST,表示这是一笔新增资源的请求,
  • 其他种类的请求,比如查询类的,用GET表示,
  • 删除类的,用DELETE表示,
  • 修改类的,分为2种:
    1.如果这个修改,无论发送多少次,最后一次修改后的资源,总是和第一次修改后的一样,我们使用PUT,
    2.如果这个修改,每次修改都会让这个资源和前一次的不一样,我们使用PATCH或者POST

于是,上面变成了这样:

POST /orders

{
    "orderName": "hamburger"
}

GET /cards/886333

DELETE /orders/123456

level 3 - 完美服务

忽然有一天,一个客户抱怨说,他下了单,不知道去哪里取消,一个服务员回复说,你不会看我们的宣传单吗?上面写着:DELETE /orders/{orderId},
顾客反问道,谁会去看那个啊,服务员不服,又说道,你瞎吗... ,然后,两人就打了起来。
有了上面的教训,我们可以继续优化,客户下了单之后,不仅给他们返回订单的编号,还给顾客返回所有可以对这个订单做的操作,于是变成了下面这样:

//input
POST /orders

{
    "orderName": "hamburger"
}

//output
{
    "orderId": "123456",
    "link": {
        "rel": "cancel",
        "url": "/order/123456"
    }
}

这次返回时多了一项link信息,里面包含了一个rel属性和url属性,relrelationship的意思,这里的关系是cancelurl则告诉你如何执行这个cancel操作,接着你就可以这样子来取消订单啦,

DELETE /orders/123456

上面讲的Level0 ~ Level3,来自Leonard Richardson提出的Richardson Maturity Model
rustful

typescript

原始数据类型

定义变量时,使用冒号在后面加上它的类型。

let isDone: boolean = false;
let decLiteral: number = 6;
let myName: string = 'Tom';
//声明一个void类型的变量没有什么用,因为你只能将它赋值为`undefined`和`null`
let unusable: void = undefined;
let u: undefined = undefined;
let n: null = null;

任意值

1.在任意值上访问任何属性都是允许的

let anyThing: any = 'hello';
console.log(anyThing.myName);
console.log(anyThing.myName.firstName);

2.变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型

let something;
something = 'seven';
something = 7;

something.setName('Tom');

类型推论

如果没有明确的指定类型,那么TypeScript会依照类型推论(Type Inference)的规则推断出一个类型。

let myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

//等价于
let myFavoriteNumber: string = 'seven';
myFavoriteNumber = 7;

联合类型

联合类型(Union Types)表示取值可以为多种类型中的一种。

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;

TypeScript不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法:

function getLength(something: string | number): number {
    return something.length;
}

// index.ts(2,22): error TS2339: Property 'length' does not exist on type 'string | number'.
//   Property 'length' does not exist on type 'number'.

对象的类型——接口

TypeScript中,我们使用接口(Interfaces)来定义对象的类型。

interface Person {
    name: string;
    age: number;
}

let tom: Person = {
    name: 'Tom',
    age: 25
};

接口一般首字母大写。有的编程语言中会建议接口的名称加上I前缀。

注意

  • 赋值的时候,变量的形状必须和接口的形状保持一致,多一些或少一些都不行
  • 可选属性
interface Person {
    name: string;
    age?: number;
}

let tom: Person = {
    name: 'Tom'
};
  • 任意属性
interface Person {
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    name: 'Tom',
    gender: 'male'
};
  • 只读属性
interface Person {
    readonly id: number;
    name: string;
    age?: number;
    [propName: string]: any;
}

let tom: Person = {
    id: 89757,
    name: 'Tom',
    gender: 'male'
};

tom.id = 9527;

// index.ts(14,5): error TS2540: Cannot assign to 'id' because it is a constant or a read-only property.

数组的类型

1.「类型 + 方括号」表示法

let fibonacci: number[] = [1, '1', 2, 3, 5];

// Type 'string' is not assignable to type 'number'.

2.数组泛型

let fibonacci: Array<number> = [1, 1, 2, 3, 5];

3.用接口表示数组

interface NumberArray {
    [index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];

4.类数组

function sum() {
    let args: number[] = arguments;
}

// Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join, and 24 more.

function sum() {
    let args: {
        [index: number]: number;
        length: number;
        callee: Function;
    } = arguments;
}

事实上常用的类数组都有自己的接口定义,如IArguments, NodeList, HTMLCollection

function sum() {
    let args: IArguments = arguments;
}

函数的类型

参数定义自己的类型,冒号后面定义返回值

function sum(x: number, y: number): number {
    return x + y;
}

观察者模式与订阅发布模式

很久以前,我去网上寻找vue响应式原理时,第一次知道了Object.defineProperty和这两种设计模式,那会我不懂什么是设计模式,最近在看一些关于设计模式的知识,
重新看见这两个名词,感觉自己略微懂了,防止忘记,记录一下。

它们其实非常相似,区别在于观察者模式是发布者直接接触订阅者,而发布订阅模式,发布者和订阅者通过中间平台间接接触

生活中的观察者模式

周一刚上班,前端开发李雷就被产品经理韩梅梅拉进了一个钉钉群——“员工管理系统需求第99次变更群”。这个群里不仅有李雷,还有后端开发 A,测试同学 B。三位技术同学看到这简单直白的群名便立刻做好了接受变更的准备、打算撸起袖子开始干了。此时韩梅梅却说:“别急,这个需求有问题,我需要和业务方再确认一下,大家先各忙各的吧”。这种情况下三位技术同学不必立刻投入工作,但他们都已经做好了本周需要做一个新需求的准备,时刻等待着产品经理的号召。

一天过去了,两天过去了。周三下午,韩梅梅终于和业务方确认了所有的需求细节,于是在“员工管理系统需求第99次变更群”里大吼一声:“需求文档来了!”,随后甩出了"需求文档.zip"文件,同时@所有人。三位技术同学听到熟悉的“有人@我”提示音,立刻点开群进行群消息和群文件查收,随后根据群消息和群文件提供的需求信息,投入到了各自的开发里。上述这个过程,就是一个典型的观察者模式

观察者模式定义了一种一对多的依赖关系,让多个观察者对象同时监听某一个目标对象,当这个目标对象的状态发生变化时,会通知所有观察者对象,使它们能够自动更新。

在我们上文这个钉钉群里,一个需求信息对象对应了多个观察者(技术同学),当需求信息对象的状态发生变化(从无到有)时,产品经理通知了群里的所有同学,以便这些同学接收信息进而开展工作:角色划分 --> 状态变化 --> 发布者通知到订阅者,这就是观察者模式的“套路”。

在实践中理解定义

结合我们上面的分析,现在大家知道,在观察者模式里,至少应该有两个关键角色是一定要出现的——发布者订阅者。用面向对象的方式表达的话,那就是要有两个类。

首先我们来看这个代表发布者的类,我们给它起名叫Publisher。这个类应该具备哪些“基本技能”呢?大家回忆一下上文中的韩梅梅,韩梅梅的基本操作是什么?首先是拉群(增加订阅者),然后是@所有人(通知订阅者),这俩是最明显的了。此外作为群主&产品经理,韩梅梅还具有踢走项目组成员(移除订阅者)的能力。OK,产品经理发布者类的三个基本能力齐了,下面我们开始写代码:

// 定义发布者类
class Publisher {
  constructor() {
    this.observers = []
    console.log('Publisher created')
  }
  // 增加订阅者
  add(observer) {
    console.log('Publisher.add invoked')
    this.observers.push(observer)
  }
  // 移除订阅者
  remove(observer) {
    console.log('Publisher.remove invoked')
    this.observers.forEach((item, i) => {
      if (item === observer) {
        this.observers.splice(i, 1)
      }
    })
  }
  // 通知所有订阅者
  notify() {
    console.log('Publisher.notify invoked')
    this.observers.forEach((observer) => {
      observer.update(this)
    })
  }
}

ok,搞定了发布者,我们一起来想想订阅者能干啥——其实订阅者的能力非常简单,作为被动的一方,它的行为只有两个——被通知、去执行(本质上是接受发布者的调用,这步我们在Publisher中已经做掉了)。既然我们在Publisher中做的是方法调用,那么我们在订阅者类里要做的就是方法的定义:

// 定义订阅者类
class Observer {
    constructor() {
        console.log('Observer created')
    }

    update() {
        console.log('Observer.update invoked')
    }
}

最后,一段合并的代码:

观察者模式

class Publisher {
  constructor() {
    this.observers = [];
  }
  addObserver(observer) {
    this.observers.push(observer);
  }
  removeObserver(observer) {
    this.observers.forEach((item, index) => {
      if (item === observer) this.observers.splice(index, 1);
    });
  }
  notifyObersers() {
    this.observers.forEach((item) => {
      item.update(this);
    });
  }
}
class Observer {
  update(publisher) {
    // 更新操作
  }
}
// 在上面两个类中扩展出具体的类
class PMPublisher extends Publisher {
  constructor() {
    super();
    this.state = null;
    this.observers = [];
  }
  getState() {
    return this.state;
  }
  setState(state) {
    this.state = state;
    this.notifyObersers();
  }
}
class PMObserver extends Observer {
  constructor(id) {
    super();
    this.id = id;
    this.state = null;
  }
  update(publisher) {
    this.state = publisher.getState();
    this.work();
  }
  work() {
    console.log(`员工 ${this.id} 接收状态 ${this.state},开始干活啦~`);
  }
}
// 测试一下
const xiaoqiang = new PMPublisher(),
xiaoting = new PMObserver("#01"),
xiaoyong = new PMObserver("#02"),
xiaolong = new PMObserver("#03");
xiaoqiang.addObserver(xiaoting);
xiaoqiang.addObserver(xiaoyong);
xiaoqiang.addObserver(xiaolong);
xiaoqiang.setState("开会");

发布——订阅模式

class EventEmitter {
  constructor() {
    this.handlers = {};
  }

  // 注册事件监听器
  on(event, callback) {
    if (!this.handlers[event]) this.handlers[event] = [];
    this.handlers[event].push(callback);
  }

  // 移除某个事件回调队列里的指定回调函数
  off(event, callback) {
    const callbacks = this.handlers[event],
      index = callbacks.indexOf(callback);
    if (index !== -1) {
      callbacks.splice(index, 1);
    }
  }

  // 触发目标事件
  emit(event, ...params) {
    if (this.handlers[event]) {
      this.handlers[event].forEach((callback) => {
        callback(...params);
      });
    }
  }

  // 为事件注册单次监听器
  once(event, callback) {
    // 对回调函数进行包装,使其执行完毕自动被移除
    const wrapper = (...params) => {
      callback(...params);
      this.off(event, wrapper);
    };
    this.on(event, wrapper);
  }
}

简化拷贝写法:给全局JSON对象挂载一个deep

简化拷贝写法

解构的属性的key是动态时

解构的属性的key是动态时
解构的属性的key是动态时

原文地址:https://www.cnblogs.com/wangxi01/p/12807744.html