排列组合算法公式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)

       })

 

粘性定位

postion 属性值共有6个:

  1. position: static // 默认值
  2. position: inherit // 继承父元素的postion值
  3. position: relative // 相对定位,遵循文档流
  4. position: absolute // 绝对定位,脱离文档流
  5. position: fixed // 固定定位
  6. position: sticky // 粘性定位

sticky是CSS规范中新加入的一个属性值。
这个属性值的作用表现为当一个列表元素位于一个可滚动区域内时,当滚动到特定的位置,例如顶部的时候,就会触发fixed特性,固定在顶部这样。
粘性定位的应用场景随处可见,例如segmentfault的文章页面中,顶部的导航条就使用了sticky。或者很多新闻类的app里面的粘性标题栏等等。

代码示例:

HTML

<main>
       <header>标题一</header>
       <div class="content">
       </div>
       <header>标题二</header>
       <div class="content">
       </div>
       <header>标题三</header>
       <div class="content">
       </div>
       <header>标题四</header>
       <div class="content">
       </div>
   </main>

CSS

main {
    width: 400px;
    height: 300px;
    margin: 200px auto;
    overflow: auto;
}
header {
    position: sticky;
    top: 0;
    height: 30px;
    background-color: blanchedalmond;
}

JS

let num = 20
let html = ''
for (var i = 0; i < num; i++) {
    html += `<p>${i + 1}</p>`
}
let contentsDom = document.getElementsByClassName('content')
let contentsArr = [...contentsDom]
contentsArr.forEach(item => {
    item.innerHTML = html
})

 

将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
})

LeetCode: 206 反转链表

示例:
1->2->3->4->5->null
5->4->3->2->1->null

解题思路:
1. 先思考如何反转2个节点,将n+1的next指向n
2. 双指针遍历链表,然后重复上面的动作

解题步骤:
1. 双指针遍历
2. 反转双指针

var reverseList = function (head) {
    let p1 = head
    let p2 = null
    while (p1) {
        const tmp = p1.next
        p1.next = p2
        p2 = p1
        p1 = tmp
    }
    return p2
};

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。
思路更清晰,相同业务的代码都放到同一个函数里面。可读性更好。

快速了解Webpack

本文的目的就是让读者对webpack快速的有一个整体的认识,了解其主要的功能。如果对其中某一项感兴趣,详细的使用方式建议查阅webpack官网文档。

首先了解一下gulp与webpack的区别
gulp:强调流程,通过配置一系列的task,定义task处理的事物(例如文件压缩合并、雪碧图、启动server、 版本控制等),然后定义执行顺序,来让gulp执行task,从而构建前端项目的流程。
webpack:是一个前端模块化方案,侧重模块打包,把开发中的所有资源(图片、js文件、css文件等)都看成模块,通过loader(加载器)和plugins(插件)对资源进行处理,打包成符合生产环境部署的前端资源。
相同:可以文件合并与压缩(css)
不同点:
虽然都是前端自动化构建工具,但看他们的定位就知道不是对等的。
gulp严格上讲,模块化不是他强调的东西,他旨在规范前端开发流程。
webpack更是明显强调模块化开发,而那些文件压缩合并、预处理等功能,不过是他附带的功能。

webpack五个核心概念
1. Entry – 如何,以哪个文件为起点开始打包,分析构建内部依赖图。
2. Output – 把bundles输出到哪里,以及如何命名。
3. Loader – webpack默认只能处理JS,如果需要处理其他必须html/css/img等资源就需要加载对应的Loader。相当于翻译官
4. Plugins – 做一些功能更复杂的操作,比如压缩代码,定义环境变量,优化插件,语法检查,dll等
5. Mode – development 本地调试,简单配置,能运行即可。production – 配置各种优化项,保证上线之后的性能

devServer
自动编译,自动打开浏览器,自动刷新

性能优化
## 开发环境
优化打包构建速度 – HMR
优化代码调式 – sourceMap(压缩后的js文件可以映射到源文件,快速找到代码行)
## 生成环境
打包速度
代码运行的性能 – oneOf、缓存、tree shanking、代码分割、懒加载、PWA、多进程打包、external、dll。

