十年未睡,呕心沥血写出来的TypeScript心得

const g: undefined = undefined

/**

  • 只能存放Symbol()类型的值

  • 直接使用时会报错,欲知详情如何,且听下回分解~

  • */

// const h: symbol = Symbol()




### []( )标准库声明



*   内置对象类型

*   上边我们尝试使用全局的Symbol函数,创建一个symbol会报错

*   为什么?

    *   Symbol实际上时JS的一个内置的标准对象,就像Array,Object性质是相同的,只不过Symbol是ES6新增的,对于这种内置的对象,其实它自身也是有类型的,而且这些内置的类型都在TS当中已经帮我们定义好了

*   解决:

    *   将target修改为es2015即可

    *   lib选项去指定我们引用的标准库

        *   这时打开01文件内部的console会报错

            *   console对象在浏览器中是BOM对象提供的,而我们刚刚所设置的lib当中只设置了ES2015,所以所有的标准库都被覆盖掉了,这里需要将所有默认的标准库再添加回来

            *   TS中将BOM/DOM都归为一个DOM标准库当中

*   标准库

    *   内置对象所对应的声明文件

    *   在代码中使用内置对象,就必须要引用对应的标准库,否则TS找不到对应的类型,就会报错



### []( )中文错误消息



*   TS是支持多语言化的错误消息,默认会根据操作系统和开发工具的语言设置选择一个错误消息的语言

*   在使用tsc命令时加上一个 --locale zh-CN



yarn tsc --locale zh-CN || tsc --locale zh-CN




### []( )作用域问题



*   我们在学习TS的过程当中,肯定会涉及到在不同的文件当中去尝试TS的一些不同的特性,这种情况下可能会遇到不同文件当中有相同变量名称的情况



// const a = 123

/**

  • const a: 123

  • 无法重新声明块范围变量“a”。ts(2451)

  • 02-原始数据类型.ts(2, 7): 此处也声明了 “a”。

  • */




*   解决

    

    1.  自调用函数

    

    ```

    (function () {

        const a = 123

    })()

    

    ```

    

    2.  使用export导出,也就是使用ESmodules,这样可以将文件作为一个文件模块,模块是有单独的模块作用域的

    

    ```

    const a =123

    

    export {}

    

    ```

    

*   这种问题在实际开发时一般不会用到,因为在绝大多数中,我们每个文件都会以一个模块来工作,只不过我们后边每一个案例,里边难免会用到一些重复的变量



### []( )Object类型



*   TS中的Object并不单指对象类型,而是泛指所有的非原始类型

    *   对象,数组,函数

*   在TS中限制对象类型,我们应该使用更为专业的接口,接口我们会有专门的介绍

*   这里我们只需要知道两点

    1.  object类型他并不单指对象,而是除了原始类型之外的其他类型

    2.  对象的类型限制,我们可以使用这种类似对象字面量的语法方式,但是更专业的方式时使用接口



export {} //仅是为了作用域,实际开发不需要

const foo: object = function () {} // {} [] function(){}

/**

  • 如果我们需要一个普通的对象类型,我们要使用类似对象字面量的语法去标记

  • 限制对象obj必须要有一个foo的属性,属性值必须为number,多个成员时逗号间隔继续添加

  • 对象类型限制的要求:赋值的对象结构,必须要和我们这里类型的结构完全一致,不能多也不能少

  • 也就是说如果多出来一个属性,语法上就会报出类型错误

  • */

const obj: { foo: number, bar: string } = {foo: 123, bar:‘TS’}




### []( )数组类型



*   TS当中定义数组的方式和Flow的方式完全一致,他也有两种方式

    

    1.  使用Array泛型

    2.  使用元素类型 + \[\]

    

    ```

    // 纯数字组成的数组

    const arr1: Array<number> = [1, 2, 3]

    const arr2: number[] = [1, 2, 3]

    

    ```

    

*   在TS中使用强类型的优势



// function sum ( …args ) {

// return args.reduce((prev, current) => prev + current, 0)

// }

