js手写代码

基础

判断数据类型

export function getType(x) {
    const originType = Object.prototype.toString.call(x); // '[object String]'
    const spaceIndex = originType.indexOf(' ');
    const type = originType.slice(spaceIndex + 1, -1); // 'String'
    return type.toLowerCase(); // 'string'
}
// 功能测试
console.info(getType(null)); // 'null'
console.info(getType(undefined));
console.info(getType(100));
console.info(getType('abc'));
console.info(getType(true));
console.info(getType(Symbol()));
console.info(getType({}));
console.info(getType([]));
console.info(getType(() => { }));

sleep机制链式调用 

class LazyMan {
    constructor(name) {
        this.tasks = []; // 任务列表
        this.name = name;
        setTimeout(() => {
            this.next();
        });
    }
    next() {
        const task = this.tasks.shift(); // 取出当前 tasks 的第一个任务
        if (task)
            task();
    }
    eat(food) {
        const task = () => {
            console.info(`${this.name} eat ${food}`);
            this.next(); // 立刻执行下一个任务
        };
        this.tasks.push(task);
        return this; // 链式调用
    }
    sleep(seconds) {
        const task = () => {
            console.info(`${this.name} 开始睡觉`);
            setTimeout(() => {
                console.info(`${this.name} 已经睡完了 ${seconds}s,开始执行下一个任务`);
                this.next(); // xx 秒之后再执行下一个任务
            }, seconds * 1000);
        };
        this.tasks.push(task);
        return this; // 链式调用
    }
}
const me = new LazyMan('双越');
me.eat('苹果').eat('香蕉').sleep(2).eat('葡萄').eat('西瓜').sleep(2).eat('橘子');

对象

深度比较lodash.isEaqual 

判断类型区分基本数据和对象类型;再对对象类型执行操作,先判断长度相等再递归。

// 判断是否是对象或数组
function isObject(obj) {
    return typeof obj === 'object' && obj !== null
}
// 全相等(深度)
function isEqual(obj1, obj2) {
    if (!isObject(obj1) || !isObject(obj2)) {
        // 值类型(注意,参与 equal 的一般不会是函数)
        return obj1 === obj2
    }
    if (obj1 === obj2) {
        return true
    }
    // 两个都是对象或数组,而且不相等
    // 1. 先取出 obj1 和 obj2 的 keys ,比较个数
    const obj1Keys = Object.keys(obj1)
    const obj2Keys = Object.keys(obj2)
    if (obj1Keys.length !== obj2Keys.length) {
        return false
    }
    // 2. 以 obj1 为基准,和 obj2 一次递归比较
    for (let key in obj1) {
        // 比较当前 key 的 val —— 递归!!!
        const res = isEqual(obj1[key], obj2[key])
        if (!res) {//有不想等的就不再继续比较了
            return false
        }
    }
    // 3. 全相等
    return true
}

// 测试
const obj1 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
const obj2 = {
    a: 100,
    b: {
        x: 100,
        y: 200
    }
}
// console.log( obj1 === obj2 )
console.log( isEqual(obj1, obj2) )

const arr1 = [1, 2, 3]
const arr2 = [1, 2, 3, 4]

js深拷贝

/**
 * 深拷贝
 */
 
const obj1 = {
    age: 20,
    name: 'xxx',
    address: {
        city: 'beijing'
    },
    arr: ['a', 'b', 'c']
}
console.log(typeof null)
const obj2 = deepClone(obj1)
obj2.address.city = 'shanghai'
obj2.arr[0] = 'a1'
console.log(obj1.address.city)
console.log(obj1.arr[0])
 
/**
 * 深拷贝
 * @param {Object} obj 要拷贝的对象
 */
