这篇文章用来整理this指向相关面试题,我们请看内容

##面试题一

var name = "window";

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

function sayName() {
  var sss = person.sayName;

  sss(); // 绑定: 默认绑定, window -> window

  person.sayName(); // 绑定: 隐式绑定, person -> person

  (person.sayName)(); // 绑定: 隐式绑定, person -> person

  (b = person.sayName)(); // 术语: 间接函数引用, window -> window
}

sayName();

上面代码执行之后依次打印window person person window

解释一下,这里的第一个window是上面的person对象里面的sayName函数,那么我们这里调用的this为什么不是指向我们preson对象呢?原因是我们sayName方法使用环境,将sayName函数重新赋值给了一个变量sss,我们调用sss()执行sayName方法,当我们调用sss的时候我们当前的环境是在windows中,所以我们可以理解,假如我们直接调用person.sayName()才是打印person对象,也就是我们第二个打印出person的隐式绑定的方法。

第二个person的由来:第二个person我们知道当小括号包裹一段内容这段内容可以看做一个整体,并且先运行,所以我们可以将第一个小括号里的内容先运行出来,没错他就是我们的sayName方法,众所周知,函数体在JavaScript中是堆内存,所以我们执行这里只是调用了这个内存地址,并不关person的事,然后我们将这个内存地址运行的时候就需要在后面加个新的小括号了,这里讲的就是函数的调用。

最后一个window打印的时候,是将我们的person.sayName方法间接赋值给了一个全局变量b并且执行,假如在最后我们打印b变量会发现是ƒ () {console.log(this.name);}和我们打印sss的时候情况一模一样,所以就能体会到JavaScript实际使用中的玄妙了吧。