/**

  • 如果是JS,我们就需要担心sum接收到的参数类型

  • 很多时候我们需要加上一大堆判断,确保每一个参数都是number

  • 但在TS中就只需要给这个类型去添加一个 数字数组的类型注解

  • */

function sum ( …args: number[] ) {

return args.reduce((prev, current) => prev + current, 0)

}

// sum(1, 2, ‘3’) // 传入错误值就会报错




### []( )元组类型



*   特殊的数据结构

    *   元组 — 明确元素数量以及每一个元素类型的一个数组

    *   各个元素的元素类型没必要完全相同,在TS当中可以使用类似数组字面量的语法去定义元组类型

*   一般会用来在一个函数中返回多个返回值

    *   React当中的useState这个hook当中,我们返回的就是一个元组类型

    *   ES2017当中的Object.entries()这个方法获取一个对象当中所有的键值数组,得到的每一个键值就是一个元组,因为他是一个固定长度的



export {}

const tuple: [number, string] = [1, ‘hhh’]

// 只能存放一个number和字符串,不能多也不能少

// 可以使用数组下标的方式访问元组的元素

// const age = tuple[0]

// const name = tuple[1]

// 数组结构也可以提取到每一个元素

const [age, name] = tuple

Object.entries({

foo: 123,

bar: 456

})




### []( )枚举类型



*   我们在应用开发过程中,经常会用到某几个数值,去代表某种状态

*   特点

    *   可以给一组数值分别取上一个更好理解的名字

    *   一个枚举值,只会存在几个固定的值,不会出现超出范围的可能性

*   很多传统的编程语言当中,枚举是一个很常见的数据结构,但在JS中并没有这种数据结构

*   很多时候我们都需要使用一个对象模拟实现枚举



const PostStatus = {

Draft: 0,

Unpublished: 1,

Published: 2

}

const post = {

title : 'hello TS',

content: 'TS is a typed superset of JavaScript',

status: PostStatus.Draft // 2 // 1 // 0

}




*   在TS中用一个专门的枚举类型我们可以使用enum这个关键词去声明一个枚举



enum PostStatus {

Draft = 0,

Unpublished = 1,

Published = 2

}

const post = {

title : 'hello TS',

content: 'TS is a typed superset of JavaScript',

status: PostStatus.Draft // 2 // 1 // 0

}




*   注意点

    

    *   这里使用的是 赋值号 而不是 冒号

    *   枚举类型的只可以不用去使用赋值号指定,不指定的话会默认从0开始累加,

    *   如果给枚举当中第一个成员指定了具体的值,会变得值会在这个基础之上累加

    *   枚举的值可以是number之外,也可以是string,也就是字符串枚举

        *   由于字符串无法像数字一样自增,所以如果是字符串枚举的话,需要我们手动给每一个成员初始化一个明确的初始化的值

        *   字符串枚举并不常见

    

    ```

    // enum PostStatus {

    //     Draft,

    //     Unpublished,

    //     Published

    // }

    

    // enum PostStatus {

    //     Draft = 6,

    //     Unpublished,

    //     Published

    // }

    

    enum PostStatus {

        Draft = 'a',

        Unpublished = 'b',

        Published = 'c'

    }

    

    enum PostStatus {

        Draft,

        Unpublished,

        Published

    }

    

    const post = {

        title : 'hello TS',

        content: 'TS is a typed superset of JavaScript',

        status: PostStatus.Draft // 2 // 1 // 0

    }

    

    ```

    

*   关于枚举我们还需要了解一点

    *   枚举类型会入侵到我们运行时的代码

    *   换句话说就是:他会影响我们编译后的结果

    *   我们在TS中使用的大多数类型他在经过编译转换过后最终都会被移除掉,但是枚举不会,最终会编译为双向的键值对对象

        *   双向键值对 — 通过键可以获取值,也可以通过值再去获取键

    *   目的是

        *   可以动态的根据枚举值,去获取枚举的名称,也就是说在代码当中可以通过索引器的方式去获取对应的名称

    *   如果确定在代码中不会使用索引器的方式去访问枚举,那我们建议大家使用常量枚举

        *   常量枚举的用法就是在enum这个关键词前加上一个const



