手写call、apply、bind与this指向问题

本文给大家介绍更完善的手写方法

虽然手写call,apply,bind网上已经大把大把的文章,且本身知识点比较集中,但是网上的大部分方法都会有这样或者那样的问题,并不完善

本文带大家从另一个角度,更加完善的理解、学习如何手写call、apply、bind

blog传送门

一、用法与区别

  1. call:

    call() 方法使用一个指定的 this 值和单独给出的一个或多个参数来调用一个函数。

  2. apply:

    apply() 方法调用一个指定 this 值的函数,以及以一个数组(或一个类数组对象)的形式提供的参数。

  3. bind:

    bind() 方法创建一个新的函数,在 bind() 被调用时,这个新函数的 this 被指定为 bind() 的第一个参数,而其余参数将作为新函数的参数,供调用时使用。

总结:call和apply传参不同,但都会调用函数。bind传参和call一样,但是bind会返回this改变后的函数,并不会立即调用。

var name = 'lisi'

const obj = {
    name: 'zhangsan'
}

function getName (age, hobby) {
  console.log(`我叫${this.name},今年${age}岁,喜欢${hobby}`,)
}

getName(20,'电脑') // '我叫lisi,今年20岁,喜欢电脑'
getName.call(obj, 18, '跳舞') // '我叫zhangsan,今年18岁,喜欢跳舞'
getName.apply(obj, [18, '跳舞']) // '我叫zhangsan,今年18岁,喜欢跳舞'
getName.bind(obj, 18, '跳舞')() // '我叫zhangsan,今年18岁,喜欢跳舞'

二、什么是this

this 是指当前函数中正在执行的上下文环境,也就是说,this指向谁是调用时确定的,而非文本定义。

其次关于this,需要记住一句话 ———— 谁调用就指向谁

三、如何判断this指向

关于this指向问题,非严格模式下一般有以下几种情况:

  1. 全局环境下的this指向: window

    console.log(this) // window
    
  2. 函数内的this:

    var name = 'lisi'
    function getName () {
    console.log(this.name)
    }
    
    getName() // lisi
    // 等同于
    window.getName() // lisi
    
  3. 对象中的this:

    const obj = {
     name: 'zhangsan',
      getName: function () {
        console.log(this.name)
      }
    }
    obj.getName() // zhangsan
    
  4. 箭头函数中的this: 箭头函数中没有 this, 它会绑定最近的非箭头函数作用域中的this。首先从它的父级作用域找,如果父级作用域还是箭头函数,就再往上找,一层一层的直到找到this的指向

  5. 构造函数中的this: 指向实例,因为通过new关键字构建后已经改变this指向

  6. 原型链中的this: 1, 看是谁调用 2, 进行this替换 3, 基于原型链确认结果

    function Person (name, age) {
      this.name = name
      this.age = age
    }
    
    Person.prototype.getAge = function () {
      console.log(this.age)
    }
    
    Person.prototype.addHobby = function () {
     this.hobby = '干饭'
    }
    
    const personA = new Person('zhangsan', 18)
    personA.name // zhangsan  this=>personA
    personA.getAge() // 18 this=>personA
    personA.__proto__.name // undefined this=>Person.prototype
    Person.prototype.age // undefined this=>Person.prototype
    personA.addHobby() // personA实例上添加hobby字段 this=>personA
    Person.prototype.addHobby() // Person实例上添加hobby字段 this=>Person
    
    

四、手写call、apply、bind

根据上述的第三条this指向,我们可以通过构造一个对象来使得改变this的指向,那么我们手写call就有了下面的思路

Function.prototype.myCall = function (ctx, ...args) {
  ctx.fn = this
  ctx.fn(...args)
  delete ctx.fn
}

// test
const obj = {
  name: 'zhangsan'
}
function test () {
  console.log(this.name)
}
test() // undefined
test.myCall(obj) // zhangsan