面试题二

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 () {
    // console.log(this) // 第一个表达式this -> person1
    // console.log(this) // 第二个表达式this -> person2
    // console.log(this) // 第三个表达式this -> person1
    
    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
person1.foo4.call(person2)(); // person2
person1.foo4().call(person2); // person1

上面执行之后在控制台打印:
person1 person2 window window window window person2 person1 person2 person1person1.foo1()
这里打印person1,由于是由person1调用的foo1函数,是隐式调用的方式,this指向persion1对象。所以这里的thisname就是person1person1.foo1.call(person2)
这里为什么会打印出person2呢,这是由于我们使用了call显式绑定,person2对象的name属性又是person2,我们又知道显式绑定的优先级高于隐式绑定,所以才打印我们的person1而不是person2

person1.foo2()
这里又为什么打印window呢?原因就是我们的箭头函数,箭头函数是没有作用域的,所以 foo2: () => console.log(this.name)中的this指向的就是window对象,那么有人又要有疑问了,箭头函数没有作用域的话他指向的name不应该是person1吗?不是的,在JavaScript中Object类型没有他的作用域的,我们的方法才具备块级作用域。所以就打印我们的window,这里的window是我们在顶级作用域定义的name,假如我们顶级没定义name的话,打印就的就是空。
person1.foo2.call(person2)
这里我们打印出来window,是为什么呢?由于我们看到使用了call显式调用数据,那不应该打印persion2吗?不是我们不能调用persion2,因为箭头函数没有作用域,这个特性要大于其他的特点。所以还是要乖乖打印顶级定义的name变量。

person1.foo3()()
这里打印我们window,原因是什么呢?person1.foo3函数调用返回了一个函数,注意这里返回的函数是一个函数地址,也就是把函数所对应的栈内存地址返回了,第二个括号调用我们的占内存地址,也就是我们的堆内存。由堆内存执行this。这里执行的this所对应的是我们的顶级作用域window,打印出的也就是变量name

person1.foo3.call(person2)()
这里我们调用的不应该打印我们person2对应的值吗?不是的,原因是foo3调用返回是一个方法,不能用call即使使用了call所改变的也不是我们person1函数的name。所以还是打印的结果和person1.foo3()()一样都是window

person1.foo3().call(person2)
这里打印出来的就是我们的person2,为什么是这个结果呢,由于我们将person1.foo3调用了,并且使用call关键词显示绑定为person2对应的name

person1.foo4()()
这里输出我们的person1,因为返回的是一个箭头函数,又因为箭头函数是没有作用域的,所以这里的this就是指向我们的person1,打印我们的person1

person1.foo4.call(person2)()
这里person1.foo4使用了显示调用call为什么在这里可以使用person2的name,并且把他打印出来,person1.foo4返回的是箭头函数,也就是我们内存地址,在没有调用箭头函数之前将person2对象绑定到person1.foo4函数中。紧接着调用,就能打印出person2对应的值

person1.foo4().call(person2)
这里打印出person1,原因是箭头函数的问题,箭头函数没有作用域所以也就不能绑定this,显式绑定就失去了作用,依然打印person1不打印person2

面试题三

var name = 'window'

/*
  1.创建一个空的对象
  2.将这个空的对象赋值给this
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
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)
    }
  }
}

// person1/person都是对象(实例instance)
var person1 = new Person('person1')
var person2 = new Person('person2')


// 面试题目:
person1.foo1() // 隐式绑定: person1
person1.foo1.call(person2) // 显式绑定: person2

person1.foo2() // 上层作用域查找: person1
person1.foo2.call(person2) // 上层作用域查找: 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(隐式绑定)

上面代码执行后打印:
person1 person2 person1 person1 window window person2 person1 person2 person1

person1.foo1()
这里打印我们的person1,为什么打印person1,由上面代码看出我们的person1是由一个方法调用出来的,这个类的参数是person1,Person类中的this就是指向我们的方法,我们person1调用foo(),这里foo1是有自己的作用域的,但是在自己作用域中没找到name,就到上级作用域找,找到person1

person1.foo1.call(person2)
打印person2是为什么呢?由于我们的foo1使用的是默认绑定打印this.name,call显式绑定person2,显式绑定优先级要高于隐式绑定,所以我们当然要调用person2this.name打印
person1.foo2()
这里主要是由于我们的箭头函数没有作用域,直接去上级找this.name打印person1person1.foo2.call(person2)
这里打印person1person1.foo2是一个箭头函数,没有作用域,所以调用call然并卵。还是打印person1中的nameperson1.foo3()()
window为什么呢?person1.foo3返回的是一个方法,这个方法又在最顶层首先调用并且再次调用person1方法
window为什么呢?person1.foo3返回的是一个方法,这个方法又在最顶层首先调用并且再次调用person1方法

** person1.foo3.call(person2)()**
这里也是返回window,原因是person1.foo3并没调用函数就直接call绑定,返回的依然是window,依旧是默认值
person1.foo3().call(person2)
这里打印我们的person2,显式绑定和上面的操作不同这里调用foo3方法之后call,可以改变name的值,用了显示绑定。
person1.foo4()()
这里打印person1,返回的是箭头函数没有作用域,直接上层查找返回person1,属于隐式绑定
person1.foo4.call(person2)()
这里person1.foo4调用person2函数,虽然说person1.foo4返回的是一个箭头函数但是我们将person2绑定到了person1.foo4上,在调用之后this.name改变了,打印出我们的person2person1.foo4().call(person2)
这里打印person1,方法person1.foo4先调用,因为是箭头函数所以call绑定就失效了。还是调用隐式绑定的person1

面试题四

var name = 'window'

/*
  1.创建一个空的对象
  2.将这个空的对象赋值给this
  3.执行函数体中代码
  4.将这个新的对象默认返回
*/
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(隐式绑定)

上述代码执行后将会打印出来:
window window person2 obj person2 obj

person1.obj.foo1()()
这里有一对象person1调用obj对象,然而obj返回了一个函数,这个函数打印了this.name,调用obj对象又调用foo1,使用了默认绑定,返回的是顶层定义的name.

person1.obj.foo1.call(person2)()
这里打印的为什么和上面的一样呢?原因其实很简单我们在一个作用域调用另外一个作用域返回的方法,这里的作用域使用call显式绑定也就不起作用了,因为foo1不是用this.foo1 = 定义的。这里使用的默认绑定返回windowperson1.obj.foo1().call(person2)
这里打印person2,为啥就起作用了,person1.obj.foo1是一个内存地址,内存地址调用之后,将返回的方法绑定到了person2上,这里用了显示绑定,返回person2person1.obj.foo2()()
这里打印了obj,person1.obj.foo2返回了一个箭头函数,箭头函数没有作用域,直接在上层作用域查找,打印obj
person1.obj.foo2.call(person2)()
这里将person2绑定到person1.obj.foo2方法上,使用了显式绑定,打印person2person1.obj.foo2().call(person2)
这里先调用person1.obj.foo2,然后再绑定person2这里绑定失败,所以打印obj使用了隐式绑定
感谢大家观看,我们下次见