js手写代码

new

原理: 

  1. 新生成了一个对象
  2. 链接到原型
  3. 绑定 this
  4. 返回新对象

实现: 

function create() {
    // 创建一个空的对象
    let obj = new Object()
    // 获得构造函数,以便实现作用域的绑定
    let Con = [].shift.call(arguments)
    // 由于通过new操作创建的对象实例内部的不可访问的属性[[Prototype]](有些浏览器里面为__proto__)
    // 指向的是构造函数的原型对象的,所以这里实现手动绑定。
    obj.__proto__ = Con.prototype
    // 绑定 this,执行构造函数
    let result = Con.apply(obj, arguments)
    // 确保 new 出来的是个对象
    return typeof result === 'object' ? result : obj
}

其他思路:

function create() {
    // 获得构造函数,以便实现作用域的绑定
    let Con = [].shift.call(arguments);
    // Object.create()方法创建一个新对象,使用现有的对象来提供新创建的对象的__proto__
    var newObj = Object.create(Con.prototype);
    // 作用域的绑定
    Con.apply(newObj, arguments);
    return newObj ;
}

instanceof 

 原理:

instanceof 接收两个参数,第一个为instance(实例),第二个为要Constructor(构造函数),返回值为boolean,函数内的实现其实就是对instance的原型链进行遍历(其实就是一个链表遍历),如果遍历到了某一项原型等于这个Constructor的原型,则返回true

实现: 

function myInstanceof(instance, Constructor){
    if(typeof instance !== "object" || instance === null){
        throw "the instance must be 'Object'";
    }
    if(typeof Constructor !== "function"){
        throw "the Contructor must be 'Function'"
    }   

    let prototype = instance.__proto__;
    // 遍历链表用while
    while(true){
        if (prototype === null){
            return false;
        }else if(prototype === Constructor.prototype){
            return true;
        }else{
            prototype = prototype.__proto__;
        }
    }
}

函数防抖(debounce)和 函数节流(throttle)

函数防抖 

函数防抖,就是指触发事件后在 n 秒内函数只能执行一次,如果在 n 秒内又触发了事件,则会重新计算函数执行时间。

简单的说,当一个动作连续触发,则只执行最后一次。

应用场景:

连续的事件,只需触发一次回调的场景有:

  • 搜索框搜索输入。只需用户最后一次输入完,再发送请求
  • 手机号、邮箱验证输入检测
  • 窗口大小Resize。只需窗口调整完成后,计算窗口大小。防止重复渲染

实现:

/**
   * @desc 函数防抖
   * @param func 目标函数
   * @param wait 延迟执行毫秒数
   * @param immediate true - 立即执行, false - 延迟执行
   */
function debounce(func, wait, immediate) {
    var timer;  // 维护一个 timer

    return function () {
        // 因为需要防抖一般是浏览器事件,那么我们就需要确保使用 debounce 处理过的事件处理程序不能出现一些意外的错误
        // 比如 this 指向和 event 对象
        // 以下两句代码用于解决 this 和 event 不正确的问题
        var context = this;  // 取debounce执行作用域的this
        var args = arguments;

        if (timer) clearTimeout(timer);
        if (immediate) {
            // 如果已经执行过,不再执行
            var hasCallback = !timer;
            timer = setTimeout(function(){
                timer = null; // 考虑到不再触发之后的情况,如果不置 timer 为null 就无法再实现下次的立即执行!
            }, wait)
            if (hasCallback) func.apply(context, args)
        }
        else {
            timer = setTimeout(function(){
                func.apply(context, args);  // 用apply指向调用debounce的对象,相当于this.func(args);
            }, wait);
        }
    }
}

函数节流

每隔一段时间,只执行一次函数。

应用场景:

间隔一段时间执行一次回调的场景有:

  • 滚动加载,加载更多或滚到底部监听
  • 谷歌搜索框,搜索联想功能
  • 高频点击提交,表单重复提交

原理:

通过使用定时任务,延时方法执行。在延时的时间内,方法若被触发,则直接退出方法。从而,实现函数一段时间内只执行一次。

实现:

function throttle(fn, interval) {
  let canRun = true;
  return function (...args) {
    if (!canRun) return // 如果 interval 时间内再次触发会在此处阻拦
    canRun = false;
    setTimeout(() => {
      canRun = true;
      fn.apply(this, args); // this 指向这个节流函数的调用者
    }, interval)
  }
}

异同比较

相同点:

  • 都可以通过使用 setTimeout 实现。
  • 目的都是,降低回调执行频率。节省计算资源。

