Hexo


  • 首页

  • 分类

  • 归档

Lottie动画参数解析

发表于 2020-08-18

背景:

  1. 简单动画可以通过animation/transition 来实现,但是复杂的动画就很难用这个来实现了,这时候可以使用Lottie来实现相关动画官网地址。
  2. 动效师会生成一个json文件,前端同学使用lottie-web这个npm包,结合动效师给的json文件,就可以做出对应的动画效果了。
  3. 但是有一个问题是,动效师给的json文件渲染出来的动画是固定的,比如缩放尺寸,比如位移大小,比如图片等等。有时候我们需要修改这些数据来达到动态动画的目的。比如下图中蘑菇落地的位置是不确定的,这时候我们就需要修改json的数据了

下面我们简单介绍一下lottie动画相关参数含义。github地址 这个地址介绍了一些参数,但不是很全

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
{
"v": "5.4.4",
"fr": 60, // 帧率
"ip": 352, // in point 起始点
"op": 425, // out point 结束点。(op-ip) / fr = 动画时间
"w": 2048, // 画布宽度
"h": 1536, // 画布高度
"nm": "名字",
"ddd": 0, // 3D层,
"assets": [ // 资源(一般是图片和合成组的静态资源)
{
"id": "image_0",
"w": 422,
"h": 432,
"u": "images/",
"p": "img_1.png",
"e": 0
},
],
"fonts": { // 使用的字体
"list": [
{
"fName": "FZLANTY_CUJW--GB1-0",
"fFamily": "FZLanTingYuanS-B-GB",
"fStyle": "Regular",
"ascent": 78.38134765625
}
]
},
layers:[]
// 图层。layers里面记录了图层在展示的时候实际的位置和它做动画的关键帧。我们一把要改的就是在这里
}

下面看看layers

看看layers[2].ks字段含义

看看看看layers[2].ks.p即位移相关参数含义

与ks并列的有一个t字段,其内部控制图层文本显示

看完上面lottie输出的json数据含义,大概能得知对应的定制化修改方案了:

1.找到动画的元素对应哪一层,也就是layers的哪一个元素。(蘑菇上的文本在第0层,蘑菇图片在第2层,如果不清楚哪个元素处于哪一层,可以找制作动效的动效师问问)

2.当前需要修改的是蘑菇层的起点和终点位置。需要得到起始位置和结束位置(现在暂时认为已经计算出相关位置),并传入json中,修改对应的参数。

3.同时蘑菇跳动之后的文本显示也需要做修改,不同的蘑菇显示的文本内容不同,而json中是固定的文本,所以需要修改。

4.同时注意如果json是2倍的,需要将对应的位置数据处理成2倍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 前面计算出蘑菇跳动的起始坐标和结束坐标 startPoint endPoint
const answerRightAnimator = await lottiePlayer.play(
'name',
dom,
{
jsonPreHandler: preJson => {
let json = JSON.parse(JSON.stringify(preJson));
json.layers[0].t.d.k[0].s.t = 'apple'; // 修改跳动后蘑菇上的文本内容
// json是2倍的
json.layers[1].ks.p.k = endPoint;
json.layers[2].ks.p.k[0].s = startPoint;
json.layers[2].ks.p.k[0].e = endPoint;
json.layers[2].ks.p.k[1].s = startPoint;
json.layers[2].ks.p.k[1].e = endPoint;
return json;
},
renderer: 'canvas',
// renderer: 'svg'
// canvas渲染性能比svg更好
},
);

了解了lottie的json数据字段含义之后,就能对lottie动画做动态修改了。上面所说的是在lottie播放前动态修改属性,还有一种需求可能是动画播放之后,再去修改json的属性,这时候可以使用lottie-api 这个库。

web设备检测

发表于 2020-03-24
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
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
<!DOCTYPE html>
<html>
<head>
<style>
#video {
width: 340px;
height: 237px;
/* 看自己要变成镜子 */
transform: scale(-1 , 1)
}
#take-photo-btn {
width: 100px;
height: 50px;
border-radius: 10px
}
#capture-img {
width: 340px;
}
#microphone-audio {
width: 20px;
height: 20px;
background: red;
}
</style>
</head>
<body>
<div class="app">
<div class="detect-camera-box">
<video id="video" autoplay></video>
<button id="take-photo-btn">拍照</button>
<img id="capture-img">
</div>
<div class="detect-microphone-box">
<audio id="microphone-audio" autoplay></audio>
</div>
</div>
<!-- <script>
// 设备检测一般包括检测摄像头,扬声器,麦克风这三样。检测摄像头和麦克风需要通过getUserMedia,检测扬声器只需要能播放一个固定的audio音频就代表没问题

