js中常说的绑定this到底是何意?

112

由上一篇js高频中编写new,bind,apply,call等函数时,频繁设计关于绑定this的说法。那么何为绑定this?this到底是什么?要把这弄清楚就显得尤为重要了

JS中的this是什么?

理解this之前,我们要先纠正一个观点,就是this既不是指向函数本身,也不指函数的词法作用域。 如果仅通过this的英文解释,太容易产生误导了。

它实际上是在函数被调用时才发生的绑定(也就是这时的this才被赋予意义),也就是说this具体指向什么,取决于你是怎么调用的函数。也就是说谁调用的this,this就指向谁。

为什么要设计出this来?

使API设计的更加简洁且易于复用。是想如果我们要实现一些继承,拿取其他地方的变量都要将变量收集重新放到一个对象中将会使工程量变得极其笨重, 不简洁且到处是copy,这显然会使JS被人唾弃。因此设计者设计一个this,让程序遵守一定规则使this变成一个代替你copy的身轻如燕,能量巨大的存在(也没这么神奇啦)

JS中的this说明

  • ES6中的箭头函数采用的是词法作用域。
  • this既不指向自身,也不指向函数的词法作用域。
  • this的指向取决于函数的调用方式。

this绑定规则有四个:

    1. 默认绑定
    1. 隐式绑定
    1. 显式绑定
    1. new

1. 默认绑定最easy

独立函数调用时 ,不管是否在调用栈中,this都指向全局对象(浏览器中为window)

注意:严格模式下,不能将全局对象用于默认绑定

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

bar(); // 2

2. 隐式绑定(核心在这)

当函数引用有上下文对象时,隐式绑定规则会把函数调用中的this绑定到这个上下文对象

对象属性引用链中只有最后一层在调用位置中起作用

要求:对象内部必须包含一个指向函数的属性,该对象可通过这个属性间接引用函数

function foo() {
    console.log(this.a)
}

var obj1 = {
    a: 42,
    foo: foo // obj1对象内部包含一个指向函数foo的属性foo
}

var obj2 = {
    a: 2,
    obj1: obj1
}

obj2.obj1.foo() // 42 只有最后一层obj1起作用

2.1 隐式丢失

function foo () {
    console.log(this.a)
}
var obj = {
    a: 2,
    foo: foo
}
var bar = obj.foo; // 这里bar将引用foo函数本身(相当于独立函数了),所以不带有函数对象的上下文
var a = 'global a'; // a是全局对象的属性
bar(); //"global a" 独立函数调用指向foo所以为

2.2 和回调函数的情况下(参数传递时的隐式赋值)

function foo() {
    console.log(this.a)
}
var obj = {
    a: 2,
    foo: foo
}
var a = 'global a'
function doFoo(fn) {
    // 参数传递时,相当于fn = obj.foo,就和上个例子一样了
    fn()
}
doFoo(obj.foo); // 'global a'

3. 显式绑定(也叫硬绑定)

采用call()和apply(), 通过传入一个对象(若为基本类型,会被封装函数转化为对象---装箱),将this绑定到该对象。

  • 可以查看call和apply的内部实现,本质是借用隐式绑定的,加函数,绑定属性,删除多余函数,返回新对象
funtion foo() {
    console.log(this.a)
}
vat obj = {
    a: 2
}
var bar = function() {
    foo.call(obj)
}
bar(); // 2
// 硬绑定后bar无论怎么调用,都不会影响foo函数的this绑定**
setTimeout(bar, 100); //2
bar.call(window)

硬绑定的典型应用是如下包裹函数

function foo(something) {
    console.log(this.a, something)
    return this.a + something
}
var obj = {
    a: 2
}
var bar = function() {
    return foo.apply(obj,arguments) // 将obj对象编码进去
}
var b = bar(3) // 2, 3
console.log(b) // 5

即将内部函数用apply硬绑定到某个对象,无论怎么调用这个包裹函数,都不会影响内部函数的this

bing辅助函数如下

function  foo(something) {
    console.log(this.a, something)
    return this.a + something
}
function bind(fn, obj) {
    //返回函数是为了便于接收arguments
    return function() {
        return fn.apply(obj, arguments) // 利用参数将obj传入进去
    }
}

var obj = {
    a: 2
}
var bar = bind(foo,obj) ;// bind(foo, obj)会返回一个包裹函数
var b = bar(3); // 2 3
console.log(b) // 5

总结: 上述包裹函数,想要包裹其他函数,只能一个一个重复写,硬编码的方式导致不能被重用。当某种功能需要多次重复使用时,将其抽象出来成为函数。

4. new绑定

任何函数都可能被用作构造函数,当函数被new操作符“构造调用”时,会执行下面的操作(new的作用考点)

  1. 创建一个新对象(若该函数不是js内置的,则创建一个新的Object对象)
  2. 将this绑定到这个对象
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 若函数没有返回其他对象,则自动返回这个新对象;若函数有return返回的是非对象,则还是自动返回这个新对象,覆盖那个非对象。
function foo(a) {
    this.a = a
}
var bar = new foo(2)
console.log(bar.a)

4.1 补充说明

4.1.1 间接引用

function foo() {
    console.log(foo)
}
var a = 2
var o = { a: 3, foo: foo }
var p = { a: 4 }
o.foo() // 3
(p.foo = o.foo)() // 2 由于p.foo = o.foo的返回值是目标函数的引用,所以调用位置是foo() 独立函数调用

4.2 箭头函数:

箭头函数不使用上述四个this规则,而是根据作用域来决定this

function foo() {
    return a => {
        // `this` here is lexically adopted from `foo()`
        console.log(this.a)
    }
}

var obj1 = {
    a: 2
}
var obj2 = {
    a: 3
}
// foo不是箭头函数,它的this被硬绑定到了obj1上
var bar = foo.call(obj1)// foo.call( obj1 )返回箭头函数,所以bar为箭头函数
bar.call(obj2); // 箭头函数的this无法被修改,new也不行

如下为和箭头一样的模式

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

参考jb51