### []( )函数类型



*   对函数的输入输出进行限制,也就是参数与返回值

*   在JS中有两种函数定义的方式

    *   函数声明

    *   函数表达式

*   这里需要了解两种方式下函数如何进行类型的约束



export {}

// function fnuc (a: number, b: number): string {

// return ‘func’

// }

// 参数限制为number,返回值限制为string;形参与实参必须完全一致,从类型到数量

// fnuc(1, 2)

// 可选参数

// function fnuc (a: number, b?: number): string {

// return ‘func’

// }

// fnuc(1)

// 或者使用参数默认值,也可以让其成为可选参数

// function fnuc (a: number, b: number = 100): string {

// return ‘func’

// }

// fnuc(1)

// 不管可选参数还是默认参数,都要出现在参数列表的最后

// 参数会按照位置进行传递,如果可选参数出在了必选参数的前边,那必选参数就没法拿到正确的参数

// 任意参数

function fnuc (a: number, b: number = 100, …rest: number[]): string {

return 'func'

}

fnuc(1)

// 函数表达式

const func1 = function (a: number, b: number): string {

return 'func1'

}

/**

  • 这个函数表达式最终会放到一个变量当中的,接受这个函数的变量也应该是有类型的

  • 一般TS可以根据我们这个函数表达式推断出来我们变量的类型

  • 如果我们是把一个函数作为参数传递,也就是作为回调函数这种方式

  • 作为回调函数这种情况下,我们就必须要去约束这个回调函数这个参数的类型

  • 这种情况就可以使用类似箭头函数的方式去表示我们的参数可以接受什么样一个函数

  • 这种方式在定义接口时会经常用到

  • */




### []( )任意类型



*   由于JS自身时弱类型的关系,很多内置的API本身就支持接收任意类型的参数

*   而TS又是基于JS之上的,所以我们难免会在代码中需要用一个变量去接收任意类型的数据

*   注意:

    *   any类型仍然属于动态类型,特点和普通的JS变量是一样的,可以接受任意类型的数值,而且在运行过程当中还可以接受其它类型的值

*   因为他有可能会存放任意类型的值,所以说TS他不会对any这种类型做类型检查,这也就意味着,我们仍然可以像在JS一样在他上调用任意的成员,语法上都不会报错

*   但他也还是会存在类型安全的问题,所以我们轻易不要使用这种类型



function string (value: any) {

return JSON.stringify(value)

}

string(‘string’)

string(100)




### []( )隐式类型推断



*   在TS中,如果我们没有通过明确的通过类型注解去标记一个类型,那TS会根据这个变量的使用情况去推断这个变量的类型 — 隐式类型推断



export {}

let age = 18// TS会判断为number类型

// age = ‘tri’ // 报错原因:age已经被判断为number

// 这种用法相当于给age添加了 number的类型注解

// 无法推断时会将它标记为any,如:

let foo

foo = 18;

foo = ‘string’

foo = true




*   虽然TS中支持隐式类型判断,而且这种隐式类型推断可以帮我们简化一部分代码,但是我们仍然建议大家尽可能给每一个变量添加明确的类型,这样便于我们后期更直观的理解我们的代码



### []( )类型断言



*   在一些特殊形况下TS无法推断出变量的具体类型,而我们作为开发者我们根据代码的使用情况,我们是可以明确知道这个变量到底是什么类型的



// 假定来自一个明确的接口

const nums = [110, 120, 119, 112]

const res = nums.find(i => i > 0)

/**

  • 我们这个时候是明确知道这个返回值一定是一个number的

  • 但是TS推断为了number或者undefined

  • 那这个返回值就不能作为一个number来使用

  • 这时我们可以去断言这个res为一个number类型的

  • */




*   断言的方式

    

    1.  使用as关键词

    2.  使用<>

    

    ```

    const num1 = res as number

    /**

    * 此时编译器能明确知道我们的num1是一个number

    * */ 

    

    const num2 = <number>res

    /**

    * 两者效果一致,但是这种尖括号的方式她有个小问题

    * 

    * 当我们在代码当中使用了JSX的时候

    * 

    * 这里的<number>会和JSX中的标签产生语法冲突

    * 

    * 推荐使用as的方式

    * */  

    

    ```

    

