Skip to content

Latest commit

 

History

History
1090 lines (957 loc) · 29.6 KB

1 手写代码.md

File metadata and controls

1090 lines (957 loc) · 29.6 KB

1.实现 new 操作符

function New() {
    var obj = {};
    var constructor = Array.prototype.shift.call(arguments);
    if (typeof constructor !== "function") {
        return console.warn(`${constructor} is not a constructor`);
    }
    obj.__proto__ = constructor.prototype;
    var ret = constructor.apply(obj, arguments);
    if (typeof ret === "function" || (typeof ret === "object" && ret !== null)) {
        return ret;
    }
    return obj;
}

2.实现 JSON.stringify

function jsonStringify(obj) {
    const type = typeof obj;
    if (/undefined|function|symbol/.test(type)) {
        return undefined;
    }
    if (/number|boolean/.test(type) || obj === null) {
        return String(obj);
    }
    if (type === "string") {
        return `"${obj}"`;
    }
    let result = [];
    switch (Object.prototype.toString.call(obj)) {
        case "[object Array]":
            for (let v of obj) {
                let value = jsonStringify(v);
                value = value === undefined ? "null" : value;
                result.push(value);
            }
            return "[" + result.join() + "]";

        case "[object Object]":
            for (let [key, value] of Object.entries(obj)) {
                value = jsonStringify(value);
                if (value !== undefined) {
                    result.push(`"${key}":${value}`);
                }
            }
            return "{" + result.join() + "}";
        case "[object Date]":
            return `"${obj.toJSON ? obj.toJSON() : obj.toString()}"`;
        default:
            return "{}";
    }
}

3.实现 JSON.parse

function jsonParse(json) {
    return eval("(" + json + ")");
}
function jsonParse2(json) {
    return new Function("return " + json)();
}

JSON.parse 三种实现方式 youngwind/blog#115

4.实现 call & apply

实现 call

Function.prototype._call = function(context) {
    context = context || window;
    context.fn = this;
    var args = [];
    for (var i = 1, len = arguments.length; i < len; i++) {
        args.push("arguments[" + i + "]");
    }
    var result = eval("context.fn(" + args + ")");
    delete context.fn;
    return result;
};

用 ES6 实现 call

Function.prototype._call = function(context = window, ...args) {
    context.fn = this;
    let result = context.fn(...args);
    delete context.fn;
    return result;
};

实现 apply

Function.prototype._apply = function(context, argsArr) {
    context = context || window;
    argsArr = argsArr || [];
    context.fn = this;

    var args = [];
    for (var i = 0, len = argsArr.length; i < len; i++) {
        args.push("argsArr[" + i + "]");
    }

    var result = eval("context.fn(" + args + ")");
    delete context.fn;
    return result;
};

用 ES6 实现 call apply bind

// call
Function.prototype.call2 = function(context = window, ...args) {
    context.fn = this;
    const result = context.fn(...args);
    delete context.fn;
    return result;
};
// apply
Function.prototype.apply2 = function(context = window, arr = []) {
    context.fn = this;
    const result = context.fn(...arr); // 相当于执行了context.fn(arguments[1], arguments[2]);
    delete context.fn;
    return result; // 因为有可能this函数会有返回值return
};
// bind
Function.prototype.bind2 = function() {
    const fn = this;
    const args = [...arguments];
    return function() {
        fn.call(...args, ...arguments);
    };
};

5.实现 bind

Function.prototype._bind = function(context) {
    if (typeof this !== "function") {
        throw Error("bind must be called on a function");
    }
    var source = this;
    var args = Array.prototype.slice.call(arguments, 1);
    var fNOP = function() {};

    var bound = function() {
        var bindArgs = Array.prototype.slice.call(arguments);
        return source.apply(this instanceof bound ? this : context, args.concat(bindArgs));
    };

    // Function.prototype doesn't have a prototype property
    if (source.prototype) {
        fNOP.prototype = source.prototype;
    }
    bound.prototype = new fNOP();

    return bound;
};

另一种写法,参考 underscore

var bound = function() {
    var bindArgs = Array.prototype.slice.call(arguments);
    if (!(this instanceof bound)) {
        return source.apply(context, args.concat(bindArgs));
    }
    var fNOP = function() {};
    if (source.prototype) {
        fNOP.prototype = source.prototype;
    }
    var newObj = new fNOP();
    var ret = source.apply(newObj, args.concat(bindArgs));
    if (ret !== null && (typeof ret === "object" || typeof ret === "function")) {
        return ret;
    }
    return newObj;
};

