前端那些你不知道的小技巧(JS篇)

351 阅读6分钟
原文链接: liaolongdong.com

前端那些你不知道的小技巧(JS篇)

用一行代码实现五星评价功能

// 评价5星
var rate = 5;

var result = '★★★★★☆☆☆☆☆'.slice(5 - rate, 10 - rate);

console.log(result); // ★★★★★

统计字符串中相同字符出现的次数

var str = 'aaabbbccc66aabbc6';

var strInfo = str.split('').reduce((p, c) => (p[c]++ || (p[c] = 1), p), {});

console.log(arrInfo); // {6: 3, a: 5, b: 5, c: 4}

数组去重

方法一:使用Set

var arr = [1, 2, 3, 2, 6, '2', 3, 1];

var resultArr = [...new Set(arr)]; // Array.from(new Set(arr));

console.log(resultArr); // [1, 2, 3, 6]

方法二:结合使用数组filter方法和indexOf()方法

var arr = [1, 2, 3, 2, 6, '2', 3, 1];

function uniqueArr (arr) {
    return arr.filter(function (ele, index, array) {
        // 利用数组indexOf()方法,返回找到的第一个值的索引
        // 如果数组元素的索引值与indexOf方法查找返回的值不相等,则说明该值重复了,直接过滤掉
        return array.indexOf(ele) === index;
    })
}

console.log(uniqueArr(arr)); // [1, 2, 3, 6, "2"]

将类数组对象转成数组

方法一:利用每个函数都包含call()apply()方法,配合使用数组的slice()方法

var likeArrObj = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
}

var arr1 = Array.prototype.slice.call(likeArrObj); // 或者使用[].slice.call(likeArrObj);
var arr2 = Array.prototype.slice.apply(likeArrObj); // 或者使用[].slice.apply(likeArrObj);

console.log(arr1); // [1, 2, 3]
console.log(arr2); // [1, 2, 3]

方法二:使用ES6的Array.from()或者扩展运算符...

var likeArrObj = {
    0: 1,
    1: 2,
    2: 3,
    length: 3
}

var arr = Array.from(likeArrObj);
// var arr1 = [...likeArrObj]; // likeArrObj is not iterable

console.log(arr); // [1, 2, 3]

注意:ES6 中的扩展运算符…也能将某些数据结构转换成数组,但是这种数据结构必须是可迭代的对象

用一行代码将多维数组转成一维数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

var resultArr = arr.toString().split(',').map(Number);

console.log(resultArr); // [1, 2, 2, 3, 4, 5, 5, 6, 7, 8, 9, 11, 12, 12, 13, 14, 10]

将数组扁平化并去除其中重复数据,最终得到一个升序且不重复的数组

var arr = [ [1, 2, 2], [3, 4, 5, 5], [6, 7, 8, 9, [11, 12, [12, 13, [14] ] ] ], 10];

var resultArr = Array.from(new Set(arr.toString().split(',').map(Number))).sort((a, b) => (a - b));

console.log(resultArr); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]

数字金额千分位格式化

方法一:使用Number.prototype.toLocaleString()

var num = 123455678;
var num1 = 123455678.12345;

var formatNum = num.toLocaleString('en-US');
var formatNum1 = num1.toLocaleString('en-US');

console.log(formatNum); // 123,455,678
console.log(formatNum1); // 123,455,678.123

方法二:使用正则表达式

var num = 123455678;
var num1 = 123455678.12345;

var formatNum = String(num).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
var formatNum1 = String(num1).replace(/\B(?=(\d{3})+(?!\d))/g, ',');

console.log(formatNum); // 123,455,678
console.log(formatNum1); // 123,455,678.12,345

用一行代码实现一个简易的模板字符串功能

String.prototype.render = function (context) {
    return this.replace(/\{\{(.*?)\}\}/g, (match, key) => context[key.trim()]);
};

// 用户信息对象
var userInfo = {
    name: 'Better',
    position: 'front-end engineer'
}
'我叫,是位'.render(userInfo); // 我叫Better,是位front-end engineer

小数取整

var num = 123.123

// 常用方法
console.log(parseInt(num)); // 123
// “双按位非”操作符
console.log(~~ num); // 123
// 按位或
console.log(num | 0); // 123
// 按位异或
console.log(num ^ 0); // 123
// 左移操作符
console.log(num << 0); // 123

交换两个值

方法一:利用一个数异或本身等于0和异或运算符合交换率

var a = 3;
var b = 4
a ^= b; // a = a ^ b
b ^= a;
a ^= b;

console.log(a, b);

方法二:使用ES6解构赋值

let a = 1;
let b = 2;

[b, a] = [a, b];

console.log(a, b); // 2 1

// 使用babel转换代码
// var a = 1;
// var b = 2;
// var _ref = [a, b];
// b = _ref[0];
// a = _ref[1];
// console.log(a, b);

注意:ES6解构赋值低版本浏览器运行会报错,需使用babel进行转换

用一行代码实现深拷贝

常用的简单实现方式:类型判断+递归

function deepClone(obj) {
    var newObj = obj instanceof Array ? [] : {};
    for (var i in obj) {
        newObj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
    }
    return newObj;
}

