0%

问题描述

记录一次奇怪的 DEBUG 过程

事情的起因是我在搭建一个前端项目, 第一次打开可以, 但是如果再刷新页面则会报错.

仔细查看我的代码, 发现没有什么问题, 最后跟踪错误到了vue-router

控制台报错

1
2
3
4
Uncaught (in promise) TypeError: api.now is not a function
at vue-router.esm-bundler.js?v=44d7056b:2545:31
at triggerAfterEach (vue-router.esm-bundler.js?v=44d7056b:3166:13)
at vue-router.esm-bundler.js?v=44d7056b:3069:13

继续跟踪

1
2
3
4
5
6
7
8
9
10
11
api.addTimelineEvent({
layerId: navigationsLayerId,
event: {
title: 'End of navigation',
subtitle: to.fullPath,
time: api.now(), <--
data,
logType: failure ? 'warning' : 'default',
groupId: to.meta.__navigationId,
},
});

如果出错的地方在业务逻辑, 则可以很容易的找出错误原因, 但是这个地方报错, 让我毫无头绪.

Google …

没想到还真的找到了解决方案, 而且还就在该文章发布时间的前半个月(还热乎着…)

解决方案

That’s from the Vue devtools plugin. And only happens if you are still on the 6.0 beta of Vue devtools.

Happened to me today when checking in another browser that still was on the 6.0 beta version instead of the stable one we released recently.

Solution: remove the beta, upgrade to the stable release

大意是说, 如果你开发时使用了 Vue Devtools 这个插件, 并且还是 6.0 beta 版本时, 就会发生这个问题.

解决方案也很简单, 卸载这个测试版, 安装稳定版

https://chrome.google.com/webstore/detail/vuejs-devtools/nhdogjmejiglipccpnnnanhbledajbpd

现在想想, 这个插件我一年前装的, 然后就没管过, 我不清楚它会不会自动更新, 但是确实在我意想不到的地方给我挖了个大坑….

总结

这个事情总结出什么经验呢?

BUG 有时候就是发生的这么毫无逻辑…让人捉摸不定

参考链接

https://github.com/vuejs/router/issues/1338

前言

在 Python 中经常会做的一件事就是将某个对象序列化, 序列化有很多种方式, JSON 是最常用的其中一种(方便和前端交换数据).

但是一个明显的问题是, Python 标准库json, 仅仅能dumps其内置的基本数据类型, 例如 string, integer, boolean, list, dict …

并且 JSON 作为一种数据交换格式, 有其固定的数据类型.
有时候我们需要将我们编写的一个类序列化, 此时该怎么办?

JSON 的介绍可以参考其官网说明

序列化非标准类型时遇到的问题

如果 json.dumps 一个非标准类型(例如一个我们编写的类)会发生什么事?

我们先定义一个类Person

1
2
3
4
class Person:

def __init__(self):
self.name = "naonao"

尝试用 json 序列化

1
2
3
4
5
6
7
import json

# 创建一个实例
p = Person()

# 尝试序列化
json.dumps(p)

毫不意外的报错了

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
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
Input In [76], in <module>
4 p = Person()
6 # 尝试序列化
----> 7 json.dumps(p)

File /opt/conda/lib/python3.9/json/__init__.py:231, in dumps(obj, skipkeys, ensure_ascii, check_circular, allow_nan, cls, indent, separators, default, sort_keys, **kw)
226 # cached encoder
227 if (not skipkeys and ensure_ascii and
228 check_circular and allow_nan and
229 cls is None and indent is None and separators is None and
230 default is None and not sort_keys and not kw):
--> 231 return _default_encoder.encode(obj)
232 if cls is None:
233 cls = JSONEncoder

