this绑定

前言

日常灵魂拷问,你能复述this绑定的四条基本原则吗?

this绑定规则

你是否常常因为不知道怎么判断this绑定到谁身上而懊悔;你是否常常因为this的调用出错而无语;你是否常常因为this的各种骚操作而不知所措。
只要你看了这篇文章,上面的问题都能迎刃而解。

默认绑定

最常用的函数调用类型:独立函数类型
思考以下代码:

function foo() {
  console.log(this.a)
}
var a = 2
foo() // 2

为什么输出a呢?因为函数调用时应用了this的默认绑定,因此this指向全局对象。
因此可以得知:当函数直接使用不带任何修饰的函数引用进行调用时,只能使用默认绑定,无法应用其他规则。
当函数内部使用严格模式,则不能将全局对象用于默认绑定,this会绑定到undefined

隐式绑定

另一条需要考虑的规则是调用位置是否有上下文对象,或者说是否被某个对象拥有或者包含。
思考下面代码:

function foo() {
  console.log( this.a )
}
var obj = {
  a: 2,
  foo
}
obj.foo(); //2

当obj调用foo时,会使用obj上下文来引用函数。当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象。因此调用foo()时this被绑定到obj,this.a和obj.a是一样的。
注意:对象熟悉引用链中只有上一层或者说最近一层在调用位置中起作用。如下:

function foo() {
  console.log(this.a)
}
let obj1 = {
  a: 1,
  foo
}
let obj2 = {
  a: 2,
  obj1
}
obj2.obj1.foo()
隐式绑定丢失

隐式绑定有一个问题就是会丢失绑定对象,然后应用默认绑定,从而使this绑定到全局对象或者undefined上。
思考下面代码:

function foo() {
  console.log( this.a )
}
var obj = {
  a: 1,
  foo
}
var bar = obj.foo;
var a = 2;
bar(); //2

这里bar虽然使obj.foo的引用,但是实际上引用的foo函数,因此bar() === foo(),所以应用了默认绑定。
参数传递也会隐式丢失。

function foo() {
  console.log( this.a )
}
var obj = {
  a: 1,
  foo
}
var a = 2;
function doFoo(fn) {
  fn()
}

doFoo(obj.foo); // 2

相当于doFoo内容进行以下赋值:

fn = obj.foo
fn()
所以也是默认绑定

显示绑定

JavaScript提供了call和apply方法能实现显示绑定。
它们的第一个参数是一个对象,接着在调用函数时将其绑定到this上,因此你可以直接指定this的绑定对象。

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1
}
foo.call(obj); //1
硬绑定

硬绑定可以解决隐式丢失的问题,上码:

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1
}
var bar = function() {
  foo.call(obj);
}

在bar的内部,我们用call将obj绑定到了foo上,之后不管怎样调用bar,this都会指向obj。
因为硬绑定非常的实用,所以ES5提供内置方法Function.prototype.bind()。
示例:

function foo() {
  console.log(this.a)
}
var obj = {
  a: 1
}
var bar = foo.bind(obj)
bar() // 1
软绑定

硬绑定让this强制绑定到指定对象,但是会大大降低函数的灵活性,使用硬绑定后无法使用隐式绑定或者显示绑定来修改this(可以使用new),所以有了软绑定。但是没咋懂,大概如下:

if(!Function.prototype.softBind) {
  Function.prototype.softBind = function(obj) {
    var fn = this;
    var curried = [].slice.call(arguments, 1);
    var bound = function() {
      return fn.apply(
        (!this || this === (window || global))? obj : this,
        curried.concat.apply(curried, arguments)
      );
    };
    bound.prototype = Object.create( fn.prototype );
    return bound;
  }
}

new绑定

首先,我们来了解,使用new来调用函数,会执行下面的操作:

  1. 创建一个全新的对象
  2. 这个新对象会被执行[[Prototype]]连接
  3. 这个新对象会绑定到函数调用的this
  4. 如果函数没有返回其他对象,那么new表达式中的函数调用会自动返回这个新对象
    思考下面代码:
function foo(a) {
  this.a = a
}
var bar = new foo(2)
console.log(bar.a) //2

在用new调用foo时,会构造一个新对象并把它绑定到foo()调用中的this上。

绑定优先级排序

new绑定 > 显示绑定 > 隐式绑定 > 默认绑定
按照优先级可以更改函数的绑定

posted @ 2021-09-09 21:00  卿六  阅读(38)  评论(0编辑  收藏  举报