js中普通函数和箭头函数this指向
以下是js中常见this指向问题
函数中this指向
var name = '张三' function a(){ var name = '李四'; console.log(this.name); } a();//张三 const b = ()=>{ var name = '王五'; console.log(this.name); } b();//张三
两个函数最中输出都是张三,意味着它们的this均指向window。
普通函数是谁调用this指向谁,a()调用明显是被window调用,所以指向window。
箭头函数则是执行上下文是父级的执行上下文,此时b()的父级也是window,而window的上下文也是window,所以依旧指向window。
对象中this指向
var name = '张三' var obj = { name:'李四', fn:function(){ console.log(this.name) }, fn1:()=>{ console.log(this.name) } } obj.fn();//李四 obj.fn1();//张三
同理
普通函数是谁调用this指向谁,obj.fn()调用是被obj调用,所以指向obj。
箭头函数则是执行上下文是父级的执行上下文,此时obj.fn1()的父级是obj,而obj的上下文也是window,所以依旧指向window。
定时器中this指向
var name = '张三'; function a() { var name = '李四'; console.log(this.name); } var b =()=>{ var name = '李四'; console.log(this.name); } var obj = { name:'李四', fn:function(){ console.log(this.name); }, fn1:()=>{ console.log(this.name); } } setTimeout(a,1000);//张三 setTimeout(b,1000);//张三 setTimeout(obj.fn,1000);//张三 setTimeout(obj.fn1,1000);//张三
定时器的调用最终都是window调用,所以this均指向window,加了call绑定this之后输出结果如上,普通函数将this指向obj后,输出结果变成了李四,此时this指向obj,而箭头函数将this通过call指向obj后,因为没有其他函数包裹,this还是指向window。要想this指向
obj,可以进行如下改造
var obj = { name:'李四', fn:function(){ console.log(this.name); }, fn1:()=>{ console.log(this.name); }, fn2:function(){ return ()=>{ console.log(this.name); } } } const c = obj.fn2() setTimeout(c,1000);//李四
将箭头函数作为返回值,此时箭头函数中的this指向即是外层普通函数的this指向。
事件绑定的this指向
1. 在 element 上绑定事件
此时 this 均指向 window
<button onclick="a()">点击</button> <button onclick="b()">点击</button> <script>function a() { console.log(this);//window } var b =()=>{ console.log(this);//window } </script>
2. js 使用 addEventListener 绑定事件
此时的普通函数 this 指向该元素,箭头函数this指向window
<button id="btn1">点击</button> <button id="btn2">点击</button> <script> function a() { console.log(this);//<button id="btn1">点击</button> } var b =()=>{ console.log(this);//window } document.getElementById('btn1').addEventListener('click',a); document.getElementById('btn2').addEventListener('click',b);
区别
借用MDN的话:
箭头函数表达式的语法比函数表达式更简洁,并且没有自己的this
,arguments
,super
或new.target
等,也不能用作构造函数。
箭头函数表达式更适用于那些本来需要匿名函数的地方。
执行上下文
也称执行环境,当JavaScript解释器初始化执行代码时,它首先默认进入全局执行环境;从此刻开始,函数的每次调用都会创建一个新的执行环境;每个函数都有自己的执行环境,当执行流进入一个函数时,函数的环境就会被推入一个环境栈中(execution stack);在函数执行完后栈将其环境弹出,保存在其中的所有变量和函数定义也随之销毁(全局执行环境直到应用程序退出也随之销毁,把控制权返回给之前的执行环境;ECMAScript程序中的执行流正是由这个便利的机制控制着;执行环境可以分为创建和执行两个阶段;在创建阶段,解析器首先会创建一个变量对象(variable object,也称为活动对象activation object),它由定义在执行环境中的变量/函数声明/参数组成;在这个阶段,作用域链会被初始化,this的值也会被最终确定;在执行阶段,代码被解释执行
function Fn1(){ function Fn2(){ alert(document.body.tagName);//BODY //other code... } Fn2(); } Fn1();
执行环境栈:
活动对象(activation object)
如果所在环境是函数,那么就会把这个函数的活动对象作为变量对象(在函数中,变量对象==活动对象)。它一开始只包含arguments对象。一般而言,函数执行过程,可以分成两步:
1.进入执行环境;2.执行代码。
比如,下面这个test函数:
function test(a, b) { var c = 10; function d() {} var e = function _e() {}; (function f() {}); g = 10; } test(10);
在执行test(10)的时候,分成了两步:
1. 进入执行环境。此时,会用arguments对象初始化活动对象(AO, activation object)。并且,会把形参、var声明的变量和函数声明放入活动对象AO中。
AO: { arguments: { callee: test, length: 1, 0: 10 }, a: 10, b: undefined, c: undefined, d: <reference to FunctionDeclaration "d">, e: undefined }
2. 执行代码。AO会变成:
AO: { arguments: { callee: test, length: 1, 0: 10 }, a: 10, b: undefined, c: 10, d: <reference to FunctionDeclaration "d">, e: <reference to FunctionDeclaration "_e"> }
作用域链
作用域链与一个执行上下文相关,是内部上下文所有变量对象(包括父变量对象)的列表,用于变量查询。
作用域链的用途,是保证对执行环境有权访问的所有变量和函数的有序访问。
全局执行环境的变量对象始终都是作用域链中的最后一个对象。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通