// 简单检测的话,直接用getUserMedia 得到的 stream 赋值给 video.srcObject 或者 audio.srcObject就行
detect_camera_microphone_easy();

function detect_camera_microphone_easy() {
navigator.mediaDevices.getUserMedia({
// 检测摄像头
video: {
width: {
max: 640,
ideal: 340,
},
height: {
max: 480,
ideal: 237,
},
},
// 检测麦克风
audio: true
}).then(stream => {
const videoTracks = stream.getVideoTracks();
const mediaStream_video = new MediaStream(videoTracks);
document.getElementById('video').srcObject = mediaStream_video;
document.getElementById('take-photo-btn').addEventListener('click', function() {
imageCapture(mediaStream_video)
});

const audioTracks = stream.getAudioTracks();
const mediaStream_audio = new MediaStream(audioTracks);
document.getElementById('microphone-audio').srcObject = mediaStream_audio;
});
function imageCapture(stream) {
const videoTracks = stream.getVideoTracks();
const captureImg = document.getElementById('capture-img');
new ImageCapture(videoTracks[0]).takePhoto().then(blob => {
captureImg.src = URL.createObjectURL(blob);
});
}
}
</script> -->
<script>
// 上面那个 script 中的js是超级简单的设备检测版本,当前这个 script 中的js是一个稍微复杂一点点的版本
// 稍微全面一点的话,设备检测的时候会涉及到 如下问题:
// **1. 设备管理器:选择设备ID, 默认设备等等** : navigator.mediaDevices.enumerateDevices()
// **2. 检测摄像头:摄像头获取到的是否是单色,如果是单色就证明没获取成功,界面是全黑或者全白**: 通过canvas获取图片二进制数据,对每一个数据进行比较,如不是完全一致则说明不是全黑或全白
// **3. 检测麦克风:声音大小** : this.audioInput ====> this.jsAudioNode ====> this.audioContext.destination,在 onaudioprocess 事件里面的event.inputBuffer.getChannelData(0)获取音频数据

// 1. 设备管理器: 获取到用户的设备,主要通过window.navigator.mediaDevices.enumerateDevices
class deviceManager {
constructor() {
this.deviceNameMap = {
speaker: '扬声器',
camera: '摄像头',
microphone:'麦克风',
};
}
getDevices() {
return new Promise(resolve => {
// 用户的所有设备
let userDevices = {
speaker: [],
camera: [],
microphone: [],
};
if (
!window.navigator.mediaDevices ||
!window.navigator.mediaDevices.enumerateDevices
) {
resolve(userDevices);
return;
}

// 如果获取设备列表超过3秒还没返回就直接返回空设备对象,让程序直接选用默认设备
const deviceEnumTimer = setTimeout(() => {
resolve(userDevices);
}, 3000);

window.navigator.mediaDevices.enumerateDevices().then(devicesInfo => {
clearTimeout(deviceEnumTimer);
console.log('devicesInfo',devicesInfo);
devicesInfo.forEach(deviceInfo => {
const kind2type = {
'audioinput': 'microphone',
'audiooutput': 'speaker',
'videoinput': 'camera'
}
const type = kind2type[deviceInfo.kind];
this.setOneDevice(type, deviceInfo, userDevices);
});
resolve(userDevices);
})
});
}
setOneDevice(type, deviceInfo, userDevices) {
let label = deviceInfo.label || deviceInfo.devicename;
let name = label || this.deviceNameMap[type] + (userDevices[type].length + 1)

let deviceId = deviceInfo.deviceId || deviceInfo.deviceid;
userDevices[type].push({
deviceId,
name,
groupId: deviceInfo.groupId,
// 获取不到设备名称便为自定义加的名称
isCustomName: !label,
});
}
}

function detect_camera(deviceId) {
stopCaptureVideo();
startCaptureVideo(deviceId)
}
function detect_microphone(deviceId) {
stopCaptureAudio();
startCaptureAudio(deviceId);
}

