(JavaScript)丢失的this(函数绑定)

本文探讨了JavaScript中因方法脱离对象导致的this丢失问题及解决方案,包括使用包装函数和bind方法,确保方法调用时能正确引用对象上下文。

丢失的 “this”

一旦方法被传递到与对象分开的某个地方———this就会丢失

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

/*
sayHi()是直接调用的,而不是通过对象来调用的,所以this的值丢失了
类似于
let f = user.sayHi;
setTimeout(f, 1000);
setTimeout(sayHi(){    //丢失了user的上下文
	alert(`Hello, ${this.firstName}!`);
}, 1000);
*/
setTimeout(user.sayHi, 1000); // Hello, undefined!

浏览器中的 setTimeout 方法有些特殊:它为函数调用设定了 this=window(对于 Node.js,this 则会变为计时器(timer)对象,但在这儿并不重要)。所以对于 this.firstName,它其实试图获取的是 window.firstName,这个变量并不存在。在其他类似的情况下,通常 this 会变为 undefined


解决方案1:包装器

使用一个包装函数:

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

//这里在外边包了一个函数,sayHi()是通过user来调用的
//this就有了值
setTimeout(function() {
  user.sayHi(); // Hello, John!
}, 1000);

//简单写法
setTimeout(() => user.sayHi(), 1000); // Hello, John!

上面的写法有一个小漏洞:

如果在 setTimeout 触发之前(有一秒的延迟!)			    
user 的值改变了怎么办?那么,突然间,它将调用错误的对象!
let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

setTimeout(() => user.sayHi(), 1000);

// ……user 的值在不到 1 秒的时间内发生了改变
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

// Another user in setTimeout!

解决方案2:bind

func.bind(context)
func函数绑定了context对象

let user = {
  firstName: "John",
  sayHi() {
    alert(`Hello, ${this.firstName}!`);
  }
};

// user.sayHi()函数绑定了user对象
let sayHi = user.sayHi.bind(user); 

// 可以在没有对象(译注:与对象分离)的情况下运行它
sayHi(); // Hello, John!

setTimeout(sayHi, 1000); // Hello, John!

// 即使 user 的值在不到 1 秒内发生了改变
// sayHi 还是会使用预先绑定(pre-bound)的值,该值是对旧的 user 对象的引用
user = {
  sayHi() { alert("Another user in setTimeout!"); }
};

bindAll

如果一个对象有很多方法,并且我们都打算将它们都传递出去
那么我们可以在一个循环中完成所有方法的绑定
for (let key in user) {
  if (typeof user[key] == 'function') {
    user[key] = user[key].bind(user);
  }
}

偏函数

bind不仅可以绑定this,还可以绑定参数(arguments)


let bound = func.bind(context, [arg1], [arg2], ...);

func绑定了context对象,且func函数第一个参数值为arg1,第二个参数值为arg2.......

function mul(a, b) {
  return a * b;
}
//绑定了null,第一个参数值是2
let double = mul.bind(null, 2);

alert( double(3) ); // = mul(2, 3) = 6
alert( double(4) ); // = mul(2, 4) = 8
alert( double(5) ); // = mul(2, 5) = 10

在没有上下文情况下的partial

只想绑定参数,但是没有上下文this


partial(func, [arg1], [arg2], ...)

函数func的第一个参数是agr1,第二个参数是arg2...

// 用法:
let user = {
  firstName: "John",
  say(time, phrase) {
    alert(`[${time}] ${this.firstName}: ${phrase}!`);
  }
};

// 添加一个带有绑定时间的 partial 方法
user.sayNow = partial(user.say, new Date().getHours() + ':' + new Date().getMinutes());

user.sayNow("Hello");
// 类似于这样的一些内容:
// [10:00] John: Hello!

习题一:二次 bind

function f() {
  alert(this.name);
}

f = f.bind( {name: "John"} ).bind( {name: "Ann" } );

f();

//解决方案
//答案:John。
//一个函数不能被重绑定

习题二:bind后的函数属性

函数的属性中有一个值。bind 之后它会改变吗?为什么,阐述一下?
function sayHi() {
  alert( this.name );
}
sayHi.test = 5;

let bound = sayHi.bind({
  name: "John"
});

alert( bound.test ); // 输出将会是什么?为什么?

//解决方案
//答案:undefined。
//bind 的结果是另一个对象。它并没有 test 属性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值