1.this到底是什么

this是在运行时绑定的,并不是在编写时绑定,它的上下文取决于函数调用时的各种条件**,this的绑定和函数声明的位置没有关系,只取决于函数的调用方式。**当一个函数被调用时,会创建一个活动记录(上下文)。这个记录会包含函数在哪里被调用 函数的调用方式 传入的参数等信息,this就是这个记录的一个属性,会在函数执行过程中用到。

2.调用位置

通常来说,寻找调用位置就是寻找“函数被调用的位置”,最重要的是要分析调用栈,调用位置就在当前正在执行函数的前一个调用中。
**

3.绑定规则

函数执行过程中调用位置决定this的绑定对象,具体有以下四条规则。

3.1 默认绑定

独立的函数调用,函数内部的this在非严格模式下会指向全局的window,实际上独立函数调用上层上下文是window,示例如下:

  1. function bar() {
  2. console.log(this.a)
  3. }
  4. var a = 12
  5. bar() // 12

3.2 隐式绑定

函数被某个对象所调用,那么该函数被调用时隐式绑定规则会把函数调用中的this绑定到这个上下文对象,对象属性引用链中只有上一层或者最后一层在调用位置中起作用,示例如下:

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj2 = {
  5. a: 42,
  6. foo: foo
  7. }
  8. var obj1 = {
  9. a: 2,
  10. obj2: obj2
  11. }
  12. obj1.obj2.foo() //42

注意:被隐式绑定的函数在某些情况下会丢失绑定对象,也就是说它会应用默认绑定,从而把this绑定到全局对象或者undefined上,取决于是否在严格模式。1:函数赋值。2:函数作为参数传递(通常是回调函数)或者函数别名,示例如下:

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a: 2,
  6. foo: foo
  7. }
  8. var bar = obj.foo
  9. var a = 'oops,global'
  10. bar() // oops,global 虽然bar是obj.foo的一个引用,但实际上,它引用的是foo函数本身,因此此时的bar其实是一个不带任何修饰的函数调用
  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var obj = {
  5. a: 2,
  6. foo: foo
  7. }
  8. var a = "oops,global"
  9. setTimeout(obj.foo,100) // oops,global 参数传递实际上是一种隐式赋值,因此我们传入函数时也会被隐式赋值。

3.3 显式绑定

Function.prototype.call Function.prototype.apply Function.prototype.bind 它们的第一个参数接受一个对象,在调用时将this绑定到该对象。如果传入的参数是一个基本类型(字符串,布尔,数字),这个原始值会被转换成它的对象形式(装箱).
第三方库的许多函数及js语言和宿主环境的许多内置函数都提供了一个可选的参数,通常被称为上下文(context),其作用和bind一样,确保你的回调函数使用指定的this。这些函数实际上是通过call或allpy实现了显示绑定。

3.4 new 绑定

使用new来调用构造函数,或者说发生构造函数调用时,会执行以下动作:

  1. 创建一个全新对象
  2. 这个对象会被执行[[Prototype]]连接
  3. 这个新对象被绑定到函数调用的this
  4. 如果函数没有返回其他各对象,那么new 表达式中的函数调用会自动返回这个新对象

使用new调用函数时,我们会构造一个新对象兵把它绑定到函数调用的this上。
**

4.优先级

new绑定 > call apply bind > 隐式绑定 > 默认绑定

5.特殊情况

5.1 被忽略的this

如果把null或者undefined作为this的绑定对象传入call apply或bind,这些值在调用时会被忽略,实际上应用的是默认绑定

  1. function foo() {
  2. console.log(this.a)
  3. }
  4. var a = 2
  5. foo.call(null) // 2

null或者undefined传入call apply 或bind时,通常有两种用途:
1.apply展开数组
2.bind函数柯里化

  1. function foo(a,b) {
  2. console.log( "a":+a+" ,b: "+b )
  3. }
  4. //把数组展开成参数
  5. foo.apply(null,[2,3]) //a: 2, b: 3
  6. //使用bind进行柯里化
  7. var bar = foo.bind(null,2)
  8. bar(3) // a: 2, b: 3

但是传入null或undefined会将this绑定到全局变量,某些情况下会造成全局污染,因此更安全的做法是传入一个特殊的对象,把this绑定到这个对象不会对程序产生任何副作用,示例如下:

  1. function foo(a,b) {
  2. console.log("a":+a+" ,b: "+b)
  3. }
  4. var dmz = Object.create(null)
  5. foo.apply(dmz,[2,3])
  6. var bar = foo.bind(dmz,2)
  7. bar(3)

5.2 间接引用

另一个需要注意的是,可能会创建一个函数的间接引用(函数赋值,函数作为参数传递),在这种情况下,调用这个函数会应用默认绑定规则。

5.3软绑定

与硬绑定(bind)类似,强制将this绑定到传入的第一个参数,同时提高硬绑定的灵活性,首先检查调用时的this,如果this绑定到全局对象或者undefined,那就把指定的默认对象绑定到this,否则不会修改this

  1. if(!Function.prototype.softBind) {
  2. Function.prototype.softBind = function(context) {
  3. let fn = this
  4. let arg = [].slice.call(arguments,1)
  5. let bound = function() {
  6. return fn.apply(
  7. !this || this === (window || global)?
  8. context : this,
  9. arg.concat([...arguments])
  10. )
  11. }
  12. bound.prototype = Object.create(fn.prototype)
  13. return bound
  14. }
  15. }

6.箭头函数

箭头函数不使用this的四条标准规则,而是根据词法作用域(声明时)来决定this,箭头函数的绑定无法被修改!

  1. function foo() {
  2. return a => {
  3. console.log(this.a)
  4. }
  5. }
  6. var obj1 = {
  7. a: 2
  8. }
  9. var obj2 = {
  10. a: 3
  11. }
  12. var bar = foo.call(obj1)
  13. bar.call(obj2) //2