likes
comments
collection
share

董老师的话充满力量——手写call、apply、bind

作者站长头像
站长
· 阅读数 9

前言:

大家好,我是小瑜。最近在网上看到了东方甄选和董宇辉的小作文事件,我也一直是众多吃瓜群众的一员,看完后董宇辉俞敏洪的联合直播,心中也有很多感触。

董宇辉老师说,一放假,就喜欢往家里跑,因为接近土地可以感到踏实。一个千万网红这种纯粹质朴的精神着实让人很贴切。

特别是董老师的另外一番话:你必须人生中有一段经历是自己走过去的。你充满了痛苦,然后充满了孤独,但这个东西叫做成长,好的生活和幸福的经历是不能带来成长的,所以能见到很多人,四五十岁看着还很幼稚,说明他从来没有受到苦,能让你成长的东西,就是让你反思的东西,因为在历史的长河中进化是痛苦的,逼不得已,人才会进步很成长,所以成长都不快乐。但同时也恭喜你一直在成长。

在学习以及编写这篇文章的时候,我也是痛苦的,同时也有所收获。接下来给大家分享this指向以及手写call、apply、bind。

在这之间给大家简单举几个例子说明下this指向的不同

普通函数调用

// 谁调用就是谁, 直接调用window
function sayHi() {
  console.log(this); // window
}
sayHi()  // === window.sayHi()

对象中的方法调用

const obj = {
  name: 'zs',
  objSayHi() {
    console.log(this) // obj
    setTimeout(() => {
     console.log(this, 'setTimeout'); // obj
      }, 1000),
    function inner() {
      console.log(this); // window
    }
    inner() 
  },
  qwe: () => console.log(this) // window
}
obj.objSayHi()
obj.qwe()

obj.objSayHi() => obj

  • 因为是 **obj **对象调用,所以 **this **指向 **obj **这个对象

obj.qwe() => window

  • 对于箭头函数 qwe,它捕获的是定义时外部的 this 上下文。在浏览器中全局范围内的箭头函数 qwethis 指向的是全局对象 window(或者是全局的 this,具体取决于执行上下文)。

inner() => window

  • inner() 函数是通过常规函数声明方式定义的。在 JavaScript 中,常规函数声明方式中的 this 在严格模式下指向 undefined,而在非严格模式下(例如浏览器环境中),this 指向全局对象(在浏览器中通常是 window 对象)。因此,当 inner() 函数在 objSayHi() 方法内部被调用时,其 this 指向全局对象 window

setTimeout => obj

  • objSayHi 方法中,setTimeout 中的回调函数使用了箭头函数。箭头函数内部的 this 会捕获最近的普通函数(非箭头函数)的 this 值,也就是 objSayHi 被调用时的 this。因此,setTimeout 中的箭头函数捕获到的 this 值指向的是 obj 对象。

总结:浏览器环境中, 谁调用this指向谁,但是箭头函数的this义是外部的 this 上下文。通过常规函数声明方式定义this指向window。其他关于this指向可以参考这张图

董老师的话充满力量——手写call、apply、bind

修改this指向

call

第1个参数为this,第2-n为传入该函数的参数

function myThis1(name, age) {
    console.log(this);
    console.log(name);
    console.log(age);
   }
const obj = {
    name: 'zs',
    age: 18
}
myThis1.call(obj, 'ls', 20) // {name:"zs",age:18} ls 18

apply

第1个参数为this,第2-n已数组的方式传递

function myThis1(name, age) {
    console.log(this);
    console.log(name);
    console.log(age);
   }
const obj = {
    name: 'zs',
    age: 18
}
myThis1.apply(obj, ['王五', 18]) // {name:"zs",age:18} 王五 18

bind

bind() 方法创建一个新函数,当调用该新函数时,它会调用原始函数并将其 this 关键字设置为给定的值,同时,还可以传入一系列指定的参数

手写call函数

要求实现

const obj = {
  name: 'zs',
  age: 20
}
function myFn(a, b, c, d, e) {
  console.log(`大家好,我的名字叫${this.name} 我今年${this.age}岁了`, a, b, c, d, e);
}
myFn.myCall(obj, 1, 2, 3, 4, 5)

