美文网首页
前端手写代码

前端手写代码

作者: 书虫和泰迪熊 | 来源:发表于2020-07-22 16:37 被阅读0次

    下面介绍一些常用的源码实现
    实现一个深拷贝
    实现 new 操作符
    实现instanceof
    防抖
    节流
    函数柯里化
    实现 call,apply
    实现 bind
    实现Ajax
    Promise.all

    实现一个深拷贝

    深拷贝为对象创建一个相同的副本,两者的引用地址不同。
    一般方法:JSON的序列化和反序列化(JSON.Parse(JSON.stringify(obj))),但是这种方式有两个不足:
    1.undefined, null, symbol 类型的值会被删除。
    2.碰见循环引用的时候回报错
    循环引用解决方案:可以使用一个WeakMap结构存储已经被拷贝的对象,每一次进行拷贝的时候就先向WeakMap查询该对象是否已经被拷贝,如果已经被拷贝则取出该对象并返回.

    下面我们简单实现一个深拷贝

    function isObj(obj) {
        return (typeof obj === 'object' || typeof obj === 'function') && obj !== null
    }
    function deepCopy(obj, hash = new WeakMap()) {
        if(hash.has(obj)) return hash.get(obj)
        let cloneObj = Array.isArray(obj) ? [] : {}
        hash.set(obj, cloneObj)
        for (let key in obj) {
            cloneObj[key] = isObj(obj[key]) ? deepCopy(obj[key], hash) : obj[key];
        }
        return cloneObj
    }
    // 测试
    let o1 = {
        a: 1,
        b: null,
        c: undefined,
        d: "hello",
        e: [111,222,333,444],
        f: true,
        s: Symbol('ww'),
    }
    o1.oo = o1   // 循环引用
    let o2 = deepCopy(o1)
    console.log(o2)
    

    实现 new 操作符

    大体可以分为3步
    1.创建一个新的对象
    2.将新对象的的 _ proto _ 指向 构造函数的 prototype 对象
    3.执行构造函数,并将 this 指向新的对象

    function myNew(fn, ...args) {
        if(typeof fn !== 'function') {
            throw fn + 'is not a constructor'
        }
        let obj = {};
        obj.__proto__ = fn.prototype;
        // let obj = Object.create(null)  也使用 ES5 的 Object.create() 来代替上面两行代码
        let res = fn.apply(obj, args);
        return res instanceof Object ? res : obj;  
        // return 的时候需要对返回的东西进行判断,若是对象则返回,如果不是对象,则返回新创建的对象。
    }
    // 测试
    function Person(name, age) {
        this.name = name;
        this.age = age
    }
    console.log(myNew(Person, "dingding", 100))
    

    实现instanceof

    a instanceof b 用于判断 构造函数 b 的 prototype 是否存在于 a 的原型链上
    常用于判断引用类型

    function myInstanceof(left, right) {
        left = left.__proto__;
        
        while(left !== right.prototype) {
            left = left.__proto__
            if(left === null) 
            return false
        }
        return true;
    }
    
    
    // 测试
    var a = []
    var b = {}
     
    function Foo(){}
    var c = new Foo()
     
    function child(){}
    function father(){}
    child.prototype = new father() 
    var d = new child()
     
    console.log(instance_of(a, Array)) // true
    console.log(instance_of(b, Object)) // true
    console.log(instance_of(b, Array)) // false
    console.log(instance_of(a, Object)) // true
    console.log(instance_of(c, Foo)) // true
    console.log(instance_of(d, child)) // true
    

    防抖

    在事件被触发n秒后再执行回调函数,如果在这n秒内又被触发,则重新计时

    function ajax() {
        console.log("我是 ajax")
    }
    function debounce(cb, delay) {
        let timer = null;
        return function(args) {
            let that = this;   // 获得函数的作用域
            clearTimeout(timer);  // 每次事件被触发,都会清除当前的timeer,然后重写设置超时调用
            timer = setTimeout(function(){
                cb.apply(that, args);
            }, delay)
        }
    }
    
    // 测试,模拟一个在 2100毫秒内每隔半秒就重复触发的事件
    var rsu = debounce(ajax, 1000,);
    let itv = setInterval(()=> {
        rsu(888)
    }, 500)
    setTimeout(()=> {
        clearInterval(itv)
    }, 2100)
    

    节流

    使得一定时间内只触发一次函数。原理是通过判断是否到达一定时间来触发函数

    function jl(fn, time, ...args) {
        let lock = false;
        return function() {
            console.log(1111)
            if(lock) return;
            setTimeout(() => {
                fn.apply(this, ...args);
                lock = true;
            }, time)
        }
    }
    function ajax() {
        console.log(999999999)
    }
    // 测试
    var fun = jl(ajax, 2000);
    let st = setInterval(()=> {
        fun()
    }, 600)
    setTimeout(()=> {
        clearInterval(st)
    }, 2000)
    

    函数节流不管事件触发有多频繁,都会保证在规定时间内一定会执行一次真正的事件处理函数,而函数防抖只是在最后一次事件后才触发一次函数。 比如在页面的无限加载场景下,我们需要用户在滚动页面时,每隔一段时间发一次 Ajax 请求,而不是在用户停下滚动页面操作时才去请求数据。这样的场景,就适合用节流技术来实现。

    函数柯里化

    实现 sum(11, 22, 33) => sum(11, 22)(33)() = 66 的效果
    传入参数时,不执行,而是先记忆起来,延迟计算,什么时候想要计算,直接 sum() 就行

    var currying = function (fn) {
        var args = []
        return function() {
            if(arguments.length === 0) {
                return fn.apply(this, args)
            }
            // [].slice.call(arguments) 将函数的实际参数转化成数组
            Array.prototype.push.apply(args, [].slice.call(arguments))
            // arguments.callee 返回当前匿名函数
            // rguments 的主要用途是保存函数参数, 但这个对象还有一个名叫 callee 的属性,
            // 返回正被执行的 Function 对象,也就是所指定的 Function 对象的正文,这有利于匿名函数的递归或者保证函数的封装性
            return arguments.callee
        }
    }
    var tempFun = function() {
        var sum = 0; 
        for(var i = 0; i < arguments.length; i++) {
            sum += arguments[i]
        }
        return sum
    }
    // 测试
    var sum = currying(tempFun);
    sum(11,22)
    sum(33)
    sum()
    

    call 和 apply

    用法:https://www.jianshu.com/p/aa2eeecd8b4f

    原本操作

    var o1 = {
     name: 'dingding'
    }
    function say() {
        console.log(222, this.name)
    }
    o1.fun = say;
    o1.fun()     // 222 dingding
    

    如果不使用 o1.fun = say 进项绑定
    手写apply

    Function.prototype.myapply = function(context, [...args]) {
      context = context || window;   // 判断上下文是否为空,若为空则指向window 对象
      context.fn = this;      
      context.fn(...args);
      delete context.fn;      // 要删除fn,不能影响原对象
    }
    
    // 测试
    var o1 = {
     name: 'dingding'
    }
    function say() {
        console.log(222, this.name)
    }
    say.apply(o1)
    

    手写call

    Function.mycall = function(context) {
      context = context || window;
      let args = [...arguments].slice(1);   // 截取arguments,从下标1开始的
      context.fn = this;
      context.fn(args);
      delete context.fn;
    }
    

    bind

    bind()方法主要就是将函数绑定到某个对象,bind()会创建一个函数,函数体内的this对象的值会被绑定到传入bind()第一个参数的值,
    例如,f.bind(obj),实际上可以理解为obj.f(),这时,f函数体内的this自然指向的是obj
    bind 基础用法:https://www.jianshu.com/p/25a855c01896

    下面实现myBind 方法可以绑定对象,可以传参, 还要注意函数柯里化的情况

    Function.prototype.myBind = function(context) {
        const self = this;
        let args = [...arguments].slice(1);   // args: [7, 8]
        
        return function() {
            // 考虑函数柯里化的情况
            let newArgs = [...arguments];     //newArgs: [9]
            return self.apply(context, newArgs.concat(args))
        }
    }
    
    // 测试
    function a(m, n, o){
        return this.name + ' ' + m + ' ' + n + ' ' + o;
    }
    var b = {name : 'kong'};
    console.log(a.myBind(b, 7, 8)(9)); 
    

    实现Ajax

    实现一个ajax其实主要是一个XMLHttpRequest对象以及其API方法的一个使用的问题。而在这里我建议尽量封装成promise的形式,方便使用。

    function ajax({url, methods, body, headers}) {
        return new Promise((resolve, reject) => {
            let request = new XMLHttpRequest();
            request.open(url, methods);
            for(let key in headers) {
                let value = headers[key]
                request.setRequestHeader(key, value);
            }
            request.send(body)
            request.onreadystatechange = () => {
                if(request.readyState === 4) {
                    if(request.status >= '200' && request.status < 300) {
                        resolve(request.responeText);
                    } else {
                        reject(request)
                    }
                }
            }
           
        })
    }
    

    Promise.all

    // 假设一下Promise其他所有函数都正常工作,但Promise.all功能失效了,我们现在就要为程序重写一个Promise.all
    Promise.all = function(promises) {
        let results = [];
        let promiseCount = 0;
        return new Promise((resolve, reject) => { 
            for(let i = 0; i < promises.length; i++) {            // 使用let保证promise顺序执行
                Promise.resolve(promises[i]).then(res => {    // 传入的 promises 元素可能不是 Promise 类型的,使用 Promise.resolve(arr[i]) 转换。
                    promiseCount++;
                    results[i] = res;
                    if(promiseCount === promises.length) {        // 当所有函数都正确执行了,resolve输出所有返回结果。
                        resolve(results);
                    }
                }, err => {
                    reject(err);
                })
            }
        })
    }
    
    let p1 = new Promise((resolve) => {
        setTimeout(()=> {
            console.log("p1 resolve");
            resolve(111);
        }, 1000)
    })
    let p2 = new Promise((resolve) => {
        console.log('p2 resolve');
        resolve(222);
    })
    let p3 = new Promise((resolve) => {
        console.log('p3 resolve');
        resolve(333);
    })
    
    var p = Promise.all([p1, p2, p3]);
    console.log(1212, p)
    p.then(e => {
        console.log(e)
    });
    



    若有错误,欢迎留言~~

    image.png

    相关文章

      网友评论

          本文标题:前端手写代码

          本文链接:https://www.haomeiwen.com/subject/mmguhktx.html