this函数的指向问题

20 阅读4分钟

总结:

函数内部的this指向取决于函数的调用方式,而不是它被定义的位置。当函数被独立调用时,在非严格模式下,this指向全局对象。在严格模式下,独立调用的函数中的this是undefined。

因此,当你看到函数内部的this指向全局对象时,通常是因为这个函数被独立调用了。要改变这种行为,可以使用箭头函数、显式绑定或闭包(保存this)等方法

核心原因:调用方式决定 this

关键点this 的指向不取决于函数在哪里定义,而取决于函数如何被调用

1. 独立函数调用(最常见的陷阱)

javascript

var name = 'Global Name';

function showThis() {
  console.log(this); // window
  console.log(this.name); // 'Global Name'
}

// 独立调用 - this 指向 window
showThis(); 

// 等同于:
window.showThis(); // 浏览器环境下实际上是这样

为什么?

  • 当函数被独立调用时(前面没有 对象.),JavaScript 使用默认绑定规则
  • 在非严格模式下,默认绑定会将 this 指向全局对象

2. 嵌套函数中的陷阱

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  outer: function() {
    console.log('outer this:', this.name); // 'Object'
    
    function inner() {
      console.log('inner this:', this.name); // 'Global' - 为什么??
    }
    
    inner(); // 独立调用!
  }
};

obj.outer(); // outer 作为方法调用,inner 作为独立函数调用

解析

  • obj.outer() → outer 的 this 指向 obj
  • inner() → 独立调用,inner 的 this 指向 window

3. 回调函数中的陷阱

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  handleClick: function() {
    console.log('handleClick this:', this.name); // 'Object'
  },
  setupEvent: function() {
    console.log('setupEvent this:', this.name); // 'Object'
    
    // 回调函数 - 独立调用!
    setTimeout(function() {
      console.log('setTimeout this:', this.name); // 'Global'
    }, 100);
  }
};

obj.setupEvent();

解析

  • obj.setupEvent() → this 指向 obj
  • setTimeout 的回调函数被独立调用this 指向 window

为什么会这样设计?

历史原因和设计哲学

  1. 早期设计决策:JavaScript 最初设计时,函数被认为是"独立"的实体
  2. 动态语言特性:JavaScript 是动态语言,函数可以很容易地在不同上下文中重用
  3. 灵活性:这种设计让函数可以在不同对象间共享

javascript

// 同一个函数可以在不同对象上使用
function introduce() {
  return `Hello, I'm ${this.name}`;
}

var person = { name: 'Alice' };
var company = { name: 'Tech Corp' };

// 显式绑定
console.log(introduce.call(person));  // "Hello, I'm Alice"
console.log(introduce.call(company)); // "Hello, I'm Tech Corp"

如何解决这个问题?

方法1:使用箭头函数(推荐)

箭头函数没有自己的 this,会继承外层作用域的 this

javascript

var obj = {
  name: 'Object',
  outer: function() {
    console.log('outer this:', this.name); // 'Object'
    
    const inner = () => {
      console.log('inner this:', this.name); // 'Object' - 继承 outer 的 this
    };
    
    inner();
  }
};

obj.outer();

方法2:保存 this 引用(传统方式)

javascript

var obj = {
  name: 'Object',
  outer: function() {
    var self = this; // 保存 this 的引用
    console.log('outer this:', this.name); // 'Object'
    
    function inner() {
      console.log('inner self:', self.name); // 'Object' - 使用保存的引用
    }
    
    inner();
  }
};

obj.outer();

方法3:使用 bind()

javascript

var obj = {
  name: 'Object',
  outer: function() {
    console.log('outer this:', this.name); // 'Object'
    
    const inner = function() {
      console.log('inner this:', this.name); // 'Object'
    }.bind(this); // 绑定 outer 的 this
    
    inner();
  }
};

obj.outer();

严格模式的影响

在严格模式下,独立调用的函数中 this 是 undefined,而不是 window

javascript

'use strict';

var name = 'Global';

function showThis() {
  console.log(this); // undefined
  console.log(this?.name); // TypeError: Cannot read property 'name' of undefined
}

