Fork me on GitHub

JavaScript算法套路之二叉树的层序遍历

二叉树的层序遍历,也就是二叉树的广度月优先遍历,层序遍历,很好理解,就是一层一层的遍历。想要实现二叉树的层序遍历,还需要借助队列。队列的先进先出,符合一层一层的遍历。

1
2
3
4
5
6
7
8
9
10
11
12
        1
/ \
2 3
/ \ / \
4 5 6 7

// 遍历得到
[
[1],
[2, 3]
[4, 5, 6, 7]
]

相对来说,其实层序遍历比较简单,话不多说。我们直接看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let levelOrder = (root) => {
let result = [];
let queue = [];
if (!root) return result;
queue.push(root);
while (queue.length) {
let list = [];
for (let i = 0; i < queue.length;i++) {
let cur = queue.shift();
list.push(cur.val);
cur.left && queue.push(cur.left);
cur.right && queue.push(cur.right);
}
result.push(list);
}
return result;
}

是不是很简单呢?

JavaScript算法套路之二叉树的前、中、后序迭代遍历

递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。所以。我们用栈当然就能实现同样的遍历了。

栈的特点是先进性后出,所以在遍历二叉树的时候,要反过来遍历,比如前序遍历,正常情况下,应该是中,左,右,那么我们的遍历顺序就应该是中,右,左,出栈的时候正好是中,左,右。下面我们来实现以下前序遍历:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/

const preorderTraversal = (root) => {
const result = [];
// 判断,如果没有root了,就返回结果
if (!root) return result;
// 把中间节点放入道栈中
const stack = [root];
let cur = null;
while (stack.length) {
cur = stack.pop();
result.push(cur.val);
cur.right && stack.push(cur.val);
cur.left && stack.push(cur.val);
}
return result;
};

这就是迭代方法的前序遍历,那既然能写出来前序遍历,那二叉树的后续遍历是不是也就可以自然而然的写出来了呢?与前序遍历不同的是:先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/

const backOrder = (root) => {
let result = [];
if (!root) return result;
let stack = [root];
let cur = null;
do {
cur = stack.pop();
result.push(cur.val);
cur.left && stack.push(cur.val);
cur.right && stack.push(cur.val);
} while (stack.length)
return result.reverse();
};

这个时候,你会不会觉得中序遍历和前序、后续遍历也很相似呢?其实不然,中序遍历的顺序是左中右,要先遍历二叉树的顶部节点,然后再遍历一层一层的向下访问,直到左枝叶的最底部,再开始处理节点。那么这个时候,就需要使用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。来看代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/

const midOrder = (root) => {
let stack = [];
let result = [];
let cur = root;
while (stack.length || cur) {
if (cur) {
stack.push(cur);
cur = cur.left;
} else {
stack.pop();
result.push(cur.val);
cur = cur.right;
}
}
return result;
};

写了这么多的代码,好像有点乱,那就二叉树的前、中后序迭代遍历能不能总结个套路呢?当然没问题的啦。。。我们怎么做呢?看:我们将要处理的节点放入到栈中,紧接着放入一个空指针作为标记。上代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/

// 前序遍历
const preOrder = (root) => {
let result = [];
let stack = [];
if (root) {
stack.push(root);
}
while (stack.length) {
let cur = stack.pop();
if (!cur) {
result.push(stack.pop().val);
continue;
}
if (cur.right) stack.push(cur.right);
if (cur.left) stack.push(cur.left)
stack.push(cur);
stack.push(null);
}
return result;
}

// 中序遍历
const midOrder = (root) => {
let result = [];
let stack = []
if (root) stack.push(root);
while (stack.length) {
let cur = stack.pop();
if (!cur) {
result.push(stack.pop());
continue;
}
if (cur.right) stack.push(cur.right);
stack.push(cur);
stack.push(null);
if (cur.left) stack.push(cur.left);
}
return result;
}

// 后序遍历
let backOrder = (root) => {
let result = [];
let stack = [];
if (root) stack.push(root);
while (stack.length) {
let cur = stack.pop();
if (!cur) {
result.push(stack.pop());
continue;
}
stack.push(cur);
stack.push(null);
if (cur.right) stack.push(cur.right);
if (cur.left) stack.push(cur.left);
}
return result;
}