bind 方法的兼容实现 lessfish/underscore-analysis#19

6.实现一个 Object.create

function create(proto) {
    if (Object.create) {
        return Object.create(proto);
    }
    function F() {}
    F.prototype = proto;
    return new F();
}

7.实现函数柯里化

function currying(func) {
    var args = Array.prototype.slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        if (newArgs.length === func.length) {
            return func.apply(this, newArgs);
        }
        newArgs.unshift(func);
        return currying.apply(this, newArgs);
    };
}

8.实现 Promise

/**
 * 1. new Promise时,需要传递一个 executor 执行器,执行器立刻执行
 * 2. executor 接受两个参数,分别是 resolve 和 reject
 * 3. promise 只能从 pending 到 rejected, 或者从 pending 到 fulfilled
 * 4. promise 的状态一旦确认,就不会再改变
 * 5. promise 都有 then 方法,then 接收两个参数,分别是 promise 成功的回调 onFulfilled,
 *      和 promise 失败的回调 onRejected
 * 6. 如果调用 then 时,promise已经成功,则执行 onFulfilled,并将promise的值作为参数传递进去。
 *      如果promise已经失败,那么执行 onRejected, 并将 promise 失败的原因作为参数传递进去。
 *      如果promise的状态是pending,需要将onFulfilled和onRejected函数存放起来,等待状态确定后,再依次将对应的函数执行(发布订阅)
 * 7. then 的参数 onFulfilled 和 onRejected 可以缺省
 * 8. promise 可以then多次,promise 的then 方法返回一个 promise
 * 9. 如果 then 返回的是一个结果,那么就会把这个结果作为参数,传递给下一个then的成功的回调(onFulfilled)
 * 10. 如果 then 中抛出了异常,那么就会把这个异常作为参数,传递给下一个then的失败的回调(onRejected)
 * 11.如果 then 返回的是一个promise,那么需要等这个promise,那么会等这个promise执行完,promise如果成功,
 *   就走下一个then的成功,如果失败,就走下一个then的失败
 */

// promise 三个状态
const PENDING = "Pending";
const FULFILLED = "Fulfilled";
const REJECTED = "Rejected";

