排列组合算法公式JS版

之前做过的彩票项目中用到了排列和组合公式,分享给需要的人。

/**
         * 排列结果  包括重复内容
         */
        arrCPermute: function (arr, num) {
            var r = [];
            (function f(t, a, n) {
                if (n == 0) return r.push(t);
                for (var i = 0, l = a.length - n; i <= l; i++) {
                    f(t.concat(a[i]), a.slice(i + 1), n - 1);
                }
            })([], arr, num);
            return r;
        },
        /**
         *  排列结果  不包括重复内容
         */
        arrCPermute1: function (arr, num) {
            var r = [];
            (function f(t, a, n) {
                if (n == 0) return r.push(t);
                for (var i = 0, l = a.length; i < l; i++) {
                    f(t.concat(a[i]), a.slice(0, i).concat(a.slice(i + 1)), n - 1);
                }
            })([], arr, num);
            return r;
        },
        /**
         * 排列公式(从n个不同元素中取出m个元素的组合数)
         * @param n
         * @param m
         * @returns {number}
         */
        mathP: function (n, m) {
            var c = 1;
            for (var i = n - m; i < n; c *= ++i);
            return c;
        },
        /**
         * 组合公式(从n个不同元素中取出m个元素的组合数)
         * @param n
         * @param m
         * @returns {number}
         */
        mathC: function (n, m) {
            var n1 = 1,
                n2 = 1;
            for (var i = n, j = 1; j <= m; n1 *= i--, n2 *= j++);
            return n1 / n2;
        },

双色球计算组合时可以这样使用

lotNum = util.mathC(redNum, 5) * util.mathC(blueNum, 2); //红球的组合*篮球的组合

 

微信小程序手写签名组件

业务需求:在微信小程序内的商家入驻协议页面的下面设置一个空白区域,可以让用户在手机屏幕上进行手指滑动,从而实现手写签名的功能。

设计思路:
1 创建一个canvas对象
2 监听用户手指触摸事件bindtouchstart=”start”
3 开始触摸时保存坐标x,y
4 触摸移动时bindtouchmove=”move” 执行绘制操作ctx.stroke
5 保存图片通过wx.canvasToTempFilePath把临时路径返回出去用户上传接口使用

全部代码:

WXML

<view>
   <canvas 
    class="c-autograph"
    style="width:{{canvasW}}px;height:{{canvasH}}px;"
    canvas-id="autograph" 
    bindtouchcancel="cancel" 
    bindtouchmove="move" 
    bindtouchstart="start" 
    bindtouchend="end" 
    bindlongtap="longtap" 
    disable-scroll="true" 
    binderror="error"
    ></canvas>
</view>

JS

/*
 * @Author: liuxiaofan
 * @Description: v2.0.0
 * @Date: 2020-03-10 15:56:34
 * @LastEditors: liuxiaofan
 * @LastEditTime: 2020-04-20 15:40:55
 */
// 采坑: 如果html标签里面写了 type="2d",会导致真机空白
var ctx = null; //canvas实例
var touchs = [];
Component({
    properties: {

    },
    data: {
        canvasW: 140,
        canvasH: 56,
        lineColor: '#333', // 笔划的颜色
        LineWidth: 3, //笔划粗细
        count: 0 //画了几笔
    },
    ready() {
        this.autoWidth()
        this.init()
    },
    methods: {
        autoWidth() {
            const dpr = wx.getSystemInfoSync().pixelRatio // 获取设备的屏幕分辨率是2x还是3x
            let canvasW, canvasH;
            if (dpr >= 2) {
                canvasW = this.data.canvasW * 2
                canvasH = this.data.canvasH * 2
            }
            if (dpr >= 3) {
                canvasW = this.data.canvasW * 2.21
                canvasH = this.data.canvasH * 2.21
            }
            
            // 最近小高度100
            if (canvasH < 100) {
                canvasH = 100
            }
            
            if (canvasW < 280) {
                canvasW = 280
            }

            this.setData({
                canvasW,
                canvasH,
            })
        },
        init: function (options) {
            ctx = wx.createCanvasContext('autograph', this); // 如果是组件的形式,第2个参数要写this
            ctx.setStrokeStyle(this.data.lineColor);
            ctx.setLineWidth(this.data.LineWidth);
            ctx.setLineCap('round'); // 笔触形态
            ctx.setLineJoin('round'); // 交叉点形态
        },
        // 画布的触摸移动开始手势响应
        start: function (event) {
            // console.log("触摸开始" + event.changedTouches[0].x);
            // console.log("触摸开始" + event.changedTouches[0].y);
            //获取触摸开始的 x,y
            let point = { x: event.changedTouches[0].x, y: event.changedTouches[0].y }
            touchs.push(point);
        },
        // 画布的触摸移动手势响应
        move: function (e) {
            let point = { x: e.touches[0].x, y: e.touches[0].y }
            touchs.push(point);
            if (touchs.length >= 2) {
                this.draw(touchs);
            }
        },
        // 画布的触摸移动结束手势响应
        end: function (e) {
            this.setData({
                count: this.data.count + 1
            })
            // console.info("触摸结束" + this.data.count);
            //清空轨迹数组
            for (let i = 0; i < touchs.length; i++) {
                touchs.pop();
            }

        },
        // 画布的长按手势响应
        longtap: function (e) {
            // console.log("长按手势" + e);
        },
        cancel() { },
        error: function (e) {
            // console.log("画布触摸错误" + e);
        },
        //绘制
        draw: function (touchs) {
            let point1 = touchs[0];
            let point2 = touchs[1];
            // console.info(point1, point2)
            touchs.shift();
            ctx.moveTo(point1.x, point1.y);
            ctx.lineTo(point2.x, point2.y);
            ctx.stroke();
            ctx.draw(true);
        },
        //清除操作
        clearClick: function () {
            //清除画布
            ctx.clearRect(0, 0, 750, 700);
            ctx.draw(true);
            this.setData({
                count: 0
            })
        },
        //保存图片
        saveClick: function (callback) {
            if (this.data.count < 3) {
                wx.showModal({
                    title: '提示',
                    content: '签名不符合规范,请重新签名',
                    showCancel: false
                })
            } else {
                wx.canvasToTempFilePath({
                    canvasId: 'autograph',
                    fileType: 'jpg',
                    destWidth: 800,
                    destHeight: 500,
                    success: function (res) {
                        callback && callback(res.tempFilePath) //把图片路径回调出去
                    },
                    fail(res) {
                        console.info('保存失败', res)
                    }
                }, this)
            }

        }
    }
})

在父组件内的使用方式

this.selectComponent("#autograph").saveClick(function (tempFilePath) {
           that.creatMerchant(tempFilePath)

       })

 

将NodeList(类数组)转换为数组(Array)

原生DOM元素通常使用document下的querySelectorAll、getElementsByClassName等方法获取。
这样的DOM对象被称为NodeList。不能使用数组的方法进行操作,例如forEach()。

NodeList 不是一个数组,是一个类似数组的对象(Like Array Object)。虽然 NodeList 不是一个数组,但是可以使用 Array.from() 将其转换为数组。
除了Array.from之外还可以使用下面3种方式转换

  • Array.apply(null,contentsDom)
  • Array.prototype.slice.call(contentsDom)
  • […contentsDom]

代码示例:

let num = 20
let html = ''
for (var i = 0; i < num; i++) {
    html += `<p>${i + 1}</p>`
}
let contentsDom = document.getElementsByClassName('content')
let contentsArr=Arry.from(contentsDom)
let contentsArr=Array.apply(null,contentsDom)
let contentsArr=Array.prototype.slice.call(contentsDom)
let contentsArr = [...contentsDom]

contentsArr.forEach(item => {
    item.innerHTML = html
})

vue3 新特性概览

之前看过尤雨溪关于VUE3介绍的一个视频直播,下面总结了一下直播里面提到的内容。

Performance 性能提升
模板编译的优化;
优化了Slots操作,把函数传给子组件让子组件自己决定更新哪个节点;
静态内容提取,直接渲染不需要转化;
重点是proxy;全语言特性支持(支持对象、数组、集合、字典的增删)
proxy的模式叫 lazy by default 就是只有数据被用到的时候才会被监听,使得实例的初始化快了1倍,内存减半。
同时尽最大的努力向下兼容2.0

Tree shaking support
按需引入,移除无用代码使得runtime体积更小。比如只有你使用了v-mode的时候才会引入v-mode相关的代码。体积由原来的20k可以简化到10k。

Better Typescript support
全部用TS重写,对TS支持更好。
内部的模块清晰的解耦,降低了阅读源码的难度
插件化设计 比如v-for、v-if等
带位置信息的parser(source map)
包括原生的Class API和TSX

更好的多端渲染支持Custom Renderer API
比如从runtime-core里面去自定义需要的功能,比如修改dom相关的模块,对于使用vue去开发小程序之类的用户更加友好。

响应式数据监听 API —— 封装了一个观察者对象
首先引入vue下的observable对象
把需要监听的数据传入observable,比如:
const state = observable({
count:0
})
然后每次count发生改变的时候,就可以通过一个effect方法就监听count的变化

renderTriggered 轻松排查组件更新的原因
vue组件里面写一个renderTriggered方法,然后在里面写一个debugger。这样每次组件更新之后就会把具体的信息通过event参数返回出来。
例如修改了count的值1为2。导致了组件更新,那么event里面就会有一个对象,还会告诉你具体的哪一行代码触发了这次更新。
{
key:count,
oldValue:1,
newValue:2
}

