董老师的话充满力量——手写call、apply、bind
前言:
大家好,我是小瑜。最近在网上看到了东方甄选和董宇辉的小作文事件,我也一直是众多吃瓜群众的一员,看完后董宇辉俞敏洪的联合直播,心中也有很多感触。
董宇辉老师说,一放假,就喜欢往家里跑,因为接近土地可以感到踏实。一个千万网红这种纯粹质朴的精神着实让人很贴切。
特别是董老师的另外一番话:你必须人生中有一段经历是自己走过去的。你充满了痛苦,然后充满了孤独,但这个东西叫做成长,好的生活和幸福的经历是不能带来成长的,所以能见到很多人,四五十岁看着还很幼稚,说明他从来没有受到苦,能让你成长的东西,就是让你反思的东西,因为在历史的长河中进化是痛苦的,逼不得已,人才会进步很成长,所以成长都不快乐。但同时也恭喜你一直在成长。
在学习以及编写这篇文章的时候,我也是痛苦的,同时也有所收获。接下来给大家分享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 上下文。在浏览器中全局范围内的箭头函数 qwe 的 this 指向的是全局对象 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为传入该函数的参数
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 关键字设置为给定的值,同时,还可以传入一系列指定的参数
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函数
要求实现
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)
简单思路:
- ** **原本并不存在 **myCall **方法,那么如何去创建这个方法?
- 如何让函数内部的 **this **为 某个对象?
- 如何将调用时传入的参数传入到 **myFn **函数中?
实现思路1:通过函数原型的方式,给原型添加 myCall 方法,这样通过原型链就可以使用
Function.prototype.myCall = function () {
console.log('myCall被调用了');
}
myFn.myCall()
实现思路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)
很明显 **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对象,但是还差参数没有传递,接下去就去实现
实现思路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'));
来源:juejin.cn/post/7313135267572121612