function Promise(excutor) {
    this.status = PENDING; // 初始状态
    this.value = undefined; // fulfilled状态时 返回的信息
    this.reason = undefined; // rejected状态时 拒绝的原因
    this.onFulfilledCallbacks = []; // 存储fulfilled状态对应的onFulfilled函数
    this.onRejectedCallbacks = []; // 存储rejected状态对应的onRejected函数

    let self = this; // 缓存当前promise实例对象
    function resolve(value) {
        // 只能由pending状态 => fulfilled状态 (避免调用多次resolve reject)
        if (self.status === PENDING) {
            self.status = FULFILLED;
            self.value = value;
            self.onFulfilledCallbacks.forEach(cb => cb(self.value));
        }
    }

    function reject(reason) {
        // 只能由pending状态 => rejected状态 (避免调用多次resolve reject)
        if (self.status === PENDING) {
            self.status = REJECTED;
            self.reason = reason;
            self.onRejectedCallbacks.forEach(cb => cb(self.reason));
        }
    }

    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

/**
 * resolve中的值几种情况:
 * 1.普通值
 * 2.promise对象
 * 3.thenable对象/函数
 */

/**
 * 对resolve 进行改造增强 针对resolve中不同值情况 进行处理
 * @param  {promise} promise2 promise1.then方法返回的新的promise对象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {
        // 如果从onFulfilled中返回的x 就是promise2 就会导致循环引用报错
        return reject(new TypeError("循环引用"));
    }

    // 判断 x 是否是 promise 对象
    if (x != null && (typeof x === "object" || typeof x === "function")) {
        let called = false; // 避免多次调用
        try {
            let then = x.then;
            if (typeof then === "function") {
                then.call(
                    x,
                    y => {
                        if (called) return;
                        called = true;
                        resolvePromise(promise2, y, resolve, reject);
                    },
                    reason => {
                        if (called) return;
                        called = true;
                        reject(reason);
                    },
                );
            } else {
                // 说明是一个普通对象/函数
                resolve(x);
            }
        } catch (e) {
            if (called) return;
            called = true;
            reject(e);
        }
    } else {
        resolve(x);
    }
}

/**
 * [注册fulfilled状态/rejected状态对应的回调函数]
 * @param  {function} onFulfilled fulfilled状态时 执行的函数
 * @param  {function} onRejected  rejected状态时 执行的函数
 * @return {function} newPromsie  返回一个新的promise对象
 */
Promise.prototype.then = function(onFulfilled, onRejected) {
    let self = this;
    let promise2;
    // 处理参数默认值 保证参数后续能够继续执行
    onFulfilled =
        typeof onFulfilled === "function"
            ? onFulfilled
            : value => {
                  return value;
              };
    onRejected =
        typeof onRejected === "function"
            ? onRejected
            : reason => {
                  throw reason;
              };

    // then里面的FULFILLED/REJECTED状态时 为什么要加setTimeout ?
    // 原因:
    // 其一 2.2.4规范 要确保 onFulfilled 和 onRejected 方法异步执行(且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行) 所以要在resolve里加上setTimeout
    // 其二 2.2.6规范 对于一个promise,它的then方法可以调用多次.(当在其他程序中多次调用同一个promise的then时 由于之前状态已经为FULFILLED/REJECTED状态,则会走的下面逻辑),所以要确保为FULFILLED/REJECTED状态后 也要异步执行onFulfilled/onRejected

    // 其二 2.2.6规范 也是resolve函数里加setTimeout的原因
    // 总之都是 让then方法异步执行 也就是确保onFulfilled/onRejected异步执行

    // 如下面这种情景 多次调用p1.then
    // p1.then((value) => { // 此时p1.status 由pending状态 => fulfilled状态
    //     console.log(value); // resolve
    //     // console.log(p1.status); // fulfilled
    //     p1.then(value => { // 再次p1.then 这时已经为fulfilled状态 走的是fulfilled状态判断里的逻辑 所以我们也要确保判断里面onFuilled异步执行
    //         console.log(value); // 'resolve'
    //     });
    //     console.log('当前执行栈中同步代码');
    // })
    // console.log('全局执行栈中同步代码');
    //

    if (self.status === FULFILLED) {
        // 成功态
        promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onFulfilled(self.value);
                    resolvePromise(promise2, x, resolve, reject); // 新的promise resolve 上一个onFulfilled的返回值
                } catch (e) {
                    reject(e); // 捕获前面onFulfilled中抛出的异常 then(onFulfilled, onRejected);
                }
            });
        });
        return promise2;
    }

    if (self.status === REJECTED) {
        // 失败态
        promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(self.reason);
                    resolvePromise(promise2, x, resolve, reject);
                } catch (e) {
                    reject(e);
                }
            });
        });
        return promise2;
    }

    if (self.status === PENDING) {
        // 当异步调用resolve/rejected时 将onFulfilled/onRejected收集暂存到集合中
        // 为什么加setTimeout?
        // 2.2.4规范 onFulfilled 和 onRejected 只允许在 execution context 栈仅包含平台代码时运行.
        // 注1 这里的平台代码指的是引擎、环境以及 promise 的实施代码。实践中要确保 onFulfilled 和 onRejected 方法异步执行,且应该在 then 方法被调用的那一轮事件循环之后的新执行栈中执行。

        promise2 = new Promise((resolve, reject) => {
            self.onFulfilledCallbacks.push(value => {
                setTimeout(() => {
                    try {
                        let x = onFulfilled(value);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
            self.onRejectedCallbacks.push(reason => {
                setTimeout(() => {
                    try {
                        let x = onRejected(reason);
                        resolvePromise(promise2, x, resolve, reject);
                    } catch (e) {
                        reject(e);
                    }
                });
            });
        });
        return promise2;
    }
};

Promise.resolve

Promise.resolve = function(value) {
    if (value instanceof Promise) {
        return value;
    }
    return new Promise((resolve, reject) => {
        if (value && value.then && typeof value.then === "function") {
            setTimeout(() => {
                value.then(resolve, reject);
            });
        } else {
            resolve(value);
        }
    });
};

Promise.reject

Promise.reject = function(reason) {
    return new Promise((resolve, reject) => reject(reason));
};

Promise.prototype.catch

Promise.prototype.catch = function(onRejected) {
    return this.then(null, onRejected);
};

Promise.prototype.finally

Promise.prototype.finally = function(callback) {
    return this.then(
        value => {
            return Promise.resolve(callback()).then(() => {
                return value;
            });
        },
        err => {
            return Promise.resolve(callback()).then(() => {
                throw err;
            });
        },
    );
};

Promise.race

Promise.race = function(promises) {
    if (promises.length === 0) {
        return;
    }
    return new Promise((resolve, reject) => {
        promises.forEach((promise, index) => {
            Promise.resolve(promise).then(resolve, reject);
        });
    });
};

Promise.all

Promise.all = function(promises) {
    return new Promise((resolve, reject) => {
        let count = 0;
        let result = [];
        if (promises.length === 0) {
            resolve(result);
        } else {
            for (let i = 0; i < promises.length; i++) {
                // promises[i] 可能是普通值
                Promise.resolve(promises[i]).then(data => {
                    result[i] = data;
                    if (++count === promises.length) {
                        resolve(result);
                    }
                }, reject);
            }
        }
    });
};

9.防抖和节流

防抖 debounce

function debounce(func, wait) {
    var timer = null;
    return function() {
        var context = this;
        var args = arguments;
        if (timer !== null) {
            clearTimeout(timer);
        }
        timer = setTimeout(function() {
            func.apply(context, args);
        }, wait);
    };
}

节流 throttle

function throttle(func, wait) {
    var timer = null;
    var previous = 0;
    return function() {
        var now = Date.now();
        var remaining = wait - (now - previous);
        var context = this;
        var args = arguments;
        if (remaining <= 0 || remaining > wait) {
            if (timer) {
                clearTimeout(timer);
                timer = null;
            }
            previous = now;
            func.apply(context, args);
        } else if (!timer) {
            timer = setTimeout(function() {
                previous = Date.now();
                func.apply(context, args);
                timer = null;
            }, remaining);
        }
    };
}

10.实现一个 JS 深拷贝

function deepClone(obj) {
    if (!obj || typeof obj !== "object") {
        return obj;
    }

    if (obj.nodeType && "cloneNode" in obj) {
        return obj.cloneNode(true);
    }

    var result;
    switch (Object.prototype.toString.call(obj)) {
        case "[object Array]":
            result = [];
            for (let v of obj) {
                result.push(deepClone(v));
            }
            return result;

        case "[object Object]":
            result = obj.constructor ? new obj.constructor() : {};
            for (let [k, v] of Object.entries(obj)) {
                result[k] = deepClone(v);
            }
            return result;

        case "[object Date]":
            return new Date(obj.getTime());

        case "[object RegExp]":
            var flags = "";
            if (obj.global) flags += "g";
            if (obj.multiline) flags += "m";
            if (obj.ignoreCase) flags += "i";
            return new RegExp(obj.source, flags);

        default:
            return obj;
    }
}

11.实现一个 instanceOf

function instanceOf(source, target) {
    let proto = source.__proto__;
    let prototype = target.prototype;
    while (proto) {
        if (proto === prototype) return true;
        proto = proto.__proto__;
    }
    return false;
}

浅谈 instanceof 和 typeof 的实现原理 https://juejin.im/post/5b0b9b9051882515773ae714

12. 简单实现 async/await 中的 async 函数

function async(generator) {
    return new Promise(function(resolve, reject) {
        const it = generator();
        function step(next) {
            let result;
            try {
                result = next();
            } catch (e) {
                return reject(e);
            }
            if (result.done) {
                return resolve(result.value);
            }
            Promise.resolve(result.value).then(
                function(v) {
                    step(function() {
                        return it.next(v);
                    });
                },
                function(e) {
                    step(function() {
                        return it.throw(e);
                    });
                },
            );
        }
        step(function() {
            return it.next(undefined);
        });
    });
}

13. 基于 Promise 的 ajax 封装

function ajax(
    options = {
        url: "",
        method: "GET",
        data: {},
        timeout: 60,
    },
) {
    options.method = (options.method || "GET").toUpperCase();
    const paramString = formatParams(options.data);

    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.timeout = (options.timeout || 60) * 1000;

        if (options.method === "GET") {
            let url = options.url + (options.url.indexOf("?") > -1 ? "&" : "?") + paramString;
            xhr.open("GET", url);
            // xhr.setRequestHeader("User-Agent", "Mozilla/5.0 (Linux; X11)");
            xhr.send(null);
        }

        if (options.method === "POST") {
            xhr.open("POST", options.url);
            // 设置请求头
            xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            // 跨域携带cookie
            // xhr.withCredentials = true;
            xhr.send(paramString);
        }

        xhr.onload = function() {
            const result = {
                status: xhr.status,
                statusText: xhr.statusText,
                headers: xhr.getAllResponseHeaders(),
                data: xhr.response || xhr.responseText,
            };
            if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {
                resolve(result);
            } else {
                reject(result);
            }
        };
        // 错误处理
        xhr.onerror = function() {
            reject(new TypeError("请求出错"));
        };
        xhr.ontimeout = function() {
            reject(new TypeError("请求超时"));
        };
        xhr.onabort = function() {
            reject(new TypeError("请求被终止"));
        };
    });
}

function formatParams(data) {
    let arr = [];
    for (var key in data) {
        if (data.hasOwnProperty(key)) arr.push(encodeURIComponent(key) + "=" + encodeURIComponent(data[key]));
    }
    return arr.join("&");
}

14.JSONP 的原理是什么?

function jsonp(url, data) {
    return new Promise((resolve, reject) => {
        // 1.将传入的data数据转化为url字符串形式
        var queryString = url.indexOf("?") == -1 ? "?" : "&";
        var arr = [];
        for (var key in data) {
            arr.push(key + "=" + encodeURIComponent(data[key]));
        }
        queryString += arr.join("&");
        // 2.处理url中的回调函数
        var callbackName =
            "jsonp_callback_" +
            Math.random()
                .toString(16)
                .replace(".", "");
        queryString += "&callback=" + callbackName;

        // 3.创建一个script标签并插入到页面中
        var script = document.createElement("script");
        script.src = url + queryString;

        // 4.挂载回调函数
        window[callbackName] = function(data) {
            resolve(data);
            // 处理完回调函数的数据之后,删除jsonp的script标签
            document.body.removeChild(script);
        };

        document.body.appendChild(script);
    });
}

15. 如何实现数组的随机排序?

Fisher–Yates Shuffle

// 方法一
function shuffle(arr) {
    var len = arr.length;
    for (var i = 0; i < len - 1; i++) {
        var random = Math.floor(Math.random() * (len - i));
        var temp = arr[random];
        arr[random] = arr[len - i - 1];
        arr[len - i - 1] = temp;
    }
    return arr;
}

// 方法二
function shuffle(arr) {
    let len = arr.length,
        random;
    while (len) {
        random = (Math.random() * len--) >>> 0; // 无符号右移位运算符向下取整
        [arr[len], arr[random]] = [arr[random], arr[len]];
    }
    return arr;
}

// 从前往后遍历元素的方式
function shuffle(a) {
    var length = a.length;
    var shuffled = Array(length);

    for (var index = 0, rand; index < length; index++) {
        rand = ~~(Math.random() * (index + 1));
        if (rand !== index) shuffled[index] = shuffled[rand];
        shuffled[rand] = a[index];
    }

    return shuffled;
}

数组乱序 lessfish/underscore-analysis#15

16. 实现异步循环打印

var sleep = function(time, value) {
    return new Promise(function(resolve, reject) {
        setTimeout(function() {
            resolve(value);
        }, time);
    });
};

var start = async function() {
    for (let i = 0; i < 6; i++) {
        let result = await sleep(1000, i);
        console.log(result);
    }
};

start();

17. 深度优先遍历

/*深度优先遍历三种方式*/
function deepTraversal1(node, nodeList = []) {
    if (node !== null) {
        nodeList.push(node);
        let children = node.children;
        for (let i = 0; i < children.length; i++) {
            deepTraversal1(children[i], nodeList);
        }
    }
    return nodeList;
}
function deepTraversal2(node) {
    let nodes = [];
    if (node !== null) {
        nodes.push(node);
        let children = node.children;
        for (let i = 0; i < children.length; i++) {
            nodes = nodes.concat(deepTraversal2(children[i]));
        }
    }
    return nodes;
}
// 非递归
function deepTraversal3(node) {
    let stack = [];
    let nodes = [];
    if (node) {
        // 推入当前处理的node
        stack.push(node);
        while (stack.length) {
            let item = stack.pop();
            let children = item.children;
            nodes.push(item);
            for (let i = children.length - 1; i >= 0; i--) {
                stack.push(children[i]);
            }
        }
    }
    return nodes;
}

18.广度优先遍历

function BFS(node) {
    let nodes = [];
    let stack = [];
    if (node) {
        stack.push(node);
        while (stack.length) {
            let item = stack.shift();
            let children = item.children;
            nodes.push(item);
            for (let i = 0; i < children.length; i++) {
                stack.push(children[i]);
            }
        }
    }
    return nodes;
}

19. 解析 url 参数

var q = function(url) {
    let result = {};
    const query = url.split("?")[1];
    if (!query) return result;
    const pairs = query.split("&");
    pairs.forEach(pair => {
        let [key, value] = pair.split("=");
        try {
            value = value ? decodeURIComponent(value).replace(/\+/g, " ") : "";
        } catch (e) {
            return;
        }
        if (/^\d+(\.\d+)?$/.test(value)) {
            value = Number(value);
        }
        if (typeof result[key] === "undefined") {
            result[key] = value;
        } else {
            result[key] = [].concat(result[key], value);
        }
    });
    return result;
};

数组 flatten

var flatten = function(array) {
    var result = [];
    for (var i = 0, length = array.length; i < length; i++) {
        var value = array[i];
        if (Array.isArray(value)) {
            result = result.concat(flatten(value));
        } else {
            result.push(value);
        }
    }
    return result;
};

function flatten(array) {
    return array.reduce((result, current) => {
        return Array.isArray(current) ? result.concat(flatten(current)) : result.concat(current);
    }, []);
}
console.log(flatten([1, [2, [[3, 4], 5], 6]]));
console.log(flatten(["abc", ["a", [[3, "a"], { a: "a" }], null, false]]));

解析 url

var parseURL = function(url) {
    var result = {};
    var keys = ["href", "origin", "protocol", "host", "hostname", "port", "pathname", "search", "hash"];
    var regexp = /(((?:https?|ftp|file):)\/\/(([^:\/\?#]+)(:\d+)?))(\/[^?#]*)?(\?[^#]*)?(#.*)?/;

    var match = regexp.exec(url);
    if (match) {
        for (var i = keys.length - 1; i >= 0; i--) {
            result[keys[i]] = match[i] ? match[i] : "";
        }
    }
    return result;
};

function URLParser(url) {
    const a = document.createElement("a");
    a.href = url;
    // 非浏览器环境  const urlObj = new URL(url);
    return {
        protocol: a.protocol,
        username: a.username,
        password: a.password,
        hostname: a.hostname, // host 可能包括 port, hostname 不包括
        port: a.port,
        pathname: a.pathname,
        search: a.search,
        hash: a.hash,
    };
}

console.log(parseURL("https://www.cnblogs.com:8080/speeding/p/5097790.html?xxx=9999#test"));

数组去重

var unique = function(arr) {
    // 实现一
    // return [...new Set(arr)]
    // 实现二
    const result = [];
    arr.forEach(item => {
        if (result.indexOf(item) === -1) result.push(item);
    });
    return result;
};
var arrarr = [1, 2, 3, 3, 4, 4, 5, 5, 6, 1, 9, 3, 25, 4];
console.log(unique(arrarr));

转义函数 escapeHtml

var escapeMap = {
    "<": "&lt;",
    ">": "&gt;",
    "&": "&amp;",
    '"': "&quot;",
    "'": "&#x27;",
    "`": "&#x60;",
};

var escapeHtml = function(htmlStr) {
    var source = "(?:" + Object.keys(escapeMap).join("|") + ")";
    var reg = RegExp(source, "g");
    return htmlStr.replace(reg, function(match) {
        return escapeMap[match];
    });
};

千位分隔

// 如何将浮点数点左边的数每三位添加一个逗号,如 12000000.11 转化为『12,000,000.11』?
function milliFormat(num) {
    return (
        num &&
        num.toString().replace(/\d+/, function(s) {
            return s.replace(/(\d)(?=(\d{3})+$)/g, "$1,");
        })
    );
}

模版引擎

function render(template, data) {
    return template.replace(/{{\s*(\w+)\s*}}/g, function(a, b) {
        return data[b] || "";
    });
}

var t = '<p><a href="{{url}}">{{name}}</a><span>{{greetting}}</span></p>';
console.log(
    render(t, {
        url: "http://www.alibaba.com",
        name: "Alibaba",
        greetting: "Welcome",
    }),
);

Underscore _.template 方法使用详解 lessfish/underscore-analysis#26

数据结构与算法

窥探数据结构的世界- ES6 版 https://juejin.im/post/5cd1ab3df265da03587c142a

在 JavaScript 中学习数据结构与算法 https://juejin.im/post/594dfe795188250d725a220a

十大经典排序算法 https://www.cnblogs.com/onepixel/p/7674659.html

Data Structure Visualizations https://www.cs.usfca.edu/~galles/visualization/Algorithms.html

从斐波那契数列求值优化谈 _.memoize 方法 lessfish/underscore-analysis#23