*   注意:

    *   类型断言并不是类型转换,这里并不是把一个类型转换为了另外一个类型,因为类型转换是代码在运行时的一个概念,我们这个地方类型的断言只是在编译过程中的一个概念,当代码编译过后,断言也就不会存在了



### []( )接口



*   可以理解为一种规范或者契约,是一种抽象的概念,可以约定一种对象的结构,我们去使用一个接口就必须要遵循这个接口的全部的约定

*   在TS当中接口最直观的体现就是可以约定一个对象当中具体应该有哪些成员,这些成员的类型又是什么样的



export {}

interface Post {

/**

 * 这里可以使用逗号分隔,但是标准是用分号,分号也可以省略

 * */ 

title: string;

content: string

}

function pointPost (post: Post) {

console.log(post.title)

console.log(post.content)

}

/**

  • 这个函数对对象有一些要求,必须要title属性和content属性

  • 只不过这种要求实际上是隐形的,没有明确的表达

  • 这种情况就可以使用接口表现出来约束

  • */

pointPost({

'title': 'hi, TS',

'content': 'hi'

})




*   一句话总结:接口就是用来约束对象的结构,一个对象去实现一个接口,他就必须要拥有这个接口当中所有的成员



### []( )接口补充



*   对于接口中约定的成员还有一些特殊的用法

    *   可选成员

        

        *   如果说我们在一个对象当中,我们某一个成员他是可有可无的,我们对于约束这个对象的接口,我们可以使用可选成员这样一个特性

        *   这种用法其实就相当于对其类型标记了一个指定类型或者是undefined

        

        ```

        interface Post {

            title: string;

            content: string;

            subtitle?: string

        }

        

        const hello:Post = {

            'title': 'hi, TS',

            'content': 'hi'

        }

        

        ```

        

    *   只读成员

        

        *   初始化过后就不可再次修改

        

        ```

        interface Post {

            title: string;

            content: string;

            subtitle?: string;

            readonly summary: string

        }

        

        const hello:Post = {

            'title': 'hi, TS',

            'content': 'hi',

            'summary':'初始化后不可修改'

        }

        // hello.summary = 'hhhhhhhh'  // 报错

        

        ```

        

    *   动态成员

        

        *   一般适用于一些具有动态成员的对象,例如程序中的缓存对象,他在运行当中就会出现一些动态的键值

        

        ```

        interface Cache {

            [key: string]: string

            // key是不固定的,string是key的类型注释,外边的string是设置给这个动态属性的值

        }

        

        const cache: Cache = {}

        

        cache.foo = 'value'

        cache.foo = '123'

        

        ```

        



### []( )类的基本使用



*   Classes

*   描述一类具体事务的抽象特征

*   例如手机就是一个类,特征就是可以接打电话,收发短信,在这个类型下,还会有一些细分的子类,这种子类她一定会满足父类的所有特征,然后多出一些额外的特征,例如智能手机,除了接打电话收发短信,还可以使用一些APP

*   我们是不能直接使用这个类的,而是使用这个类的具体事务,比如你手上的智能手机

*   类也是一样的,它可以用来描述一类具体对象的抽象成员,在ES6以前,JS都是通过 函数+原型 模拟实现的类,ES6之后JS就有了专门的class

*   在TS中,除了可以使用所有ES标准当中所用类的功能,还添加了一些额外的功能与用法,例如对类成员有特殊的访问修饰符,还有一些抽象类的概念



class Person {

/**

 * 在TS中,类的属性必须要有一个初始值可以在 = 后边赋值

 * 

 * 也可以在构造函数中初始化

 * 

 * 两者必须有其中一个

 * */ 

name: string // = '初始值'

age: number // = 123

constructor (name: string, age: number) {

    /**

     * 我们在TS中我们需要明确在类型当中去声明他所拥有的一些属性

     * 

     * 而不是直接在构造函数当中动态通过this添加

     * */ 

    this.name = name

    this.age = age

}

}




