如何使用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 相关文章推荐
[全兼容哦]--实用、简洁、炫酷的页面转入效果loing
May 07 Javascript
js特殊字符转义介绍
Nov 05 Javascript
javascript事件冒泡和事件捕获详解
May 26 Javascript
javascript实现确定和取消提示框效果
Jul 10 Javascript
jQuery 插件封装的方法
Nov 16 Javascript
javascript 数组去重复(在线去重工具)
Dec 17 Javascript
详解Angular的内置过滤器和自定义过滤器【推荐】
Dec 26 Javascript
Vue通过URL传参如何控制全局console.log的开关详解
Dec 07 Javascript
vue 引入公共css文件的简单方法(推荐)
Jan 20 Javascript
使用vue-cli打包过程中的步骤以及问题的解决
May 08 Javascript
JS实现的自定义map方法示例
May 17 Javascript
vue实现分页栏效果
Jun 28 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
模拟OICQ的实现思路和核心程序(一)
2006/10/09 PHP
CodeIgniter钩子用法实例详解
2016/01/20 PHP
如何判断图片地址是否失效
2007/02/02 Javascript
基于jQuery的获得各种控件Value的方法
2010/11/19 Javascript
JavaScript 盒模型 尺寸深入理解
2012/12/31 Javascript
Javascript合并表格中具有相同内容单元格示例
2013/08/11 Javascript
jquery cookie实现的简单换肤功能适合小网站
2013/08/25 Javascript
jQuery中事件对象e的事件冒泡用法示例介绍
2014/04/25 Javascript
jQuery使用height()获取高度需要注意的地方
2014/12/13 Javascript
jQuery获取radio选中项的值实例
2016/06/18 Javascript
JavaScript简单获取页面图片原始尺寸的方法
2016/06/21 Javascript
基于JavaScript实现活动倒计时效果
2017/04/20 Javascript
php中and 和 &&出坑指南
2018/07/13 Javascript
10个最受欢迎的 JavaScript框架(推荐)
2019/04/24 Javascript
微信小程序HTTP接口请求封装代码实例
2019/09/05 Javascript
在vue+element ui框架里实现lodash的debounce防抖
2019/11/13 Javascript
JavaScript布尔运算符原理使用解析
2020/05/06 Javascript
Python闭包实现计数器的方法
2015/05/05 Python
编写Python爬虫抓取暴走漫画上gif图片的实例分享
2016/04/20 Python
Python中的多行注释文档编写风格汇总
2016/06/16 Python
Python 基于Twisted框架的文件夹网络传输源码
2016/08/28 Python
Python数据可视化正态分布简单分析及实现代码
2017/12/04 Python
python merge、concat合并数据集的实例讲解
2018/04/12 Python
numpy 进行数组拼接,分别在行和列上合并的实例
2018/05/08 Python
python tkinter窗口最大化的实现
2019/07/15 Python
python gdal安装与简单使用
2019/08/01 Python
python opencv鼠标事件实现画框圈定目标获取坐标信息
2020/04/18 Python
pd.DataFrame统计各列数值多少的实例
2019/12/05 Python
python 中值滤波,椒盐去噪,图片增强实例
2019/12/18 Python
keras 自定义loss损失函数,sample在loss上的加权和metric详解
2020/05/23 Python
Python实现曲线拟合的最小二乘法
2021/02/19 Python
美国最大的在线寄售和旧货店:Swap.com
2018/08/27 全球购物
意大利文具和办公产品在线商店:Y-Office
2020/02/27 全球购物
社会实践活动总结
2015/02/05 职场文书
《当代神农氏》教学反思
2016/02/23 职场文书
php双向队列实例讲解
2021/11/17 PHP