function deepClone(obj = {}) {
    if (typeof obj !== 'object' || obj == null) {//obj == null等价于obj===null||obj===undefined
        // obj 是 null ,或者不是对象和数组,直接返回
        return obj
    }
 
    // 初始化返回结果
    let result
    if (obj instanceof Array) {
        result = []
    } else {
        result = {}
    }
 
    for (let key in obj) {
        // 保证 key 不是原型的属性
        if (obj.hasOwnProperty(key)) {
            // 递归调用
            result[key] = deepClone(obj[key])
        }
    }
 
    // 返回结果
    return result
}

注意:Object.assign只有第一层数据是深拷贝 

手写instanceof

export function myInstanceof(instance, origin) {
    if (instance == null)
        return false; // null undefined
    const type = typeof instance;
    if (type !== 'object' && type !== 'function') {
        // 值类型
        return false;
    }
    let tempInstance = instance; // 为了防止修改 instance
    while (tempInstance) {
        if (tempInstance.__proto__ === origin.prototype) {
            return true; // 配上了
        }
        // 未匹配
        tempInstance = tempInstance.__proto__; // 顺着原型链,往上找
    }
    return false;
}
// 功能测试
console.info(myInstanceof({}, Object));
console.info(myInstanceof([], Object));
console.info(myInstanceof([], Array));
console.info(myInstanceof({}, Array));
console.info(myInstanceof('abc', String));

函数

自己实现new

export function customNew(constructor, ...args) {
    // 1. 创建一个空对象,继承 constructor 的原型
    const obj = Object.create(constructor.prototype);
    // 2. 将 obj 作为 this ,执行 constructor ,传入参数
    constructor.apply(obj, args);
    // 3. 返回 obj
    return obj;
}
class Foo {
    constructor(name, n) {
        this.name = name;
        this.city = '北京';
        this.n = n;
    }
    getName() {
        return this.name;
    }
}
const f = new Foo('双越', 100);
// const f = customNew<Foo>(Foo, '双越', 100)
console.info(f);
console.info(f.getName());

 bind

Function.prototype.customBind = function (context, ...bindArgs) {
    // context 是 bind 传入的 this
    // bindArgs 是 bind 传入的各个参数
    const self = this; // 当前的函数本身
    return function (...args) {
        // 拼接参数
        const newArgs = bindArgs.concat(args);
        return self.apply(context, newArgs);
    };
};
// 功能测试
function fn(a, b, c) {
    console.info(this, a, b, c);
}
// @ts-ignore
const fn1 = fn.customBind({ x: 100 }, 10);
fn1(20, 30);

call/apply

Function.prototype.customCall = function (context, ...args) {
    if (context == null)
        context = globalThis;
    if (typeof context !== 'object')
        context = new Object(context); // 值类型,变为对象
    const fnKey = Symbol(); // 不会出现属性名称的覆盖
    context[fnKey] = this; // this 就是当前的函数
    const res = context[fnKey](...args); // 绑定了 this
    delete context[fnKey]; // 清理掉 fn ,防止污染
    return res;
};
// @ts-ignore
Function.prototype.customApply = function (context, args = []) {
    if (context == null)
        context = globalThis;
    if (typeof context !== 'object')
        context = new Object(context); // 值类型,变为对象
    const fnKey = Symbol(); // 不会出现属性名称的覆盖
    context[fnKey] = this; // this 就是当前的函数
    const res = context[fnKey](...args); // 绑定了 this
    delete context[fnKey]; // 清理掉 fn ,防止污染
    return res;
};
function fn(a, b, c) {
    console.info(this, a, b, c);
}
// @ts-ignore
fn.customCall({ x: 100 }, 10, 20, 30);
-ignore;
fn.customApply({ x: 200 }, [100, 200, 300]);

 

数组

获取数组中的最大值

数组flatern,考虑多层级

function flat(arr) {
    // 验证 arr 中,还有没有深层数组 [1, 2, [3, 4]]
    const isDeep = arr.some(item => item instanceof Array)
    if (!isDeep) {
        return arr // 已经是 flatern [1, 2, 3, 4]
    }

    const res = Array.prototype.concat.apply([], arr)
    return flat(res) // 递归
}

const res = flat( [1, 2, [3, 4, [10, 20, [100, 200]]], 5] )
console.log(res)

66c36152108eacc929a90a28fd86b87d.png

数组去重

// 传统方式
function unique(arr) {
    const res = []
    arr.forEach(item => {
        if (res.indexOf(item) < 0) {
            res.push(item)//没有就push
        }
    })
    return res
}

// 使用 Set (无序,不能重复)
function unique(arr) {
    const set = new Set(arr)
    return [...set]
}

const res = unique([30, 10, 20, 30, 40, 10])
console.log(res)

函数柯里化

export function curry(fn) {
    const fnArgsLength = fn.length; // 传入函数的参数长度
    let args = [];
    // ts 中,独立的函数,this 需要声明类型
    function calc(...newArgs) {
        // 积累参数
        args = [
            ...args,
            ...newArgs
        ];
        if (args.length < fnArgsLength) {
            // 参数不够,返回函数
            return calc;
        }
        else {
            // 参数够了,返回执行结果
            return fn.apply(this, args.slice(0, fnArgsLength));
        }
    }
    return calc;
}
function add(a, b, c) {
    return a + b + c;
}
// add(10, 20, 30) // 60
const curryAdd = curry(add);
const res = curryAdd(10)(20)(30); // 60
console.info(res);

BOM 

获取当前页面url参数