function stopCaptureAudio() {
if (audioStream) {
audioStream.getAudioTracks()[0].stop();
audioStream = null;
microphoneRecorder.stopRecord();
}
}
// 通过track.stop()停止音视频
function stopCaptureVideo() {
if(videoStream) {
videoStream.getTracks().forEach(track => track.stop());
videoStream = null;
}
}
// 使用某个特定设备
function startCaptureVideo(deviceId) {
let constraints = {
audio: false,
video: {
width: {
max: 640,
ideal: 340,
},
height: {
max: 480,
ideal: 237,
},
},
};
if(deviceId) {
constraints.video.deviceId = {
exact: deviceId
}
}
window.navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
document.getElementById('video').srcObject = stream;
videoStream = stream;
document.getElementById('take-photo-btn').addEventListener('click', function() {
imageCapture(stream)
});
})
}

function startCaptureAudio(deviceId) {
let constraints = {
audio: true
};
if(deviceId) {
constraints.audio.deviceId = {
exact: deviceId
}
}
window.navigator.mediaDevices
.getUserMedia(constraints)
.then(stream => {
document.getElementById('microphone-audio').srcObject = stream;
audioStream = stream;
microphoneRecorder.listenVolumnChange(stream, volumn => {
// 获取到音量值时候可以通过图表展示在页面等等
});
});
}

// 2. 检测摄像头:摄像头获取到的是否是单色,如果是单色就证明没获取成功,界面是全黑或者全白
function checkImageIsValid(img) {
const canvas = document.createElement('canvas');
const width = 100;
canvas.width = width;
canvas.height = img.naturalHeight / img.naturalWidth * width;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for(let i = 0; i < imageData.length; i++) {
if(imageData[i] === imageData[i - 1]) return false;
}
return true;
}
function imageCapture(stream) {
const videoTracks = stream.getVideoTracks();
const captureImg = document.getElementById('capture-img');
new ImageCapture(videoTracks[0]).takePhoto().then(blob => {
captureImg.src = URL.createObjectURL(blob);
});
// 如果需要检测图片是否是单色则需要调用checkImageIsValid
captureImg.onload = function() {
const valid = checkImageIsValid(captureImg);
console.log('valid===', valid);
}
}

// 3. 检测麦克风:声音大小。要获取声音大小需要得到麦克风获取到的声音数据,用这个数据来得到声音的大小。另外得这些数据之后也可以实现录音功能
// 录音功能可以看这篇文章: https://juejin.im/post/5b8bf7e3e51d4538c210c6b0
class MicorphoneRecorder {
constructor() {
// 这些参数可以通过入参获取,这里暂且写死
this.numberOfAudioChannels = 2;
this.bufferSize = 4096;
// audioContext 不能在没有用户操作的时候去创建,所以不能放在constructor里面
this.audioContext = null;
this.jsAudioNode = null;
this.audioInput = null;
}
setupAudioContext() {
this.audioContext = new (window.AudioContext || window.webkitAudioContext)();
if(this.audioContext['createScriptProcessor' || 'createJavaScriptNode']) {
this.jsAudioNode = (this.audioContext['createScriptProcessor' || 'createJavaScriptNode'])(
this.bufferSize,
this.numberOfAudioChannels,
this.numberOfAudioChannels
);
} else {
throw new Error('WebAudio API has no support on this browser.');
}
}
// 监听音量变化
listenVolumnChange(mediaStream, onVolumnChange) {
this.setupAudioContext();
// this.audioInput ====> this.jsAudioNode ====> this.audioContext.destination
this.jsAudioNode.connect(this.audioContext.destination);
this.audioInput = this.audioContext.createMediaStreamSource(mediaStream);
this.audioInput.connect(this.jsAudioNode);
let time = 1;
// 通过jsAudioNode.onaudioprocess事件获取音频数据
this.jsAudioNode.onaudioprocess = event => {
// onaudioprocess会疯狂触发,为了避免疯console,这里只打印一次
while(time) {
console.log('event-onaudioprocess', event);
time--;
}
this.onAudioProcess(event, onVolumnChange);
}
}
// 当前做的操作是通过音频二进制数据检测音量,其实也可以存储这些音频二进制数据,实现录音
onAudioProcess(audioProcessingEvent, onVolumnChange) {
var inputBuffer = audioProcessingEvent.inputBuffer;
// The output buffer contains the samples that will be modified and played
var outputBuffer = audioProcessingEvent.outputBuffer;

// 获取左声道和右声道的数据
for (var channel = 0; channel < outputBuffer.numberOfChannels; channel++) {
// inputData是一个数组,这个数据代表什么呢?它是通过采样采来的,表示声音的强弱,声波被麦克风转换为不同强度的电流信号,这些数字就代表了信号的强弱。它的取值范围是[-1, 1],表示一个相对比例。
var inputData = inputBuffer.getChannelData(channel);
var outputData = outputBuffer.getChannelData(channel);

// Loop through the 4096 samples
for (var sample = 0; sample < inputBuffer.length; sample++) {
// 不给outputBuffer设置内容,扬声器不会播放出声音
// make output equal to the same as the input
outputData[sample] = inputData[sample];

// add noise to each output sample
// outputData[sample] += ((Math.random() * 2) - 1) * 0.2;
}
}

// 具体怎样表示一个时刻的音量?这个就看自己怎么处理这些数据了。我现在使用下面的代码处理方式来作为音量的大小,这只是我自己定的一个标准,其实使用平均值,或者最大值都行
let sum = 0;
for(let i = 0; i < inputBuffer.length; i++) {
sum += inputBuffer[i] * inputBuffer[i];
}
let volumn = Math.sqrt(sum / inputBuffer.length);
if (typeof onVolumnChange === 'function') {
// 稍微提升一下,不然太小
onVolumnChange(volumn * 3);
}
}
stopRecord() {
this.audioInput.disconnect();
this.jsAudioNode.disconnect();
}
}



