请用一句话总结this指向
谁调用它,this就指向谁。
this的指向,是在调用函数时根据执行上下文所动态决定的。
具体环节和规则如下:
-
在函数体中,简单调用该函数时(非显式/隐式绑定下),严格模式下this绑定到undefined,否则绑定到全局对象window/global;
-
一般构造函数new调用,绑定到新创建的对象上;
-
一般由上下文对象调用,绑定在该对象上;
-
箭头函数中,根据外层上下文绑定的this决定指向
实战例题分析
例题组合1:全局环境下的this
函数在浏览器全局环境中被简单调用,非严格模式下this指向window;在use strict指明严格模式的情况下就是undefined。
function f1(){
console.log(this) //window
}
function f2(){
‘use strict’
console.log(this) //undefined
}
const foo = {
bar: 10,
fn: function(){
console.log(this) // 这里的this仍然指向window,虽然fn函数在foo对象中作为方法被引用,但是在赋值给fn1后,fn1的
console.log(this.bar) // 执行仍然是在window的全局环境中。因此此时输出window和undefined。
} // console.log(window) console.log(window.bar)
}
var fn1 = foo.fn
fn1() // window undefined
const foo = {
bar: 10,
fn: function(){ // 此时this指向的是最后调用它的对象
console.log(this) // {bar:10,fn:f}
console.log(this.bar) // 10
}
}
foo.fn()
结论:在执行函数时,如果函数中的this是被上一级的对象所调用,那么this指向的就是上一级的对象;否则指向全局环境。
例题组合2:上下文对象调用中的this
const student = {
name: 'Lucas',
fn: function() {
return this
}
}
console.log(studen.fn() === student) //true
当存在更复杂的调用关系时:
const person = {
name: 'Lucas',
brother:{
fn: function() {
name: 'mike'
return this.name
}
}
}
console.log(person.brother.fn()) // mike this指向最后调用它的对象
const o1 = {
text: 'o1',
fn: function () {
return this.text
}
}
const o2 = {
text: 'o2',
fn: function () {
return o1.fn()
}
}
const o3 = {
text: 'o3',
fn: function () {
var fn = o1.fn //裸奔调用
return fn()
}
}
console.log(o1.fn()). // o1
console.log(o2.fn()) // o1
console.log(o3.fn()) // undefined
如果想让console.log(o2.fn())输出o2要怎么做?(不能使用bind/call/apply)
const o1 = {
text: 'o1',
fn: function () {
return this.text
}
}
const o2 = {
text: 'o2',
fn: o1.fn // this指向最后调用它的对象
}
例题组合3:bind/call/apply 改变this指向
bind/call/apply 三个⽅法的区别
他们都是⽤来改变相关函数 this 指向的,但是 call/apply 是直接进⾏相关函数调⽤;bind 不会执⾏相关函数,⽽是返回⼀个新的函数,这个新的函数已经⾃动绑定了新的 this 指向,开发者需要⼿动调⽤即可。再具体的 call/apply 之间的区别主要体现在参数设定上。
用代码来总结
const target = {}
fn.call(target,'arg1','arg2')
相当于
const target = {}
fn.apply(target,[arg1],[arg2])
相当于
const target = {}
fn.bind(target,'arg1','arg2')()
-
fn.call(obj, arg1, arg2, ...),
调用一个函数, 具有一个指定的this值和分别地提供的参数(参数的列表)。 -
fn.apply(obj, [argsArray])
,调用一个函数,具有一个指定的this值,以及作为一个数组(或类数组对象)提供的参数。
const foo = {
name: 'lucas',
logName: function() {
console.log(this.name)
}
}
const bar = {
name: 'mike'
}
console.log(foo.logName.call(bar)) // mike
注意:对 call/apply/bind 的⾼级考察往往会结合 构造函数以及组合式实现继承。
例题组合4:构造函数和this
function Foo(){
this.bar = "Lucas"
}
const instance = new Foo()
console.log(instance.bar) // Lucas
这样的场景往往伴随着下一个问题,new操作符调用构造函数具体做了什么?
- 创建一个新的对象
- 将构造函数的this指向这个新对象
- 为这个对象添加属性、方法等
- 最终返回新对象
以上过程也可以用代码表述:
var obj = {}
Obj.proto = Foo.prototype
Foo.call(obj)
需要指出的是,如果在构造函数中出现了显示return的情况,那么需要注意两种场景:
Function Foo(){
this.user = "Lucas"
const o ={}
return o
}
const instance = new Foo()
console.log(instance.user) // undefined 此时返回的是空对象
Function Foo(){
this.user = "Lucas"
return 1
}
const instance = new Foo()
console.log(instance.user) // Lucas 此时返回的目标对象实例this
结论:如果构造函数中显示返回一个值,并且返回的是一个对象,那么this就指向这个返回的对象;如果返回的不是一个对象,那么this仍然指向实例。
例题组合5:箭头函数的this指向
结论:箭头函数使用this不适用以上标准准则,而是根据外层(函数或者全局)上下文来决定。
const foo ={
fn: function (){
setTimeout(function(){
console.log(this)
})
}
}
console.log(foo.fn())
这道题中,this出现在setTimeout()中的匿名函数里,因此this指向window对象。如果需要this指向foo这个object对象,可以巧用箭头函数去解决:
const foo ={
fn: function (){
setTimeout(() => {
console.log(this)
})
}
}
console.log(foo.fn()) // {fn: f}
例题组合6:this优先级相关
我们常常把通过call、apply、bind、new对this绑定的情况称为显式绑定;根据调用关系确定的this指向成为隐式绑定。
那么显示绑定和隐式绑定谁的优先级更高呢?
function foo(a){
console.log(this.a)
}
const obj1 = {
a: 1,
foo: foo
}
const obj2 = {
a: 2,
foo: foo
}
obj1.foo.call(obj2) // 2
obj1.foo.call(obj1) // 1
说明:call、apply的显示绑定一般来说优先级更高
function foo(a){
this.a = a
}
const obj1 = {}
var bar = foo.bind(obj1)
bar(2)
console.log(obj1.a)
上述代码通过bind,将bar函数中的this绑定为obj1对象。执行bar(2)后,obj1.a值为2.即经过bar(2)执行后,obj1对象为:{a:2}
当再使用bar作为构造函数时:
var baz = new bar(3)
console.log(baz.a) //3
bar函数本身是通过bind方法构造的函数,其内部已经对将this绑定为obj1,它再作为构造函数,通过new调用时,返回的实例已经与obj1解绑。
结论:new绑定修改了bind绑定中的this,因此new绑定的优先级比显式bind绑定更高
function foo(){
return a =>{
console.log(this.a)
};
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
const bar = foo.call(obj1)
console.log(bar.call(obj2)) // 2
由于foo()的this绑定到obj1,bar(引用箭头函数)的this也会绑定到obj1,箭头函数的绑定无法被修改。
如果将foo完全写成箭头函数的形式:
var a = 123
const foo = () => a=> {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) // 123
const a = 123
const foo = () => a=> {
console.log(this.a)
}
const obj1 = {
a: 2
}
const obj2 = {
a: 3
}
var bar = foo.call(obj1)
console.log(bar.call(obj2)) // undefined
原因是因为使用const声明的变量不会挂载到window全局变量上。因此this指向window时,自然找不到a变量。
典型面试:实现一个bind函数(模拟bind)
Function.prototype.bind = Function.prototype.bind ||
function (context) {
var me = this;
var args = Array.prototype.slice.call(arguments,1);
return function bound(){
var innerArgs = Array.prototype.slice.call(arguments);
var finalArgs = args.concat(innerArgs);
return me.apply(context,finalArgs);
}
}