*   类的属性在使用之前,必须要先在类型中去声明,目的其实就是为了给我们的属性去做一些类型的标注,除此之外,我们可以按照ES6当中的语法为这个类去声明一些方法



### []( )类的访问修饰符



*   private — 标注为私有属性,只能在类的内部访问到



export {}

class Person {

name: string

private age: number // 标注为私有属性,只能在类的内部访问到



constructor (name: string, age: number) {

    this.name = name

    this.age = age

}

}

const tom = new Person(‘tom’, 18)

console.log(tom.name)

// console.log(tom.age) // 报错




*   public — 标注为公有成员,TS中为默认!



export {}

class Person {

...

private age: number // 标注为私有属性,只能在类的内部访问到

...

}

const tom = new Person(‘tom’, 18)

// console.log(tom.age) // 报错




*   protected — 受保护的,外部访问不到



export {}

class Person {

...

protected gender: boolean

...

}

const tom = new Person(‘tom’, 18)

console.log(tom.gender) // 报错,不能在外部被访问到




*   private 与 protected 区别

    

    *   protected只允许在此类当中去访问对应的成员

    

    ```

    export {}

    

    class Person {

        public name: string // 标注为公有成员,TS中为默认!

        private age: number // 标注为私有属性,只能在类的内部访问到

        protected gender: boolean

    

        constructor (name: string, age: number) {

            this.name = name

            this.age = age

            this.gender = true

        }

    }

    

    class Student extends Person {

        constructor(naem: string, age: number) {

            super(name, age)

            console.log(this.gender)    // 可以访问到

        }

    }

    

    ```

    

*   作用:

    

    *   控制一些类当中成员的可访问级别

*   注意点:

    

    *   对于构造函数的访问修饰符默认是public,如果设置为private,这个类型就不能在外部被实例化,也不能被继承,这种情况我们就只能在这个类的内部去添加一个静态方法,然后在静态方法中去创建这个类型的实例,因为private只允许在内部去访问

    

    ```

    class Student extends Person {

        private constructor(naem: string, age: number) {

            super(name, age)

            console.log(this.gender)    // 可以访问到

        }

    

        static create (name: string, age: number) {

            return new Student(name, age)

        }

    }

    // const jack = new Student()

    const jacked = Student.create('jack', 18)

    

    ```

    *   如果把构造函数当中的类型标记为protected,这样一个类型也是不能够在外部被实例化的,但是相比于private,他是**允许被继承的**



### []( )类的只读属性



export {}

class Person {

public name: string 

private age: number 

protected readonly gender: boolean



constructor (name: string, age: number) {

    this.name = name

    this.age = age

    this.gender = true

}



sayHi (msg: string): void {

    console.log(`我是${this.name},${msg}`)

}

}

const tom = new Person(‘tom’, 18)

console.log(tom.name)

tom.gender = false




### []( )类与接口



*   相比于类,接口的概念要更抽象一点

*   我们接着拿之前手机的例子来去作对比,那我们说手机是一个类型,这个类型的实例都是可以接打电话收发短信,因为手机这个类的特征就是这样,但是我们能够打电话的不仅只有手机,之前还有座机也可以打电话,但是座机并不属于手机这个类目,而是个单独的类目,因为他不能收发短信,在这种情况下,就会出现不同的类与类之间出现共同的特征,对于这些公共的特征,我们一般会使用接口去抽象,手机可以理解为,手机也能接打电话,因为它实现了这个协议,座机也可以打电话,因为他也实现了这个协议,这里说的协议,**程序当中就叫做接口**



export {}

interface Eat {

eat (food: string): void

}

interface Run {

run (distance: number): void

}

class Person implements Eat, Run {

eat (food: string): void {

    console.log(`优雅的进餐:${food}`)

}

run (distance: number): void {

    console.log(`直立行走:${distance}`)

}

}

class Animal implements Eat, Run {

eat (food: string): void {

    console.log(`呼噜呼噜的吃:${food}`)

}

run (distance: number): void {

    console.log(`爬行:${distance}`)

}

}




### []( )抽象类



