js面向对象,TS访问修饰符,递归

113 阅读5分钟

1.js面向对象

Everything is object (万物皆对象)

对象到底是什么,我们可以从两个层次来理解。

(1) 对象是单个事物的抽象。

一本书、一辆汽车、一个人都可以是对象,一个数据库、一张网页、一个与远程服务器的连接也可以是对象。当实物被抽象成对象,实物之间的关系就变成了对象之间的关系,从而就可以模拟现实情况,针对对象进行编程。

(2) 对象是一个容器,封装了属性(property)和方法(method)。

属性是对象的状态,方法是对象的行为(完成某种任务)。比如,我们可以把动物抽象为animal对象,使用“属性”记录具体是哪一种动物,使用“方法”表示动物的某种行为(奔跑、捕猎、休息等等)。

在实际开发中,对象是一个抽象的概念,可以将其简单理解为:数据集或功能集

ECMAScript-262 把对象定义为:无序属性的集合,其属性可以包含基本值、对象或者函数。 严格来讲,这就相当于说对象是一组没有特定顺序的值。对象的每个属性或方法都有一个名字,而每个名字都映射到一个值。

提示:每个对象都是基于一个引用类型创建的,这些类型可以是系统内置的原生类型,也可以是开发人员自定义的类型。

面向对象与面向过程:

  • 面向过程就是亲力亲为,事无巨细,面面俱到,步步紧跟,有条不紊
  • 面向对象就是找一个对象,指挥得结果
  • 面向对象将执行者转变成指挥者
  • 面向对象不是面向过程的替代,而是面向过程的封装

面向对象的特性:

  • 封装性
  • 继承性
  • [多态性]抽象

1.1在 es6 中,官方给出了 class 关键字来实现面向对象风格的写法,但本质上是寄生组合式继承的语法糖。

class Father{
    constructor(nickname,money,house){
        this.name= nickname
        this.money = money
        this.house = house
        this.eat = function(){
            console.log('我是Father上的方法eat');
        }
        console.log(nickname);
       console.log(this); //在class中 this执行实例化出来的对象

    }
    jump(){
        console.log('我是Father方法 跳一跳');
    }
}
class Son extends Father{
    constructor(name,money,house){;
        // console.log(this);
      super() //他的作用  更改this指向,复制父类的属性和方法,访问this之前一定要先调用super
      
      this.name = name
      this.money = money
      this.house = house
      console.log(this);
      this.type = 'hhhhh'
    }
    fn(){
        console.log('hhhh');
    }
}
let fa = new Father('爸爸','有钱','有别墅')
console.log(fa);
let s = new Son('儿子','有钱','有别墅')
console.log(s);

2.TS修饰符

/ 类的修饰符 // // 设置这个修饰符 就相当于是限制属性的使用范围

  • public 公共的 如果一个属性定义为public 那么在子类 父类 不管是类的内部还是外部都能使用
  • protected 受保护的只在类里面以及子类里面能用 在外面不能用
  • private 私有的 只在自己的类中能访问 子类 还有外面都不能访问
  • readonly 只读 就是一个属性在赋值了之后 就不能再修改了

image.png

3.使用递归解决层级比较深的数据

简单地理解递归就是:自己调用自己

主要有两个关键步骤:

  • 写出递归公式
  • 找到终止条件

先来看个简单的例子:如何求1+2+3+4+...+n的和?相信用for循环的方法大家都知道如何编写:

function sum(n) {
    var total = 0
    for (int i = 1; i <= n; i++) {
    	total = total + i
    }
    return total
}

那如何改为递归的写法呢?

第一步: 写出递归公式 那如何改为递归的写法呢?

细心观察就会发现,其实就是n与n-1和n-2的关系

sum(n) = sum(n-1) + n
···
···
···
sum(100) = sum(99) + 100
sum(99) = sum(98) + 99
···
···
···
sum(5) = sum(4) + 5
sum(4) = sum(3) + 4
sum(3) = sum(2) + 3
sum(2) = sum(1) + 2
sum(1) = 1

细心观察就会发现,其实就是n与n-1和n-2的关系

sum(n) = sum(n-1) + n
···
···
···
sum(100) = sum(99) + 100
sum(99) = sum(98) + 99
···
···
···
sum(5) = sum(4) + 5
sum(4) = sum(3) + 4
sum(3) = sum(2) + 3
sum(2) = sum(1) + 2
sum(1) = 1

将如上转化成递归公式就是

function sum(n) {
    return sum(n-1) + n
}

如何用递归思想实现数组的扁平化❓ // 数组的扁平化 就是多层数组的嵌套 转换为一层数组

