手动实现Javascript中 apply,call,bind,new,节流防抖函数,让你一次性全搞懂。

一、写在前面

趁着做毕设的空闲时间,打算梳理一下 Javascript 中几个比较重要的函数,并手动来实现一下。可以加深自己对这些函数的理解,从而更方便在平时项目中使用,所谓知其然,亦知其所以然也。梳理了一下主要有以下几个函数,往后会不断更新。

  • 节流,防抖函数
  • call,apply 和 bind 方法
  • new方法

二、节流防抖(throttle & debounce)

1)、什么是节流、防抖。

函数的节流和防抖是前端对于前端代码性能优化的一个重要的组成部分,节流,顾名思义,就是当我们执行一个函数多次时,我们只让它在一定时间段执行一次,达到节制的目的。防抖就是一段时间内一个函数多次执行时,我们让它只执行一次。这里引出别人的概念。

函数防抖(debounce):
在事件被触发n秒后再执行回调,如果在这n秒内又被触发,则重新计时;典型的案例就是输入搜索:输入结束后n秒才进行搜索请求,n秒内又输入的内容,就重新计时。

函数节流(throttle):
规定在一个单位时间内,只能触发一次函数,如果这个单位时间内触发多次函数,只有一次生效; 典型的案例就是鼠标不断点击触发,规定在n秒内多次点击只有一次生效。

上面的概念其实也给了我们实现函数的思路,下面就来实现一下。

// 节流函数
function throttle(fn) {
    // 先设置标识
    let flag = true
    return function () {
        // 触发时间段还没过去
        if (!flag) return
        // 改变状态
        flag = false
        // 这里我没有写在setTimeout里,因为我想点击立即触发。
        fn.apply(this, arguments)
        setTimeout(() => {
            // 一秒后变为true标识该函数可以再次触发了
            flag = true
        }, 1000)
    }
}

// 防抖函数
function debounce(fn) {
    // 开始定时器为null
    let time = null
    return function () {
        // 清除之前的 time,永远保留这段时间内的最后一个执行
        clearTimeout(time)
        time = setTimeout(() => {
            fn.apply(this, arguments)
        }, 500)
    }
}

三、call, apply, bind 方法

首先,这三者都是用来调用函数的,我们 Javascript 中的函数除了可以直接调用外,还可以用这三个函数来手动调用。那么问题就来了,为什么要手动去调用呢?

其实当我们调用一个函数时候,是在全局对象window 上调用的,这是时候 this 自然就指向 window ,所以调用结果是一样的。关于 this指向的问题 后面会出一篇文章详细讲讲。

// 浏览器中(因为在node中this默认指向一个空对象)
let name = 'rinvay'
function getName() {
    console.log(this.name);
}
window.getName() // rinvay
this.getName() // rinvay

那想想当我们需要在另一个对象而不是window对象上调用这个函数,是不是就没办法了,这个时候就需要我们的这三个函数出场了。下面来介绍这三个函数的基本用法。首先介绍 apllycall 的区别,这两个函数都是立即调用函数 第一个参数就是函数this的指向,其余参数在函数执行的时候传给函数。两个函数唯一的不同就是其余参数的传递形式,call个是一个个传apply是放在一个数组中

let person = {
    name: '树街猫'
}
let name = 'rinvay'

function getInfo(age, height) {
    console.log(this.name, age, height);
}
getInfo(21, 178) // rinvay 21 178

// call(this,arg1,arg2,...) 函数
getInfo.call(person, 21, 178) // 树街猫 21 178

// apply(this,[arg1,arg2,...]) 函数
getInfo.apply(person, [21, 178]) // 树街猫 21 178

bind这个函数比较特殊,它不同于上面两个。她改变了this指向之后不会立即执行该函数而是返回一个新的绑定函数

let person = {
    name: '树街猫'
}
let getInfoBind = getInfo.bind(person, 21, 178)
// 得到后执行
getInfoBind() // 树街猫 21 178

