前端手写代码

1.手写call

    Function.prototype.myCall = function (context,...rest) {
        //检测改变后的上下文对象的类型
        var type = typeof context;
        //判断改变后的上下文对象是null和undefined的时候 this应该指向window
        if (context === null || context === undefined) {
            context = window;
        }
        //如果改变后的上下文对象是基本包装类型,则this指向其包装对象
        switch (type) {
            case "number":
                context = new Number(context);
                break;
            case "boolean":
                context = new Boolean(context);
                break;
            case "string":
                context = new String(context);
                break;
        }
        //rest就是要传递给函数的参数,只不过是一个数组类型
        console.log(rest)

        //因为fn1.myCall调用,所以这里的this指向的就是fn1
        //context就是改变之后的上下文对象
        //给context扩展一个方法,这个方法就是fn1,
        //给context扩展的方法名要是一个独一无二的值,防止覆盖原有方法
        var key = Symbol();
        context[key] = this;

        //然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context,传入参数
        const result = context[key](...rest);

        //此时改变之后的上下文对象context就会多一个方法,所以使用完成之后要删除掉这个方法
        delete context[key];

        //函数的返回值
        return result;
    }

 2.手写bind

Function.prototype.mybind = function (obj) {
    if (obj === undefined || obj === null) {
    	obj = window;
    }
    obj = Object(obj);
    obj.fn = this;
  return function (...args) {
    obj.fn(...args);
    delete obj.fn;
  };
};
function fn(a, b, c) {
  console.log(this, a, b, c);
}
fn.mybind({ name: "hello" })(1, 2, 3);

3.手写apply

    //类似于call 
    Function.prototype.myApply = function (context,...rest) {
        var type = typeof context;
        //判断改变后的上下文对象是null和undefined的时候 this应该指向window
        if (context === null || context === undefined) {
            context = window;
        }
        //如果改变后的上下文对象是基本包装类型,则this指向其包装对象
        switch (type) {
            case "number":
                context = new Number(context);
                break;
            case "boolean":
                context = new Boolean(context);
                break;
            case "string":
                context = new String(context);
                break;
        }
        //因为fn1.myCall调用,所以这里的this指向的就是fn1
        //context就是改变之后的上下文对象
        //给context扩展一个方法,这个方法就是fn1,
        var key = Symbol();
        context[key] = this;
        //然后调用context的扩展的这个方法,fn1就会被调用,并且this指向了context
        var result = context[key](...rest[0]);
        //此时改变之后的上下文对象context就会多一个方法,所以使用完成之后要删除掉这个方法
        delete context[key];

        return result;

    }

4.防抖

    //真正的事件发生时的逻辑代码
    function inputChange(e) {
        console.log("表单改变 请求数据");
        console.log(e);
        console.log(this);
    }
    oIpt.oninput = debounce(inputChange, 800)

    //防抖函数
    function debounce(fn, time) {
        //初始化一个计时器
        var timer = null;
        //事件函数
        return function () {
            //每次触发的时候,先把上一次的未执行的计时器清掉,然后重新开始计时(那么上一次的没有执行的逻辑函数就不会再执行了,而是重新计时)
            clearTimeout(timer)
            //在计时器中 arguments是不符合的,需要使用这个位置的arguments,需要保存起来
            var arg = arguments;
            //保存外边的this 在计时器函数中使用
            var that = this;
            //每次触发事件,先不执行,要延迟一定的时间再执行
            timer = setTimeout(function () {
                fn.call(that, arg[0]);
            }, time)
        }
    }

5.节流

/*
  函数的节流(throttle)与防抖(debounce)
     作用:为了节约函数的性能(让函数调用次数更少)
     节流(throttle):让函数在单位时间内只调用一次,第一次调用生效
            应用场景:发送验证码按钮
     防抖(debounce):让函数在单位时间内只调用一次,最后一次调用生效
            应用场景:搜索栏
*/

    //move才是真正的事件发生时的逻辑代码
    function move(e) {
        //以下是真正逻辑代码code
        console.log(1);
        console.log(e);
        console.log(this);
    }

    //把move传递给节流函数,节流函数调用以后 返回一个新函数 赋值给move事件
    oBox.onmousemove = throttle(move, 200);


    function scroll() {
        console.log("滚了")
    }
    window.onscroll = throttle(scroll, 300)


    //节流函数(高阶函数)
    function throttle(fn, time) {
        //绑定事件的时候,先初始化一个上一次的时间(第一次的话没有上一次,所以初始化时间为0即可)
        var lastTime = 0;

        //这个函数是事件触发的时候真正调用的事件函数
        return function () {
            //这个函数就负责书写看门狗,当允许通过的时候 再调用真正的逻辑代码move
            var nowTime = Date.now();
            if (nowTime - lastTime < time) {
                return;
            }
            lastTime = nowTime;

            //arguments所在的函数就是真正的事件函数,所以拥有实参event  把event事件对象传递给fn move中就可以使用event事件对象了
            // fn(arguments[0]);
            //改变了fn的this为事件触发的对象
            fn.call(this, arguments[0])
        }
    }

