1. 首页
  2. 前端基础

手写apply、call、bind函数

this指向

 javascript
// this指向
function fn () {
  console.log(this)
}

var obj = {
  name: '张三'
}
obj.fn = fn;

obj.fn() // 输出:obj
fn() // 输出window


1、非严格模式下默认指向全局的window
2、this指向调用者

在上述代码中

  • obj.fn()中函数 fn 的调用者是 obj,所以最后输出obj对象
  • fn()可以看作是window.fn(),window 是函数 fn 的调用者,所以输出window对象。

详情可见 this指向问题

call、apply、bind的用法

 javascript
function sum (a, b) {
  console.log(a + b, this);
}

var obj = {
  name: '橘子君'
}

sum(1, 2) // window
sum.call(obj, 1, 2) // obj
sum.apply(obj, [1, 2]) // obj
sum.bind(obj)(1, 2) // obj


可以看到,默认状况下,this指向的是window,而call、apply、bind函数改变了this的指向.

call的实现方式

 javascript
sum.call(obj, 1, 2)


先看看参数,第一个参数是目标对象,即需要修改的this指向的参数,其余参数为sum函数的真实参数

这里需要解决两个问题

  • 1、怎么修改this指向
  • 2、参数长度可变问题

第二个问题很好解决,用ES6的解构就行 第一个问题也好解决,最开始的时候说过函数中的this指向被调用者,那么我们在call函数内部用obj.sum()就可以将this指向调用者obj

 javascript
/**
 * @params {Object} ctx 即目标对象
 * @params {any} args 真实的参数
*/
Function.prototype._call = function (ctx, ...args) { // 用结构的方法解决可变参数的问题
  ctx.fn = this; // 修改this的指向
  return ctx.fn(...args); // 此时的this指向的就是ctx
}

function sum (a, b) {
  console.log(a + b, this);
}

var obj = {
  name: '橘子君'
}
sum._call(obj, 1, 2); // 输出:3 {name: "橘子君", fn: ƒ}

var obj2 = {
  name: '橘子君',
  fn (a, b) {
    console.log(a * b, this)
  }
}
sum._call(obj2, 1, 2) // 输出:3 {name: "橘子君", fn: ƒ}
obj2.sum(1, 2) // 输出:3 {name: "橘子君", fn: ƒ}


可以看到,这里实现了call的基本功能,但是这种写法又出现了两个问题

  • 目标对象ctx中新增了一个sum方法
  • 当目标对象中存在一个同名方法时(即上述代码中的fn),会覆盖对象自身的同名方法

第一个问题好解决,可以用delete方法进行删除

至于第二个问题,在ES6中新引入了一种数据类型:Symbol

Symbol(): 创造一个独一无二的值,但不是字符串,而是一个新的数据类型

 javascript
var obj = {};
var n = obj[Symbol()] = 1
console.log(obj[Symbol()]) // 输出:undefined
console.log(n) // 输出: 1


因此我们可以用Symbol()来解决上述遗留的第二个问题

 javascript
Function.prototype._call = function (ctx, ...args) { // 用结构的方法解决可变参数的问题
  ctx = ctx || window; // 默认为window
  let Sym = Symbol('fn'); // 创建临时属性
  ctx[Sym] = this; // 修改this的指向
  let result = ctx[Sym](...args);
  delete ctx[Sym]; // 删除临时属性
  return result;
}

function sum (a, b) {
  console.log(a + b, this);
}

var obj = {
  name: '橘子君'
}

sum._call(obj, 1, 2); // 输出:3 {name: "橘子君"}


到此为止,call的方法已经实现了。

apply的实现方式

apply和call的实现方法可以说是一样的,甚至说apply比call要简单,因为它的参数数量是固定的,只有2个,即目标对象和参数数组

call的实现方式中,因为我们调用的时候用了解构,所以我们只要针对call方法修改一行代码就行了。

 javascript
Function.prototype._apply = function (ctx, args) { // 固定参数数量
  ctx = ctx || window;
  let Sym = Symbol('fn');
  ctx[Sym] = this;
  let result = ctx[Sym](...args);
  delete ctx[Sym];
  return result;
}

function sum (a, b) {
  console.log(a + b, this);
}

var obj = {
  name: '橘子君'
}

sum._apply(obj, [1, 2]); // 输出:3 {name: "橘子君"}


bind的实现方式

bind函数是一个高阶函数,因为它返回的不是一个具体数值,而是一个函数

 javascript
var obj = {}
function sum(a, b) {
  console.log(a + b)
};
sum.bind(obj); // 输出:ƒ sum () {}
sum.bind(obj)(1, 2) // 输出:3


既然如此,我们先来看看怎么实现它。

 javascript
Function.prototype._bind = function (ctx) {
  ctx = ctx || window;
  let Sym = Symbol('fn');
  ctx[Sym] = this;
  return function (...args) { // 相比call和apply,bind方法只是多了一层外壳而已。
    // 调用方法和删除临时属性放到了返回函数体中
    let result = ctx[Sym](...args);
    delete ctx[Sym];
    return result;
  }
}
var obj = {
  name: '橘子君'
}
function sum(a, b) {
  console.log(a + b)
  console.log(this);
};
sum._bind(obj)(1,2) // 输出:3 {name: '橘子君'}


相比apply和call方法,不同的是bind方法将sum函数的实际调用和删除临时属性放到了返回的函数体中,其他的基本都是一致的。


更多相关文档,请见:

作者:前端橘子君
链接:https://juejin.im/post/6894163324125052941

看完两件小事

如果你觉得这篇文章对你挺有启发,我想请你帮我两个小忙:

  1. 关注我们的 GitHub 博客,让我们成为长期关系
  2. 把这篇文章分享给你的朋友 / 交流群,让更多的人看到,一起进步,一起成长!
  3. 关注公众号 「画漫画的程序员」,公众号后台回复「资源」 免费领取我精心整理的前端进阶资源教程

JS中文网是中国领先的新一代开发者社区和专业的技术媒体,一个帮助开发者成长的社区,目前已经覆盖和服务了超过 300 万开发者,你每天都可以在这里找到技术世界的头条内容。欢迎热爱技术的你一起加入交流与学习,JS中文网的使命是帮助开发者用代码改变世界

本文著作权归作者所有,如若转载,请注明出处

转载请注明:文章转载自「 Js中文网 · 前端进阶资源教程 」https://www.javascriptc.com

标题:手写apply、call、bind函数

链接:https://www.javascriptc.com/4644.html

« 手把手实现Promise
怎么用原生JS做出VUE的模板渲染»
Flutter 中文教程资源

相关推荐

QR code