文章目录
学习JS(2)
从代码本身写法,解决问题上看。
看一段代码好不好
//判断一个mat2d矩阵是否是单位矩阵
function isUnitMatrix2d(m){
return m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0;
}
依次判断六个点,没有用循环,在图形渲染中,调用多,因此性能最好。
get layerTransformInvert() {
if(this[_layerTransformInvert]) return this[_layerTransformInvert];
const m = this.transformMatrix;
if(m[0] === 1 && m[1] === 0 && m[2] === 0 && m[3] === 1 && m[4] === 0 && m[5] === 0) {
return null;
}
this[_layerTransformInvert] = mat2d.invert(m);
return this[_layerTransformInvert];
}
这是一段真实的代码https://github.com/spritejs/spritejs/blob/master/src/node/layer.js#L131
每一帧都需要判断,用最快的方式。
需要看使用场景,关注什么
风格;效率;约定;使用场景;设计;…
当年的 Left-pad 事件
用来补齐字符串位数模块,后来被作者下了,很多依赖它的常见的npm库用不了了。
事件本身的槽点:
NPM模块粒度;(这么小的功能模块化)
代码风格;
代码质量/效率.
考虑了String转换,判断占位符,while循环补占位符,
二次幂的方法补齐性能更好,用位数循环O(logn)复杂度,代码更简洁,效率提升;
运用repeat函数。
repeat:
结构:
再优化,与它的-1进行&=运算,二进制数1的个数有几个循环几次,性能更好。
需要补的位数不是很大,没有必要性能提升。(Repeat polyfill / MDNhttps://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/repeat)
不做过度的优化和设计,增加复杂度。
交通灯-状态切换
异步
实现一个切换多个交通灯状态切换的功能
版本一(不好)
用多个setTimeout嵌套,但是异步函数嵌套多了会有回调的问题。
如果用setInterval的话,时间不一样不可以用一个setInterval来实现。
数据抽象也不好。
const traffic = document.getElementById('traffic');
(function reset(){
traffic.className = 's1';
setTimeout(function(){
traffic.className = 's2';
setTimeout(function(){
traffic.className = 's3';
setTimeout(function(){
traffic.className = 's4';
setTimeout(function(){
traffic.className = 's5';
setTimeout(reset, 1000)
}, 1000)
}, 1000)
}, 1000)
}, 1000);
})();
版本二(比较好)
数据抽象,数据封装,用start方法。
如果要添加新的数据修改stateList即可。
const traffic = document.getElementById('traffic');
const stateList = [
{state: 'wait', last: 1000},
{state: 'stop', last: 3000},
{state: 'pass', last: 3000},
];
function start(traffic, stateList){
function applyState(stateIdx) {
const {state, last} = stateList[stateIdx];
traffic.className = state;
setTimeout(() => {
applyState((stateIdx + 1) % stateList.length);
}, last)
}
applyState(0);
}
start(traffic, stateList);
版本三
过程抽象,循环;将循环过程写成setState方法当作主体;可以将traffic封装进去。不太容易理解
const traffic = document.getElementById('traffic');
function wait(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
function poll(...fnList){
let stateIndex = 0;
return async function(...args){
let fn = fnList[stateIndex++ % fnList.length];
return await fn.apply(this, args);
}
}
async function setState(state, ms){
traffic.className = state;
await wait(ms);
}
let trafficStatePoll = poll(setState.bind(null, 'wait', 1000),
setState.bind(null, 'stop', 3000),
setState.bind(null, 'pass', 3000));
(async function() {
// noprotect
while(1) {
await trafficStatePoll();
}
}());
版本四(√)
更符合直觉的思路,既优雅又不违背直觉和思维习惯。
setState/wait交替执行,set是瞬间执行的,wait是异步执行的。
const traffic = document.getElementById('traffic');
function wait(time){
return new Promise(resolve => setTimeout(resolve, time));
}
function setState(state){
traffic.className = state;
}
async function start(){
//noprotect
while(1){
setState('wait');
await wait(1000);
setState('stop');
await wait(3000);
setState('pass');
await wait(3000);
}
}
start();
代码
HTML
<ul id="traffic" class="wait">
<li></li>
<li></li>
<li></li>
</ul>
CSS
#traffic {
display: flex;
flex-direction: column;
}
#traffic li{
display: inline-block;
width: 50px;
height: 50px;
background-color: gray;
margin: 5px;
border-radius: 50%;
}
#traffic.stop li:nth-child(1) {
background-color: #a00;
}
#traffic.wait li:nth-child(2) {
background-color: #aa0;
}
#traffic.pass li:nth-child(3) {
background-color: #0a0;
}
判断是否是4的幂
版本一
如果是4的幂可以不断被4整除,时间复杂度为logn。
版本二
优化:直接右移两位,不用判断是否能被4整除,如果能被4整除最后两位为00,因此能用11&来判断。
版本三
js有效精度的整数53位。
时间复杂度从logn降到常数。
有效精度的整数有限,如果是4的幂,转为二进制后的数最高位是1,后面都是0,并且是偶数个0(跟1010&,考虑精度:0xAAAAAAAA)。
版本四
转换为字符串,用正则表达式判断。转为字符串不会太长,性能也没问题。
洗牌
数组随机次序
不正确版本
上面的例子事实上统计发现,越小的数出现在前面的概率越高,越大的数出现在后面的概率越高。
这个算法不是一个等概率分布的算法,前后有一半的概率交换,但是还是前面的数倾向于在前面。
正确性验证:
将上述方法执行一百万次,将各数分别相加,发现整体越往后的数越大。
因此这个方法不正确。
实现版本
洗牌算法要保证正确性,每次随机从剩下的牌中挑出一个,第一次从n张牌中随机抽一张放在第一位,第二次从剩下n - 1 张牌中随机抽一张放在第二位,……
时间复杂度O(n)的算法
也进行一百万次的结果相加,可以看出每一位基本可以看做数值相等,符合预期。
抽牌
不用全部洗一遍,只抽取少量的牌,计算量小,性能更高。
分红包
版本一
每一次选大的红包进行切分,相对来说比较平均,差别不大。
function generate(amount,count){
let ret = [amount];
while(count > 1){
//挑选出最大一块进行切分
let cake = Math.max(...ret ),
idx = ret.indexOf(cake),
part = 1 + Math.floor((cake / 2) * Math.random()),
rest = cake - part;
ret.splice( idx,1,part,rest );
count--;
}
return ret;
}
版本二
随机选择n - 1个数分成n段。
function * draw(cards){
const c = [...cards];
for(let i = c.length; i > 0; i--) {
const pIdx = Math.floor(Math.random() * i);
[c[pIdx], c[i - 1]] = [c[i - 1], c[pIdx]];
yield c[i - 1];
}
}
function generate(amount, count){
if(count <= 1) return [amount];
const cards = Array(amount - 1).fill(0).map((_, i) => i + 1);
const pick = draw(cards);
const result = [];
for(let i = 0; i < count; i++) {
result.push(pick.next().value);
}
result.sort((a, b) => a - b);
for(let i = count - 1; i > 0; i--) {
result[i] = result[i] - result[i - 1];
}
return result;
}
抽牌思路相似
总结
这里对文章进行总结:
以上就是今天的笔记内容,本文仅仅简单从代码上通过几个例子讨论JS的写法,更多有关前端的知识参考后续文章。