HMR(hot module replacement) – 热模块替换
监听细微变化进行局部更新
devServer:{
hot:ture
}

oneOf
rules配置项下面把所有的loader都放到oneOf数组里面,性能更好

缓存
只编译代码改变了的模块,其他模块开启babel缓存
rules下的babel-loader配置cacheDirectory:true
文件资源缓存,根据chunk生成hash值
content hash:根据文件的内容生成hash值

tree shaking
去除那些没有使用的代码
前提:
* 必须es6的模块化方案
* 开启production环境
package.json中配置
sideEffects:[“*.css”] // 保证css不会被摇掉

code split – 代码分割
entry里面配置多个入口,单页面还是多页面。如果所有的文件都在一个bundle里面的话bundle体积就会非常大。所以要把bundle拆开多个小文件。
optimization:{
splitChunks:{
chunks:’all’
}
}

懒加载
先进行代码分割,再把要加载的文件import的时候加上webpackChunkName 标识
预加载的标识为 webpackPrefetch:ture
import(/* webpackChunkName:’test’, webpackPrefetch:true */ ‘.test’).then(({mul}) = > {
console.log(mul(4, 5))
})

PWA (Progressive web apps,渐进式 Web 应用)
* Discoverable, 内容可以通过搜索引擎发现。
* Installable, 可以出现在设备的主屏幕。
* Linkable, 你可以简单地通过一个URL来分享它。
* Network independent, 它可以在离线状态或者是在网速很差的情况下运行。
* Progressive, 它在老版本的浏览器仍旧可以使用,在新版本的浏览器上可以使用全部功能。
* Re-engageable, 无论何时有新的内容它都可以发送通知。
* Responsive, 它在任何具有屏幕和浏览器的设备上可以正常使用——包括手机,平板电脑,笔记本,电视,冰箱,等。
* Safe, 在你和应用之间的连接是安全的,可以阻止第三方访问你的敏感数据。
workboxWepackPlugin

多进程打包
npm i thread-loader -D
在babel-loader外面套一个thread-loader
缺点:进程启动大概为600ms,进程开销比较大。
适用于工作消耗时间比较长的编译loader,比如编译js的babel-loader相对css就比较耗时。

externals
忽略一些包被打包进来,注意如果要使用这个库就要在html文件里面的script标签里面引用
externals:{
jquery:’jQuery’
}

dll – 和externals类似,可以将某些第三方依赖包统一打包到dll文件中。
把一些固有的库都先打包到dll文件里面。这样打包1次后面就不用每次都打包了。可以让第2次打包的过程变快。提升构建速度。

二分查找

题目:例如这样一个[1,3,4,5,7]有序的数组,如何快速找到某个值的下标?
这是一个经典的二分查找算法题。

解题思路:

  1. 先找到数组的中间下标值
  2. 然后判断目标值如果大于中间值,就在数组的右半边继续查找
  3. 如果目标值小于中间值,就在数组的左半边继续查找
  4. 然后不断的重复上面的步骤。

时间复杂度是O(logN) 因为遍历减半了就是n的一半=logN,如果遍历了全部就是N

let binarySeach = function (arr, target) {
    let s = 0 // 开始下标
    let e = arr.length - 1 //结束下标
    while (s <= e) { // 如果开始下标小于等于结束下标就标识区间内有值
        let mid = Math.floor((s + e) / 2) //当前数组的中间值下标
        let item = arr[mid] // 中间值
        if (item < target) { //如果中间的值小于目标值
            s = mid + 1 // 把开始下标移动到中间值的后面,在2叉树的右边继续搜索了
        } else if (item > target) { //如果中间值大于目标值
            e = mid - 1 // 把结束下标移动到中间值的前面一位,在2叉树的左边数组继续搜索
        } else { // 如果中间值等于目标值直接返回当前下标
            return mid
        }
    }
    return -1 // 找不到就返回-1
}
let res = binarySeach([1, 2, 3, 4, 5], 2)
console.log(res)

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才会被释放。这就是闭包会引起内存泄漏的原因。

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