本文将带你彻底搞懂JavaScript中的函数声明特性和this指向问题,从基础概念到实际应用,让你在面试和开发中游刃有余!
🎯 前言
在JavaScript的世界里,this和函数声明一直是让开发者头疼的问题。你是否遇到过以下困惑:
- 为什么函数内部不能修改函数名?
var声明的变量为什么会挂载到window上?this的指向到底由什么决定?
今天我们就来一次性解决这些问题!
🔍 函数声明的奇特现象
让我们先看一个有趣的例子:
"use strict"; // 启用严格模式
var b = 10;
(function b(){
b = 20; // 这行代码不会生效!
console.log(b); // 非严格模式输出的是函数本身,而不是20 严格模式抛出异常
})()
为什么会这样?
这涉及到JavaScript函数声明的一个重要特性:函数体内的函数名是只读的。
- 返回结果是函数本身
- 在严格模式下,尝试修改会报错:
Assignment to constant variable - 函数在其内部作用域中具有更高的优先级
- 虽然JavaScript是弱类型语言,可以随意修改变量,但函数名在函数体内部是只读的
这个设计保证了函数的完整性和安全性,避免了意外的函数覆盖。
🌍 不同运行环境下的全局对象
JavaScript可以在多种环境中运行,每种环境的全局对象都不同:
浏览器环境
<!DOCTYPE html>
<html>
<body>
<script>
"use strict"
var a = 1;
console.log(window.a); // 1
function f() {
console.log('haha');
}
console.log(window.f); // function f(){...}
let aa = 1;
const bb = 2;
// aa和bb不会挂载到window上
</script>
</body>
</html>
Node.js环境
var a = 1;
console.log(window.a); // ReferenceError: window is not defined
console.log(global.a); // 1 - Node.js中的顶层对象是global
ES5 vs ES6的差异
ES5时代:
var和function声明的全局变量会成为顶层对象的属性- 全局变量和顶层对象的属性是等价的(这是当初的设计决定)
ES6时代:
let和const声明的全局变量不会污染顶层对象- 这些变量存在于一个独立的块级作用域(Script作用域)中
- 避免了对全局对象的污染
🎪 this指向的终极解析
this是JavaScript中最容易混淆的概念之一。让我们通过实例来彻底理解它:
核心概念
this是函数执行时立即生成的对象,由调用方式决定,指向最后的调用者
1. 普通函数调用
var name = "老帅哥";
function func() {
console.log(this); // Window对象(非严格模式)
console.log(this.name); // "老帅哥"
}
func(); // 普通函数调用,this指向window
在严格模式下:
"use strict";
function func() {
console.log(this); // undefined
}
func();
2. 对象方法调用
var name = "windowsName";
var a = {
name: '贝利亚',
fn: function(){
console.log(this); // 对象a
console.log(this.name); // "贝利亚"
}
}
a.fn(); // 对象方法调用,this指向调用者对象a
// 但是要注意这种情况:
var b = a.fn;
b(); // 普通函数调用,this指向window,输出"windowsName"
3. 构造函数调用
function Person(name, age){
this.name = name; // this指向新创建的实例
this.age = age;
}
const p = new Person("labubu", 2);
console.log(p.name); // "labubu"
4. 事件处理函数
// HTML: <input type="text" id="ipt"/>
document.getElementById('ipt').addEventListener('keydown', function(){
console.log(this); // input元素
console.log(this.value); // 输入框的值
});
5. 箭头函数中的this
箭头函数是ES6的亮点,它的this行为完全不同:
var name = "windowsName";
var a = {
name: "Tom",
func1: function(){
console.log(this.name); // "Tom"
},
fun2: function(){
// 普通函数,this指向对象a
setTimeout(() => {
// 箭头函数没有自己的this,继承外层作用域的this
this.func1(); // 调用成功,输出"Tom"
}, 1000);
}
}
a.fun2();
箭头函数的特点:
- 本身没有
this this来自作用域链中的上一层- 无法通过
call、apply、bind改变this指向
📊 this指向规则总结
| 调用方式 | this指向 | 示例 |
|---|---|---|
| 普通函数调用 | window(非严格)/undefined(严格) | func() |
| 对象方法调用 | 调用者对象 | obj.method() |
| 构造函数调用 | 新创建的实例 | new Constructor() |
| 事件处理函数 | 事件源元素 | element.onclick |
| 箭头函数 | 继承外层作用域 | () => {} |
🚀 实战技巧
1. 确保this指向的方法
// 方法1:使用bind
var obj = {
name: 'test',
method: function() {
console.log(this.name);
}
};
var fn = obj.method.bind(obj);
fn(); // "test"
// 方法2:使用箭头函数
var obj = {
name: 'test',
method: function() {
setTimeout(() => {
console.log(this.name); // "test"
}, 100);
}
};
2. 避免常见陷阱
// 陷阱:方法赋值后调用
var obj = {
name: 'obj',
getName: function() {
return this.name;
}
};
var getName = obj.getName;
console.log(getName()); // undefined (在严格模式下会报错)
// 解决方案
var getName = obj.getName.bind(obj);
console.log(getName()); // "obj"
🎯 总结
- 函数声明:函数名在函数体内是只读的,这是JavaScript的安全机制
- 全局变量:ES5的
var会污染全局对象,ES6的let/const不会 - this指向:由函数的调用方式决定,而不是定义方式
- 箭头函数:没有自己的this,继承外层作用域
掌握这些概念,你就能在JavaScript的世界中游刃有余了!记住,理论结合实践才是王道,多写多练才能真正掌握这些知识点。