这样三个函数的基本使用搞清楚了,还有就是也可以不传参,不改变this指向,就和直接调用函数的效果一样。

搞清楚了用法,我们趁热打铁来实现一下。先写步骤,再写代码。

1)call 和 apply

  1. 将函数的 this指向 指向 obj
  2. 传入参数并执行函数

实现代码

// call 函数实现
Function.prototype._call = function (obj,...rest) {
    if (typeof this !== 'function') return
    // 这里this指向调用 _call 的函数,也就是函数本身。所以将函数赋值给对象的fn属性。对象调用函数时候函数的this自然指向对象。
    // 这里用了Symbol.for API 大概就是在没有fn的时候运行Symbol.for('fn')会创建一个fn放入注册表,有了之后会返回这个值。
    obj[Symbol.for('fn')] = this;
    // 取到fn这个值,对象调用函数。
    obj[Symbol.for('fn')](...rest);
    // 这里需要删除这个属性,否则越来越多。
    delete obj[Symbol.for('fn')]
}

// apply 函数 其实只是参数的处理变了而已,
Function.prototype._apply = function (obj) {
    if (typeof this !== 'function') return
    obj[Symbol.for('fn')] = this;
    obj[Symbol.for('fn')](...arguments[1]);
    delete obj[Symbol.for('fn')]
}

2)bind函数

下面来说说bind函数,这个和上面的区别就是它在绑定后不执行,返回一个绑定函数

  1. 将函数的 this指向 指向 obj
  2. 传入参数并返回函数

但是这里bind有点不一样,它支持函数柯里化,就是如果有两个参数,可以先传一个参数,执行返回函数的时候再传入这里需要除了剩余参数 。

Function.prototype._bind = function (obj) {
    if (typeof this !== 'function') {
        return;
    }
    var _this = this;
    //从第二个参数截取
    var args = Array.prototype.slice.call(arguments, 1)
    return function () {
        // 剩余参数的处理合并再传入
        return _this.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
    }
};

这里三个函数都实现完毕了,当然可能有点小瑕疵,希望发现问题的评论区留言。

四、new方法

平时我们都是怎么用的 new 呢,无非就是new 一个类嘛,我们 Javascript 叫做构造函数,其实就是普通函数。我们ES中的calss关键字其实只是语法糖,实质还是一个函数,可以打印类型看一看。下面演示一下

function Person(name, age) {
    this.name = name
    this.age = age
}

let p1 = new Person('rinvay', 21)
console.log(p1.name); // rinvay

感觉它做的事情就是创建了一个新的对象把Person里面的属性给了这个对象,再返回。哈哈哈,这只是感觉,其实过程是这样的。

  1. 创建一个空对象,将它的引用赋给 this,继承函数的原型。
  2. 通过 this 将属性和方法添加至这个对象
  3. 最后返回 this 指向的新对象,也就是实例(如果没有手动返回其他的对象)

这里只描述了如何将构造器中的属性方法给实例,那么原型上的实例呢?这里我们就用以构造器原型去完成创建第一步那个空对象,下面代码实现一下。这里给出实现步骤

  1. 以构造器的prototype属性为原型,创建新对象;
  2. 将this(也就是上一句中的新对象)和调用参数传给构造器,执行;
  3. 如果构造器没有手动返回对象,则返回第一步创建的新对象,如果有,则舍弃掉第一步创建的新对象,返回手动return的对象。
function _new(constructor, ...rest) {
    let obj = Object.create(constructor.prototype)
    let res = constructor.apply(obj, rest)
    return typeof res === 'object' ? res : obj
}

好了这里常用的api函数就差不多实现完成了,凌晨了也该睡觉了。

喜欢的话点个关注吧,你的点赞关注是我最大的动力。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 156,907评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,546评论 1 289
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,705评论 0 238
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,624评论 0 203
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,940评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,371评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,672评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,396评论 0 195
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,069评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,350评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,876评论 1 256
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,243评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,847评论 3 231
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,004评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,755评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,378评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,266评论 2 259

推荐阅读更多精彩内容