// 传统方式:location.search
function query(name) {
    const search = location.search.substr(1) // 类似 array.slice(1)
    // search: 'a=10&b=20&c=30'
    const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`, 'i')
    const res = search.match(reg)
    if (res === null) {
        return null
    }
    return res[2]
}
query('d')

//新api: URLSearchParams
function query(name) {
    const search = location.search
    const p = new URLSearchParams(search)
    return p.get(name)
}
console.log( query('b') )

正则解析:

开始或& :abc前面 值不要有& &或没有结尾

整个--前括号--值--后括号

 

DOM

EventBus自定义事件

export default class EventBus {
  /**
     * {
     *    'key1': [
     *        { fn: fn1, isOnce: false },
     *        { fn: fn2, isOnce: false },
     *        { fn: fn3, isOnce: true },
     *    ]
     *    'key2': [] // 有序
     *    'key3': []
     * }
     */
    constructor() {
        this.events = {};
    }
    on(type, fn, isOnce = false) {//存储函数
        const events = this.events;
        if (events[type] == null) {
            events[type] = []; // 初始化 key 的 fn 数组
        }
        events[type].push({ fn, isOnce });
    }
    once(type, fn) {//存储函数
        this.on(type, fn, true);
    }
    off(type, fn) {//删除函数
        if (!fn) {
            // 解绑所有 type 的函数
            this.events[type] = [];
        }
        else {
            // 解绑单个 fn
            const fnList = this.events[type];
            if (fnList) {
                this.events[type] = fnList.filter(item => item.fn !== fn);
            }
        }
    }
    emit(type, ...args) {//执行函数
        const fnList = this.events[type];
        if (fnList == null)
            return;
        // 注意
        this.events[type] = fnList.filter(item => {
            const { fn, isOnce } = item;
            fn(...args);
            // once 执行一次就要被过滤掉
            if (!isOnce)
                return true;
            return false;
        });
    }
}

//功能测试
const e = new EventBus();
function fn1(a, b) { console.log('fn1', a, b); }
function fn2(a, b) { console.log('fn2', a, b); }
function fn3(a, b) { console.log('fn3', a, b); }
//绑定事件
e.on('key1', fn1);
e.on('key1', fn2);
e.once('key1', fn3);
e.on('xxxxxx', fn3);
//触发事件
e.emit('key1', 10, 20); // 触发 fn1 fn2 fn3
//解绑事件
e.off('key1', fn1);
e.emit('key1', 100, 200); // 触发 fn2

//单元测试
import EventBus from './event-bus-split-on-once'

describe('EventBus 自定义事件', () => {

    it('解绑单个事件', () => {
        const event = new EventBus()

        const fn1 = jest.fn()
        const fn2 = jest.fn()

        event.on('key1', fn1)
        event.on('key1', fn2)

        event.off('key1', fn1)

        event.emit('key1', 10, 20)

        expect(fn1).not.toBeCalled()
        expect(fn2).toBeCalledWith(10, 20)
    })
    it('once', () => {
        const event = new EventBus()

        let n = 1

        const fn1 = jest.fn(() => n++)
        const fn2 = jest.fn(() => n++)

        event.once('key1', fn1)
        event.once('key1', fn2)

        // 无论 emit 多少次,只有一次生效
        event.emit('key1')
        event.emit('key1')
        event.emit('key1')
        event.emit('key1')
        event.emit('key1')

        expect(n).toBe(3)
    })
})

遍历DOM树

function visitNode(n) {
    var _a;
    if (n instanceof Comment) {
        // 注释
        console.info('Comment node ---', n.textContent);
    }
    if (n instanceof Text) {
        // 文本
        const t = (_a = n.textContent) === null || _a === void 0 ? void 0 : _a.trim();
        if (t) {
            console.info('Text node ---', t);
        }
    }
    if (n instanceof HTMLElement) {
        // element
        console.info('Element node ---', `<${n.tagName.toLowerCase()}>`);
    }
}
/**
 * 深度优先遍历
 * @param root dom node
 */
function depthFirstTraverse1(root) {
    visitNode(root);
    const childNodes = root.childNodes; // .childNodes 和 .children 不一样
    if (childNodes.length) {
        childNodes.forEach(child => {
            depthFirstTraverse1(child); // 递归
        });
    }
}
/**
 * 深度优先遍历
 * @param root dom node
 */
function depthFirstTraverse2(root) {
    const stack = [];
    // 根节点压栈
    stack.push(root);
    while (stack.length > 0) {
        const curNode = stack.pop(); // 出栈
        if (curNode == null)
            break;
        visitNode(curNode);
        // 子节点压栈
        const childNodes = curNode.childNodes;
        if (childNodes.length > 0) {
            // reverse 反顺序压栈
            Array.from(childNodes).reverse().forEach(child => stack.push(child));
        }
    }
}
/**
 * 广度优先遍历
 * @param root dom node
 */
function breadthFirstTraverse(root) {
    const queue = []; // 数组 vs 链表
    // 根节点入队列
    queue.unshift(root);
    while (queue.length > 0) {
        const curNode = queue.pop();
        if (curNode == null)
            break;
        visitNode(curNode);
        // 子节点入队
        const childNodes = curNode.childNodes;
        if (childNodes.length) {
            childNodes.forEach(child => queue.unshift(child));
        }
    }
}
const box = document.getElementById('box');
if (box == null)
    throw new Error('box is null');
depthFirstTraverse2(box);

Map

哈希表存储数据,时间复杂度是O(1);而且必须是有序的。

export default class LRUCache {//缓存最近使用的,删除沉水数据
    constructor(length) {
        this.data = new Map();//数据容器
        if (length < 1)
            throw new Error('invalid length');
        this.length = length;//内存长度
    }
    set(key, value) {
        const data = this.data;
        if (data.has(key)) {
            data.delete(key);//删除
        }
        data.set(key, value);//重建set
        if (data.size > this.length) {
            // 如果超出了容量,则删除 Map 最老的元素
            const delKey = data.keys().next().value;//iterator第一个元素
            data.delete(delKey);
        }
    }
    get(key) {
        const data = this.data;
        if (!data.has(key))
            return null;
        const value = data.get(key);
        data.delete(key);//删除
        data.set(key, value);//重建
        return value;//返回val
    }
}
const lruCache = new LRUCache(2);
lruCache.set(1, 1); // {1=1}
lruCache.set(2, 2); // {1=1, 2=2}
console.info(lruCache.get(1)); // 1 {2=2, 1=1}此时1=1的优先级更高
lruCache.set(3, 3); // {1=1, 3=3}此时2=2被删除
console.info(lruCache.get(2)); // null
lruCache.set(4, 4); // {3=3, 4=4}
console.info(lruCache.get(1)); // null
console.info(lruCache.get(3)); // 3 {4=4, 3=3}
console.info(lruCache.get(4)); // 4 {3=3, 4=4}

双向链表实现

删除A:删除A->B/ B->A/ head->B

新增D:新增C->D D->C tail->D

B移动到D后面:B与AC断开/AC连接 DB连接/tail->B

export default class LRUCache {
    constructor(length) {
        this.data = {};
        this.dataLength = 0;
        this.listHead = null;
        this.listTail = null;
        if (length < 1)
            throw new Error('invalid length');
        this.length = length;
    }
    moveToTail(curNode) {
        const tail = this.listTail;
        if (tail === curNode)
            return;
        // -------------- 1. 让 prevNode nextNode 断绝与 curNode 的关系 --------------
        const prevNode = curNode.prev;
        const nextNode = curNode.next;
        if (prevNode) {
            if (nextNode) {
                prevNode.next = nextNode;
            }
            else {
                delete prevNode.next;
            }
        }
        if (nextNode) {
            if (prevNode) {
                nextNode.prev = prevNode;
            }
            else {
                delete nextNode.prev;
            }
            if (this.listHead === curNode)
                this.listHead = nextNode;
        }
        // -------------- 2. 让 curNode 断绝与 prevNode nextNode 的关系 --------------
        delete curNode.prev;
        delete curNode.next;
        // -------------- 3. 在 list 末尾重新建立 curNode 的新关系 --------------
        if (tail) {
            tail.next = curNode;
            curNode.prev = tail;
        }
        this.listTail = curNode;
    }
    tryClean() {
        while (this.dataLength > this.length) {
            const head = this.listHead;
            if (head == null)
                throw new Error('head is null');
            const headNext = head.next;
            if (headNext == null)
                throw new Error('headNext is null');
            // 1. 断绝 head 和 next 的关系
            delete headNext.prev;
            delete head.next;
            // 2. 重新赋值 listHead
            this.listHead = headNext;
            // 3. 清理 data ,重新计数
            delete this.data[head.key];
            this.dataLength = this.dataLength - 1;
        }
    }
    get(key) {
        const data = this.data;
        const curNode = data[key];
        if (curNode == null)
            return null;
        if (this.listTail === curNode) {
            // 本身在末尾(最新鲜的位置),直接返回 value
            return curNode.value;
        }
        // curNode 移动到末尾
        this.moveToTail(curNode);
        return curNode.value;
    }
    set(key, value) {
        const data = this.data;
        const curNode = data[key];
        if (curNode == null) {
            // 新增数据
            const newNode = { key, value };
            // 移动到末尾
            this.moveToTail(newNode);
            data[key] = newNode;
            this.dataLength++;
            if (this.dataLength === 1)
                this.listHead = newNode;
        }
        else {
            // 修改现有数据
            curNode.value = value;
            // 移动到末尾
            this.moveToTail(curNode);
        }
        // 尝试清理长度
        this.tryClean();
    }
}

requestAnimationFrame

// 3s 把宽度从 100px 变为 640px ,即增加 540px
// 60帧/s ,3s 180 帧 ,每次变化 3px

const $div1 = $('#div1')
let curWidth = 100
const maxWidth = 640

// setTimeout
function animate() {
    curWidth = curWidth + 3
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        setTimeout(animate, 16.7) // 自己控制时间
    }
}
animate()

// RAF
function animate() {
    curWidth = curWidth + 5
    $div1.css('width', curWidth)
    if (curWidth < maxWidth) {
        window.requestAnimationFrame(animate) // 时间不用自己控制
    }
}
animate()

 b6d0c150b252c466c37b2aae6b137889.png

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值