// 开始调用
let userDevices = null;
let videoStream = null;
let audioStream = null;
const deviceManage = new deviceManager();
const microphoneRecorder = new MicorphoneRecorder();
deviceManage.getDevices().then(_userDevices => {
userDevices = _userDevices;
detect_camera(userDevices.camera[0].deviceId);
detect_microphone(userDevices.microphone[0].deviceId);
});
</script>
</body>
</html>

javascript中的二进制

发表于 2020-03-06

一.类型数组

  1. Javascript类型数组是什么:JavaScript类型化数组是一种类似数组的对象,并提供了一种用于访问原始二进制数据的机制。正如你可能已经知道,Array 存储的对象能动态增多和减少,并且可以存储任何JavaScript值。JavaScript引擎会做一些内部优化,以便对数组的操作可以很快。然而,随着Web应用程序变得越来越强大,尤其一些新增加的功能例如:音频视频编辑,访问WebSockets的原始数据等,很明显有些时候如果使用JavaScript代码可以快速方便地通过类型化数组来操作原始的二进制数据将会非常有帮助。
  2. 类型化数组和普通数组:但是,不要把类型化数组与正常数组混淆,因为在类型数组上调用 Array.isArray() 会返回false。此外,并不是所有可用于正常数组的方法都能被类型化数组所支持(如 push 和 pop)。
  3. 缓冲和视图:
    缓冲:一个缓冲(由 ArrayBuffer 对象实现)描述的是一个数据块。缓冲没有格式可言,并且不提供机制访问其内容
    视图:为了访问在缓冲对象中包含的内存,你需要使用视图。视图提供了上下文 — 即数据类型、起始偏移量和元素数 — 将数据转换为实际有类型的数组。

  1. 缓冲(ArrayBuffer)

    ArrayBuffer 是一种数据类型,用来表示一个通用的、固定长度的二进制数据缓冲区。你不能直接操纵一个ArrayBuffer中的内容;你需要创建一个类型化数组的视图或一个描述缓冲数据格式的DataView,使用它们来读写缓冲区中的内容

  2. 类型数组视图
    类型化数组视图具有自描述性的名字和所有常用的数值类型像Int8,Uint32,Float64 等等。有一种特殊类型的数组Uint8ClampedArray。它仅操作0到255之间的数值。例如,这对于Canvas数据处理非常有用。

(1)这里可以说下 Uint8ClampedArray 和 Uint8Array的区别:就在于处理超出边界的值时有区别。
具体解析可以看stackoverflow链接

(2)用代码看看ArrayBuffer和类型数组

(3)转换为普通数组

  1. 数据视图(DataView)
    DataView 是一种底层接口,它提供有可以操作缓冲区中任意数据的读写接口。这对操作不同类型数据的场景很有帮助,例如:类型化数组视图都是运行在本地字节序模式(参考 Endianness),可以通过使用 DataView 来控制字节序。默认是大端字节序(Big-endian),但可以调用读写接口改为小端字节序(Little-endian)。

二. Blob对象

Blob: Binary Large Object 二进制类型的大对象

在Web中,Blob类型的对象表示不可变的类似文件对象的原始数据,通俗点说,Blob对象就是二进制数据,但它是类似文件对象的二进制数据,因此可以像操作File对象一样操作Blob对象,实际上,File继承自Blob
(1)通过代码认识Blob

(2)从Blob中提取数据

疑问:既然Blob有blob.arrayBuffer()方法来获取blob对象的ArrayBuffer,为啥还要使用FileReader?