File /opt/conda/lib/python3.9/json/encoder.py:199, in JSONEncoder.encode(self, o)
195 return encode_basestring(o)
196 # This doesn't pass the iterator directly to ''.join() because the
197 # exceptions aren't as detailed. The list call should be roughly
198 # equivalent to the PySequence_Fast that ''.join() would do.
--> 199 chunks = self.iterencode(o, _one_shot=True)
200 if not isinstance(chunks, (list, tuple)):
201 chunks = list(chunks)

File /opt/conda/lib/python3.9/json/encoder.py:257, in JSONEncoder.iterencode(self, o, _one_shot)
252 else:
253 _iterencode = _make_iterencode(
254 markers, self.default, _encoder, self.indent, floatstr,
255 self.key_separator, self.item_separator, self.sort_keys,
256 self.skipkeys, _one_shot)
--> 257 return _iterencode(o, 0)

File /opt/conda/lib/python3.9/json/encoder.py:179, in JSONEncoder.default(self, o)
160 def default(self, o):
161 """Implement this method in a subclass such that it returns
162 a serializable object for ``o``, or calls the base implementation
163 (to raise a ``TypeError``).
(...)
177
178 """
--> 179 raise TypeError(f'Object of type {o.__class__.__name__} '
180 f'is not JSON serializable')

TypeError: Object of type Person is not JSON serializable

我们仔细观察报错信息, 提示 Person 不是一个 JSON 序列化对象

1
TypeError: Object of type Person is not JSON serializable

那么问题来了, 我们如何把各种各样的 Python 对象序列化成 JSON 格式?

Google 和查阅官方文档后你会发现 dumps 方法提供了一个 cls 参数, 我们可以自己编写一个序列化类, 告诉该他该如何dumps这个对象.

解决方案

例如

1
2
3
4
5
6
7
8
9
class PersonEncoder(json.JSONEncoder):

# 重写该方法, 告诉 json
# 如何 dumps 指定的对象
def default(self, _object):
if isinstance(_object, Person):
# 这里编写如何序列化 Person
# 对象的代码
return dict(name=_object.name)

我们再尝试一下

1
json.dumps(p, cls=PersonEncoder)

得到结果

1
'{"name": "naonao"}'

成功了, 完美的解决了问题.

有点遐思

但是现在有一个问题, 如果序列化少量类, 我们只需要在default这个方法下编写少量的代码即可. 但通常一个应用不可能仅仅只有几个少量的类, 类多了怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def default(self, _object):
if isinstance(_object, Person):
...
elif isinstance(_object, Person1):
...
elif isinstance(_object, Person2):
...
elif isinstance(_object, Person3):
...
elif isinstance(_object, Person4):
...
elif isinstance(_object, Person5):
...
elif isinstance(_object, Person6):
...
elif isinstance(_object, Person7):
...

这种写法, 一点也不优雅!

我们需要一种更优雅的解决方案

更优雅的解决方案

该函数可以解决这个问题

1
from functools import singledispatch

详细说明可以看官方文档

我们先创建两个类

1
2
3
4
5
6
7
8
9
10
11
12
13
import json
from functools import singledispatch

class Person:

def __init__(self):
self.name = "naonao"
self.age = 18

class Animal:

def __init__(self):
self.name = "dog"

创建两个实例

1
2
person = Person()
animal = Animal()

接下来我们可以这样定义序列化器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@singledispatch
def serialize(_object):
return json.dumps(_object)

@serialize.register(Person)
def _(_object):
return dict(name=_object.name,
age=_object.age)

@serialize.register(Animal)
def _(_object):
return dict(name=_object.name)

class CustomJSONEncoder(json.JSONEncoder):

def default(self, _object):
return serialize(_object)

尝试一下

1
2
3
4
5
data = {
"person": person,
"animal": animal
}
json.dumps(data, cls=CustomJSONEncoder)

完美解决

1
'{"person": {"name": "naonao", "age": 18}, "animal": {"name": "dog"}}'

接下来有新的类(或是数据类型)进行序列化时, 我们仅仅需要参照这个格式

1
2
3
4
5
6
                    # 目标类
