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

前言:

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

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

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

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

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

普通函数调用

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

对象中的方法调用

javascript 复制代码
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指向可以参考这张图

修改this指向

call

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

javascript 复制代码
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已数组的方式传递

javascript 复制代码
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 关键字设置为给定的值,同时,还可以传入一系列指定的参数

javascript 复制代码
function myThis1(name, age) {
    console.log(this);
    console.log(name);
    console.log(age);
   }
const obj = {
    name: 'zs',
    age: 18
}

![image.png](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/92cfe20fc6374374bacf97bcc3d31ac6~tplv-k3u1fbpfcp-jj-mark:0:0:0:0:q75.image#?w=814&h=308&s=145955&e=png&b=fafafa)
const fn = myThis1.bind(obj, '赵六',30)
fn()  //  {name:"zs",age:18} 赵六 30

手写call函数

要求实现

javascript 复制代码
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 方法,这样通过原型链就可以使用

javascript 复制代码
Function.prototype.myCall = function () {
  console.log('myCall被调用了');
}
myFn.myCall()

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

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

javascript 复制代码
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)

很明显 **thisArg 就是 obj **对象 而 this就是 myFn 这个函数,那么就可以根据谁调用this就指向谁的原则,将obj这个对象也就是 **thisArg **添加 myCall 方法 = this

javascript 复制代码
  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对象,但是还差参数没有传递,接下去就去实现

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

javascript 复制代码
  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 动态生成唯一的属性名

javascript 复制代码
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 只是第二个参数需要改为数组

javascript 复制代码
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

javascript 复制代码
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'));
相关推荐
海威的技术博客2 分钟前
JS中的原型与原型链
开发语言·javascript·原型模式
雪落满地香8 分钟前
前端:改变鼠标点击物体的颜色
前端
余生H41 分钟前
前端Python应用指南(二)深入Flask:理解Flask的应用结构与模块化设计
前端·后端·python·flask·全栈
outstanding木槿1 小时前
JS中for循环里的ajax请求不数据
前端·javascript·react.js·ajax
酥饼~1 小时前
html固定头和第一列简单例子
前端·javascript·html
一只不会编程的猫1 小时前
高德地图自定义折线矢量图形
前端·vue.js·vue
所以经济危机就是没有新技术拉动增长了1 小时前
二、javascript的进阶知识
开发语言·javascript·ecmascript
m0_748250931 小时前
html 通用错误页面
前端·html
来吧~1 小时前
vue3使用video-player实现视频播放(可拖动视频窗口、调整大小)
前端·vue.js·音视频
Bubluu1 小时前
浏览器点击视频裁剪当前帧,然后粘贴到页面
开发语言·javascript·音视频