如何使用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 设计模式 推荐
Oct 28 Javascript
JavaScript打印iframe内容示例代码
Aug 20 Javascript
利用js实现在浏览器状态栏显示访问者在本页停留的时间
Dec 29 Javascript
使用控制台破解百小度一个月只准改一次名字
Aug 13 Javascript
基于jQuery实现简单的折叠菜单效果
Nov 23 Javascript
AngularJS向后端ASP.NET API控制器上传文件
Feb 03 Javascript
JS实现消息来时让网页标题闪动效果的方法
Apr 20 Javascript
Bootstrap开关(switch)控件学习笔记分享
May 30 Javascript
微信小程序实战之轮播图(3)
Apr 17 Javascript
webpack构建react多页面应用详解
Sep 15 Javascript
Vue.js2.0中的变化小结
Oct 24 Javascript
Echart折线图手柄触发事件示例详解
Dec 16 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中设置、使用、删除Cookie的解决方法
2013/05/06 PHP
php session_start()出错原因分析及解决方法
2013/10/28 PHP
php计算几分钟前、几小时前、几天前的几个函数、类分享
2014/04/09 PHP
php+mysql+jquery实现简易的检索自动补全提示功能
2017/04/15 PHP
tp5(thinkPHP5)操作mongoDB数据库的方法
2018/01/20 PHP
JavaScript实现页面滚动图片加载(仿lazyload效果)
2011/07/22 Javascript
基于jQuery+HttpHandler实现图片裁剪效果代码(适用于论坛, SNS)
2011/09/02 Javascript
你可能不知道的JavaScript的new Function()方法
2014/04/17 Javascript
Markdown与Bootstrap相结合实现图片自适应属性
2016/05/04 Javascript
拖动时防止选中
2017/02/03 Javascript
JavaScript 总结几个提高性能知识点(推荐)
2017/02/20 Javascript
nodejs6下使用koa2框架实例
2017/05/18 NodeJs
Require.js的基本用法详解
2017/07/03 Javascript
Vue-router 类似Vuex实现组件化开发的示例
2017/09/15 Javascript
微信小程序实现滑动切换自定义页码的方法分析
2018/12/29 Javascript
js实现弹出框的拖拽效果实例代码详解
2019/04/16 Javascript
小程序卡片切换效果组件wxCardSwiper的实现
2020/02/13 Javascript
jquery实现手风琴案例
2020/05/04 jQuery
Vue组件生命周期运行原理解析
2020/11/25 Vue.js
django 自定义用户user模型的三种方法
2014/11/18 Python
详细介绍Python函数中的默认参数
2015/03/30 Python
详解python的数字类型变量与其方法
2016/11/20 Python
浅谈Python中的可变对象和不可变对象
2017/07/07 Python
Python3爬虫教程之利用Python实现发送天气预报邮件
2018/12/16 Python
Python基于datetime或time模块分别获取当前时间戳的方法实例
2019/02/19 Python
基于PyQt4和PySide实现输入对话框效果
2019/02/27 Python
Pytorch卷积层手动初始化权值的实例
2019/08/17 Python
python模块hashlib(加密服务)知识点讲解
2019/11/25 Python
python+requests接口自动化框架的实现
2020/08/31 Python
毕业实习个人鉴定范文
2013/12/10 职场文书
工程项目建议书范文
2014/03/12 职场文书
总账会计岗位职责
2015/04/02 职场文书
2015年社区环境卫生工作总结
2015/04/21 职场文书
认识实习感想
2015/08/10 职场文书
mysql部分操作
2021/04/05 MySQL
Python循环之while无限迭代
2022/04/30 Python