*   某种程度上和接口有点类似,它也可以用来去约束子类当中必须要有某一个成员

*   抽象类它可以包含一些具体的实现,而接口只能是成员的一个抽象,不包含具体的实现

*   一般比较大的类目比较建议使用抽象类,例如刚才的动物类,其实他就应该是抽象的,因为我们所说的动物只是一个泛指,并不够具体,在他的下边一定会有一些更细化的分类



abstract class Animal {

eat (food: string): void {

    console.log(`呼噜呼噜的吃:${food}`)

}



abstract run (distance: number): void

}

// 被定义为抽象类过后,他就只能够被继承,不能够再使用new的方式去实例

class Dog extends Animal {

run (distance: number): void {

    console.log('四角爬行',distance)

}

}

const d = new Dog()

d.eat(‘狗’)

d.run(100)




### []( )泛型



*   定义函数,接口,或者类的时候,我们没有去指定具体的类型,等到我们使用的时候,再去指定具体的类型的一个特征

*   以函数为例,泛型就是我们在声明这个函数时,不去指定具体的类型,等到调用时传递一个具体的类型,目的是为了最大程度的复用我们的代码



export {}

// 创建一个指定长度的数组

// function createNumberArray (length: number, value: number): number[] {

// const arr = Array(length).fill(value)

// return arr

// }

// const res = createNumberArray(3, 100)

function createArray (length: number, value: T): T[] {

const arr = Array<T>(length).fill(value)

return arr

}

const res = createArray(3, 100)

const res1 = createArray(3, ‘100’)




*   总结:

    *   泛型其实就是把我们定义时不能够明确的类型,改为一个参数,让我们在使用的时候再去传递



### []( )类型声明



*   实际的项目开发中,我们肯定会用到一些第三方的npm模块,而这些模块并不是一定通过TS编写的,所以它所提供的成员就不会有强类型的体验

*   说白了就是一个成员在定义的时候因为某种原因导致没有明确的类型,然后我们使用时再为他单独做出一个明确的声明,这种用法存在的原因就是为了考虑兼容一些普通的JS模块,由于TS的社区非常的强大,目前绝大多数常用的npm模块都已经提供了对应的声明,我们只需要安装一下它所对应的类型声明模块即可

*   如果没有对应的声明,我们就只能通过declare语句去声明所对应的模块类型



import {camelCase} from ‘lodash’

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
调用时传递一个具体的类型,目的是为了最大程度的复用我们的代码


export {}



// 创建一个指定长度的数组

// function createNumberArray (length: number, value: number): number[] {

//     const arr = Array<number>(length).fill(value)

//     return arr   

// }



// const res = createNumberArray(3, 100)



function createArray<T> (length: number, value: T): T[] {

    const arr = Array<T>(length).fill(value)

    return arr

}



const res = createArray<number>(3, 100)

const res1 = createArray<string>(3, '100')



  • 总结:

    • 泛型其实就是把我们定义时不能够明确的类型,改为一个参数,让我们在使用的时候再去传递

类型声明

  • 实际的项目开发中,我们肯定会用到一些第三方的npm模块,而这些模块并不是一定通过TS编写的,所以它所提供的成员就不会有强类型的体验

  • 说白了就是一个成员在定义的时候因为某种原因导致没有明确的类型,然后我们使用时再为他单独做出一个明确的声明,这种用法存在的原因就是为了考虑兼容一些普通的JS模块,由于TS的社区非常的强大,目前绝大多数常用的npm模块都已经提供了对应的声明,我们只需要安装一下它所对应的类型声明模块即可

  • 如果没有对应的声明,我们就只能通过declare语句去声明所对应的模块类型


import {camelCase} from 'lodash' 
#  最后

**自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。**

**深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。**

**因此收集整理了一份《2024年Web前端开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。**

[外链图片转存中...(img-bbhj0ZlK-1715137117699)]

[外链图片转存中...(img-Wa965Yel-1715137117699)]

[外链图片转存中...(img-kOfrNYMY-1715137117699)]

**既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!**

[**如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!**](https://bbs.csdn.net/topics/618191877)

**由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!**
  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值