不同点:

  • 函数防抖,在一段连续操作结束后,处理回调,利用 clearTimeout 和 setTimeout 实现。函数节流,在一段连续操作中,每一段时间只执行一次,频率较高的事件中使用来提高性能。
  • 函数防抖关注一定时间连续触发,只在最后执行一次,而函数节流侧重于一段时间内只执行一次。

深拷贝deepclone

简单版

function deepClone(obj){
    var cloneObj;
    //当输入数据为简单数据类型时直接复制
    if(obj === null || Object.prototype.toString.call(obj)==="[object RegExp]" || typeof obj !== 'object'){
        cloneObj = obj;
    }else{
       //检测输入数据是数组还是对象
       cloneObj = Array.isArray(obj) ? [] : {};
       Object.keys(obj).forEach((key)=>{
            cloneObj[key] = deepClone(obj[key])
        })
    }
    return cloneObj;
}

改进版

原理:

  • 判断类型是否为原始类型,如果是,无需拷贝,直接返回

  • 为避免出现循环引用,拷贝对象时先判断存储空间中是否存在当前对象,如果有就直接返回

  • 开辟一个存储空间,来存储当前对象和拷贝对象的对应关系

  • 对引用类型递归拷贝直到属性为原始类型

 实现:

const deepClone = (target, cache = new WeakMap()) => {
    if(target === null || typeof target !== 'object') {
        return target;
    }
    if(cache.get(target)) {
        return target;
    }
    const copy = Array.isArray(target) ? [] : {};
    cache.set(target, copy);
    Object.keys(target).forEach(key => copy[key] = deepClone(target[key], cache));
    return copy;
}

尤雨溪版

function find(list, f) {
    return list.filter(f)[0]
}
 
 
function deepCopy(obj, cache = []) {
    // just return if obj is immutable value
    if (obj === null || typeof obj !== 'object') {
        return obj
    }
 
 
    // if obj is hit, it is in circular structure
    const hit = find(cache, c => c.original === obj)
    if (hit) {
        return hit.copy
    }
 
 
    const copy = Array.isArray(obj) ? [] : {}
    // put the copy into cache at first
    // because we want to refer it in recursive deepCopy
    cache.push({
        original: obj,
        copy
    })
    Object.keys(obj).forEach(key => copy[key] = deepCopy(obj[key], cache))
 
 
    return copy
}

call

原理:

  • 第一个参数为null或者undefined时,this指向全局对象window,值为原始值的指向该原始值的自动包装对象,如 StringNumberBoolean

  • 为了避免函数名与上下文(context)的属性发生冲突,使用Symbol类型作为唯一值

  • 将函数作为传入的上下文(context)属性执行

  • 函数执行完成后删除该属性

  • 返回执行结果

实现:

Function.prototype.myCall = function(context, ...args) {
    if(typeof this !== 'function') {
        throw new TypeError('not a function!');
    }
    // 不传入第一个参数,那么默认为 window
    context = context || window;
    const key = Symbol();
    // 给 context 添加一个属性
    // eg: getValue.call(a, 'yck', '24') => a.fn = getValue
    context[key] = this;
    // eg: getValue.call(a, 'yck', '24') => a.fn('yck', '24')
    const result = context[key](...args);
    delete context[key];
    return result;
}

apply

原理:

  • 前部分与call一样

  • 第二个参数可以不传,但类型必须为数组或者类数组【注:javascript中常见的类数组有 arguments对象和 DOM方法的返回结果。比如 document.getElementsByTagName()。】

 实现:

Function.prototype.myApply = function(context) {
    if(typeof this !== 'function') {
        throw new TypeError('not a function!');
    }
    context =  context || window;
    const key = Symbol();
    const args = arguments[1];
    context[key] = this;
    let result;
    // 需要判断是否存在第二个参数,如果存在并且是数组类型,就将第二个参数展开
    // Array.prototype.slice.call将类数组转换成数组
    if(args && Array.isArray(Array.prototype.slice.call(args))) {
        result = context[key](...args);
    } else {
        result = context[key]();
    }
    delete context[key];
    return result;
}

bind

原理:

  • 使用 call / apply 指定 this

  • 返回一个绑定函数

  • 当返回的绑定函数作为构造函数被new调用,绑定的上下文指向实例对象

  • 设置绑定函数的prototype 为原函数的prototype

实现:

Function.prototype.myBind = function(context, ...args) {
    if(typeof this !== 'function') {
        throw new TypeError('not a function!');
    }
    const fn = this
    const bindFn = function (...newFnArgs) {
        // bind返回的函数如果作为构造函数,搭配new关键字出现的话,我们绑定的this就需要被忽略
        fn.call(
            this instanceof bindFn ? this : context,
            ...args, ...newFnArgs
        )
    }
    bindFn.prototype = Object.create(fn.prototype)
    return bindFn
}

 

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值