默认绑定
首先介绍的就是最常用的函数调用类型了:独立函数调用,可以把这条规则看作是无法应用其他规则时候的默认规则。来看这个例子:
var a = 4;
function foo() {
var a = 2;
console.log(this.a);
}
foo(); //4
我们可以看到当调用foo时,this.a被解析成了全局的a。为什么因为在本例子中,函数调用时应用了默认绑定,因此this指向全局对象。
那么我们怎么知道函数是否应用了默认绑定呢1在代码中,foo()时直接使用不带任何修饰的函数引用进行调用的,因此只能使用默认绑定,无法应用其他规则。
如果在严格模式下不能将全局对象用于默认绑定,因此this会绑定到undefined
//严格模式下 不能将全局对象用于默认绑定
var b = 4;
function bar() {
"use strict";
console.log(this.b);
}
bar(); //TypeError this是undefined
这里有一个小细节我们应该知道的,只有foo()运行在非严格模式下,默认绑定才能绑定到全局对象,但是在严格模式下调用foo()则不影响默认绑定。
var c = 6;
function baz() {
console.log(this.c);
}
(function () {
"use strict";
baz(); //6
})();
隐式绑定
另一条需靠考虑的规则是调用位置是否有上下文对象。思考下面一段代码:
function foo() {
console.log(this.a);
}
const obj = {
a: 2,
foo: foo,
};
obj.foo(); //2
首先我们注意到的是foo的声明方式及其之后是如何被当作引用属性添加到obj中的。
这种方式的调用位置会使用obj的上下文对象来引用函数,当函数引用有上下文对象时候,隐式绑定规则会把函数调用中的this绑定到这个上下文对象上。因为调用foo()时this被绑定到obj,因此this.a和obj.a是一样的。
对象属性引用链中只有上一层在调用位置中起作用,举例来说:
function bar() {
console.log(this.a);
}
const obj2 = {
a: 4,
bar: bar,
};
const obj1 = {
a: 2,
obj2: obj2,
};
obj1.obj2.bar(); //4
隐式丢失
一个最常见的this绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,因此会绑定到全局对象或者undefined(这取决于是否应用严格模式),请看下一个例子:
var c = "window";
function baz() {
console.log(this.c);
}
const obj3 = {
a: "obj3",
baz: baz,
};
const fn = obj3.baz;//函数别名!
fn();//window !
这个例子在调用baz之前,fn指向baz,在调用fn的时候,就相当于baz不带任何修饰直接独立调用,所以应用默认绑定规则绑定到全局对象(这里没有使用严格模式)。
还有一种更微妙的情况就是在传入回调函数的时候,来看一个例子:
var e="window"
function doDoo(doo) {
doo(); //函数在这里调用
}
function doo() {
console.log(this.e);
}
const obj5 = {
e: 5,
doo: doo,
};
doDoo(obj5.doo);//window!
之前博主有在【js参数传递方式】中犯了一个错误,在文章末尾。在这里也相当于var doo=doo,然后在再执行doo,就是和上面情况一样的。
那么如果我们把函数传入内置的函数而不是我们自己声明的呢?也是一样的,
setTimeout(obj5.doo, 100); //window
显式绑定
就像我们刚才看到的那样,在分析隐式绑定时,我们必须在一个对象内部包含一个指向函数的主属性,从而把this隐式绑定到这个对象上。
那么如果我们不想在对象内部包含函数引用,而想在某个对象上强制调用函数,改怎么做呢?
function foo() {
console.log(this.a);
}
const obj = {
a: 20,
};
foo.apply(obj); //20
foo.call(obj); //20
这里我们可以使用call,apply强制把this绑定到你传入的对象上,当传入一个原始值(boolean,number等)来当作绑定的对象,这个原始值会被转换成它的对象形式(也就是 new Boolean,new Number())。这通常称为装箱。
但是显式绑定仍然无法解决之前提出的丢失绑定的问题。不过我们可以使用显式绑定的一个变种–硬绑定来解决此问题。
var e = "window";
var e = "window";
function doDoo(doo) {
doo.call(obj5); //函数在这里调用
}
function doo() {
console.log(this.e);
}
const obj5 = {
e: 5,
doo: doo,
};
const bar=function(){
doo.call(obj5)
}
doDoo(bar); //5!
//硬绑定的bar不可能再修改他的this
bar.call(window)//5
博主在初学React的时候遇到的onClick调用的时候传入的是undefiend最后使用bind或者箭头函数来解决的.
class App extends React.Component {
constructor() {
super();
this.state = {
message: "Hello World",
};
}
btnClick() {
let { message } = this.state;
message = "Hello React";
this.setState({
message,
});
}
render() {
//硬绑定
this.btnClick = this.btnClick.bind(this);
return (
<div>
<button onClick={this.btnClick}>切换文本</button>
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
硬绑定的典型应用场景就是创建一个包裹函数,负责接收参数并返回值.
function foo() {
console.log(this.a);
}
const obj = {
a: 3,
};
function bind(fn, obj) {
return function () {
fn.call(obj);
};
}
const bar = bind(foo, obj);
setTimeout(bar, 100); //3
硬绑定是一种非常常用的模式,所以ES5提供了内置的方法Function.prototype.bind,用法如下:
const baz = foo.bind({ a: 10 });
setTimeout(baz, 100); //10
API调用的"上下文"
javaScript中许多新的内置函数,都提供了一个可选参数,通常被称为上下文,起作用和bind一样,确保你的回到函数使用指定的this。
function foo(item) {
console.log(item, this.id);
}
const obj = {
id: "拉拉",
};
[1, 2, 3].forEach(foo, obj);
//1 拉拉
//2 拉拉
//3 拉拉
New绑定
我们之前在讲以构造函数创建对象的时候讲到了new的过程,在此过程中,我们讲到创建一个对象,讲这个对象的[[prototype]]指向构造函数的原型,并将this绑定到这个对象…
function foo(a) {
this.a = a;
}
const bar = new foo(3);
console.log(bar.a);//3
使用new来调用foo时,我们会创建一个新对象并把她绑定到foo调用中的this上。
以上就是this绑定的四种规则,那么这时候你是不是又想知道,当它们应用在一起的时候,哪一个规则起作用呢?等着博主出下一篇文章来讲解this规则优先级!