回答:其实两个都可以获取到blob的arrayBuffer

三.ReadableStream

这部分可以直接看掘金文章:从 Fetch 到 Streams —— 以流的角度处理网络请求

网络原理知识记录(二)

发表于 2019-05-23

域名解析

一:域名服务器层级结构

根域名服务器(全球只有13台) 顶级域名服务器 二级域名服务器 主机名

www.gogokid.com

Com:顶级域名

gogokid:二级域名

www:主机名

域名解析请求发送到根域名服务器之后,根域名服务器将.com这个顶级域名地址返回,然后顶级域名服务器.com将.gogokid这个二级域名服务器地址返回,然后.gogokid这个二级域名服务器返回了www主机的ip,这个ip就是www.gogokid.com 域名对应的ip地址

二. 递归查询和迭代查询

递归查询和迭代查询的区别

(1)递归查询

递归查询是一种DNS 服务器的查询模式,在该模式下DNS 服务器接收到客户机请求,必须使用一个准确的查询结果回复客户机。如果DNS 服务器本地没有存储查询DNS 信息,那么该服务器会询问其他服务器,并将返回的查询结果提交给客户机。

(2)迭代查询

DNS 服务器另外一种查询方式为迭代查询,DNS 服务器会向客户机提供其他能够解析查询请求的DNS 服务器地址,当客户机发送查询请求时,DNS 服务器并不直接回复查询结果,而是告诉客户机另一台DNS 服务器地址,客户机再向这台DNS 服务器提交请求,依次循环直到返回查询的结果

为止。

从客户端到本地DNS服务器是属于递归查询,而DNS服务器之间就是迭代查询

javascript基础总结

发表于 2019-05-15

一. 继承

二. lazyMan 笔试题(题目自行上网搜索)

参考链接

  1. 使用队列和next调用下一个任务(同步方式处理)
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
class LazyMan {
constructor(name) {
this.tasks = [];
const task = name => {
console.log(`Hi! This is ${name}`);
this.next();
};
this.tasks.push(task);
setTimeout(() => {
this.next();
}, 0);
}
eat(food) {
const task = () => {
console.log('eat ' + food);
this.next();
};
this.tasks.push(task);
return this;
}
sleep(time) {
const task = () => {
setTimeout(() => {
console.log('Wake up after ' + time + 'ms');
this.next();
}, time);
};
this.tasks.push(task);
return this;
}
sleepFirst(time) {
const task = () => {
setTimeout(() => {
console.log('Wake up after ' + time + 'ms');
this.next();
}, time);
};
this.tasks.unshift(task);
return this;
}
next() {
const task = this.tasks.shift();
task && task.apply(this);
}
}

function lazyMan(name) {
return new LazyMan(name);
}

lazyMan('ff')
.eat('food')
.sleep(2000)
.eat('lunch')
.sleepFirst(1000);

// 通过next函数掉起下一个函数,实现sleep。如果使用promise实现sleep的话,返回的是一个promise对象而不是this
// 用一个队列存储函数,可以随意变化函数的执行顺序
// 每次调用eat或者sleep函数的时候只是将函数存入队列,而constructor里面的setTimeout才是开始从队列里面取出函数来执行。有的方式是通过一个end()函数来作为标志,开始从队列里面取出函数执行
  1. 使用promise(异步方式处理)
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
class LazyMan {
constructor(name) {
this.name = name;
this._preSleepTime = 0;
this.sayName = this.sayName.bind(this);
this.p = Promise.resolve()
.then(() => {
if (this._preSleepTime > 0) {
return this.holdOn(this._preSleepTime);
}
})
.then(this.sayName);
}
holdOn(time) {
return new Promise(resolve => {
setTimeout(() => {
console.log('Wake up after ' + time + 'ms');
resolve();
}, time);
});
}
sayName() {
console.log(`Hi! This is ${this.name}`);
return this;
}
eat(meal) {
this.p = this.p.then(() => {
console.log('eat ' + meal);
});
return this;
}
sleep(time) {
this.p = this.p.then(() => this.holdOn(time));
return this;
}
sleepFirst(time) {
this._preSleepTime = time;
return this;
}
}
new LazyMan('ff')
.eat('food')
.sleep(2000)
.eat('lunch')
.sleepFirst(1000);

