关于this那些事儿(下)

149 阅读5分钟

默认绑定

首先介绍的就是最常用的函数调用类型了:独立函数调用,可以把这条规则看作是无法应用其他规则时候的默认规则。来看这个例子:

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.aobj.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规则优先级!