理解很重要。。。。

JavaScript算法套路之二叉树的前、中、后序递归遍历

Leetcode算法刷了那么多,是不是过了没多久就全忘记了,再遇到类似的题目,只能隐隐约约的记得曾经刷过,但依旧毫无思路。对,这是绝大多数人的通病,当然我也不例外,所以我潜心研究这到底是为什么,到底是为什么。终于找到了终结所在–光刷题,只是做了题,根本没有明白其内在原理,根本没有总结过做算法的套路。那么我就从二叉树的遍历开始,来总结一下相应的算法套路。

二叉树预备知识

1
2
3
4
5
     1
/ \
2 5
/ \ \
3 4 6

数据结构的节点,最多有俩个子节点,就是二叉树。我是前端,那么前端的朋友一定会说,前端没有二叉树这种数据结构,如何表示呢?用数组,对,就是数组来表示,应该是这样的:

1
const tree = [1, 2, null, null, 5, 3, 4, null, 6];

二叉树的遍历

二叉树的遍历由深度优先和广度优先之分,那我们今天就先来说说深度优先,即:

  • 前序遍历
  • 中序遍历
  • 后续遍历

那么你一定想知道这三种遍历有什么区别呢?对,共同点,都是从左枝叶开始遍历,那区别就是遍历中间节点点的位置在哪儿,我们来看下上面二叉树的遍历顺序:

1
2
3
4
5
6
7
8
//  前序遍历
123456

// 中序遍历
324156

// 后序遍历
342651

所以我们就明白了。

总结套路

标题我们已经写过了,我们是用递归的方法来实现二叉树的遍历。那么我们要总结一下,如果写程序:

  • 要确定参数是什么
    那二叉树的遍历,无论是前序遍历、中序遍历还是后续遍历,我们需要有树,要有节点,那么我们的参数就是节点喽。。。
  • 终止条件是什么
    递归遍历,我们必须先想好终止的条件是什么。要不然就会造成栈溢出的情况对吧。那么对于二叉树来说,只要没有节点了,我们就改终止遍历了对吧,所以终止条件就是节点不存在
  • 单层递归的逻辑
    我们说了,二叉树前、中、后的遍历,是看根节点遍历的位置,所以这就是逻辑,对吧,是不是很简单。。。

代码实现

我们就照着以上总结的逻辑来用Javascript实现吧:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/**
* Definition for a binary tree node.
* function TreeNode(val, left, right) {
* this.val = (val===undefined ? 0 : val)
* this.left = (left===undefined ? null : left)
* this.right = (right===undefined ? null : right)
* }
*/
/**
* @param {TreeNode} root
* @return {number[]}
*/

// 前序遍历
var preOrderTraversal = function(root) {
let result = [];
function order (node) {
if (!node) return;
result.push(node.val);
order(node.left);
order(node.right);
}
order(root);
return result;
};

// 中序遍历
var midOrderTraversal = function(root) {
let result = [];
function order (node) {
if (!node) return;
order(node.left);
result.push(node.val);
order(node.right);
}
order(root);
return result;
};

// 后续遍历
var backOrderTraversal = function(root) {
let result = [];
function order (node) {
if (!node) return;
order(node.left);
order(node.right);
result.push(node.val);
}
order(root);
return result;
};

是不是就发现很简答了,所以不要慌,要总结,才能学会。。。

JavaScript算法套路之二叉树理论基础

学习算法,数据结构必须了解。据说二叉树是在学习算法路上的第一道要突破的屏障。那么就来总结一下二叉的一些简单理论:

二叉树的定义

二叉树是树的一种,二叉树最多只能有俩个节点,分为左孩子和右孩子。

二叉树的分类

  • 满二叉树
  • 完全二叉树
  • 二叉搜索树
  • 平衡二叉搜索树

    等等,种类很多,这里就不一一细数。

二叉树的存储方式

二叉树的存储方式,可以是链式存储,也可以是顺序存储。

链式存储

链式存储就是用指针,就是类似于链表,但是二叉树会比链表多一个指针,树有俩个指针,左和右,分别指向自己的左孩子和右孩子。

顺序存储

顺序存储就是用数组的方式来存储二叉树。一层一层的存储。

1
2
3
4
5
     1