// 注意promise then函数是微任务,所以会先执行eat sleep sleepFirst 再执行.then里面对_preSleepTime的判断
// 注意holdOn函数需要返回一个promise,否则sleep不会停留几秒钟,会立刻执行下面的eat函数
  1. 使用队列 + promise + setTimeout/async 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
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
class LazyMan {
constructor(name) {
this.name = name;
this.queue = [];
this.sayName();
// 下面这行和setTimeout功能类似:用来触发调用队列里面的函数
Promise.resolve().then(() => this.callByOrder(this.queue));
// 或者这样写
// setTimeout(async () => {
// for (let todo of this.queue) {
// await todo()
// }
// }, 0)
}

callByOrder(queue) {
let sequence = Promise.resolve();
this.queue.forEach(item => {
sequence = sequence.then(item);
});
}

sayName() {
this.queue.push(() => {
console.log(`Hi! this is ${this.name}!`);
});
return this;
}

holdOn(time) {
return () =>
new Promise(resolve => {
setTimeout(() => {
console.log(`Wake up after ${time} second`);
resolve();
}, time * 1000);
});
}

sleep(time) {
this.queue.push(this.holdOn(time));
return this;
}

eat(meal) {
this.queue.push(() => {
console.log(`eat ${meal}`);
});
return this;
}

sleepFirst(time) {
this.queue.unshift(this.holdOn(time));
return this;
}
}

new LazyMan('ff')
.eat('food')
.sleep(2)
.eat('lunch')
.sleepFirst(1);

总结:

  1. 维护顺序可以用队列,可以用promise.then
  2. 触发队列(promise.then)内事件执行可以用setTimeout或者promise.then
  3. 如果队列内函数全部是同步处理的话,直接可以用一个next函数来触发下一个函数。

​ 如果队列内函数有异步操作的话,需要将队列内函数分装成async await 函数以保证执行顺序。

关于Element和Node

发表于 2019-05-09

一. Element和Node的关系

EventTarget <=== Node <==== Elememt <==== HTMLElement <===== HTMLDivElement

上图显示的是继承关系,Node继承了EventTarget,Elememt继承了Node,等等。

由此可见Node是Element的父类。

Element的集合叫ElementCollection,Node的集合叫NodeList,他们都是类数组。

二. 继承Node的还有哪些?

继承Node类的除了Element还有TextNode,CommentNode等等。

简单的说就是Node是一个基类,DOM中的Element,Text和Comment都继承于它。
换句话说,Element,Text和Comment是三种特殊的Node,它们分别叫做ELEMENT_NODE,
TEXT_NODE和COMMENT_NODE。

所以我们平时使用的html上的元素,即Element是类型为ELEMENT_NODE的Node。

Node实例node有一个属性是nodeType,node.nodeType属性返回节点类型的常数值。不同的类型对应不同的常数值,12种类型分别对应1到12的常数值,他们都是继承了Node的某种特定Node类型。

元素节点   Node.ELEMENT_NODE(1)
属性节点   Node.ATTRIBUTE_NODE(2)
文本节点   Node.TEXT_NODE(3)
CDATA节点 Node.CDATA_SECTION_NODE(4)
实体引用名称节点 Node.ENTRY_REFERENCE_NODE(5)
实体名称节点  Node.ENTITY_NODE(6)
处理指令节点  Node.PROCESSING_INSTRUCTION_NODE(7)
注释节点   Node.COMMENT_NODE(8)
文档节点   Node.DOCUMENT_NODE(9)
文档类型节点  Node.DOCUMENT_TYPE_NODE(10)
文档片段节点  Node.DOCUMENT_FRAGMENT_NODE(11)
DTD声明节点 Node.NOTATION_NODE(12)

三. Node和Element的应用

1 方法名字里面有element的一般是返回的Element

比如 document.getElementById(‘main’);

2 children() 和 childNodes()区别

注意: $0 为 浏览器里面选中的dom元素

(1)$0.children 返回的是 子节点中element 元素的集合(不包括text和comment类型的node)
HTMLCollection(2) 长度为2 的HTMLCollection

(2)$0.childNodes 返回的是 所有子节点(包括element,text和comment等类型node)

NodeList(5) 长度为5的NodeList

3 firstChild() lastChild() 返回的是Node(不一定是Element,可能是Text或者Comment)

4 创建不同节点类型

1
2
3
4
5
6
var div = document.createElement('div');
var text = document.createTextNode('测试');

var attr = document.createAttribute('data-a');
attr.value = 'eeeee';
$0.setAttributeNode(attr); // 等效于 $0.setAttribute('data-a', 'eeeee');

关于javascript数字类型的底层表示和位运算

发表于 2019-05-09 | 分类于 javascript