showThis(); // 严格模式下,独立调用的 this 是 undefined

总结

场景this 指向原因
func()window独立调用,默认绑定
obj.method()obj方法调用,隐式绑定
嵌套函数 inner()window独立调用,不是方法调用
回调函数window被事件循环独立调用
箭头函数外层 this词法作用域,继承父级

核心要点

  1. 调用方式决定 this,不是定义位置
  2. 独立调用的函数this 指向全局对象
  3. 使用箭头函数保存 this 引用来避免这个问题
  4. 严格模式下独立调用的 this 是 undefined

理解这个机制后,你就能预测和控制 this 的行为了!

JavaScript this 指向练习题

以下是一系列关于 this 指向的练习题,涵盖了各种常见场景。请先尝试自己解答,然后再查看解析。

基础题目

题目 1

javascript

var name = 'Global';

function sayName() {
  console.log(this.name);
}

sayName();

题目 2

javascript

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

obj.sayName();

题目 3

javascript

var name = 'Global';

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

var fn = obj.sayName;
fn();

题目 4

javascript

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

var obj2 = {
  name: 'Object2'
};

obj2.sayName = obj1.sayName;
obj2.sayName();

嵌套函数题目

题目 5

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  outer: function() {
    console.log('outer:', this.name);
    
    function inner() {
      console.log('inner:', this.name);
    }
    
    inner();
  }
};

obj.outer();

题目 6

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  outer: function() {
    console.log('outer:', this.name);
    
    const inner = () => {
      console.log('inner:', this.name);
    };
    
    inner();
  }
};

obj.outer();

回调函数题目

题目 7

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  handleClick: function() {
    setTimeout(function() {
      console.log(this.name);
    }, 100);
  }
};

obj.handleClick();

题目 8

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  handleClick: function() {
    setTimeout(() => {
      console.log(this.name);
    }, 100);
  }
};

obj.handleClick();

构造函数题目

题目 9

javascript

function Person(name) {
  this.name = name;
  console.log(this.name);
}

var person = new Person('Alice');

题目 10

javascript

function Person(name) {
  this.name = name;
  
  setTimeout(function() {
    console.log('Timeout:', this.name);
  }, 100);
}

var person = new Person('Alice');

显式绑定题目

题目 11

javascript

var obj1 = {
  name: 'Object1'
};

var obj2 = {
  name: 'Object2'
};

function sayName() {
  console.log(this.name);
}

sayName.call(obj1);
sayName.call(obj2);

题目 12

javascript

var obj = {
  name: 'Object',
  sayName: function(greeting) {
    console.log(greeting + ', ' + this.name);
  }
};

var boundFn = obj.sayName.bind({ name: 'Bound' });
boundFn('Hello');

综合题目

题目 13

javascript

var name = 'Global';

var obj = {
  name: 'Object',
  method: function() {
    console.log('1:', this.name);
    
    const arrow = () => {
      console.log('2:', this.name);
    };
    
    function regular() {
      console.log('3:', this.name);
    }
    
    arrow();
    regular();
    
    setTimeout(() => {
      console.log('4:', this.name);
    }, 10);
    
    setTimeout(function() {
      console.log('5:', this.name);
    }, 10);
  }
};

obj.method();

题目 14

javascript

var length = 10;

function fn() {
  console.log(this.length);
}

var obj = {
  length: 5,
  method: function(fn) {
    fn();
    arguments[0]();
  }
};

obj.method(fn, 1);

题目 15

javascript

var name = 'Global';

function Person(name) {
  this.name = name;
  
  this.sayName = function() {
    console.log('1:', this.name);
  };
  
  this.sayNameArrow = () => {
    console.log('2:', this.name);
  };
}

Person.prototype.delayedSayName = function() {
  setTimeout(function() {
    console.log('3:', this.name);
  }, 10);
};

Person.prototype.delayedSayNameArrow = function() {
  setTimeout(() => {
    console.log('4:', this.name);
  }, 10);
};

var person = new Person('Alice');
person.sayName();
person.sayNameArrow();
person.delayedSayName();
person.delayedSayNameArrow();

做完再去问ai吧加油!!!!!