// test
var obj = {
    number: 1,
    string: 'abc',
    bool: true,
    undefined: undefined,
    null: null,
    symbol: Symbol('s'),
    arr: [1, 2, 3],
    date: new Date(),
    userInfo: {
        name: 'Better',
        position: 'front-end engineer',
        skill: ['React', 'Vue', 'Angular', 'Nodejs', 'mini programs']
    },
    func: function () {
        console.log('hello better');
    }
}
console.log(deepClone(obj));

从打印的结果来看,这种实现方式还存在很多问题:这种方式只能实现特定的object的深度复制(比如对象、数组和函数),不能实现null以及包装对象Number,String ,Boolean,以及Date对象,RegExp对象的复制。

一行代码实现方式:结合使用JSON.stringify()JSON.parse()

var obj = {
    number: 1,
    string: 'abc',
    bool: true,
    undefined: undefined,
    null: null,
    symbol: Symbol('s'),
    arr: [1, 2, 3],
    date: new Date(),
    userInfo: {
        name: 'Better',
        position: 'front-end engineer',
        skill: ['React', 'Vue', 'Angular', 'Nodejs', 'mini programs']
    },
    func: function () {
        console.log('hello better');
    }
}

var copyObj = JSON.parse(JSON.stringify(obj));

console.log(copyObj);

从打印结果可以得出以下结论:

  1. undefinedsymbolfunction类型直接被过滤掉了
  2. date类型被自动转成了字符串类型

一行代码生成数字字母组成的随机字符串

Math.random().toString(16).substring(2); // 586a3ef1e79ec
Math.random().toString(32).substring(2); // 1iqn39075lo

如何实现a == 1 && a == 2 && a == 3

方法一:结合使用数组的toString()shift()方法

var a = [1, 2, 3];
// a.join = a.shift;
// a.valueOf = a.shift;
a.toString = a.shift;

console.log(a == 1 && a == 2 && a == 3); // true

原理:当复杂类型数据与基本类型数据作比较时会发生隐性转换,会调用toString()或者valueOf()方法

方法二:原理和方法一一样都是修改toString()方法

var a = {
    value: 1,
    toString: function () {
        return a.value++;
    }
}
console.log(a == 1 && a == 2 && a == 3); // true

实现add(1)(2)(3)这类方法以及扩展方法

// 普通写法
var add = function (a) {
    return function (b) {
        return function (c) {
            return a + b + c;
        }
    }
}

console.log(add(1)(2)(3)); // 6

// 扩展写法
function addExtend (x) {
    var sum = x;
    var temp = function (y) {
        sum = sum + y;
        return temp;
    }
    temp.toString = function () {
        return sum;
    }
    return temp;
}

console.log(addExtend(1)(2)(3)); // ƒ 6
console.log(typeof addExtend(1)(2)(3)); // function
console.log(Number(addExtend(1)(2)(3))); // 6
console.log(Number(addExtend(1)(2)(3)(4)(5))); // 15

自己实现一个bind函数

原理:使用apply()或者call()方法

初始版本

Function.prototype.bind = function (obj, arg) {
    var context = this;
    var arg = [].slice.call(arguments, 1);
    return function (newArg) {
        arg = arg.concat([].slice.call(newArg));
        return context.apply(obj, arg);
    }
}

考虑到原型链

Function.prototype.bind = function (obj, arg) {
    var context = this;
    var arg = [].slice.call(arguments, 1);
    var bound = function (newArg) {
        arg = arg.concat([].slice.call(newArg));
        return context.apply(obj, arg);
    }

    // 这里使用寄生组合继承
    var F = function () {};
    F.prototype = context.prototype;
    bound.prototype = new F();
    return bound;
}

使用setTimeout实现setInterval功能

我们平时开发中尽量避免使用setInterval重复定时器,这种重复定时器的规则有两个问题:

  1. 某些间隔会被跳过
  2. 多个定时器的代码执行时间可能会比预期小
var i = 0;
function count () {
    console.log(i++);
    setTimeout(count, 1000);
}
setTimeout(count, 1000);

或者使用arguments.callee

var i = 0;
setTimeout(function () {
    // do something
    console.log(i++);
    setTimeout(arguments.callee, 1000);
}, 1000);

如何实现sleep效果

方法一:使用promise

function sleep (time) {
    return new Promise(function (resolve, reject) {
        console.log('start');
        setTimeout(function () {
            resolve();
        }, time);
    });
}

sleep(1000).then(function () {
    console.log('end');
});
// 先输出start,延迟1000ms后输出end

方法二:使用async/await

function sleep (time) {
    return new Promise(function (resolve, reject) {
        setTimeout(function () {
            // do something
            resolve();
        }, time);
    });
}

async function test () {
    console.log('start');
    var result = await sleep(1000);
    console.log('end');
    return result;
}

test(); // 先输出start,延迟1000ms后输出end

方法三:使用generate

function *sleep (time) {
    yield new Promise((resolve, reject) => {
        console.log('start');
        setTimeout(() => {
            // do something
            resolve();
        }, time);
    });
}

sleep(1000).next().value.then(() => {console.log('end');}); // 先输出start,延迟1000ms后输出end

持续更新中,欢迎大家留言,收集更多的实用小技巧,共同学习,共同进步