一. 前言

javascript有6种基本类型(String,Number,Boolean,Null, Undefined,Symbol)。

其中Number是表示数字的唯一类型。C++有int,float,double, unsigned等各种数字类型,但是js只有一种数字类型,那就是Number。

在js中所有的数字都是双倍精度的浮点数类型,也就是c++中的double类型,每个数字使用64位(8个字节)存储。但是做有些运算的时候(位运算,parseInt()等 ),是以32位带符号的整数进行运算的,并且返回值也是一个32位带符号的整数。

二. 浮点型数字表示

查看浮点型数字存储表示

理解js 里面的 MAX_VALUE MIN_VALUE MAX_SAFE_INTEGER MIN_SAFE_INTEGER

注意:这些整数中可以连续、准确地表示的那些整数称为 安全的整数

三. 位运算(32位有符号整数)

位运算会先把数字(不管是2.1这样的小数, 还是2这样的整数)转换成32位有符号整数,然后再做操作,返回的也是32位有符号整数。

所有的按位操作运算符都会被转成补码(正数的补码和原码相同,负数的补码是原码除符号位取反之后再+1)

用二进制表示一个数字,一般我们习惯用原码表示。但是在运算的时候计算机会使用每个数字的补码表示来做运算

1
2
a>>b 有符号右移: 右移过程中保留符号位,左侧填充符号位,右侧被移出的位被丢弃。
效果:正数返回正数,负数返回负数。效果类似于除以2或者4或者8
1
2
a>>>b 无符号右移: 右移过程中不管符号位是什么,左侧填充0,右侧被移出的位被丢弃。
效果:返回的都是正数。(对于正数效果是除以2/4/8,对于负数返回了一个正数,不知道这个正数有啥用)
1
2
a<<b 左移: 左移不区分是否有符号,右侧填充0,左侧被移出的位被丢弃。
效果:正数返回正数,负数返回负数。效果类似于乘以2或者4或者8
1
2
~a 按位非(NOT):对数字的补码每一位取反(0变1,1变0)
效果:结果是a+1之后的相反数,比如~(-2) === 1,~(-1) === 0
1
2
a^b 按位异或(XOR): 按位将 0和1作用时, 不一样的得到1, 一样的得到0
效果:将任一数值 x 与 0 进行异或操作,其结果为 x。将任一数值 x 与 -1 进行异或操作,其结果为 ~x。
1
2
a | b 按位或(OR): 按位将 0和1作用时, 只要有一个1则返回1,否则返回0
效果:将任一数值 x 与 0 进行按位或操作,其结果都是 x。将任一数值x与-1进行按位或操作,其结果为 -1。
1
2
a & b 按位与(AND): 按位将 0和1作用时, 必须两个都为1才返回1,否则返回0
效果:将任一数值x与0执行按位与操作,其结果都为 0。将任一数值 x 与 -1 执行按位与操作,其结果都为 x。

上面操作之所以很多都与0和-1操作,是因为0在内存的补码表示全部是0,-1在内存的补码表示全部都是1。

0在内存的补码表示全部是0:00000000

-1在内存的补码表示全部都是1: 11111111

四. 位运算在前端中的运用

1 使用 & 运算判断 奇数偶数

1
2
3
4
5
6
// 偶数 & 1 = 0
// 奇数 & 1 = 1
console.log(2 & 1) // 0
console.log(3 & 1) // 1
console.log(-2 & 1) // 0
console.log(-3 & 1) // 1

2 使用 ~, >> , >>> , << , | 来取整

1
2
3
4
console.log(6.83 >> 0)  // 6
console.log(6.83 << 0) // 6
console.log(-6.83 << 0) // -6
console.log(-6.83 >> 0) // -6
1
2
// >>>不可对负数取整
console.log(6.83 >>> 0) // 6
1
2
console.log(~~ 6.83)    // 6
console.log(~~ -6.83) // 6

这里都是利用了运算会把数字变成32位有符号整数的特点

3 使用^来完成值交换

1
2
3
4
5
6
7
var a = 5
var b = 8
a ^= b
b ^= a
a ^= b
console.log(a) // 8
console.log(b) // 5

https证书

发表于 2019-04-08 | 分类于 http

参考链接1

参考链接2

node学习

发表于 2019-02-21 | 分类于 node

编译型语言和解释型语言区别

发表于 2019-02-21

参考链接

一. 编译型语言和解释型语言

计算机编程语言种类非常多,根据与计算机硬件贴近程度和抽象程度大致可分为3类,自顶向下分别是