/ \
2 5
/ \ \
3 4 6

用数组的方式存储就是:

1
const tree = [1, 2, 5, 3, 4, null, 6];

二叉树的遍历

二叉树的遍历方式有俩种:

  1. 深度优先:先往深了走,遇到叶子节点再往回走(中间节点的遍历顺序
    • 前序遍历
    • 中序遍历
    • 后续遍历
      实现深度优先的遍历,我们一般都是通过递归的方式实现的。
  2. 广度优先:一层一层的遍历
    • 层序遍历
      实现广度优先的遍历一般都是通过队列的方式实现的,因为队列先进先出的特点能一层一层的遍历二叉树。
      接下来,就是我们如何实现了。。。

JavaScript创建二叉查找树

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
function TreeNode (data, left, right) {
this.val = data;
this.left = left;
this.right = right;
}

function BST() {
// 保存二叉树的节点
this.root = null;
this.insert = insert;
}
function insert(data) {
let node = new TreeNode(data, null, null);
if (!this.root) {
this.root = node;
} else {
let current = this.root;
let parent = null;
while (true) {
parent = current
if (data < current.val) {
current = current.left;
if (current === null) {
parent.left = node;
break;
}
} else {
current = current.right;
if (current === null) {
parent.right = node;
break;
}
}
}
}
}

Typescript--装饰器

装饰器定义

装饰器是一种特殊类型的声明,它能够被附加到类声明,方法,属性或者参数上,可以修改类的行为。通俗讲,装饰器就是一个方法,可以注入到类,方法,属性参数上来扩展类,属性,方法,参数的功能。
常见的装饰器有:类装饰器,属性装饰器,方法装饰器,参数装饰器。
装饰器的写法:普通装饰器(无法传参),装饰器工厂(可传参)
装饰器是过去几年中js的最大成就之一,已是es7的标准特性之一

类装饰器
  • 类装饰器:类装饰器在类声明之前就被声明(紧靠着类声明)。类装饰器应用于类构造函数,可以用来监视,修改或者替换类定义,传入一个参数。

  • 普通装饰器(不带参数)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//  装饰器是一个普通函数,其参数就是类,可以在装饰器中对类的属性和方法等进行扩展
function logClass (param: any) {
console.log(param)
param.prototype.run = function () {
console.log('世界那么大,我想去看看')
}
}

@logClass
class Log {
contructor () {

}
}

let log = new Log()
log.run() // 世界那么大,我想去看看
  • 类装饰器(装饰工厂)—-可传参
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    function logClass(param: string) {
    return function (target: any) {
    // target是当前的类
    // param是装饰器接受的参数
    console.log(param) // 世界那么大
    }
    }
    @logClass('世界那么大')
    class Log {
    contructor () {}
    }
    var log = new Log()
    装饰器可以重栽构造函数:类装饰器表达式会在运行时当作函数被调用,类的构造函数作为唯一的参数。如果类装饰器返回一个值,它会使用提供的构造函数来替换类的声明。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function logClass (target: any) {
    // 重载构造函数
    return class extends target {

    }
    }
    @logClass
    class HttpClient {
    public apiUrl: string | undefined;
    constructor () {

    }
    }
    属性装饰器
    属性装饰器表达式会在运行时当作函数被调用,传入下列2个参数:
  • 对于静态成员来说是类的构造函数,对于实例成员是类的原型对象;
  • 成员的名字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function logClass (target: any) {
// 重载构造函数
return class extends target {

}
}
function logProperty (params: string) {
return function (target:any, attr: any) {
target[arr] = param
// target是类的原型对象
// param是装饰器的参数
}
}
@logClass
class HttpClient {
@logProperty('xxxxxx')
public apiUrl: string | undefined;
constructor () {

}
}
方法装饰器

他会被应用到方法的属性描述符上,可以用来监视,修改或者替换方法的定义。
方法装饰器会在运行时,传入三个参数:

  • 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • 成员的名字
  • 成员属性的描述符
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    function get(params: any) {
    return function (target:any,methodName, desc:any) {
    var oMethod = desc.value
    desc.value = function (...agrs:any[]) {
    args = args.map(value => {
    return String(value)
    })
    oMethod.apply(this, args)
    }
    }
    }
    class HttpClient {
    public url: string | undefined;
    constructor () {

    }
    @get("http://www.baidu.com")
    getData(...args:any[]) {
    console.log(args)
    }
    }
    var http = new HttpClient()
    http.getData(1222, '1122')
    方法参数装饰器
    参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型增加一些元素数据,传入三个参数:
  • 对于静态成员来说是类的构造函数,对于实例成员来说是类的原型对象
  • 参数的名字
  • 参数在函数参数列表中的索引
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    function logParams (params: any) {
    return function(target:any,paramsName:any) {
    target.url = params
    }
    }
    class HttpClient {
    public url: any | undefined
    constructor () {}
    getData(@logParams('uuid') uuid:any ) {
    console.log('ddddd')
    }
    }
    var http = new HttpClient()
    http.getData(1212132)

装饰器的执行顺序

属性装饰器 > 方法装饰器 > 方法参数装饰器 > 类装饰器
如果有多个同样的装饰器,那么会从下向上之行

Typescript--模块和命名空间

模块的概念

模块的概念:关于术语的一点说明。请务必注意一点,typescript1.5里术语名已经发生变化。内部模块现在叫做“命名空间”。外部模块现在简称为“模块”,模块在其自身的作用域里执行,而不是在全局作用域里执行。这意味着定义在一个模块里的变量,函数,类等在模块外部都是不可见的。除非能明确的使用export的形式之一导出他们,相反,如果想使用其他模块到处的变量,函数,类,接口的时候,就比如导入他们,可以用使用import的形式。

理解:
我们可以使用一些公共的功能单独抽离一个文件作为模块。
模块里的变量,函数,类,默认是私有的。如果哦我们要在外部访问模块里的数据(变量,函数,类),我们需要通过export暴露模块里的数据(变量,函数,类)。暴露后我们通过import引入模块就可以使用模块暴露出来的数据(变量,函数,类)了。

export

export default 默认导出
每个模块都可以有一个default导出,默认导出使用default关键字标记。并且一个模块只能有一个default导出,需要使用特殊形式来导出。

命名空间

在代码量较大的情况下,为了避免各种变量名相互冲突,可以将相似的函数,类,接口等放到命名空间中间。同java的包,.net的命名空间一样,typescript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象,命名空间内的对象通过export对外暴露。
命名空间和模块的区别:

  • 命名空间:内部模块,主要用于组织代码,避免命名冲突,命名空间用关键字namespace表示
  • 模块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间

Typescript--泛型的使用

泛型的定义

软件工程中,我们不仅仅要建立一致的定义良好的API,同时也要考虑重用性。组件不仅能够支持当前的数据类型,同时也能支持未来的数据类型。这在创建大型系统时提供了十分灵活的功能。

在像C#和Java这样的语言中,可以使用泛型来创建重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型使用组件。

通俗讲,泛型就是解决类接口方法的复用性,以及对不特定数据类型的支持。

泛型使用T来定义:

1
2
3
function fun<T>(name: T):T {
return name
}

定义了一个函数,函数传入的参数类型和函数的返回值类型保持一致,具体的类型是什么,由调用者决定,比如:

1
2
3
4
5
function fun<T>(name:T):T{
return name
}
fun:<string>('xiaoming') // xiaoming
fun<number>('小明') // 这是一个错误写法

注:
我们可能会想起定义函数的时候,使用any关键字,但是这里要强调的是,any关键字其实是放弃了typescript的类型校验。

泛型类

实现一个有最小堆的算法,需要同时支持返回数字和字符串俩种类型,通过类的泛型实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MainClass<T> {
public list:T[] = []
add (value: T): void {
this.list.push(value)
}
min():T{
var minNum = this.list[0]
for (var i = 0; i < this.list.length;i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
let m = MinClass<number>() // 实例化类,并且制定了类的T代表的类型是number
m.add(10)
m.add(45)
m.add(6)
m.add(2)
m.add(1)
m.add(9)
m.min() // 1
把类作为参数来约束数据传入的类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
class MysqlDb <T> {
add(info:T):boolean {
console.log(info)
return true
}
}
class User {
username: string | undefined;
password: string | undefined;

}
let user = new User()
user.name = 'xiaoming'
user.password = '123456'
var db = MysqlDb<User>() // 此时User起到了教研的作用
db.add(user)

class ArticleCate {
title: string | undefined;
desc: string | undefined;
status: number | 0;
constructor (param: {
title: string | undefined;
desc: string | undefined;
status?: number | 0;
}) {
this.title = param.title;
this.desc = param.desc;
this.status = param.status;
}
}
var a = new ArticleCate({
title: '世界那么大',
desc: '我想去看看',
status: 0
})

var mydb = new MysqlDb<ArticleCate>() // 此时ArticleCate起到了教研的作用
mydb.add(a)
泛型接口
  • 方法一
    1
    2
    3
    4
    5
    6
    7
    interface Config {
    <T>(value: T): T
    }
    var setData: Config = function<T>(value: T):T {
    return vale
    }
    setData<number>(12344)
  • 方法二
    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface Config<T> {
    (value: T): T
    }
    function getData<T>(value: T):T {
    return value
    }
    var myGetData:Config<string> = getData
    myGetData('xiaoming') // 正确
    myGetData(12345) // 错误
    That’s all!

leeCode算法--各位相加问题

题目:给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。

分析:注意这个结果,直到一位数的时候,就不在计算,那么就意味着小于等于(<=)9的时候就直接return,这是其一。其二,当大于9的时候呢?你会发现,如果是9的倍数,那就是9,否则我们就用9取余,num%9。就是这么简单,难点在于,一下子想不出这个思路。

1
2
3
4
let addDigits => (num) {
if (!num) return num
return num % 9 || 9
}

代码很简单,思路很难。。。。还得练啊。。。。

Typescript--接口的使用

接口的定义

在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范。在程序设计里,接口起到了限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心类内部的状态数据。也不关心这些类的内部方法实现的具体细节。他只关心这些类必须提供某些方法。提供这些方法的类就可以满足实际需求。typescript中的类有点类似于java,同时还增加了更加灵活的接口类型。包括属性,方法,可索引和类等。

属性接口

属性接口,就是对JSON的约束。实现一个对函数参数约束的接口。

1
2
3
function printLabel (labelInfo: {label: string}):void {
console.log(labelInfo)
}
  • 对批量方法的参数进行约束

    1
    2
    3
    4
    5
    6
    7
    8
    9
    interface FullName {
    firstName: string;
    secondName: string; // 注意必须是;结尾
    }
    function printName (name: FullName) {
    // 必须传入的对象中有firstName,secondName,且必须都是string
    }
    printName({firstName: '小明', secondName: '小红'})
    printName('小红') // 错误

    传入的参数必须要有接口中定义的字段一一对应,严格按照接口的定义传参

  • 接口的可选属性
    接口中的可选属性用?表示

    1
    2
    3
    4
    interface FullName {
    firstName: string;
    secondName?: string;
    }

    在接口FullName中,firstName是一个必传属性,但是secondName则是一个可选属性。

函数类型接口

对方法的参数以及返回值进行约束。也可以对参数进行批量约束。

  • 加密的函数类型接口
1
2
3
4
5
6
7
interface encrypt {
(key:string, value:string):string
}

var md5:encrypt = function (key:string, value:string): string {
return key + value
}

encrypt接口对md5函数实现了约束。

可索引接口

表示对数组和对象的约束。

  • 对数组的约束,可索引约束:
    1
    2
    3
    4
    interface UserArr {
    [index:number]:string
    }
    var arr:UserArr = ['111', '333']
    接口UserArr约束索引是number,而值是个string
  • 对对象的约束
    1
    2
    3
    4
    interface UserObj {
    [index:string]:string
    }
    var arr:UserObj = {name: 'xiaoming‘}
    接口UserArr约束索引是string,而值是个string
类类型接口,对类约束 和抽象类有点相似
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Animal {
name: string,
eat(str:string):void
}
class Dog implements Animal {
name: string,
constructor (name: string) {
this.name = name
}
eat (food:string) {
console.log(this.name + food)
}
}
var d = new Dag('小黑')
d.eat('粮食')
接口扩展:接口可以继承接口

接口和类一样也可以通过extends字段实现继承。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
interface Animal {
eat():void
}
interface Person extends Animal {
work():void
}
class Web implements Person {
public name: string;
constructor(name:string) {
this.name = name
}
eat() {
console.log(this.name + '喜欢吃馒头')
}
work () {
console.log(this.name + '敲代码')
}
}
let web = new Web()
web.eat('小明') // 小明喜欢吃馒头
web.work('小黑') // 小黑敲代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface Animal {
eat():void
}
interface Person extends Animal {
work():void
}
class Progromer {
public name: string;
constructor(name: string) {
this.name = name
}
coding () {
console.log(this.name + '敲打吗')
}
}
class Web extends Progromer implements Person {
constructor(name:string) {
super(name)
}
eat() {
console.log(this.name + '喜欢吃馒头')
}
work () {
console.log(this.name + '敲代码')
}
}
let web = new Web()
web.eat('小明') // 小明喜欢吃馒头
web.work('小黑') // 小黑敲代码
web.coding('小李') // 小李敲打吗

That’s interface!

Typescript--类的使用

类的定义

在ts中类的定义是通过class来定义的。

1
2
3
4
5
6
7
8
9
10
11
class Person {
name:string // 属性,省略了public字段
constructor (name:string) { // 构造函数,实例化类时,触发的方法
this.name = name
}
run ():void {
console.log(this.name)
}
}
var p = new Person('张三')
p.run() // 张三
ts中实现继承

在ts中实现继承的方式是通过extendssuper字符来实现的。父类和子类的方法一至。子类可以扩展自己的方法。如果子类和父类有同名方法的时候,调用子类方法,那么优先执行子类方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person {
name: string
constructor (name: string) {
this.name = name
}
run ():string {
return this.name
}
}
class children extends Person {
constructor (name: string) {
super(name) // 初始化父类的构造函数
}
}
类中的修饰符

在ts的类中定义属性的时候提供了三种修饰符。
public 公有属性,在类,子类,以及类外都可以被访问
protected 保护属性,在类,子类,中可以被访问,在类之外不能被访问
private 私有属性,在类中可以被访问,在子类,以及类外是不能被访问的

如果属性不加任何修饰符,那么默认是public,表示公有属性。

静态方法和静态属性

ts中静态方法和属性通过static字符来定义,并且通过类名来调用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Person {
static name:string = '小明' // 这是定义的静态属性
constructor(name:string) {
this.name = name
}
// 静态方法
static getName ():string {
return this.name
}
// 实例方法
run ():string {
return this.name
}
}
// 调用静态方法
Person.getName() // 小明
// 调用实例方法,必须先创建实例,通过实例来调用
let p = new Person('小刚')
p.run() // 小刚
继承多态

父类定义一个方法不去实现,让子类来实现,并且在子类中可以有不同的表现形式。多态也是继承的一种表现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
public name:string
constructor(name:string) {
this.name = name
}
eat ():void {
console.log(`${this.name}爱吃肉!`)
}
}

class Girl extends Person {
constructor(name:string){
super(name)
}
eat ():void {
// do something
}
}

class Boy extends Person {
constructor(name:string){
super(name)
}
eat ():void {
// do something
}
}
抽象类和抽象方法

ts中的抽象类是提供其他类继承的基类,不能直接实例化。用abstract关键字来实现抽象类和抽象方法。抽象类中的抽象方法不包含具体的实现并且必须在派生类中实现。abstruct字段只能放到抽象类中。要求子类中必须包含抽象类中的方法。

1
2
3
4
5
6
7
8
9
10
11
12
abstruct class Person {
abstruct eat():any
}
// let p = new Person() 错误写法
class Children extends Person {
constructor(name:string) {
super(name)
}
eat():any {
// do something
}
}

Typescript--函数方法

函数的定义方式

函数的定义方式有俩种,即function定义方式和匿名函数方式,但是要求指定返回值类型。

1
2
3
4
5
6
function fun1 ():number {
return 123
}
function fun2 ():void {
console.log('hello world')
}

function方式定义的的函数,并且要求返回值是一个数字。

1
2
3
4
5
6
let fun3 = function ():string {
return 'hello world'
}
let fun4 = function ():void {
console.log('hello world')
}
typescript中的传参方式
  • ts中的参数需要指定参数的类型

    1
    2
    3
    function fun (a:number):number {
    return a
    }

    要求传入的参数a是一个number类型,并且该函数返回一个number类型的返回值。

  • ts中可以用?来表示某个参数是可选参数,但是必须要放到所有参数的最后面

    1
    2
    3
    function fun (name:string, age?:number):string {
    return `我是${name},我的年龄是${age}`
    }

    我门可以看到,其中参数age为可选参数,可以不传。

  • ts中的默认参数

    1
    2
    3
    4
    5
    6
    7
    function fun (name:string, age?:number = 30): string {
    return `我叫${name},我今年${age}岁`
    }
    console.log(fun('章三'))
    // 我叫章三,我今年30岁
    console.log(fun('李四', 90))
    // 我叫李四,问今年90岁
  • ts中剩余参数,剩余参数可以用拓展符...来表示

    1
    2
    3
    4
    function fun (a:number, b:number, c:number, d:number, e:number):number {
    return a + b + c + d + e
    }
    console.log(fun(1,2,3,4,5)) // 15

    这样的参数会很繁琐。那么我们来用…缩减一下参数。

    1
    2
    3
    4
    5
    6
    7
    8
    function fun (...res:number[]):number {
    let sum = 0
    for (let i = 0;i < res.length - 1;i++) {
    sum += res[i]
    }
    return sum
    }
    console.log(fun(1,2,3,4,5)) // 15
    函数的重载

    为同一个函数提供多个函数类型定义来进行函数重载。多个函数函数名相同,函数的参数类型,顺序,个数不同。注意函数重载与返回值类型无关。ts的函数重载比较鸡肋,最终函数逻辑的实现还是在一个函数体内去判断它的参数类型,然后做相应的操作。ts重载的作用,感觉只是多了一个参数校验的功能。也就是说在进行函数调用的时候,会对参数进行检查,只有传入的参数类型,顺序,个数与定义的重载函数的参数相同,才能调用成功,否则报错。返回值类型不会进行校验(函数重载与返回值类型无关)。

1
2
3
4
5
6
7
8
9
10
11
12
function getUser (name:string):string {}
function getUser (age:number):number {}
function getUser (str:string):any {
if (typeof str === 'string') {
return `我叫${str}`
} else {
return `我的年龄是${str}`
}
}
console.log(getUser('张三')) // 我叫张三
console.log(getUser(30)) // 我的年龄是30
console.log(getUser(true)) // 报错
箭头函数

箭头函数和js中一样,但是需要注意的this的指向问题。

Typescript--数据类型

Typescript是Javascript的超集,所以大多数的数据类型和Javascript相同。下面是Typescript的数据类型:

  • 字符串类型(String)
  • 数字类型(Number)
  • 布尔类型(Boolean)
  • 数组类型(Array)
  • 元组类型(tuple)
  • 枚举类型(enum)
  • 任意类型(any)
  • null 和 undifined
  • void类型
  • never类型

在typescript中大概有这么十种数据类型。

字符串类型

字符串类型的生明必须声明类型,一旦声明类型,不能再赋值其他类型的数据

1
2
3
4
5
var str1:string = 'this is a string!'
var str2:string = 345

str1 = 'hello world'
str2 = 123 // 报错
数字类型

数字类型的生明必须声明类型,一旦声明类型,不能再赋值其他类型的数据

1
2
3
4
5
var num1:number = 123
var num2:number = 345

num1 = 543
num2 = 'num' // 报错
布尔类型

布尔类型的值只有俩个:true和false,而且声明的时候,必须声明类型,一旦声明类型,不能再赋值其他类型的数据

1
2
3
4
var bol1:boolean = true
var bol2:boolean = false
bol1 = false
bol2 = 123 // 报错
数组类型

定义数组的方式有俩种:

1
let arr:number[] = [1,2,3,4]
1
let arr:Array<number> = [1,2,3,4]

上面是俩种定义数组的方式,但是在数组中只能是number类型,不能有其他类型的值

元组类型

元组类型属于数组的一种,元组可以指定数组中每个元素的类型。

1
let arr:[number, string, boolean] = [123, 'hello world', true]

我们可以看到,数组中的每一个元素必须要与之前的类型一一对应。

枚举类型

事先考虑到某一变量可能取到的值,尽量用自然语言中的含义清楚的词来表示他的每一个值,这种方法称为枚举方法,用这种方法定义的类型称为枚举类型。

1
2
enum Flag = {success = 1, error = -1}
console.log(Flag.success) // 1
1
2
3
4
5
6
7
enum Color { red, blue, white }
var c:Color = Color.red // 0 返回的是索引

enum Color { red, blue = 5, white }
var c:Color = Color.red // 0 返回的是索引
var c:Color = Color.blue // 5 返回的是索引
var c:Color = Color.white // 6 返回的是索引,会以上一个为基准返回
任意类型

任意类型用any来表示,实际和js中不指定类型有点相似。

1
2
3
var s:any = 123
s = 'hello'
s = true

以上的这些操作的都是被允许的。
因为在ts中没有Object这个类型,那么当数据是对象的时候,可以指定其为any类型

null 和 undifined

null和undefined是其他类型的子类型。null和undefined用来定义不同的数据类型。

1
let num:number | null | undefined
void类型

void类型表示没有任何类型,一半用于定义方法的时候没有任何返回值。

1
2
3
4
5
6
7
8
9
function run1 (): void {
console.log('2134566')
}
run1()

function run2 (): number {
return 111
}
run2()
never类型

never类型是其他类型,包含undefined 和 null,代表从不出现的值,这就意味着never类型的变量只能被never类型赋值。

1
2
3
4
var a:never
a = (() => {
throw new Error('throw')
})

never类型基本用不到!

leeCode算法--删除链表中的重复项

题目:存在一个按升序排列的链表,给你这个链表的头节点 head ,请你删除所有重复的元素,使每个元素 只出现一次 。返回同样按升序排列的结果链表。

分析:升序链表,那么重复项一定就是相邻项,所以只需要判断相邻项是否相等,如果相等则就指向下一项即。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function ListNode (val, next) {
this.val = (val === undefined ? 0 : val)
this.next = (next === undefined ? : null : next)
}
let deleteDuplicates = head => {
if (!head) return head
let current = head
while (current.next) {
if (current.val === current.next.val) {
current.next = current.next.next
} else {
current = current.next
}
}
return head
}

That’s all!

leeCode算法--求一个数的平方根

题目:实现一个函数sqrt(x),计算并返回x的平方根,其中x为非负整数,由于返回的类型为整数,结果只保留整数部分,小数部分将被舍去。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let mySqrt = x => {
var high = x, low = 1, mid
while (low <= high) {
mid = Math.floor((low + high)/2)
if (mid * mid > x){
high = mid - 1
}
else if (mid * mid < x){
low = mid + 1
}
else {
return mid
}
}
return high
}
mySqrt(8) // 2

That’s all!

leeCode算法--爬楼梯问题

题目:假设你正在爬楼梯,需要n阶才能到达楼顶,每次你可以爬1或2个台阶,你有多少种不同爬到楼顶的方法?注:给定n是一个正整数。

分析:我们看下具体的解法:
0开始: 1种 –> 1
1第一节楼梯:1种 –> 1
2第二节楼梯:2种 –> 11, 2
3第三节楼梯:3种 –> 111, 12, 21
4第四节楼梯:5种 –> 1111, 121, 112, 211, 22
5第五节楼梯:8种 –> 11111, 1112, 1121, 1211, 2111, 221, 212, 122
.
.
.
1,1,2,3,5,8,13,21,44,65…
有没有发现一个规律,有没有,有没有,有没有,我擦…这不就是意大利数学家斐波那契额在1202年发现的那个兔子增长的规律吗?没错就是斐波那契数列。直接上代码:

1
2
3
4
5
6
7
8
let climbStairs = n => {
if (n <= 2) {
return n
}
return climbStairs(n - 1) + climbStairs(n-2)
}
climbStairs(6) // 8
climbStairs(10) // 55
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
let climbStairs = n => {
let val = []
for (let i = 0; i <= n;i++) {
val[i] = 0
}
if (n < 2) return 1
val[1] = 1
val[2] = 2
for (let i = 3; i <= n;i++) {
val[i] = val[i-1] + val[i-2]
}
return val[n]
}
climbStairs(6) // 8
climbStairs(10) // 55

Yes, So easy!

  • Copyrights © 2015-2022 Lee
  • 访问人数: | 浏览次数:

请我喝杯咖啡吧~

支付宝
微信