深入 JS 之 this 的绑定规则

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第5天,点击查看活动详情

疑惑

这里先从基本记录一下 this 的指向, 然后会在后面在 深入 JS 之从 ECMAScript 规范解读 this 中, 会从规范出发,解读,为什么会是这样的结果

js 中,有一个疑惑的点 this, 它的指向问题,存在各种各样的,来看一下,它是如何绑定指向的吧

  1. 函数在调用时,JavaScript 会默认给 this 绑定一个值
  2. this 的绑定和定义的位置(编写的位置)没有关系
  3. this 的绑定和调用方式以及调用的位置有关系
  4. this 是在运行时,动态绑定的

this 绑定规则

1.默认绑定

注意: 严格模式下默认 thisundefined ,非严格模式下才是 window

作为独立函数调用时,采用的默认绑定规则

function foo() {
  console.log(this) // window
}

function test1() {
  console.log(this) // window
  test2()
}
function test2() {
  console.log(this) // window
  test3()
}
function test3() {
  console.log(this) // window
}

test1()

function fn(fnc) {
  fnc()
}

var obj = {
  bar: function () {
    console.log(this)
  }
}

fn(obj.bar) // window 因为`obj.bar`取出的是函数,函数再被独立执行的
复制代码

2.隐式绑定

作为对象方法调用时,采用隐式绑定规则

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

var obj = {
  bar: foo
}
obj.bar() // obj

var obj1 = {
  bar: foo
}
var obj2 = {
  obj1: obj1
}
obj2.obj1.bar() // obj1

var obj3 = {
  foo: foo
}
var bar = obj1.foo
// 取出函数,独立调用了
bar() // window
复制代码

3.显示绑定

使用 call、apply、bind 进行绑定

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

// bind
var obj = {
  name: 'jpliu'
}
var bar = foo.bind(obj)
bar() // obj

// call/apply
foo.call(window) // window
foo.call({ name: 'jpliu' }) // {name: 'jpliu'}
foo.call(123) // Number对象的123     Number {123}
foo.apply('123') // String 对象的'123'  String {'123'}
复制代码

4.new 绑定

通过 new 关键字实例化对象的时候,绑定为实例化的对象

function Person(name) {
  console.log(this) // Person {}
  this.name = name // Person { name: 'jpliu' }
}
var p = new Person('jpliu')
console.log(p)
复制代码

5.内置方法

// 其实算是隐式绑定,因为这些方法都是 `window`的
window.setTimeout(function () {
  // 这里或许没这么简单,这个是回调函数,与隐式绑定,没啥关系,这里是浏览器实现的黑盒
  // 在 v8 中,有一个测试用例,模拟,是采用 call 绑定的,this是指向的 window
  // 所以这个看具体如何实现
  console.log(this) // window
}, 2000)

// 2.监听点击
const boxDiv = document.querySelector('.box')
// 隐式绑定 `boxDiv` 的`onclick` 方法触发
boxDiv.onclick = function () {
  console.log(this)
}
// `addEventListener`的`this`是隐式绑定
// 当`callback`的`this`没有显示绑定时
// 使用`bind`显示绑定`addEventListener`的`this`
boxDiv.addEventListener('click', function () {
  console.log(this)
})
boxDiv.addEventListener('click', function () {
  console.log(this)
})
boxDiv.addEventListener('click', function () {
  console.log(this)
})

// 3.数组.forEach/map/filter/find
// 可以通过第二个参数绑定回调函数的`this`
var names = ['abc', 'cba', 'nba']
names.forEach(function (item) {
  console.log(item, this)
}, 'abc')
names.map(function (item) {
  console.log(item, this)
}, 'cba')
复制代码

6.规则优先级

  1. 默认规则的优先级最低

  2. 显示绑定优先级高于隐式绑定

    function foo() {
      console.log(this)
    }
    
    var obj = {
      name: 'obj',
      foo: foo.bind('aaa')
    }
    
    // [String: 'aaa']
    obj.foo()
    复制代码
  3. new 绑定优先级高于隐式绑定

    扫描二维码关注公众号,回复: 14239441 查看本文章
    var obj = {
      name: 'obj',
      foo: function () {
        console.log(this)
      }
    }
    
    // new的优先级高于隐式绑定
    // foo {}
    var f = new obj.foo()
    复制代码
  4. new 绑定优先级高于 bind

    • new 绑定和 call、apply 是不允许同时使用的,所以不存在谁的优先级更高

    • new 绑定可以和 bind 一起使用,new 绑定优先级更高

      function foo() {
        console.log(this)
      }
      
      var bar = foo.bind('aaa')
      
      // foo {} 不是 [String: 'aaa']
      var obj = new bar()
      复制代码