@serialize.register(Person)
def _(_object):
# 如何序列化这个对象
return dict(name=_object.name,
age=_object.age)

编写对应的解析器即可. 这样可比 if else ...循环嵌套可读性高了不知多少倍!

参考链接

https://juejin.cn/post/6844903510543171592

https://docs.python.org/zh-cn/3/library/functools.html

http://www.json.org/json-zh.html

1
2
3
4
5
6
7
8
class Person:
...

# 实例
p = Person()

# 实例的类
p.__class__

今天有个工作场景是一个注册函数需要执行一次注册任务, 但是该函数仅仅只能在初始化的时候执行一次注册任务, 即该函数只能执行一次.

如何实现这个逻辑?

1
2
3
4
5
6
7
8
9
10
def _register():
print("register ...")


_register()
_register()
_register()

# 多次调用也只能输出一次
>>> register ...

基于类的单例模式

… 略, 想起来再写

基于闭包

我们还可以使用闭包来实现这个逻辑.

维基百科的闭包解释

在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持头等函数的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)。环境里是若干对符号和值的对应关系,它既要包括约束变量(该函数内部绑定的符号),也要包括自由变量(在函数外部定义但在函数内被引用),有些函数也可能没有自由变量。闭包跟函数最大的不同在于,当捕捉闭包的时候,它的自由变量会在捕捉时被确定,这样即便脱离了捕捉时的上下文,它也能照常运行。捕捉时对于值的处理可以是值拷贝,也可以是名称引用,这通常由语言设计者决定,也可能由用户自行指定(如C++)。

看起来非常的绕, 笔者的简单理解就是函数打包了一份本地环境, 并且通常来将, 闭包的内部变量对于外界是完全隐藏的(但是也有办法访问到)

基于闭包实现上述逻辑

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def _register():

# 这里我们在函数内部定义了一个
# 内部变量, 表示该函数是否已经
# 执行过
_loaded = False

def _load():
# 这里写该函数的具体执行逻辑
print("fisrt run, loading ...")
print("loaded")
# 当逻辑被执行一次以后
# 我们需要将设置 _loaded = True
# 用来表示该函数已经被执行过一次
# nonlocal 表示该变量不是本地变量
nonlocal _loaded
_loaded = True

def _run():
if not _loaded:
_load()

return _run

这样我们就实现了一个只能执行一次的函数

执行

1
2
3
f = _register()
f()
f()

可以发现只执行了一次逻辑

1
2
fisrt run, loading ...
loaded

什么是setup语法糖

起初 Vue3.0 暴露变量必须 return 出来,template中才能使用;

现在只需在script标签中添加setup,组件只需引入不用注册,属性和方法也不用返回,也不用写setup函数,也不用写export default ,甚至是自定义指令也可以在我们的template中自动获得。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<my-component :num="num" @click="addNum" />
</template>

<script setup>
import { ref } from 'vue';
import MyComponent from './MyComponent .vue';

// 像在平常的setup中一样的写,但是不需要返回任何变量
const num= ref(0) //在此处定义的 num 可以直接使用
const addNum= () => { //函数也可以直接引用,不用在return中返回
num.value++
}
</script>

使用setup组件自动注册

script setup 中,引入的组件可以直接使用,无需再通过components进行注册,并且无法指定当前组件的名字,它会自动以文件名为主,也就是不用再写name属性了

1
2
3
4
5
6
7
<template>
<zi-hello></zi-hello>
</template>

<script setup>
import ziHello from './ziHello'
</script>

使用setup后新增API

因为没有了setup函数,那么propsemit怎么获取呢

setup script语法糖提供了新的API来供我们使用

defineProps

用来接收父组件传来的 props。示例:

父组件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
<template>
<div class="die">
<h3>我是父组件</h3>
<zi-hello :name="name"></zi-hello>
</div>
</template>

<script setup>
import ziHello from './ziHello'

import {ref} from 'vue'
let name = ref('赵小磊========')
</script>

