如何使用50行javaScript代码实现简单版的call,apply,bind


Posted in Javascript onAugust 14, 2019

在实现自己的call,apply,bind前,需要复习一下this.

所谓的this其实可以理解成一根指针:

其实 this 的指向,始终坚持一个原理:this 永远指向最后调用它的那个对象,这就是精髓。最关键所在

this的四种指向:

当this所在的函数被普通调用时,指向window,如果当前是严格模式,则指向undefined

function test() {
 console.log(this);
};

test();
指向window 输出下面的代码:
// Window {speechSynthesis: SpeechSynthesis, caches: CacheStorage, localStorage: Storage, sessionStorage: Storage, webkitStorageInfo: DeprecatedStorageInfo…}
严格模式
'use strict';
function test() {
 console.log(this);
};
test();
// undefined

当this所在当函数被以obj.fn()形式调用时,指向obj

var obj = {
 name: 'segmentFault',
 foo: function() {
  console.log(this.name);
 }
}
obj.foo();
// 'segmentFault'

还可以这么做

function test() {
 console.log(this.name);
}
var obj = {
 name: 'qiutc',
 foo: test
}
obj.foo();
// 'qiutc'

当call,apply加入后,this的指向被改变了

function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  const b = {
    name: "segmentFault"
  }
  a.call(b,1,2,3)    
  //输出 segmentFault和 1,2,3
  function a(a,b,c) {
    console.log(this.name);
    console.log(a,b,c)
  }
  a.apply(b,[1,2,3])
  //输出segmentFault和1,2,3

遇到bind后 :

function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  a.bind(b, 1, 2, 3)

此时控制台并没有代码输出,因为bind会重新生成并且返回一个函数,这个函数的this指向第一个参数

function a() {
    console.log(this.name);
  }
  const b = {
    name: "segmentFault"
  }
  const c = a.bind(b, 1, 2, 3)
  c()
  //此时输出segmentFault

正式开始自己实现call :

在函数原型上定义自己的myCall方法:

Function.prototype.myCall = function (context, ...arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }

四行代码实现了简单的call,思路如下:

  • 通过对象属性的方式调用函数,这个函数里面的this指向这个对象
  • 每次调用新增一个symbol属性,调用完毕删除
  • 这个symbol属性就是调用mycall方法的函数
  • 函数形参中使用...arg是将多个形参都塞到一个数组里,在函数内部使用arg这个变量时,就是包含所有形参的数组
  • 在调用 context[fn](...arg)时候,...arg是为了展开数组,依次传入参数调用函数

为了简化,今天都不做类型判断和错误边际处理,只把原理讲清楚。

自己实现apply

在函数原型上定义自己的myApply方法:

//实现自己的myApply
  Function.prototype.myApply = function (context, arg) {
    const fn = Symbol('临时属性')
    context[fn] = this
    context[fn](...arg)
    delete context[fn]
  }
  const obj2 = {
    a: 1
  }
  test.myApply(obj2, [2, 3, 4])

同理,只是apply传递的第二个参数是数组,这里我们只需要在调用时,将参数用...把数组展开即可

自己实现bind:

bind跟apply,call的本质区别,bind不会改变原函数的this指向,只会返回一个新的函数(我们想要的那个this指向),并且不会调用。但是apply和bind会改变原函数的this指向并且直接调用

bind在编写框架源码,例如koa等中用得特别多:

//实现自己的myBind
  Function.prototype.myBind = function (context, ...firstarg) {
    const that = this
    const bindFn = function (...secoundarg) {
      return that.myCall(context, ...firstarg, ...secoundarg)
    }
    bindFn.prototype = Object.create(that.prototype)
    return bindFn
  }

  var fnbind = test.myBind(obj, 2)
  fnbind(3)

同理 自己定义好原型上的myBind方法

this劫持 保留最初的调用mybind方法的那个对象

返回一个新的函数 这个新的函数内部this指向已经确定,使用的是我们的mycall方法

学习需要循序渐进,建议根据本文顺序去封装一遍,是比较轻松的,当然bind还需要判断是否是new调用.

完整版本bind

