ZONO's World

welcome to my world

第二关——链表反转拓展

1.指定区间反转

Leecode92:给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回反转后的链表 。
原题链接

1.1 思路

对应上一篇文章,我们可以分成两种解决办法:头插法、穿针引线法

1.2 头插法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next

def reverseBetween(self,head,left,right):
# 设置dummyNode是这一类问题的一般做法
dummy_node = ListNode(-1)# 创建一个虚拟节点
dummy_node.next = head
pre = deummy_node# 用于跟踪左边界之前的节点
for _ in range(left -1):# _用于占位,该函数运行left-1次
pre = pre.next# 定位到要移动区

cur = pre.next# 取最近的一个开始交换
for _ in range(right-left):
next = cur.next
cur.next = next.next
next.next = pre.next
pre.next = next
return dummy_node.next

1.3 穿针引线法

2.两两交换链表中的节点

LeeCode24.给一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题。

方法一 递归

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Definition for singly-linked list.
* function ListNode(val, next) {
* this.val = (val===undefined ? 0 : val)
* this.next = (next===undefined ? null : next)
* }
*/
/**
* @param {ListNode} head
* @return {ListNode}
*/

var swapPairs = function(head) {
if(head === null ||head.next ===null){
return head
}
const newhead = head.next;
head.next = swapPairs(newhead.next)//连接第一个和第三个
newhead.next = head;//连接第二个和第一个
return newhead
};

方法二 迭代

这里涉及到深浅拷贝问题

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
/*
@Date :2023/11/12 21:50:47
@Author :zono
@Description:链表设置
*/
function ListNode(val, next) {
this.val = val === undefined ? 0 : val;
this.next = next === undefined ? null : next;
}

// 创建链表节点
const node1 = new ListNode(1);
const node2 = new ListNode(2);
const node3 = new ListNode(3);
const node4 = new ListNode(4);

// 构建链表关系
node1.next = node2;
node2.next = node3;
node3.next = node4;

var swapPairs = function (head) {
const dummyHead = new ListNode(0);
dummyHead.next = head;
let temp = dummyHead;//这里需要思考深浅拷贝
while (temp.next !== null && temp.next.next !== null) {
const node1 = temp.next;
const node2 = temp.next.next;
temp.next = node2;
node1.next = node2.next;
node2.next = node1;
temp = node1;
}
return dummyHead.next;
};

function sort(currentNode) {
while (currentNode !== null) {
console.log(currentNode.val);
currentNode = currentNode.next;
}
}

sort(swapPairs(node1));

3.单链表加一

4.链表加法

关于async使用失败的问题

记录一个async使用axios的问题

简单描述就是要使用async,必须是promise对象,要把一般函数转过去。

问题描述:

react项目中,在使用axios时,我要写一个登录功能:要求在请求接口后跳转到首页,但是要在请求成功到token后才执行跳转,于是用到了async的await。

代码如下:

1
2
3
4
5
6
7
8
9
const onFinish = async (values) =>  {
//一个固定的读取表单的函数
console.log( values);
// 触发请求
await dispatch(fetchLogin(values))
// 登录成功后跳转首页,并提示用户(避免异步)
navigator('/')
message.success('登录成功')
}

问题就在于await无法触发

调用的函数如下:

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
const fetchLogin = (loginForm) => {
/*
@Description: 异步请求获取token,并返回应该promise函数
*/
const { username, password } = loginForm;
const requestData = {
grant_type: "",
username,
password,
scope: "",
client_id: "",
client_secret: "",
};
return (dispatch) => {
//分布异步请求,并存入
const res = request({
url: '/token',
method: 'POST',
data: formData,
headers: {
'accept': "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
})
dispatch(setToken(res.data.access_token));
};
};

解决

通过查找资料后,了解到async必须传入promise才能使用await,但查资料都说dispatch函数调用后都是promise对象(网上资料真的是),后面就尝试改为异步函数试试。(先在return后加入了async)

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
const fetchLogin = (loginForm) => {
/*
@Description: 异步请求获取token,并返回应该promise函数
*/
const { username, password } = loginForm;
const requestData = {
grant_type: "",
username,
password,
scope: "",
client_id: "",
client_secret: "",
};
return async (dispatch) => {
//分布异步请求,并存入
const res = request({
url: '/token',
method: 'POST',
data: formData,
headers: {
'accept': "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
})
dispatch(setToken(res.data.access_token));
};
};

这是一次试错,发现无效,后面在request前加入await才成功

正确函数是(封装了一下):

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
const fetchLogin = (loginForm) => {
/*
@Description: 异步请求获取token,并返回应该promise函数
*/
const { username, password } = loginForm;
const requestData = {
grant_type: "",
username,
password,
scope: "",
client_id: "",
client_secret: "",
};
return async (dispatch) => {
//分布异步请求,并存入
const res = await loginAPI(requestData);
dispatch(setToken(res.data.access_token));
};
};

function loginAPI (formData){
return request({
url: '/token',
method: 'POST',
data: formData,
headers: {
'accept': "application/json",
"Content-Type": "application/x-www-form-urlencoded",
},
});

如此就成功了。

浏览器的垃圾回收机制

V8垃圾回收机制

用于管理代码运行过程中使用的内存

存在原因

垃圾收集器会按固定时间周期性的执行,去找出那些不继续使用的变量,然后释放其内存,如果内存开销比较大,GC就会停止响应其他操作,它会阻塞其他应用程序的执行。

方法

当不再被引用时,就会被垃圾回收,联系闭包。通常两种方式。

标记清除法(常用)

从对象开始递归遍历所有可以访问的对象,把它们标记为活动对象,然后清除未被标记的对象。

然后清除标记,等待下一次标记,也就是下一次清理

引用计数法(IE)

跟踪对象的引用次数,每当对象被引用时,引用次数加一,取消引用就减一。计数为0时,说明不再被引用,于是回收。

问题:循环引用时,两对象相互引用时,永远不为零。

1
2
3
4
5
6
7
function fun(){
var f ={};
var g ={};
f.userName=g;
g.userName=f
//循环引用无法为零
}

改进:

分代收集(根据存活时间,将内存分为四代)、增量收集(将垃圾回收拆分为多个小步骤,避免js被阻塞)

缺陷

GC时会停止响应其他操作,为了安全

耗时一般在100ms以上,当遇见动画、js游戏连贯性、操作性要求比较高时,GC就需要被优化,避免停止响应。

三色标记

v8策略

新生代、老生代区域

增量标记

并发回收

具体的算法是什么,GC的优化策略是什么,包括分代回收,增量GC

Canvas

只有两属性width和height,默认300*150

接下来我们就可以利用js,获取渲染上下文,然后在它上面绘制。

1
2
3
4
5
6
7
8
9
<script>
....
// 获取canvas对象
const canvas = document.getElementById('canvas')
// 获取渲染上下文对象
const ctx = canvas.getContext('2d')
...
</script>

Canvas Api

栅格

绘制图形

路径(Path)

主要四个参数

  1. beginPath
  2. closePath
  3. stroke
  4. fill

步骤

 1. 调用beginPath
 2. 调用目标函数
 3. 闭合(非必须)

移动笔触(moveTo)

直线(lineTo)

三角形(triangle)

矩形(rect)

圆形(arc)

样式、颜色

色彩(color)

绘制文本

操作图片

动画

实现动画的步骤

清理->绘制->清理->绘制…

实战

画板

粒子效果

【Canvas实战】仿明日方舟Logo粒子动画 vue3+ts - 掘金 (juejin.cn)

深浅拷贝

之前写Leecode24题的迭代算法时,一直以为是深拷贝,不断断点测试才知道,这是浅拷贝,这才知道底层设计时,拷贝都是浅拷贝。果然算法前还是得把语言特性理解了。

深拷贝的实现方式(超全) - 掘金 (juejin.cn)

让我们先定义一个对象

1
2
3
4
5
6
7
const oldObj = {
name:'zono',
colors:['orange','green','blue'],
friend:{
name:'童馨'
}
}

浅拷贝

让我们进行一次拷贝

1
2
3
4
const newObj = oldObj;
newObj.name = 'xiao';
console.log('oldObj',oldObj);
console.log('newObj',newObj);//输出相同都是xiao

可见浅拷贝,会让原本被拷贝的源对象的属性也一起改了。

可见浅拷贝就只是换一下指向的对象,只简简单单地把栈当中的引用地址拷贝了一份

排除上文的算法题用到浅拷贝,有时我们可能想用到深拷贝

深拷贝

概念:深拷贝就是创建一个新对象,对象改变不影响原来的对象

方法:调用库或者使用第三方库

法一 递归

为了不只最外层被拷贝

一般

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function deepClone(obj){
const newObj = {};
let keys = Object.keys(obj);
let key = null;
let data = null;
for(let i = 0; i<keys.length;i++){
key = keys[i];
data = obj[key];
if(data && typeof data === 'object'){
newObj[key] = cloneDeepDi(data)
}else{
newObj[key] = data;
}
}
return newObj
}

循环引用

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
function deepCopy(obj, parent = null) {
// 创建一个新对象
let result = {};
let keys = Object.keys(obj),
key = null,
temp = null,
_parent = parent;
// 该字段有父级则需要追溯该字段的父级
while (_parent) {
// 如果该字段引用了它的父级则为循环引用
if (_parent.originalParent === obj) {
// 循环引用直接返回同级的新对象
return _parent.currentParent;
}
_parent = _parent.parent;
}
for (let i = 0; i < keys.length; i++) {
key = keys[i];
temp = obj[key];
// 如果字段的值也是一个对象
if (temp && typeof temp === 'object') {
// 递归执行深拷贝 将同级的待拷贝对象与新对象传递给 parent 方便追溯循环引用
result[key] = DeepCopy(temp, {
originalParent: obj,
currentParent: result,
parent: parent
});

} else {
result[key] = temp;
}
}
return result;
}

法二Object.assign

也只对最外层拷贝

1
2
3
4
function cloneDeepAssign(obj){
return Object.assign({},obj)
//Object.assign({},obj)
}

(温馨提示:数组拷贝方法当中,使用...sliceconcat等进行拷贝也是一样的效果,只深拷贝最外层)

法三json

复盘联系

深浅拷贝和原型链的关系

在写算法时遇见了一些基础问题,于是写一个博客记录一下。

Var(ES5)特点(也可以说问题)

  • 存在变量提升

    1
    2
    3
    4
    5
    6
    7
    console.log(a); 
    var a = 10;

    // 编译过程会转变为,就是声明的a提升到前面了
    var a;
    console.log(a); // undefined但可以调用,离谱不?
    a = 10;
  • 一个变量可多次声明,后面的声明会覆盖前面的声明

    1
    2
    3
    var a = 10;
    var a = 20;
    console.log(a); // 20,是的var又用了一次,但还是没报错
  • 在函数中使用var声明变量的时候,该变量是局部的

    1
    2
    3
    4
    5
    6
    var a = 10;
    function change(){
    var a = 20;
    }
    change();
    console.log(a); // 10

    而如果在函数内不使用var,该变量是全局的

    1
    2
    3
    4
    5
    6
    var a = 10;
    function change(){
    a = 20
    };
    change();
    console.log(a); // 20

    大家觉得这些问题,有点匪夷所思,于是ES6就改进了

Let

  • 解决了变量提升

  • 不能重复声明

    1
    2
    let a = 10
    let a = 20

    会报错

  • 局部变量,只能在代码块中使用

    1
    2
    3
    4
    {
    let a = 10;
    }
    console.log(a);

    但在不同定义域就可以

    1
    2
    3
    4
    5
    let a = 10;
    {
    let a = 20;
    }
    console.log(a);//10,如果是var的话就是20

Const(翻译是”常量“)

  • Let的所有特性

  • 只能声明一个只读变量

    实际项目中一般都是用这个,后面有改动才考虑let,var基本上淘汰了

    1
    2
    const a = 10
    a = 20 //会报错
  • 必须初始化

    1
    2
    const a;//会报错
    const a = 10;//正确
  • const 可以通过变量指向改动(const是指向的地址,对应的那个数据不能改动)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    const obj = {
    age: 17
    }
    obj.age = 18; // ok

    obj = {
    age: 18
    }
    // SyntaxError: Identifier 'obj' has already been declared

    如上,obj是被指向的地址中的数据,我们无法改动{},但可以改动{}中的age

let一般应用于基本数据类型;const 一般应用于引用数据类型,也就是函数对象等。

拿到设计时,我们首先要把项目划分为一个个模块,或者组件

划分原则:

  • 按照功能和需求划分:普通业务模块/ 组件
  • 按照复用性划分:通用业务模块
  • 通用功能模块组件【UI组件库】

1.1 模块化演化进程

未来的项目一定是 模块化/组件化 开发!!

模块化编程进化历史

  • 单例设计模式

  • AMD「require.js」

  • CommonJS(node.js)

  • CMD「sea.js」

  • ES6Module

1.2单例设计模式

也就是原生js写法,用<script src>,调用文件,把各个功能写在不同的模块下,最后合并到页面中。

问题

  • 会出现全局变量污染,也就是模块间会有相互冲突。
  • 模块间相互引用问题,用window方法多了后也会冲突

解决问题

  • 模块间相互冲突的问题:

    • 「闭包」 ;
  • 模块间相互引用的问题:

    • 使用windows.XXX = XXX 暴露方法到全局

    • 解决windows多了后冲突:直接导出整个对象,就像react组件一样,对象中有所有对应的方法。

      详细解释:让每个模块有一个独有的模块名xxxModule,也叫命名空间(回想起来c不就是吗)。

      模块闭包时,直接return对象,别人要用时,直接模块名.方法()调用就行。

如此单例设计模式就完成了。

注意:包导入的顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//单例格式
let xxxModule = (function () {
let time = new Date();
const query = function query() {
// ...
};
const handle = function handle() {
// ...
};

// 把供其它板块调用的方法,暴露到全局对象上
//「局限:暴露的内容比较多,则还会引发全局变量冲突」
// window.query = query;

return {
query,
handle
};
})();

没看例子:p132 37:17~52:30

不足之处:需要手动分析模块之间的依赖关系,按顺序依次导入相关模块;所有模块都是基于script一个个导入的,这样页面HTTP请求变多!

1.3 AMD「require.js」

https://requirejs.org

优势:在保证模块之间独立和可以相互访问的基础上,HTML中无需再导入各个模块了「不存在顺序问题」,也不需要自己去分析相互间的依赖关系!
不足:依赖模块的导入是“前置导入”,只有把依赖模块动态导入完毕,才会触发回调函数执行「阻碍代码执行速度」;代码书写的顺序也不是很方便;可能存在重复导入;

image-20231112133515879

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
//main.js
require.config({
//全局配置
baseUrl: 'js/lib',
});

require(['moduleB', 'moduleA'], function (moduleB, moduleA) {
console.log(moduleB.average(10, 20, 30, 40, 50));
});

//moduleA.js
define(function () {
return {
sum(...args) {
let len = args.length,
firstItem = args[0];
if (len === 0) return 0;
if (len === 1) return firstItem;
return args.reduce((total, item) => {
return total + item;
});
}
};
});

//moduleB.js
define(['moduleA'], function (moudleA) {
return {
// 求平均数(去掉最大最小值)
average(...args) {
let len = args.length,
firstItem = args[0];
if (len === 0) return 0;
if (len === 1) return firstItem;
args.sort((a, b) => a - b);
args.pop();
args.shift();
return (moudleA.sum(...args) / args.length).toFixed(2);
}
};
});

没看例子:p132 58:17~1:20:05

1.4 CommonJS模块化规范

node.js出现了,不用导入

每创建一个js文件就创建了一个模块,里面的代码就是独立的。用module.exports导出对象。

唯一的问题:浏览器端不支持CommonJS规范
淘宝“玉伯”仿照CommonJS规范,研发了一款插件 sea.js ,旨在把CommonJS规范搬到浏览器端运行「这种模块思想被称之为CMD」

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
/* ---A.js--- */
const sum = function sum(...params) {
return params.reduce((result, item) => {
return result + item;
});
};
// 暴露一些API
module.exports = {
sum
};

/* ---B.js--- */
let name = '哈哈';
let A = require('./A');
const average = function average(...params) {
return (A.sum(...params) / params.length).toFixed(2);
};
module.exports = {
average
};

/* ---main.js--- */
let A = require('./A');
console.log(A.sum(10, 20, 30, 40));
let B = require('./B');
console.log(B.average(10, 20, 30, 40));

缓存机制

还是没有实战—27:35

1.5 ES6Module模块规范

https://es6.ruanyifeng.com/#docs/module

可以直接运行在浏览器端;不支持IE;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
使用在html文件上时,导入文件前要加入type = module,预览要使用http协议(LiveServer),不能使用File协议。
模块的导入导出:
整体来讲,每个模块导出的是一个Module对象,我们可以基于 export/export default 为对象中加入导出的内容
@导出方法1 export default xxx;
+ xxx可以是任意类型值
+ 给Module对象新增一个 “default” 属性,属性值就是导出的值
+ 在一个模块中,export default只允许出现一次
@导出方法2 export 创建值的表达式(声明变量 = 值);
let num = 10;
export num; //这样写是错误的
export let num = 10; //这样写是正确的
---
let num = 10;
export {
num, //这样写是可以的
x:100 //这样写是错误的
};
---
+ export可以出现多次,导出多个内容
+ 声明的变量叫啥名字,最后给Module对象加入的是同名的属性名,属性值就是变量的值

1.5.2导入方法

我们会基于 import 方法导入模块「导入模块导出的内容」

  • import N from ‘模块地址(在浏览器端不能忽略.js后缀名)’;

N获取的不是整个导出的Module对象,仅仅获取的是Module中default这一项的值
不能在import期间,直接对N解构赋值,因为涉及解构赋值就会变为另外一种情况

  • 解构赋值:import {x,y} from ‘模块地址’;

直接对导出的Module对象中(排除default属性),导出的内容进行解构赋值

  • import * as M from ‘模块地址’;先导入所有导出的内容,赋值给M变量

还是没有细看

0%