如何使用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 相关文章推荐
JS.getTextContent(element,preformatted)使用介绍
Sep 21 Javascript
原生Ajax 和jQuery Ajax的区别示例分析
Dec 17 Javascript
JavaScript中document对象使用详解
Jan 06 Javascript
提升jQuery的性能需要做好七件事
Jan 11 Javascript
Javascript 字符串模板的简单实现
Feb 13 Javascript
动态生成的DOM不会触发onclick事件的原因及解决方法
Aug 06 Javascript
原生js代码实现图片放大境效果
Oct 30 Javascript
JavaScript遍历Json串浏览器输出的结果不统一问题
Nov 03 Javascript
javascript跨域请求包装函数与用法示例
Nov 03 Javascript
纯js模仿windows系统日历
Feb 04 Javascript
浅谈JsonObject中的key-value数据解析排序问题
Dec 06 Javascript
jQuery实现穿梭框效果
Jan 19 jQuery
微信小程序之数据绑定原理解析
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
详解PHP5.6.30与Apache2.4.x配置
2017/06/02 PHP
thinkphp5 migrate数据库迁移工具
2018/02/20 PHP
Yii框架 session 数据库存储操作方法示例
2019/11/18 PHP
javascript 原型模式实现OOP的再研究
2009/04/09 Javascript
javascript Base类 包含基本的方法
2009/07/22 Javascript
利用JS自动打开页面上链接的实现代码
2011/09/25 Javascript
jQuery阻止同类型事件小结
2013/04/19 Javascript
Jquery操作下拉框(DropDownList)实现取值赋值
2013/08/13 Javascript
node.js中的dns.getServers方法使用说明
2014/12/08 Javascript
JS制作简单的三级联动
2015/03/18 Javascript
JQuery显示隐藏页面元素的方法总结
2015/04/16 Javascript
jquery判断单选按钮radio是否选中的方法
2015/05/05 Javascript
jQuery垂直多级导航菜单代码分享
2015/08/18 Javascript
原生JS简单实现ajax的方法示例
2016/11/29 Javascript
JS仿JQuery选择器功能
2017/03/08 Javascript
老生常谈js中0到底是 true 还是 false
2017/03/08 Javascript
通过构造函数实例化对象的方法
2017/06/28 Javascript
vue中计算属性(computed)、methods和watched之间的区别
2017/07/27 Javascript
Vue2.0基于vue-cli+webpack Vuex的用法(实例讲解)
2017/09/15 Javascript
vue环境搭建简单教程
2017/11/07 Javascript
Element实现表格分页数据选择+全选所有完善批量操作
2019/06/07 Javascript
JS实现网页端猜数字小游戏
2020/03/06 Javascript
[01:34]DAC2018主赛事第四日五佳镜头 Gh巨牙海民助Miracle-死里逃生
2018/04/07 DOTA
Python程序员鲜为人知但你应该知道的17个问题
2014/06/04 Python
详解Django中的form库的使用
2015/07/18 Python
python3 http提交json参数并获取返回值的方法
2018/12/19 Python
linux查找当前python解释器的位置方法
2019/02/20 Python
python生成随机红包的实例写法
2019/09/02 Python
python pygame实现挡板弹球游戏
2019/11/25 Python
Pycharm中import torch报错的快速解决方法
2020/03/05 Python
python实现计算图形面积
2021/02/22 Python
毕业大学生自荐信
2014/06/17 职场文书
什么是求职信?求职信应包含哪些内容?
2019/08/14 职场文书
NGINX 权限控制文件预览和下载的实现原理
2022/01/18 Servers
win11无法添加打印机怎么办? 提示windows无法打开添加打印机的解决办法
2022/04/05 数码科技
javascript进阶篇深拷贝实现的四种方式
2022/07/07 Javascript