Function.prototype.myBind = function (objThis, ...params) {
  const thisFn = this; // 存储源函数以及上方的params(函数参数)
  // 对返回的函数 secondParams 二次传参
  let fToBind = function (...secondParams) {
    console.log('secondParams',secondParams,...secondParams)
    const isNew = this instanceof fToBind // this是否是fToBind的实例 也就是返回的fToBind是否通过new调用
    const context = isNew ? this : Object(objThis) // new调用就绑定到this上,否则就绑定到传入的objThis上
    return thisFn.call(context, ...params, ...secondParams); // 用call调用源函数绑定this的指向并传递参数,返回执行结果
  };
  fToBind.prototype = Object.create(thisFn.prototype); // 复制源函数的prototype给fToBind
  return fToBind; // 返回拷贝的函数
};

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript脚本调试方法小结
Nov 24 Javascript
IE innerHTML,outerHTML所引起的问题
Jun 04 Javascript
JQuery自适应IFrame高度(支持嵌套 兼容IE,ff,safafi,chrome)
Mar 28 Javascript
jQuery链式操作如何实现以及为什么要用链式操作
Jan 17 Javascript
jQuery标签编辑插件Tagit使用指南
Apr 21 Javascript
浅谈JavaScript字符串与数组
Jun 03 Javascript
Bootstrap进度条实现代码解析
Mar 07 Javascript
js处理包含中文的字符串实例
Oct 11 Javascript
关于layui时间回显问题的解决方法
Sep 24 Javascript
JS实现简单随机3D骰子
Oct 24 Javascript
vue把输入框的内容添加到页面的实例讲解
Nov 11 Javascript
React学习之受控组件与数据共享实例分析
Jan 06 Javascript
微信小程序之数据绑定原理解析
Aug 14 #Javascript
微信公众号平台接口开发 菜单管理的实现
Aug 14 #Javascript
vue.js中ref和$refs的使用及示例讲解
Aug 14 #Javascript
微信公众号平台接口开发 获取微信服务器IP地址方法解析
Aug 14 #Javascript
vue filter 完美时间日期格式的代码
Aug 14 #Javascript
如何对react hooks进行单元测试的方法
Aug 14 #Javascript
vue 中 命名视图的用法实例详解
Aug 14 #Javascript
You might like
PHP实现原比例生成缩略图的方法
2016/02/03 PHP
PHP 多进程与信号中断实现多任务常驻内存管理实例方法
2019/10/04 PHP
nicejforms——美化表单不用愁
2007/02/20 Javascript
可以文本显示的公告栏的js代码
2007/03/11 Javascript
动态读取JSON解析键值对的方法
2014/06/03 Javascript
点击标签切换和自动切换DIV选项卡
2014/08/10 Javascript
js单独获取一个checkbox看其是否被选中
2014/09/22 Javascript
javascript基础知识分享之类与函数化
2016/02/13 Javascript
Bootstrap风格的zTree右键菜单
2017/02/17 Javascript
JavaScript-定时器0~9抽奖系统详解(代码)
2017/08/16 Javascript
再谈Angular4 脏值检测(性能优化)
2018/04/23 Javascript
vue表单验证你真的会了吗?vue表单验证(form)validate
2019/04/07 Javascript
js+canvas实现两张图片合并成一张图片的方法
2019/11/01 Javascript
js实现百度淘宝搜索功能
2020/02/17 Javascript
vue iview实现动态新增和删除
2020/06/17 Javascript
javascript中导出与导入实现模块化管理教程
2020/12/03 Javascript
vue3.0中友好使用antdv示例详解
2021/01/05 Vue.js
python引用DLL文件的方法
2015/05/11 Python
python+selenium实现登录账户后自动点击的示例
2017/12/22 Python
基于Python中numpy数组的合并实例讲解
2018/04/04 Python
pandas groupby 分组取每组的前几行记录方法
2018/04/20 Python
python 日志增量抓取实现方法
2018/04/28 Python
pyqt 实现为长内容添加滑轮 scrollArea
2019/06/19 Python
Python3多线程版TCP端口扫描器
2019/08/31 Python
Python实现栈的方法详解【基于数组和单链表两种方法】
2020/02/22 Python
详解python中groupby函数通俗易懂
2020/05/14 Python
Ubuntu20.04环境安装tensorflow2的方法步骤
2021/01/29 Python
html5 canvas绘制网络字体的常用方法
2019/08/26 HTML / CSS
拥有超过850家商店的美国在线派对商店:Party City
2018/10/21 全球购物
农村改厕实施方案
2014/03/22 职场文书
竞选班长演讲稿400字
2014/08/22 职场文书
领导班子遵守党的政治纪律情况对照检查材料
2014/09/26 职场文书
2014年作风建设心得体会
2014/10/22 职场文书
先进班组材料范文
2014/12/25 职场文书
2016年班主任新年寄语
2015/08/18 职场文书
小学四年级作文之人物作文
2019/11/06 职场文书