// let arr = [1,2,[3,4,[5,6]]] ----> arr = [1,2,3,4,5,6]
// let arr = [1,2,[3,4,[5,6,[7]]]]
// // 递归
// function flat(arr){
//     let newArr = []
//     arr.forEach(item=>{
//         // 条件  调用自己的条件  判断item是不是数组 如何还是数组 就去调用自己,如何不是数组
//         if(Array.isArray(item)){
//           console.log(newArr);
//           newArr = newArr.concat(flat(item))
//         }else{
//            newArr.push(item)
//         }
//     })
//     return newArr

// }
// console.log(flat(arr));

4.枚举

枚举是组织收集有关联变量的一种方式 ,每个枚举成员都有一个 name 和一个 value。数字枚举成员值的默认类型是 number 类型

enum Color {
  Red, // 默认情况下,第一个枚举值是 0,然后每个后续值依次递增 1
  Green,
  Blue
}
let col = Color.Red;
col = 0; // 有效的,这也是 Color.Red

枚举是一个被命名的整型常数的集合,用于声明一组命名的常数,当一个变量有几种可能的取值时,可以将它定义为枚举类型

通俗来说,枚举就是一个对象的所有可能取值的集合 枚举的使用是通过enum关键字进行定义,形式如下:

enum xxx { ... }

类型可以分成:

  • 数字枚举
  • 字符串枚举
  • 异构枚举

#数字枚举

当我们声明一个枚举类型时,虽然没有给它们赋值,但是它们的值其实是默认的数字类型,而且默认从 0 开始依次累加:

enum Direction {
  Up, // 值默认为 0
  Down, // 值默认为 1
  Left, // 值默认为 2
  Right, // 值默认为 3
}

console.log(Direction.Up === 0) // true
console.log(Direction.Down === 1) // true
console.log(Direction.Left === 2) // true
console.log(Direction.Right === 3) // true

如果我们将第一个值进行赋值后,后面的值也会根据前一个值进行累加 1:

enum Direction {
  Up = 10,
  Down,
  Left,
  Right,
}

console.log(Direction.Up, Direction.Down, Direction.Left, Direction.Right) // 10 11 12 13

字符串枚举

枚举类型的值其实也可以是字符串类型:

enum Direction {
    Up = 'Up',
    Down = 'Down',
    Left = 'Left',
    Right = 'Right'
}

console.log(Direction['Right'], Direction.Up); // Right Up### 枚举

定义类

在 TypeScript 中,我们也是通过 Class 关键字来定义一个类, 使用 constructor 定义构造函数。

class Animal {
    public name: string;
    constructor(name: string) {
        this.name = name;
    }
    sayHi(): string {
        return `我的名字 ${this.name}`;
    }
}

继承

TS中的继承ES6中的类的继承极其相似,子类可以通过extends关键字继承一个类 例如 在构造器里访问 this 的属性之前,一定要调用 super()  ,这个是 TypeScript 强制执行的一条重要规则。

 class Person {
        constructor (name) {
          // 等价于我们的构造函数体
          this.name = name
        }

        // 原型上的方法
        sayHi () {
          console.log('hello world')
        }
      }

      // 2. 准备一个子类
      //    一个继承自 Person 的 Student 子类
      class Student extends Person {
        constructor (age, name) {
          // super(name) // 就相当于我们 es5 的借用构造函数继承
          // 只不过不需要我们写 call 去改变 this 指向了
          // 直接帮我们改变好 this 指向了, 我们只需要传递参数就可以了

          super(name)
          this.age = age
        }

        son () {
          console.log('我是 子类的方法')
        }

      }

      var s1 = new Student(18, 'Jack')
      console.log(s1)

ES6一样,子类构造函数必须加上super()执行父类的构造constructor函数

所以,大家常常说,一个子类实例,同时也是父类的实例

类的修饰符

TypeScript 中有三类访问修饰符,分别是: publicprivateprotected不写默认为 public

  • public :自己、自己的子类 和其他类都可以访问 (默认值)
  • protected :受保护的 自己和自己的子类能访问, 其他类不能访问
  • private :私有的 只能自己访问,自己的子类不能访问,其他类更不能访问
// 定义一个类
class Person {
    // name:string
    // private name:string
    protected name:string

    constructor(name:string){
        this.name = name
    }
    public eat(){
        console.log(`${this.name},好吃`)   
    }

}

// 定义一个子类
class Student extends Person {
    constructor(name:string){
        super(name)
    }
    play(){
        // 当name为protected时,子类中也可访问;
        // 但如果其为private,子类中则无法访问
        console.log('like play',this.name)  
    }
}

const p1 = new Person('张三')
// console.log(per.name)   // name 为私有,外部无法访问
per.eat()

定义接口

接口的作用类似于抽象类,不同