this的指向

81 阅读4分钟

this的指向

全局上下文

在全局执行环境中(在任何函数体外部)this都指向全局对象

// 在浏览器中,window 对象同时也是全局对象:
console.log(this === window); // true

函数上下文

this是在调用时进行绑定的。不能再执行期间被赋值,并且每次函数被调用时this的值也可能会不同。

绑定规则

默认绑定

函数调用:独立函数调用,this指向全局对象

let obj = {
  a: function() {
    console.log(this); // obj
    function b() {
      console.log(this) // window
    }
    b();
    (function (){
      console.log(this) // window
    })()
  }
}
obj.a()

隐式绑定

考虑调用位置是都有上下文对象,或者说是否被某个对象拥有或者包含

对象属性链中只有上一层或者说最后一层再调用位置中起作用

function foo () {
  console.log(this.a) // 1
}
let obj2 = {
  a: 1,
  foo: foo
}
let obj1 = {
  a: 2,
  obj2: obj2
}
obj1.obj2.foo()

隐式丢失

function foo () {
  console.log(this)
}
let obj = {
  foo: foo
}
let bar = obj.foo // 此时的bar是一个函数
bar() // window,this是再调用的时候绑定,所以此时的全局上下文

参数的传递实际上也是一种隐式赋值

// 参数传递
function foo () {
  console.log(this.a) // 3
}
function doFoo(fn) {
  fn()
}
let obj = {
  a: 1,
  foo: foo
}
var a = 3
doFoo(obj.foo) 

显式绑定

Function.prototype.apply()、Function.prototype.call()、Function.prototype.bind()

call、apply、bind作用是改变函数执行时的上下文,简而言之就是改变函数运行时的this指向

三者区别

apply:apply接受两个参数,第一个参数是this的指向,第二个参数是函数接受的参数,以数组的形式传入

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

function fn(...args){
  console.log(this.a) // 3
  console.log(args) // [1, 2]
}
let obj = {
  a: 3
}
var a = 2
fn.apply(obj,[1,2]); // this会变成传入的obj,传入的参数必须是一个数组
fn(1,2) // this指向window

call:第一个参数也是this的指向,后面传入的是一个参数列表

改变this指向后原函数会立即执行,且此方法只是临时改变this指向一次

function fn(...args){
  console.log(this.a) // 3
  console.log(args) // [1, 2]
}
let obj = {
  a: 3
}
var a = 2
fn.call(obj,1,2) // this会变成传入的obj,传入的参数必须是一个数组;
fn(1,2) // 2 this指向window

bind:第一参数是this的指向,后面传入的是一个参数列表(但是这个参数列表可以分多次传入)

改变this指向后不会立即执行,而是返回一个永久改this指向的函数

function fn(...args){
  console.log(this.a)
  console.log(args)
}
let obj = {
  a: 3
}
var a = 2
const bindFn = fn.bind(obj) // this 也会变成传入的obj ,bind不是立即执行需要执行一次
bindFn(1,2) // this指向obj
fn(1,2) // this指向window

new绑定

new 一个构造函数的时候, 会发生下列操作:

  • 在内存中创建一个新对象
  • 在这个对象内部的[[Prototype]]特性被赋值为构造函数的Prototype属性
  • 构造函数内部的this被赋值为这个新对象(即this指向新对象)
  • 如果构造函数返回非空对象,则返回该对象;否则,返回刚创建的新对象
function myNew(Fn, ...args) {
  const newObj = {} //新建一个空对象
  if (Fn.prototype !== null ) {
    newObj.__proto__ = Fn.prototype // 新建对象的原型指向构造函数的显示原型
  }
  // 构造函数的this指向这个新对象
  const res = Fn.apply(newObj, args)
  // 如果构造函数返回非空对象,则返回该对象
  if(typeof res === 'function' || (typeof res === 'object' && res !== null)) {
    return res
  }
  // 否则返回该对象
  return newObj
}
// 利用Object
function myNew2 (Fn, ...args) {
  // 创建一个空的对象,将实例化对象的原型指向构造函数的原型对象
  const newObj = Object.create(Fn.prototype)
  const res = Fn.apply(newObj, args)
  return typeof res === 'object' ? res : newObj
}
function Person(name) {
  this.name = name
}
Person.prototype.sayName = function () {
  console.log(`My name is ${this.name}`)
}
const person1 = myNew(Person, 'Jack')
const person2 = myNew2(Person, 'Jack')
console.log(person1)
console.log(person2)

判断this:

  1. 创建箭头函数时,就已经确定了它的 this 指向。箭头函数内的 this 指向外层的 this

  2. 函数是否在new中调用(new绑定)?如果是的话this绑定的是新创建的对象。

    var bar = new foo()

  3. 函数是否通过call、apply(显式绑定)或者硬绑定调用?如果是的话,this绑定的是指定的对象。

    var bar = foo.call(obj2)

  4. 函数是否在某个上下文对象中调用(隐式绑定)?如果是的话,this绑定的是那个上下文对象。

    var bar = obj1.foo()

  5. 如果都不是的话,使用默认绑定。如果在严格模式下,就绑定到undefined,否则绑定到全局对象。

    var bar = foo()