子组件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<template>
<div>
我是子组件{{name}} // 赵小磊========
</div>
</template>

<script setup>
import {defineProps} from 'vue'

defineProps({
name:{
type:String,
default:'我是默认值'
}
})
</script>

defineEmits

子组件向父组件事件传递。示例:

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div>
我是子组件{{name}}
<button @click="ziupdata">按钮</button>
</div>
</template>

<script setup>
import {defineEmits} from 'vue'

//自定义函数,父组件可以触发
const em=defineEmits(['updata'])
const ziupdata=()=>{
em("updata",'我是子组件的值')
}

</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<template>
<div class="die">
<h3>我是父组件</h3>
<zi-hello @updata="updata"></zi-hello>
</div>
</template>

<script setup>
import ziHello from './ziHello'

const updata = (data) => {
console.log(data); //我是子组件的值
}
</script>

defineExpose

组件暴露出自己的属性,在父组件中可以拿到。示例:

子组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<template>
<div>
我是子组件
</div>
</template>

<script setup>
import {defineExpose,reactive,ref} from 'vue'
let ziage=ref(18)
let ziname=reactive({
name:'赵小磊'
})
//暴露出去的变量
defineExpose({
ziage,
ziname
})
</script>

父组件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<template>
<div class="die">
<h3 @click="isclick">我是父组件</h3>
<zi-hello ref="zihello"></zi-hello>
</div>
</template>

<script setup>
import ziHello from './ziHello'
import {ref} from 'vue'
const zihello = ref()

const isclick = () => {
console.log('接收ref暴漏出来的值',zihello.value.ziage)
console.log('接收reactive暴漏出来的值',zihello.value.ziname.name)
}
</script>

参考链接

https://www.jb51.net/article/231485.htm
https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E4%BD%BF%E7%94%A8%E8%87%AA%E5%AE%9A%E4%B9%89%E6%8C%87%E4%BB%A4

常见错误解决方案

SyntaxError: Cannot use import statement outside a module

无法在模块外使用import

package.json添加"type":"modules"

TypeError [ERR_UNKNOWN_FILE_EXTENSION]: Unknown file extension ".ts"

我收到这条报错的时候,命令行命令使用的是ts-node ./myscripts.ts,改用以下命令时,问题解决。

1
node --loader ts-node/esm ./my-script.ts

参考链接

https://cloud.tencent.com/developer/article/1805310

循环赋值

1
2
3
4
5
var arr1 = new Array(100);
for(var i=0;i<arr1.length;i++){
arr1[i] = i;
}
console.log(arr1);

push方法实现

1
2
3
4
5
var arr2 = new Array();
for(var i=0;i<100;i++){
arr2.push(i);
}
console.log(arr2);

while

1
2
3
4
5
6
7
var arr3 = new Array();
var i = 0;
while(i<100){
arr3.push(i);
i++;
}
console.log(arr3);

do while

1
2
3
4
5
6
7
8
var arr4 = new Array();
var i = 0;
do{
arr4.push(i);
i++;
}
while(i<100)
console.log(arr4);

Object.keys

1
2
3
4
5
6
var arr5 = Object.keys(Array.apply(null, {length:100})).map(function(item){

return +item;

});
console.log(arr5);

Array.from

1
2
var arr6 = Array.from({length:100}, (v,k) => k);
console.log(arr6);
1
2
var arr7 = Array.from(Array(100), (v,k) =>k);
console.log(arr7);

new Array(100).keys()

1
2
var arr8 = new Array(100).keys();
console.log(Array.from(arr8));

递归

1
2
3
4
5
6
7
8
9
10
var arr = [];
var i = 0;
function MakeArray(num){
if(i<num){
arr[i] = i++;
MakeArray(num);
}
return arr;
}
console.log(MakeArray(100));

new Array(100).toString()

1
2
3
4
var arr11 = new Array(100).toString().split(‘,‘).map(function(item,index){
return index;
});
console.log(arr11);

