此名字暂不可见
No distraction,it's you and me

硬绑定方法

2024-11-29

前言

​ 在JavaScript中,this的指向分为软绑定和硬绑定。其中,在对象调用其内部方法时,this指向它的调用者,这就是软绑定;而硬绑定就需要用到JavaScript的内置方法call/aplly/bind了实现了。

1.call

​ call()方法会将this指向传入的第一个参数,并且可以接受多个参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
	console.log(this);
function who(){
console.log([this.name,this.dialogue]);
console.log(this)
}

let obj = {
name:'维克托',
dialogue:'加入光荣的进化吧',
who:who
}

obj.who()
//Window
//['维克托','加入光荣的进化吧']
//obj

​ 可以看到第一次this指向了Window,而第二次指向了调用它的obj。

​ 如果我们用call()方法改变this指向又会怎么样呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
	function who(a){
console.log([this.name,this.dialogue]);
console.log(this)
console.log(a)
}

let obj = {
name:'维克托',
dialogue:'加入光荣的进化吧',
who:who
}

let obj2 = {
name:'杰斯',
dialogue:'为了美好的明天而战'
}


obj.who.call(obj2,6)

//['杰斯','为了守护更美好的明天而战']
//obj2
//6

​ 不难看出,this指向又变为了obj2。其中,call()中,第一个参数是this指向的对象,后面的参数则是传给who()的。

自己的call()方法

​ 本人后面又在CSDN上看到了其他作者对自定义call()方法的理解,觉得很不错,就当补充放上来了😋。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
		 Function.prototype.myCall = function(context,...args){
context = context ? context : window//如果context为null,则this指向Window
context.fn = this//将原函数(this)赋给 context.fn
context.fn(...args)//调用原函数,并传递参数,this 会指向 context
delete context.fn//避免浪费内存
}

let obj1 = {
name:'iso'
say:'it is you and me'
}

let obj2 = {
name:'bluejoker',
say:function(...args){
console.log(args);
console.log(this);
}
}
obj2.say.myCall(obj1,obj1.name,obj1.say)
//['iso','it is you and me']
//obj1

2.apply

​ apply()方法会将this指向传入的第一个参数,并接受一个参数数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
	let obj1 = {
name:'iso',
say:'it is you and me'
}

let obj2 = {
name:'bluejoker',
say:function(...args){
console.log(args);
console.log(this);
}
}
obj2.say.apply(obj1,[obj1.name,obj1.say])
//['iso','it is you and me']
//obj1

自己的apply()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
	Function.prototype.myApply = function (context,arr){
context = context ? context : Window//同上
context.fn = this//同上
if(!arr){
context.fn()//判断arr是否为空或为null,undefined等,是则立刻调用context.fn()
}else if(Array.isArray(arr)){//判断是否为数组
context.fn(...arr)//是则传递参数
}else{
return TypeError("arr is not a Array")
}//不是则返回异常
delete context.fn//同上
}

let obj1 = {
name:'iso',
say:'it is you and me'
}

let obj2 = {
name:'bluejoker',
say:function(...args){
console.log(args);
console.log(this);
}
}
obj2.say.myApply(obj1,[obj1.name,obj1.say])
//['iso','it is you and me']
//obj1

3.bind

​ bind()方法会将this指向传入的第一个参数,并接受任意形式的参数,同时,bind()返回一个新的函数。(这个新函数 保留原函数的原型链。这意味着,如果我们用 bind 生成的函数作为构造函数(通过 new 调用),新创建的实例应该能够访问原函数的原型上的方法。)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
function Person(name) {
this.name = name;
console.log(this.name);
}

Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};

let obj = {
name:'Alice'
}

// 普通函数调用
const boundSayHello = Person.bind(obj,obj.name);
boundSayHello(); // 输出: Alice

// 构造函数调用
const BoundPerson = Person.bind(null, 'Bob');
const newPerson = new BoundPerson();
newPerson.sayHello(); // 输出: Hello, my name is Bob
console.log(newPerson instanceof Person); // 输出: true

自己的bind()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
	Function.prototype.myBind = function(context,...args){
if(typeof this !=="function"){
throw new Error(
"this is not a function"
)
}//判断this是否指向一个函数,否则抛出错误
let self = this//保留原本的this指向,即myBind的调用者
let fNOP = function () {}//为新绑定的函数(fBound)设置正确的原型链,以确保它继承原函 数的原型。如果直接赋值fBound.prototype = this.prototype 可能会丢失一些构造函数的特性
let fBound = function (...bindArgs) {
return self.apply(
this instanceof fNOP ? this : context,//此处 this 指的是 fBound 函数的调用者,这一部分的作用是判断 fBound 是否通过 new 关键字被调用。也就是说,我们检查当前 this 是否是 fNOP 的实例。为什么是 fNOP 呢?因为 fNOP 是一个空函数,我们会通过 new fNOP() 创建一个对象,来模拟 bind 的行为,确保 fBound 被当作构造函数调用时,能够正确地继承原函数的原型。
//如果 fBound 是通过 new 关键字调用的,那么 this 会指向新创建的对象,而不是我们手动传入的 context。
//如果 fBound 是普通函数调用(即没有使用 new),则 this 会指向传入的 context。

args.concat(bindArgs)//将最初绑定时传入的参数 args 和后续调用 fBound 时传入 的参数 bindArgs 合并,形成完整的参数列表传递给原函数。
)
}

fNOP.prototype = this.prototype//将 fNOP 的原型指向原函数的原型对象。也就是说, fNOP 继承了原函数的所有实例方法。这样,fNOP 作为一个空函数,实际上并没有自己的实例方法,但它通过设置 prototype,让任何通过 new fNOP() 创建的对象都能够访问原函数的原型上的方法。

fBound.prototype = new fNOP()//让 fBound 的原型对象(即 fBound.prototype)指向一个 fNOP 的实例。由于 fNOP 的 prototype 指向了原函数的原型,所以通过 new fNOP() 创建的对象实际上继承了原函数的原型。因此,这样就确保了 fBound 的原型链上有原函数的原型。

return fBound//最终,返回的是 fBound 函数,它已经被绑定了 this 上下文和初始参数。当你调 用 fBound 时,它会使用绑定的上下文和参数执行原始函数。
}

function Person(name) {
this.name = name;
console.log(this.name);
}

Person.prototype.sayHello = function() {
console.log(`Hello, my name is ${this.name}`);
};

let obj = {
name:'Alice'
}

// 普通函数调用
const boundSayHello = Person.myBind(obj,obj.name);
boundSayHello(); // 输出: Alice

// 构造函数调用
const BoundPerson = Person.myBind(null, 'Bob');
const newPerson = new BoundPerson();
newPerson.sayHello(); // 输出: Hello, my name is Bob
console.log(newPerson instanceof Person); // 输出: true

总结:

​ call、apply、bind 都可以改变 this 的指向,三者第一个参数都是this要指向的对象,如果没有这个参数或参数为undefined或null,则默认指向全局window。但它们略有不同:

  1. call能够接受多个参数
  2. apply接受一个数组作为参数
  3. bind能够接受任意类型的参数,并返回一个新的函数

Author: John Doe

Link: https://159357254680.github.io/2024/11/29/%E7%BB%91%E5%AE%9A/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

< PreviousPost
react Hooks
NextPost >
这是标题
CATALOG
  1. 1. 前言
  2. 2. 1.call
    1. 2.1. 自己的call()方法
  3. 3. 2.apply
    1. 3.1. 自己的apply()方法
  4. 4. 3.bind
    1. 4.1. 自己的bind()方法
  5. 5. 总结: