当前位置: 首页 > article >正文

Typescript class中的方法和函数类型的属性有何不同?

在ES5中对象是属性名和属性值的集合,对象的属性值可以是基本类型也可以是函数类型,
例如:let obj = { name: ‘jason’, say: function() { console.log(‘hello’) } } // 字面量定义obj对象
在ES6中对象的属性如果是一个函数则有了新的写法,允许省略function关键字
例如:let obj = { name: ‘jason’, say() { console.log(‘hello’) } } // 字面量定义obj对象
两种say的写法在调用时没有任何区别,都可以通过obj.say()正确调用
那么在ts中是否有区别呢???

一、class中的方法和函数

先来看一段代码:

// index.ts
abstract class A {
    abstract execute: () => string;
}

class B extends A {
    execute() {
        return B executed;
    }
}

思考一下,上面代码是否会报错??

在上面代码中,抽象类 A 声明了一个抽象的成员 execute,并且它被声明为一个属性(execute: () => string;)。这意味着 A 预期的 execute 是一个函数类型的属性,而不是一个方法。具体来说,这种写法是将 execute 作为一个函数类型的属性,而不是一个实例方法。

而在类 B 中,你定义了 execute 为一个方法(execute() {…})。这就导致了类型不匹配的错误。因为 B 中的 execute 被 TypeScript 解释为一个方法,而不是一个属性。

所以答案是会报错!
在这里插入图片描述
确实,方法和函数类型的属性在调用时看起来是相似的,因为它们都可以在实例上被调用。但它们在类型系统中的含义和行为有一些重要的区别。我们可以从以下几个方面来探讨它们之间的不同:

1. 语法上的差异

  • 方法(Method):在类中定义的方法是通过常规的方法声明语法进行定义的。例如:

    class A {
        execute(): string {
            return "Hello, world!";
        }
    }
    

    这里 execute() 是类 A 的一个实例方法。

  • 函数类型的属性(Function Property):函数类型的属性是通过将方法声明为一个函数类型的属性来定义的。例如:

    class A {
        execute: () => string = () => {
            return "Hello, world!";
        };
    }
    

    这里 execute 是类 A 的一个属性,它是一个函数类型。它的类型是 () => string,并且在类内部通过赋值来实现。

2. 类中方法与函数类型属性的区别

  • 方法:在类的实例中,方法会被作为该类的原型方法(prototype method)来定义。当你调用方法时,它是通过该实例的原型链来查找的。这使得它们可以在类的所有实例中共享。

  • 函数类型的属性:函数类型的属性是在实例对象上直接定义的。每个实例会有自己的一份这个属性。因此,execute 是作为实例的一个成员存在的,而不是共享在原型上的。这意味着每个实例可以拥有自己的 execute 函数,且它们不会共享相同的引用。

举个例子:

class A {
    execute: () => string = () => "Hello";
}

const a1 = new A();
const a2 = new A();

console.log(a1.execute === a2.execute);  // 输出: false

在上面的代码中,每个实例 a1a2 都有各自独立的 execute 函数,因此 a1.executea2.execute 并不相等。

3. 继承时的差异

当你继承一个类时,方法和函数类型属性的行为会有所不同:

  • 继承方法:在继承时,方法会被共享给所有的子类实例,因此如果你在子类中覆盖了父类的方法,它会替换掉原有的实现。

  • 继承函数类型的属性:如果在父类中使用了函数类型的属性,那么子类的实例会拥有自己独立的函数实现,并且继承过程中不会自动覆盖父类的函数实现。

举个例子:

class A {
    execute: () => string = () => "Hello from A";
}

class B extends A {
    execute: () => string = () => "Hello from B";
}

const a = new A();
const b = new B();

console.log(a.execute());  // "Hello from A"
console.log(b.execute());  // "Hello from B"

在这个例子中,AB 都有 execute 函数,但它们分别拥有独立的实现。

4. 调用时的差异

在调用时,方法和函数类型属性的调用方式是一样的:

const a = new A();
console.log(a.execute());  // 调用 execute 方法

无论是方法还是函数类型的属性,调用的语法都是一样的。不过,内部的机制(如原型链与实例属性)有所不同。

5. 类型签名的不同

在 TypeScript 中,方法和函数类型属性的类型签名也有所区别:

  • 方法的类型签名:在类的方法声明中,方法的类型是 () => string 或类似的类型,可以根据方法的参数签名进行调整。

  • 函数类型属性的类型签名:如果你使用函数类型的属性,TypeScript 会将它视为一个属性,该属性的类型是一个函数类型,而这个函数在类型系统中被看作是一个普通的属性。

这意味着 TypeScript 会区分方法和函数类型的属性,在设计时也会做出不同的类型检查。

总结

虽然在调用时它们看起来很像(都可以直接通过 instance.execute() 来调用),但方法和函数类型属性在内部的工作机制上有所不同:

  • 方法 是类的原型的一部分,所有实例共享同一份方法定义。
  • 函数类型属性 是实例的一部分,每个实例有自己独立的属性值。

这两者在类型系统、继承行为、以及实例化后如何管理的方面都存在区别。所以即使在调用时相似,背后的设计和实现有所不同。

再来看看开头的那段代码,如果想要ts不报错该如何修改呢?
有两种方法

  1. 修改 A 中 execute 的声明方式,声明为方法