参考链接

https://www.cnblogs.com/fuzitu/p/10723869.html

前言

我们经常会遇到这样的麻烦事,多个函数按顺序执行,返回结果却不是我们预期的顺序,原因一般是由于异步操作引起的,所以呢,我们需要一种解决方案来处理这种问题,从而使得异步操作按照同步的方式来执行,这样我们就可以控制异步操作输出结果的顺序了

异步操作会带来什么问题

异步操作可能会许多的问题,下面是常见的两种

1.函数执行的结果并不是按照顺序返回

1
2
3
4
5
6
7
8
9
10
11
12
function fn1(){
console.log(111)
setTimeout(function(){
console.log('wait me 3000')
},3000)
}
function fn2(){
console.log(222)
}
fn1();
fn2();

1
2
3
4
//结果
//111
//222
//wait me 3000

上面的代码,如果你期待的结果是

1
2
3
//111
//wait me 3000
//222

那就错了,因为fn1函数里面还有一个函数setTimeout,这两个函数是异步执行的,而fn1fn2是同步执行的,先执行fn1再执行fn2,在执行fn1的时候打印结果111,三秒后再执行setTimeout,但是在这三秒之前已经执行完了fn2

2.在外部获取不到异步函数里的值

下面我们看一个最简单的例子,我的需求是要在fn1函数外面打印msg

1
2
3
4
5
6
function fn1(){
setTimeout(function(){
msg='wait me 3000';
},3000);
}
fn1();

那么怎么样才能获取到msg

使用回调函数

1
2
3
4
5
6
7
8
9
function fn1(callback){
setTimeout(function(){
msg='wait me 3000';
callback(msg);
},3000);
}
fn1(data=>{
console.log(data);//wait me 3000
});

使用Promise

1
2
3
4
5
6
7
8
9
10
11
function fn1(){
return new Promise(function(res,rej){
setTimeout(function(){
msg='wait me 3000';
res(msg);
},3000);
})
}
fn1().then(data=>{
console.log(data)
})

关于Promise的信息可以参考

https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Promise

async/await解决方案

async/await的作用就是使异步操作以同步的方式去执行

异步操作同步化?

可以使用Promise中的then()来实现,那么async/await与它之间有什么区别呢

1.async函数返回的是一个Promise对象

如果一个函数加了async关键词,这个函数又有返回值,在调用这个函数时,如果函数执行成功,内部会调用Promise.solve()方法返回一个Promise对象,如果函数执行出现异常,就会调用Promise.reject()方法返回一个promise对象

要想获取到async函数的执行结果,就要调用Promisethencatch来给它注册回调函数

1
2
3
4
async function fn(){
return '111'
}
console.log(fn());//Promise { '111' }

既然是Promise对象,因此可以使用then()获取返回的结果

1
2
3
4
5
6
async function fn(){
return '111'
}
fn().then(data=>{
console.log(data)//111
})

2.await

上面介绍了async的作用,一般情况下,asyncawait配合使用才能使异步操作同步化,await就是等待的意思,等待某一个函数执行完之后,后面的代码才能开始执行

1
2
3
4
5
6
7
8
9
10
11
12
13
function fn1(){
return new Promise(resolve=>{
setTimeout(function(){
msg='wait me 3000';
resolve(msg)
},3000);
});
}
async function asyncCall(){
var result=await fn1();
console.log(result);
}
asyncCall();

如果我们没有等待fn1执行完之后再打印result,那么有可能得到是undefined

总结

aysnc await可以让我们以同步的方式编写异步代码

在很多的时候,我们是希望按照同步的方式来获得异步函数的结果,比如登录时,我们必须要在后台返回匹配成功的信息之后才能进行页面跳转,因此,使异步操作同步化这是很重要的知识点,但是这种方案是基于Promise的基础之上的,因此在学习该知识时,一定要对Promise有充分的理解

参考链接

https://juejin.cn/post/6844903984889593863