我们可以发现,上述方法可以实现改变this的指向,但是上述方法又会存在一些弊端

  1. 如果对象里面有fn方法,会替换掉对象中的fn方法,哪怕改成很偏僻的__fn等等,都会存在风险,其次也不美观
  2. 如果传入的ctx是普通数据类型或者空值,比如numberstringnull,undefined
  3. 如果函数有返回值,那么这种方法就没办法接收到返回值

那么我们该如何去优化上面的myCall呢?

思考一下

.

.

.

.

.

.

.

.

.

接下来针对上述几个问题进行改进

  1. fn命名冲突问题,我们可以借助es6的Symbol解决

    // 因为Symbol的唯一性,导致
    const a = Symbol('a')
    const b = Symbol('a')
    a == b // false
    // 所以我们使用完必须删除Symbol,保证对象的干净
    
  2. 普通数据类型和空值,我们可以通过三目去解决

  3. 返回值,我们可以单独接收,最后return出去

此处使用globalThis原因是js运行环境有两种,一种是浏览器中,一种是Node环境,所以使用js内置全局属性来判断

Function.prototype.myCall = function (ctx, ...args) {
  ctx = ctx === null || ctx === undefined ? globalThis : Object(ctx)
  const key = Symbol('fn')
  ctx[key] = this
  const res = ctx[key](...args)
  delete ctx[key]
  return res
}

// test
const obj = {
  name: 'zhangsan'
}
test.myCall(null) // this
test.myCall(2) // undefined
test.myCall(obj) // zhangsan

测试没问题后,我们可以参考myCall手写剩下的两个myApply和myBind

Function.prototype.myApply = function (ctx, args) {
  ctx = ctx === null || ctx === undefined ? globalThis : Object(ctx)
  // 防止args没有或传值不对
  args = Array.isArray(args) ? args : []
  const key = Symbol('fn')
  ctx[key] = this
  const res = ctx[key](...args)
  delete ctx[key]
  return res
}

Function.prototype.myBind = function (ctx, ...args1) {
  ctx = ctx === null || ctx === undefined ? globalThis : Object(ctx)
  const key = Symbol('fn')
  ctx[key] = this
  
  return function (...args2) {
    const res = ctx[key](...args1, ...args2)
    delete ctx[key]
    return res
  }
}

下面我们请出特约嘉宾zhangsanlisi,帮我们跑一下测试用例,验证下方法的可靠性

var name = 'lisi'
const obj = {
  name: 'zhangsan'
}

function test (...args) {
  console.log(`我是: ${this.name}, 我收到了一些参数: ${args}`)
}

function testReturnValue (...args) {
  console.log(`我是: ${this.name}, 我收到了一些参数: ${args}`)
  return '没错就是我'
}

// 思考下下面会输出什么结果
test.myCall(obj)
test.myCall(obj, 1, 2, 3)
const a = testReturnValue.myCall(obj)
const a2 = testReturnValue.myCall(obj, 1, 2, 3)

test.myApply(obj)
test.myApply(obj, 1)
test.myApply(obj, [1])

test.myBind(obj)()
test.myBind(obj, 1, 2)()
test.myBind(obj, 1, 2)(3, 4)





// 输出,从上到下,按顺序排列
// 我是: zhangsan, 我收到了一些参数:
// 我是: zhangsan, 我收到了一些参数: 1,2,3
// 我是: zhangsan, 我收到了一些参数:  a => 没错就是我
// 我是: zhangsan, 我收到了一些参数: 1,2,3  a => 没错就是我

// 我是: zhangsan, 我收到了一些参数:
// 我是: zhangsan, 我收到了一些参数:
// 我是: zhangsan, 我收到了一些参数: 1

// 我是: zhangsan, 我收到了一些参数:
// 我是: zhangsan, 我收到了一些参数: 1, 2
// 我是: zhangsan, 我收到了一些参数: 1, 2, 3, 4

参考文献:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,204评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,091评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,548评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,657评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,689评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,554评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,302评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,216评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,661评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,851评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,977评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,697评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,306评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,898评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,019评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,138评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,927评论 2 355

推荐阅读更多精彩内容