abstract class A {
    abstract execute(): string;
}

class B extends A {
    execute() {
        return `B executed`;
    }
}

这种方式中,A 中的 execute 声明成了一个方法,不再是一个属性。这样,B 中的 execute 就与 A 中的声明匹配了。

  1. 保持 A 中 execute 为属性类型,修改 B 中的 execute 为属性类型
abstract class A {
    abstract execute: () => string;
}

class B extends A {
    execute = () => {
        return `B executed`;
    }
}

这种方式中,将 B 中的 execute 定义为一个属性,并且赋予它一个函数值。这样也可以确保类型一致。

二、字面量类型对象的方法和函数

熟悉了class中的函数和方法,再来看看下面代码:

interface D {
    show: boolean;
    sleep():string;
    say: () => string;
}

const d: D = {
    show: true,
    sleep: () => 'yes',
    say() {
        return 'hello world!';
    }
}

上面的代码ts会报错吗??
上面代码中,sleepsay 似乎在类型声明上确实有不同的表现形式,但 TypeScript 并不会报错,原因在于 TypeScript 允许方法和函数类型的属性在某些情况下可以互换。

为什么没有报错

  1. sleep 是一个方法

    sleep(): string;
    

    这是一个方法类型的声明,要求 sleep 必须是一个没有参数并返回 string 的方法。在 d 中,sleep 被声明为一个函数:

    sleep: () => 'yes',
    

    TypeScript 允许方法的类型声明与函数类型的属性之间的一些自由转换。在这种情况下,sleep: () => 'yes' 是一个符合 () => string 的函数类型的表达式,它也符合方法类型的要求。因此,sleep 作为方法被使用时,TypeScript 认为它是有效的,因为它符合类型声明中的函数签名。

  2. say 是一个函数类型的属性

    say: () => string;
    

    这是一个函数类型的属性,它要求 say 必须是一个返回 string 的函数。然后在 d 中,say 被定义为一个方法:

    say() {
        return 'hello world!';
    }
    

    这里,say 被定义为一个普通的类方法,这也是 TypeScript 允许的。因为方法 say() 在语法上符合 () => string 的函数类型,它可以被当作函数类型的属性来处理。

TypeScript 在处理函数类型的属性和方法时,存在一些灵活性。虽然声明方式不同,TypeScript 允许你在对象中使用方法和函数类型的属性互换。原因如下:

  1. 方法是函数类型的一个特殊形式:在 JavaScript 中,方法本质上是对象的属性,而这个属性的值是一个函数。因此,say 作为方法,也符合 () => string 的签名,所以 TypeScript 允许它作为 say 的实现。

  2. 函数类型的属性也可以是方法:即使 say 在类型声明中是 () => string(函数类型的属性),在 d 中,say 作为方法定义同样符合函数类型 () => string 的要求。方法 say() 被编译器视为返回类型为 string 的函数,因此也符合类型 () => string 的约束。

关键点

  • 方法和函数类型的属性:在 TypeScript 中,方法本质上也是一种函数类型的属性,尤其是在类和接口中。因此,sleepsay 都可以是有效的,因为 TypeScript 会允许方法和函数类型的属性在一些情况下进行互换。

  • 灵活的类型系统:TypeScript 对方法和函数属性的检查是宽松的,在实践中,它会识别方法和函数类型属性之间的相似性,因此不会在这种情况下抛出错误。

小结

TypeScript 的类型系统允许方法和函数类型属性在某些场景下互换。方法本质上是一个函数类型的属性,只不过它的语法和特性有一些不同。所以即使 sleepsay 在声明上有所不同,TypeScript 认为它们都符合接口 D 的要求,因此不会报错。

注意:编辑器会对方法和函数进行区分

  1. webstorm
    在这里插入图片描述
  2. vscode
    在这里插入图片描述

http://www.kler.cn/a/548703.html

相关文章:

  • 每日一题——47. 全排列 II
  • Linux系统Centos安装部署nginx代理
  • 数字内容体验未来趋势:五大平台横向对比与深度解析
  • 惠普HP Color LaserJet CP1215/1210彩色打印机打印校准方法
  • . Unable to find a @SpringBootConfiguration(默认软件包中的 Spring Boot 应用程序)
  • AI大模型学习(二): LangChain(一)
  • SpringBoot+数据可视化的奶茶点单购物平台(程序+论文+讲解+安装+调试+售后)
  • GMSL 实例1:当 MAX96717 遇上 MAX96724,打通 Camera 视频数据传输
  • 运维脚本——2.备份与恢复
  • YOLO11环境搭建CUDA12.6
  • AI大模型技术基础入门
  • [NKU]C++基础课(二)--- externC、强制类型转换、类与对象、面向对象程序设计语言、对象创建和使用、类的定义、封装
  • 常用查找算法整理(顺序查找、二分查找、哈希查找、二叉排序树查找、平衡二叉树查找、红黑树查找、B树和B+树查找、分块查找)
  • TCP/UDP 简介,三次握手与四次挥手
  • 哈希-字母异位词分组
  • Vue 3 30天精进之旅:Day 23 - 性能优化
  • 【python】连接Jira获取token以及jira对象
  • domain 网络安全
  • 【JavaWeb学习Day16】
  • 为什么要选择3D机器视觉检测