experimental Hooks API
希望能把Hooks 以一种取代mixins的逻辑复用的机制引入到vue中

experimental Time slicing Support
因为Js的单线程机制,如果一次代码块过大的话就会发生阻塞。例如我一次渲染200个dom需要200毫秒,那么这期间,用户的点击操作里面的回调函数就不会马上被执行,页面上就能感觉到明显的卡顿。time slicing的原理就是把JS代码块切成一帧一帧的形式,比如16毫秒执行一次,这样就可以在每次执行的期间来执行用户的异步操作了。

Fragment 支持DOM的多节点
Teleport 就相当于React的Portal允许把DOM渲染的其他节点
Suspense 包裹节点实现异步等待

Composition API
写法更简洁,数据和方法都是放入到setup函数里面,并返回给setup。
思路更清晰,相同业务的代码都放到同一个函数里面。可读性更好。

setTimeout和setInterval的区别

先看一下他们两个的概念描述:

超时调用(setTimeout):在指定的毫秒数后调用函数或计算表达式。
setTimeout(func, 1000); // func执行的函数,1000毫秒

间歇调用(setInterval):按照指定的周期(以毫秒计)来调用函数或计算表达式
setInterval(func, 1000);

最大的区别:

超时调用会在前面的代码执行完毕之后,新建一个计时器若干秒后执行回调函数;

而间歇调用是先创建一个计时器,然后每隔一段时间就执行一次回调,不管回调函数里面的代码执行的快还是慢;

间歇调用的缺点就是因为计时器的时间固定写死了,如果回调函数里面的代码执行时间超过了计时器时间就会出问题,这个种情况在工作中很常见。
所以我平时很少用间歇调用。如果想实现一个间歇调用的效果可以用setTimeout模拟。因为setTimeout每次都是新建一个计时器执行后再新建一个计时器就可以解决这个问题,例如这样:

var interval = 200;
setTimeout(function () {
// 处理代码
setTimeout(arguments.callee, interval); //递归调用自身
}, interval)

闭包概念的深入理解

MDN上面对于闭包的定义:一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来。

说的直白一点就是闭包不仅仅只是函数本身,也包括一切与它相连的变量的一个闭包体。ES6的let语法出现之前,JS语言是没有块级作用域的,只有全局作用域和函数作用域。而其他语言例如JAVA和PHP都有。所谓块级作用域,比如代码块for循环或if等等。

先看一下下面这段代码:

// 父级函数foo
function foo(x) {
    var z = 3 // 父级函数内部的变量z
    // 返回一个匿名子函数
    return function (y) {
        // 子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar
        console.log(x + y + z)
    }
}
var bar = foo(2) //
bar(3)

我们来慢慢解释一下上面的代码:
首先我们定义一个函数foo,然后在里面定义一个变量z,然后返回一个匿名函数。这个匿名函数就是foo的子函数。
然后在最外面执行foo这个函数并把返回值,也就是里面的那个匿名子函数给到变量bar
最后执行bar这个函数,参数3就是y

先看一下匿名子函数里面能访问的变量一共有哪些。
因为是最内层,所以子函数内部可以访问的变量有自己作用域的参数y和父级函数内定义的变量z和父级函数的参数变量x,还有最外层的全局变量bar

像这种情况,赋值到bar变量的那个匿名的子函数就是一个闭包体。它的词法环境就包括了函数本身和外层的z变量和x参数变量。

接下来说一下JS的回收机制。
当一个函数执行完毕之后就会把它和它里面定义的变量一同销毁。比如上面的foo函数执行之后里面定义的z变量就会被销毁。
但是由于我们已经把闭包体赋值给了bar这个变量,所以就算执行foo(2),里面的变量z也不会被销毁。因为闭包体被变量bar引用到,除非把变量bar也销毁,z才会被释放。这就是闭包会引起内存泄漏的原因。

闭包的缺点就是会引起内存泄漏。那么闭包有什么用?
最常见的用途就是函数柯里化,请参考我的另一篇文章,专门介绍什么是函数柯里化。

原生JS实现AJAX

快速理解AJAX

1. 创建一个XMLHttpRequest实例
2. 通过open方法,填入请求类型,地址,同步还是异步
3. 设置请求头信息,比如内容格式为json
4. 通过send方法发送请求数据
5. 通过onreadstatechange方法获取响应数据,包括状态码和data

