string类型数字怎么相加_Javascript 类型增强

背景

Javascript 是一门弱类型语言。与 Java、C++ 等强类型语言不同。Javascript 通过 var 关键字声明变量(ECMAScript 6 开始允许使用 let / const 声明变量),系统会在赋值语句中自动判断其数据类型。

优点

书写简洁灵活

var a = 1;
var b = 'hello';

缺点

类型不可预测,易造成程序运行结果不符合预期

var a = 2;
var b = '1';

var plusResult = plus(a, b);
var minusResult = minus(a, b);

console.log('相加结果: ', plusResult); // 相加结果:  21
console.log('相减结果: ', minusResult); // 相减结果:  1

// 相加方法
function plus (a, b) {
  return a + b;
}

// 相减方法
function minus (a, b) {
  return a - b;
}

解决方案

静态类型检查

大多数时候,类型产生的错误是由开发人员编码不规范造成的。这些问题通常可以在编码阶段解决(非运行时)。可以使用的工具有:Facebook 出品的 flow、Microsoft 出品的 Typescript

示例

TypeScript 是 JavaScript 的强类型版本。在编译期去掉类型和特有语法,生成纯粹的 JavaScript 代码。由于最终在浏览器中运行的仍然是 JavaScript,所以 TypeScript 并不依赖于浏览器的支持,也并不会带来兼容性问题。TypeScript 是 JavaScript 的超集,这意味着它支持所有的 JavaScript 语法。并在此之上对 JavaScript 添加了一些扩展,如 class / interface / module 等。这样会大大提升代码的可阅读性。
  1. 安装 Typescript 编译器
npm install -g typescript
  1. 新建 demo.ts
var a: number = 2;
var b: string = '1';

var plusResult:number = plus(a, b);
var minusResult:number = minus(a, b);

console.log('相加结果: ', plusResult); // 相加结果:  21
console.log('相减结果: ', minusResult); // 相减结果:  1

// 相加方法
function plus (a: number, b: number): number {
  return a + b;
}

// 相减方法
function minus (a: number, b: number): number {
  return a - b;
}
  1. 编译 ts 文件
$> tsc demo.ts
$> demo(4,33): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.
demo(5,35): error TS2345: Argument of type 'string' is not assignable to parameter of type 'number'.

编译过程中抛出了错误警示,原因是:

在声明 plusminus 函数时,对参数进行了类型标注,且都是 number 类型。在调用函数 plus 时,变量 b 作为第二个参数传入。由于变量 b 在声明时被标注为 string 类型,编译器判定此处代码有误,并在控制台输出错误警示。

尽管使用 Typescript 可以避免大部分弱类型带来的副作用。但仍有一部分场景 Typescript 是无法解决的

场景复现

小明是公司的前端开发人员,小王是后端开发人员。

他们使用了目前流行的前后端分离模式,独立开发各自模块,通过协商通信协议,来完成前后端数据交互,输出产品。

接口协议 get_company_info

  • 基本信息

接口名称:获取公司信息

接口路径:GET https://api.my-service.io/get_company_info/

  • 请求参数

| 参数名称 | 是否必须 | 示例 | 备注 | | - | - | - | - | | sid | 是 | xxxxx | 用户登录态 |

  • 返回数据
{
  "data": {
    "company_id": 1234,
    "company_name": "xxx有限公司",
    "person": [
      {
        "name": "张三",
        "age": 24
      },
      {
        "name": "李四",
        "age": 22
      },
      {
        "name": "王五",
        "age": 26
      }
    ]
  },
  "errcode": 0,
  "msg": "success"
}

协议达成一致后,小明使用 Typescript 声明了接口类型:

interface Person {
  name: string,
  age: number
}

interface Company {
  company_id: number,
  company_name: string,
  person: Array<Person>
}

获取数据渲染视图

// 代码片段
const companyInfo: Company = await Service.getCompanyInfo();
const person = companyInfo.person

renderPerson(person)

一切都很完美,直到有一天...

服务器出现了点异常,返回的数据变成了这样:

{
  "data": {
    "company_id": 1234,
    "company_name": "xxx有限公司",
    "person": null
  },
  "errcode": 0,
  "msg": "success"
}

小明的前端界面瞬间挂掉了,打开调试器一看控制台:

VM218:1 Uncaught TypeError: Cannot read property 'person' of null
    at <anonymous>:1:3

小明傻了眼,原来遵守契约有时候也不靠谱啊!

之后,小明吸取了教训,把代码做了改进:

// 代码片段
const companyInfo: Company = await Service.getCompanyInfo();
const person: Person = companyInfo.person || [];

renderPerson(person);

之后基本上就没有出现什么问题了。

直到有一天,协议中 person 新增了一个字段 device

// 公用设备
interface Device {
  id: number,
  name: string,
  sn_code: string
}

interface Person {
  name: string,
  age: number,
  device: Array<Device>
}

小明想:为了避免上次的坑,得做好容错处理,契约不一定靠得住,于是改了下代码:

// 代码片段
const companyInfo: Company = await Service.getCompanyInfo();
const person: Person = companyInfo.person || [];

person.forEach(p => {
  if (isPlainObject(p)) {
    p.device = p.device || [];
  }
})

renderPerson(person);

就这样,又过了段时间...

代码变成了这样

// 代码片段
const companyInfo: Company = await Service.getCompanyInfo();
const person: Person = companyInfo.person || [];

person.forEach(p => {
  if (isPlainObject(p)) {
    p.device = p.device || [];

    p.device.forEach(i => {
      i.xxx = xxx || [];
      // ...
    })
  }
})

renderPerson(person)

最终小明因代码不可维护崩溃住院,同时小王被打住院。

复盘反思

大部分前端应用并不是纯粹的离线系统,而是需要频繁地与其它模块(角色)进行数据交互。使用 Typescript 固然可以避开同一模块中的数据弱类型问题,涉及到与未知系统的数据交互,只能依靠契约。一旦契约被破坏,运行时没有做相应的容错处理,同样会造成不符预期的结果。

小明在第一次踩坑后,明白完全依赖契约是靠不住的,蝴蝶效应的放大,对整个系统是破坏性的。之后便在运行时做了容错处理。随着项目迭代,协议变得越来越复杂,最终导致容错代码过多,代码可读性变差。

动态类型增强

在运行时处理类型,能够让程序更加稳定,随之带来的是性能的下降,代码维护性易变差等问题。

let id = sth.id
let list = sth.list

const result1 = await fetch('/api/foo', {
  id: id && !isNaN(Number(id)) && Number(id) || DEFAULT_ID,
  list: list || []
})

const params = result1.data

const result2 = await fetch('/api/bar', {
  id: params && params.id && !isNaN(Number(params.id)) && Number(params.id) || DEFAULT_ID,
})

为了解决可维护性的问题,我写了一套运行时的类型系统。

源码地址:https://github.com/dlhandsome/return-correct-data-model

使用方法很简单:

  1. 安装 rcdm 模块
npm i rcdm --save
  1. 使用
import { defineModel } from 'rcdm'

// define your own models
const people = defineModel({
  name: String,
  age: { type: Number, default: 18 }
})

const company = defineModel({
  list: { type: Array, extend: people }
})

// return the data you expected
const p0 = people() // => { name: '', age: 18 }
const p1 = people({ name: 'Tony', age: '19' }) // => { name: 'Tony', age: 19 }
const p2 = people({ name: 'Tom', age: '20a' }) // => { name: 'Tom', age: 18 }

const c0 = company() // => { list: [] }
const c1 = company({ list: [{}] }) // => { list: [{ name: '', age: 18 }] }
const c2 = company({ list: [{ name: 'Tony', age: '19' }] }) // => { list: [{ name: 'Tony', age: 19 }] }

终极方案

静态类型检查 + 动态类型增强

使用 Typescript 做为开发语言,编译时做静态类型检查。对于外部契约式协议,可以通过 rcdm 模块初始化协议数据,得到预期的数据格式与类型。

8cd4b64020070cf7307710de098c33eb.png
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值