高级语言(抽象层次更高的便于记忆和表示的英文代码)
​ |
​ |
汇编语言(抽象层次较高的对应机器硬件的cpu指令集,英文缩写的助记符号代码)
​ |
​ |
机器语言(抽像层次最低的由0、1序列所表示的机器码)

​ 众所周知,计算机底层只能识别(并执行)0、1序列的机器码,这表示所有的高级编程语言所编写的代码,最终都要以某种方式被转换成能被计算机识别的0、1序列机器码,方可被计算机接受并执行。这种将代码转换为机器码的方式可分为编译型和解释型这2类:

  1. 编译型:运行前先由编译器将高级语言代码编译为对应机器的cpu汇编指令集,再由汇编器汇编为目标机器码,生成可执行文件,然最后运行生成的可执行文件。最典型的代表语言为C/C++,一般生成的可执行文件及.exe文件。
  2. 解释型:在运行时由翻译器将高级语言代码翻译成易于执行的中间代码,并由解释器(例如浏览器、虚拟机)逐一将该中间代码解释成机器码并执行(可看做是将编译、运行合二为一了)。最典型的代表语言为JavaScript、Python、Ruby和Perl等

两种方式的异同之处:

<1>转换为机器码的时机和运行效率
​ 两种转换为机器码的方式,最大不同之处是处理转换为机器码的时机不同。编译型是在运行前(且只编译一次),需要做转换工作,并生成一个可执行的机器码文件,用户每次运行该可执行文件执行即可,效率、执行速度要比解释型的快。因为解释型是在运行时做转换工作,并不生成可执行文件,所以每次运行都需要做一下转换工作,然后再执行,效率自然就低了。

<2>可移植性
​ 编译型在编译后生成的可执行文件,是相对与本机机器指令集的,所生成的可执行文件移植到不同机器指令集的机器上,势必不一定能正常运行。而解释型的,尽管在不同配置的机器上,也是最终会解释成基于当前机器指令集的机器码并执行,故解释型的可移植性相对来说还是强于编译型的。

<3>纠错排查
​ 编译型语言,在编译阶段即可发现常见的语法或者链接等错误,此机制可在运行前帮助程序员排查出可能潜在的语法、语义和类型转换错误,编译型语言一般都有明确的变量类型检测,也被称作强类型语言,即编译型语言至少能确保所生成的可执行文件肯定是可运行的,至于执行的逻辑不对则属于程序员业务逻辑错误范畴了。
​ 而对于解释型语言,代码中的错误必须直到运行阶段方可发现,由此造成的困惑是:往往一段程序看不出问题但却在运行阶段错误连连且需要一个个排查:变量拼写错误、方法不存在等。

但也正是基于解释是在运行期执行转化的特性,一般的解释型语言通常都有自己的shell,可以在不确定某些执行结果时立即“动手执行”试一下,这就比每次都需要编译后才能运行并看到结果省去不少时间,鱼和熊掌不可兼得的理念再一次被验证了。

二. 鱼和熊掌可兼得吗?

综上所述,既然编译型与解释型各有优缺点又相互对立,所以一些语言都有把两者折衷起来的趋势:

​ Java为了实现跨平台的特性,专门在从高级语言代码转换至机器码过程的中间加入了一层中间层JVM(java虚拟机),Java首先依赖编译器将代码(.java)编译成JVM能识别的字节码文件(.class),然后由JVM解释并执行字节码,也可结合JIT(即时编译)技术,将解释生成的机器码转换为更高效的本地机器码,且该机器码可被缓存,来提高重复执行的效率。这样的结合,令程序员在使用Java时即可享受编译带来的强类型编译检查的好处,又能在执行时享受代码执行的高效性和跨平台性,何乐而不为呢?

​ Python是解释型语言,但为了效率上的考虑,也提供了编译方式,编译后生成的也是字节码的文件形式,并由Python的的VM(虚拟机)的去执行,这点可以说和Java的编译执行方式类似。不同点在于,Python的编译并非强制执行的操作,确切来说Python的编译是自动的,通常发生在对某个模块(module)的调用过程中,编译成字节码的可以节省加载模块的时间,以此达到提高效率的目的。可见,某些先进的高级语言在对编译和解释方面的拿捏舍去,都采取了一种:两手抓,两手都要硬的态度哦:)

12…6

yangXia

56 日志
14 分类
11 标签
© 2020 yangXia
由 Hexo 强力驱动
|
主题 — NexT.Pisces v5.1.4