var xhr = new XMLHttpRequest(); //ajax实例化
xhr.open(ajaxData.type, ajaxData.url, ajaxData.async); // post,xxx.com/api,sync
xhr.setRequestHeader("Content-Type", ajaxData.contentType); // json
xhr.send(convertData(ajaxData.data));
xhr.onreadystatechange = function () {
    if (xhr.readyState == 4) {
        if (xhr.status == 200) {
            ajaxData.success(xhr.response)
        } else {
            ajaxData.error()
        }
    }
}

快速理解移动端REM适配方案

我写这篇文章的时间是2020年,为什么要写上时间,因为我们知道前端技术更新的很快,可能过几年这篇文章里面介绍的技术就淘汰了。
目前主流的移动端适配方案有2种:媒体查询和rem。
不过媒体查询的方式更适合做响应式布局,所以从2017至今,移动端的设配方案就是使用rem。通常VUE项目中会安装flexible和postcss-px2rem两个插件来自动计算。
我相信有的人只是会用这2个插件,但是还没有认真了解过其原理。下面我就用最简短的说明来介绍一下其中的原理,帮助您快速理解。

首先我们知道rem是相对于html根元素的单位,默认情况html的font-size是16px,所以 1rem = 16px;

那么得出来,pc端设计稿的话如果30px换成rem的公式就是 30/16=1.8rem(目标单位/根字体单位);

为了适配不同的屏幕大小,在不同大小的屏幕上面,字体也要随着改变,所以我们不能只用16px,而需要动态的设定根字体的单位;

例如现在移动端的设计稿普遍使用ihpone6作为标准,也就是宽度为375,为了计算方便,首先用js写一个函数把html的字体单位设为375/10,也就是37.5,像这样:

let htmlDom = document.getElementsByTagName('html')[0]
let setFontSize = function () {
let htmlWidth = document.documentElement.clientWidth //(viewport宽度)
htmlDom.style.fontSize = htmlWidth / 10 + 'px' //iphone6=37.5
}
setFontSize()

具备了动态改变根字体的能力,只要给文字或者容器宽高的值设定rem就可以实现适配了;

那么根据设计稿的要求我们具体应该设置多少rem呢?这里有个公式,也非常简单,就是设计稿标注的像素值除以根字体的值就得出了rem的值;

基于iPhone6的设计稿某个文字标注30px。那么它的rem值 就是30/37.5 = 0.8rem。 所以在css中的字体大小就要写.8rem。

看到这里,大家可以尝试一下在chrome的调试看看,不过要注意一点就是,iphone6的屏幕是2x屏,所以在算rem的时候需要把30px看做30/2也就是15px,然后再用15/37.5。现在业界已经默认都使用Iphone6最为移动端UI的标准了。

但是每次写rem之前都得自己用计算器除一次也太麻烦了,所以我们可以写一个scss的函数来帮我们这样就方便很多,例如像下面这样使用。

@function px2rem($px) {
$rem: 37.5px; //iphone 6
@return ($px/$rem) + rem;
}
.box {
font-size: px2rem(15px);
}

为了方便编译scss文件,可以使用webpack来打包编译或则其他方式。

最后我写了一个小极简的demo,大家可以下载到本地运行一下加深理解。

https://github.com/yjxf8285/rem-demo

JS事件循环机制

eventloop重点:
同步任务先执行,然后是异步任务
异步任务又分宏和微
宏任务 setTimeout 最低
微任务 promise 优先级高

console.log(1); // 同步任务

(function(){ // 同步任务
console.log(2)
})();

setTimeout(function () { // 宏任务
console.log(3)
});

new Promise(function (a, b) { // 微任务
console.log(4);
a()
}).then(function () {
console.log(6)
});

console.log(7) // 同步任务

//执行顺序为 1 2 4 7 6 3

 

 

js 多重条件过滤

假设一组JSON数据中,需要你根据某一个值去筛选出来当前值所在的对象,最基本的做法:
遍历数组然后判断这个值是否等于你要查找的那个值,如果等于就返回当前遍历的节点对象。

那么问题来了,如果是5个条件呢,你要嵌套遍历5次吗?那如果是10个条件呢?嵌套遍历比较笨,而且性能也不好,所以使用es6的filter方法更好。

思路是先定义个空对象来保存筛选条件,然后再遍历数组,然后通过比较数组的数组项和条件对象的健值,找到后返回。

// 多重过滤
function multiFilter(array, filters) {
    const filterKeys = Object.keys(filters) // 先把筛选条件保存下来
    // 主要是用filter方法来过滤
    const res = array.filter((item) => {
        const isHave = filterKeys.every((key) => {
            let targetValue = filters[key] // 条件的值
            let originValue = item[key] // 元数据的值
            if (!targetValue) { // 如果条件为空就返回对象
                return true
            } else {
                if (originValue === targetValue) { // 否则返回条件相等的的对象
                    return true
                } else {
                    return false
                }
            }
        })
        return isHave
    })
    return res
}