JavaScript 实现 call、apply、bind

关于 call、apply、bind 之前写过一篇文介绍过,看这里,本文介绍代码实现。

一、call

ES5 实现
// 先将我们自己定义的myCall绑定到 Js的内置类 Function的原型上
Function.prototype.myCall = function( context ) {
  // ctx值为context,如果没传context默认window
  var ctx = context || window;
  // 将参数context后面的参数转化成数组对象
  var args = [];
  for( var i = 1; i < arguments.length; i++ ){
    args.push("arguments[" + i + "]"); 
  }
  // 防止与context原有的属性冲突,定义一个随机字符串
  var symbol = Date.now().toString(32);
  // this指向Function实例,即调用myCall的函数
  // 将调用myCall的函数挂载到ctx[symbol]
  ctx[symbol] = this;
  // 通过eval调用ctx中symbol属性的函数,缓存在result变量中
  var result = eval('ctx[symbol]('+args+')');
  // 删除ctx上自己添加的属性symbol,保持与之前的context一样
  delete ctx[symbol];
  // 返回函数执行的结果
  return result 
}
  • myCall 方法定义在 Function.prototype 上,每个定义的 function 可以直接访问 myBind 方法。
  • myCall 方法参数 context 后面的参数转化成数组对象,被保存在 args 中,这里可以用 call 方法实现,但 myCall 方法要实现的就是模拟 call,所以在这里不用 call 方法。
  • var symbol = Date.now().toString(32); 创建一个随机字符串作为ctx对象的临时键名,防止与context原有的属性冲突
  • 通过 eval 调用 ctxsymbol 属性的函数,缓存在 result 变量中
  • 删除 ctx 上自己添加的属性 symbol,保持与之前的 context 一样
  • 最后返回函数执行的结果 result
测试
var a = 10;
var b = 20;
var obj = {
  a : 1,
  b : 2,
  getStr: getStr
}
function getStr( c, d ){
  return this.a + this.b + c + d;
}
console.log(getStr(3,4)); // 37
console.log(getStr.myCall(obj,3,4)); // 10
ES6 实现
Function.prototype.myCall = function( context, ...args ) {
  let ctx = context || window;
  let symbol = Symbol('symbol');
  ctx[symbol] = this; 
  let result = ctx[symbol](...args); 
  delete ctx[symbol];
  return result;
}

ES6 语法糖加持,代码可以简化很多。

  • args 可以通过扩展运算符 直接赋给 ctx[symbol] 调用的参数中。
  • ES6 提供了 Symbol 作为唯一值,保证不会与 context 自带的属性名冲突。

二、apply

ES5 实现
Function.prototype.myApply = function( context ) {
  var ctx = context || window;
  var args = arguments[1] || [];
  var symbol = Date.now().toString(32);
  ctx[symbol] = this;
  var result = eval('ctx[symbol]('+args+')');
  delete ctx[symbol];
  return result 
}
  • apply 方法区别于 call 方法,就是传参的形式不同,代码只稍作修改便可。
  • 不用遍历 arguments 对象,只用一行代码处理 var args = arguments[1] || [];, 其他地方和 myCall 实现一样。
测试
var a = 10;
var b = 20;
var obj = {
  a : 1,
  b : 2,
  getStr: getStr
}
function getStr( c, d ){
  return this.a + this.b + c + d;
}
console.log(getStr(3,4)); // 37
console.log(getStr.myApply(obj,[3,4])); // 10
ES6 实现
Function.prototype.myApply = function( context, args ) {
  let ctx = context || window;
  let symbol = Symbol('symbol');
  ctx[symbol] = this;
  let result = ctx[symbol](...args); 
  delete ctx[symbol];
  return result 
}

ES6 实现差别不大。

三、bind

ES5 实现
Function.prototype.myBind = function(context){
  // 缓存 this,this指向Function实例化对象,即函数function
  var _this = this;
  var args = [].slice.call(arguments,1);
  return function(){
    var newArgs = [].slice.call(arguments);
    return _this.apply(context,args.concat(newArgs));
  }
}
  • 缓存 _this 是为了在 returnfunction 中使用,因为在这个 returnfunction 中产生了新的上下文环境this 指向 window 打印它会看到
Function.prototype.myBind = function(context){
  ...
  return function(){
    console.log(this); // Window {window: Window, self: Window, document: document, name: "", location: Location, …}
    ...
  }
}
  • var args = [].slice.call(arguments,1); 语句将 myBind 方法参数 context 后面的参数转化成数组对象。
  • returnfunctionvar newArgs = [].slice.call(arguments); 语句将此函数调用栈中全部参数转化成数组对象,在 apply 方法中将两个参数数组链接起来当作第二个参数传入。
  • 注意 _this.apply(context,args.concat(newArgs)); 语句前面有 return ,代码整体实现是闭包的展现形式,调用 myBind 方法时也是先缓存(或叫做函数预加载),然后二次调用。
测试
var a = 10;
var b = 20;
var obj = {
  a : 1,
  b : 2,
  getStr
}
function getStr( c, d ){
  return this.a + this.b + c + d;
}
var fn1 = getStr.myBind(obj);
var fn2 = getStr.myBind(obj,3,4);
console.log(getStr(3,4)); // 37
console.log(fn1(3,4)); // 10
console.log(fn2(5,6)); // 10
console.log(fn2()); // 10
ES6 实现
Function.prototype.myBind = function(context, ...args1){
  return (...args2) => {
    return this.apply(context,[...args1,...args2])
  }
}
  • 一样利用扩展运算符 处理参数, 使用 解构赋值 特性组合参数数组。
  • 不需要缓存 this 是因为箭头函数在定义时已经确立 this了指向,它等同于上一级作用域中 this 指向。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值