6.深拷贝

    //方案1 
    function deepClone(obj) {
        //判断类型 如果是基本类型 则直接返回 如果是对象类型,则开始拷贝
        if (checkType(obj) === 'object') {
            var newObj = {};
        } else if (checkType(obj) === 'array') {
            var newObj = [];
        } else {
            return obj;
        }
        //拷贝
        for (var key in obj) {
            //每次拷贝之前 把拷贝的递归一下,如果是基本值,则直接返回,否则再次拷贝
            newObj[key] = deepClone(obj[key]);
        }

        return newObj;
    }

    //方案2
    //不能拷贝方法
    var re = JSON.parse(JSON.stringify(obj1))

7.new操作符

    /* 
        手写new思路:
            1.声明一个对象obj,作为new的返回值(实例化对象)
            2.调用构造函数,并且把构造函数的this指向obj
            3.修改obj的隐式原型为构造函数的显示原型
            4.判断构造函数的返回值类型,如果是基本类型则正常返回obj,否则返回构造函数的返回值
    */
    function myNew(FN) {
        //声明一个对象obj,作为new的返回值(实例化对象)
        var obj = {};
        //调用构造函数,并且把构造函数的this指向obj
        var FNReturn = FN.apply(obj, Array.from(arguments).slice(1));
        //修改obj的隐式原型为构造函数的显示原型
        obj.__proto__ = FN.prototype;

        //判断类型是object 并且不是null
        if (typeof FNReturn === 'object' && FNReturn != 'null') {
            return FNReturn;
        }

        //判断是function
        if (typeof FNReturn === 'function') {
            return FNReturn;
        }

        //基本类型值
        return obj;

    }

8.实现一个instanceof

    function myInstanceof(A, B) {
        var BPro = B.prototype;
        var startA = A.__proto__;

        //如果while条件达不到,则说明B不在A的原型链上 返回false
        while (startA) {
            if (startA === BPro) {
                return true;
            }
            //每次要获取上一级的原型对象
            startA = startA.__proto__;
        }

        return false;
    }

9.手写数据代理

// 定义Vue内部的实现
function Vue(options) {
  // 将options中的data保存到Vue实例的_data上
  this._data = options.data

  // 通过defineProperty给vue实例添加data中有所有属性
  // 遍历data中的所有属性, 分别去实现数据代理
  Object.keys(this._data).forEach(key => { // key就是msg/msg2
    // 给vue实例添加带getter和setter的属性
    Object.defineProperty(this, key, {
      // 在getter中: 读取并返回data对象中对应的属性值
      get () {
        console.log('getter')
        return this._data[key]
      },
      // 在setter中: 将最新的值保存到data对象的对应属性上
      set (value) {
        console.log('setter')
        this._data[key] = value
      }
    })
  })
}

10.手写数据劫持

// 定义Vue内部的实现
function Vue(options) {

  // 初始对data对象中所有层次属性的数据劫持(实现响应式数据)
  this._defineReactive(this._data)
}

Vue.prototype = {
  /* 
  对data中所有层次属性实现数据劫持(也就是定义响应式属性)
  */
  _defineReactive (data) {
    // 如果data不是对象, 直接结束
    if (data==null || typeof data != 'object') return

    Object.keys(data).forEach(key => {
      // 取出对应的属性值
      let value = data[key]
      
      // 通过递归调用, 实现对data内部对象的深度劫持
      this._defineReactive(value)

      // 给data中的key属性通过defineProperty添加getter和setter
      Object.defineProperty(data, key, {
        get () {
          console.log(`监视到读取data的${key}属性`)
          // return data[key]  // 会导致get执行, 死循环了
          return value
        },
        set (newValue) {
          // 保存最新的值
          value = newValue

          console.log(`监视到data的${key}属性变为了${newValue}`)
          console.log('准备去更新DOM')
        }
      })
    })
  },

}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值