7.规则之外

  1. null 或者 undefined

    // 非严格模式下
    // apply/call/bind: 当传入null/undefined时, 自动将this绑定成全局对象
    foo.apply(null)
    foo.apply(undefined)
    
    var bar = foo.bind(null)
    bar()
    
    // 严格模式下,就是 `null/undefined`
    复制代码
  2. 间接函数引用

    var obj1 = {
      name: 'obj1',
      foo: function () {
        console.log(this)
      }
    }
    
    var obj2 = {
      name: 'obj2'
    }
    
    // 这里是取出了obj1.foo函数,赋值给了obj2.bar
    // = 运算法的返回值,就是右侧的值, ob1.foo 的函数定义
    // 相当于取出函数,然后独立调用, 所以指向 window
    ;(obj2.bar = obj1.foo)()
    复制代码
  3. 箭头函数,箭头函数是无法使用 bind/call/apply 绑定 this 的,箭头函数的 this 是定义的时候所处的上下文环境,无法变更,属于静态 this, 而不是动态绑定的

8.实践

var name = 'window'

var person = {
  name: 'person',
  sayName: function () {
    console.log(this.name)
  }
}

function sayName() {
  var sss = person.sayName
  sss() // window: 独立函数调用
  person.sayName() // person: 隐式调用
  person.sayName() // person: 隐式调用
  ;(b = person.sayName)() // window: 赋值表达式(独立函数调用), 使用 = 号运算符之后,返回了 person.sayName 这个函数方法,后续调用,跟 person 无关
}

sayName()
复制代码
var name = 'window'

var person1 = {
  name: 'person1',
  foo1: function () {
    console.log(this.name)
  },
  foo2: () => console.log(this.name),
  foo3: function () {
    return function () {
      console.log(this.name)
    }
  },
  foo4: function () {
    return () => {
      console.log(this.name)
    }
  }
}

var person2 = { name: 'person2' }

// person1.foo1(); // person1(隐式绑定)
// person1.foo1.call(person2); // person2(显示绑定优先级大于隐式绑定)

// person1.foo2(); // window(不绑定作用域,上层作用域是全局)
// person1.foo2.call(person2); // window

// person1.foo3()(); // window(独立函数调用)
// person1.foo3.call(person2)(); // window(独立函数调用)
// person1.foo3().call(person2); // person2(最终调用返回函数式, 使用的是显示绑定)

// person1.foo4()(); // person1(箭头函数不绑定this, 上层作用域this是person1)
// person1.foo4.call(person2)(); // person2(上层作用域被显示的绑定了一个person2)
// person1.foo4().call(person2); // person1(上层找到person1)
复制代码
var name = 'window'

function Person(name) {
  this.name = name
  ;(this.foo1 = function () {
    console.log(this.name)
  }),
    (this.foo2 = () => console.log(this.name)),
    (this.foo3 = function () {
      return function () {
        console.log(this.name)
      }
    }),
    (this.foo4 = function () {
      return () => {
        console.log(this.name)
      }
    })
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.foo1() // person1
person1.foo1.call(person2) // person2(显示高于隐式绑定)

person1.foo2() // person1 (上层作用域中的this是person1)
person1.foo2.call(person2) // person1 (上层作用域中的this是person1)

person1.foo3()() // window(独立函数调用)
person1.foo3.call(person2)() // window
person1.foo3().call(person2) // person2

person1.foo4()() // person1
person1.foo4.call(person2)() // person2
person1.foo4().call(person2) // person1
复制代码
var name = 'window'

function Person(name) {
  this.name = name
  this.obj = {
    name: 'obj',
    foo1: function () {
      return function () {
        console.log(this.name)
      }
    },
    foo2: function () {
      return () => {
        console.log(this.name)
      }
    }
  }
}

var person1 = new Person('person1')
var person2 = new Person('person2')

person1.obj.foo1()() // window
person1.obj.foo1.call(person2)() // window
person1.obj.foo1().call(person2) // person2

person1.obj.foo2()() // obj
person1.obj.foo2.call(person2)() // person2
person1.obj.foo2().call(person2) // obj
复制代码

猜你喜欢

转载自juejin.im/post/7105788363679989791
今日推荐