简单思路:

  1. ** **原本并不存在 **myCall **方法,那么如何去创建这个方法?
  2. 如何让函数内部的 **this **为 某个对象?
  3. 如何将调用时传入的参数传入到 **myFn **函数中?

实现思路1:通过函数原型的方式,给原型添加 myCall 方法,这样通过原型链就可以使用

Function.prototype.myCall = function () {
  console.log('myCall被调用了');
}
myFn.myCall()

董老师的话充满力量——手写call、apply、bind

实现思路2:在myCall调用的时候将obj传入到函数中,并根据谁调用this就指向谁的原则给对象添加this方法并执行

首先可以打印看一下thisArg,this 分别是什么

const obj = {
  name: 'zs',
  age: 20
}
Function.prototype.myCall = function (thisArg) {
  console.log('myCall被调用了',thisArg,this);
}
function myFn(a, b, c, d, e) {
  console.log(`大家好,我的名字叫${this.name} 我今年${this.age}岁了`, a, b, c, d, e);
}
myFn.myCall(obj)

董老师的话充满力量——手写call、apply、bind 很明显 **thisArg 就是 obj **对象 而 this就是 myFn 这个函数,那么就可以根据谁调用this就指向谁的原则,将obj这个对象也就是 **thisArg **添加 myCall 方法 = this

  Function.prototype.myCall = function (thisArg) {
    console.log('myCall被调用了', thisArg, this);
    thisArg.myCall = this
    thisArg.myCall()
}
function myFn(a, b, c, d, e) {
    console.log(`大家好,我的名字叫${this.name} 我今年${this.age}岁了`, a, b, c, d, e);
 }
const obj = {
      name: 'zs',
      age: 20
 }
myFn.myCall(obj, 1, 2, 3, 4, 5)

此时就发现,this已经成功指向了这个obj对象,但是还差参数没有传递,接下去就去实现

董老师的话充满力量——手写call、apply、bind

实现思路3:利用剩余参数加展开运算符传入参数

  Function.prototype.myCall = function (thisArg,...args) {
    console.log('myCall被调用了', thisArg, this);
    thisArg.myCall = this
    thisArg.myCall(...args)
}
function myFn(a, b, c, d, e) {
    console.log(`大家好,我的名字叫${this.name} 我今年${this.age}岁了`, a, b, c, d, e);
 }
const obj = {
      name: 'zs',
      age: 20
 }
myFn.myCall(obj, 1, 2, 3, 4, 5)

此时就基本上可以完成了,还有一点优化,就是查看** obj 发现 myCall **是一直存在的,因为之前通过给原型添加方法,希望的是使用完成后将myCall方法删除,这里只需要 在 **myCall 最后再添加一句 delete thisArg.myCall **即可

优化: 增加返回值并 利用 Symbol 动态生成唯一的属性名

Function.prototype.myCall = function (thisArg, ...args) {
  const key = Symbol()
  thisArg[key] = this
  const res = thisArg[key](...args)
  delete thisArg[key]
  return res
}

手写apply

apply 方法同理 call 只是第二个参数需要改为数组

Function.prototype.myApply = function (thisArg, args) {
    console.log(args);
    const key = Symbol()
    thisArg[key] = this
    const res = thisArg[key](args)
    delete thisArg[key]
    return res
}
const obj = {
    name: 'zs',
    age: 20
}
function myFn(args) {
    const div = `大家好,我的名字叫${this.name} 我今年${this.age}岁了,${args.toString()}`
    return div
}
const res = myFn.myApply(obj, [1, 2, 3, 4, 5])
console.log(res);

手写bind

Function.prototype.myBind = function (thisArg, ...args) {
    const fn = this
    return function (...args1) {
        const allArgs = [...args, ...args1]
        // 判断是否为new的构造函数
        if (new.target) {
            return new fn(...allArgs)

        } else {
            return fn.call(thisArg, allArgs)
        }
    }
}
const obj = {
    name: 'zs',
    age: 20
}
function myFn(...arg) {
    console.log(`大家好,我的名字叫${this.name} 我今年${this.age}岁了,${arg}`);
    const div = `大家好,我的名字叫${this.name} 我今年${this.age}岁了,${arg}`
    return div
}
const res = myFn.myBind(obj, '1')
console.log(res('122'));
转载自:https://juejin.cn/post/7313135267572121612
评论
请登录