慢慢理解this
this 指向 | ()=>{} 继承自父级作用域 | function(){}动态绑定(调用者) |
代码1和代码1对照提现了,谁调用就在谁的作用域里找this指定的值
代码1:
const name = "Global Alice";const obj = {name: "Object Alice",greet: function () {console.log("this.name:", this.name); // 依赖 this 绑定console.log("name:", name); // 依赖作用域链(全局 name)}
};
const greetFunc = obj.greet;
greetFunc();
// 输出:
// this.name: undefined(this 指向全局,但全局 this.name 未定义)
// name: "Global Alice"(作用域链找到全局 name)
代码1对照
const name = "Global Alice";
const obj = {name: "Object Alice",greet: function () {console.log("this.name:", this.name); // 依赖 this 绑定console.log("name:", name); // 依赖作用域链(全局 name)}
};
obj.greet();
// 输出:
// this.name: Object Alice(this 指向 obj)
// name: Global Alice(作用域链找到全局 name)
要理解 “普通函数的 this
在调用时确定,取决于调用方式”,关键看函数被调用的那一刻,是 “谁” 在调用它。这两个示例的核心差异就在于 greet
函数的调用方式不同,导致 this
指向完全不同。
一、正确示例:obj.greet()
—— 函数作为对象方法被调用
const obj = {name: 'Alice',greet: function() {console.log(`Hello, ${this.name}`);}
};obj.greet(); // 输出: Hello, Alice
- 调用方式:
greet
函数作为obj
的属性被调用(格式:对象.函数()
)。 this
指向规则:当函数作为对象的方法被调用时,this
指向该对象(即.
前面的对象)。- 这里
greet
是obj
的方法,调用时obj
是 “调用者”,因此this
指向obj
,自然能访问obj.name
(即Alice
)。
二、错误示例:greetFunc()
—— 函数被单独调用(无调用者)
const obj = {name: 'Alice',greet: function() {console.log(`Hello, ${this.name}`);}
};const greetFunc = obj.greet; // 将函数本身赋值给变量
greetFunc(); // 输出: Hello, undefined
- 调用方式:
greetFunc
是一个独立变量,直接调用(格式:函数()
),没有任何 “调用者对象”。 this
指向规则:当函数独立调用(不依附于任何对象)时,this
指向全局对象(浏览器中是window
,Node 中是global
;严格模式下为undefined
)。- 这里
greetFunc
本质上是greet
函数的 “引用”,调用时没有关联任何对象,因此this
指向全局对象(而全局对象中没有name
属性,所以输出undefined
)。
核心结论:this
指向由 “调用时的方式” 决定
两个示例中的 greet
是同一个函数,但调用方式不同,导致 this
指向完全不同:
- 当函数被对象 “持有” 并通过
对象.函数()
调用时,this
指向该对象。 - 当函数被单独取出(赋值给变量)并直接调用时,
this
指向全局对象(或严格模式下的undefined
)。
这就是 “普通函数的 this
在调用时确定,取决于调用方式” 的本质:this
指向的不是函数定义时所在的对象,而是函数被调用时的 “上下文”(调用者)。
代码2和代码2对照提现了()=>{}的this和谁调用无关,只和父级作用域有关
代码2:
const obj = {name: "Alice",greet: function() {const aaa = () => {console.log(this.name); // 继承 greet 的 this(即 obj)};aaa(); // 直接调用箭头函数}
};obj.greet(); // 输出: "Alice"
代码2对照
const obj = {name: "Alice",greet: function() {const aaa = () => {console.log(this.name); // 继承 greet 的 this(即 obj)};aaa(); // 直接调用箭头函数}
};obj.greet(); // 输出: "Alice"
代码3
const name = "全局名字";
// var name = "全局名字"const obj = {name: "Alice",greet: () => {console.log(this.name);}
};obj.greet();
// 输出 "全局名字"(箭头函数的 this 是定义时的父级 this,即全局作用域)
代码3对照
const name = "全局名字";const obj = {name: "Alice",greet: function () {console.log(this.name);}
};obj.greet();
// 输出 "Alice"(this 指向 obj)
这两段代码的核心区别在于 箭头函数和普通函数对 this
的处理方式不同,导致它们在相同的调用方式下访问不同的 name
属性。以下是详细的作用域分析和对比:
代码 1:箭头函数版本
const name = "全局名字"; // 全局变量(window.name)const obj = {name: "Alice",greet: () => { // 箭头函数console.log(this.name); // 继承全局作用域的 this}
};obj.greet(); // 输出: "全局名字"
作用域分析
全局作用域:
- 定义了变量
name
,值为"全局名字"
。 - 全局作用域的
this
指向window
对象(浏览器环境)。
- 定义了变量
对象
obj
的定义:- 对象字面量
{...}
不创建新的作用域,因此greet
箭头函数的外层作用域是全局作用域。
- 对象字面量
箭头函数的
this
:- 箭头函数的
this
继承自外层作用域(全局作用域),即window
。 this.name
实际上是window.name
,即"全局名字"
。
- 箭头函数的
代码 2:普通函数版本
const name = "全局名字"; // 全局变量(window.name)const obj = {name: "Alice",greet: function() { // 普通函数console.log(this.name); // 动态绑定 this}
};obj.greet(); // 输出: "Alice"
作用域分析
全局作用域:
- 同样定义了全局变量
name
,值为"全局名字"
。
- 同样定义了全局变量
对象
obj
的定义:greet
是一个普通函数,作为obj
的方法存在。
普通函数的
this
:- 普通函数的
this
在调用时动态绑定,取决于调用方式。 - 当
obj.greet()
被调用时,this
指向调用该方法的对象obj
。 this.name
实际上是obj.name
,即"Alice"
。
- 普通函数的
核心区别对比表
特性 | 箭头函数版本 | 普通函数版本 |
---|---|---|
函数类型 | 箭头函数 | 普通函数 |
this 的绑定方式 | 继承自外层作用域(定义时确定) | 动态绑定(调用时确定) |
外层作用域 | 全局作用域 | 全局作用域 |
this 的指向 | 全局对象 window | 调用该方法的对象 obj |
this.name 的值 | window.name ("全局名字") | obj.name ("Alice") |
一句话总结
- 箭头函数的
this
是静态的,继承自定义时的外层作用域(全局作用域),因此访问全局变量。 - 普通函数的
this
是动态的,取决于调用方式(作为obj
的方法调用时指向obj
),因此访问对象自身的属性。