摘要:中作用域的问题可以说是老生常谈,个人认为的作用域中存在着两种作用域,一种是词法作用域,一种是动态作用域。但是自动有了箭头函数后,箭头函数中的并不是动态作用域,而是属于词法作用域,再其定义时就已经确定好了,相当于。
js中作用域的问题可以说是老生常谈,个人认为js的作用域中存在着两种作用域,一种是词法作用域,一种是动态作用域。
词法作用域词法作用域就是定义在词法阶段的作用域,也就是说由我们写代码时将变量写在哪里所决定的,当然在js中大部分是这种情况。
var a = 20; function foo () { console.log(a); } foo(); // 20 function bar () { var a = 30; foo(); // 20 } bar();
这个例子就是一个很好的印证,可以发现的是,无论foo在哪里调用,其a的值永远是全局作用域中的a的值,这就是词法作用域,定义函数时,作用域是在全局,那么foo上层作用域就是全局,改变其调用位置是不能改变其作用域链的,其作用域链是在定义时就决定好的。
利用词法作用域,我们可以引申出闭包的概念,将我们上面的代码改写:
var a = 20; function bar () { var a = 30; return function foo () { console.log(a); } } bar()(); // 30
foo函数在bar函数内定义,所以foo函数的作用域链上层对应的是bar的作用域,利用闭包将foo暴露出去,由于foo函数仍然保持着对bar作用域的引用,所以bar内部的作用域依然存在,没有被回收,这也是闭包能够产生私有变量等效果的原因。
改变作用域对没错,词法作用域可以被改变,通过eval或者with就可以将作用域改变,但是这并不被提倡。
eval改变作用域:
var a = 20; function bar () { eval("var a = 30;"); console.log(a); } bar(); // 30
eval内的代码可以看做,本身就写在了那一行,但是在严格模式下,eval有着自己的作用域,所以严格模式下上面的代码会报出一个ReferenceError。
with改变作用域:
function foo (obj) { with (obj) { b = 2; a = 2; } } var obj = {a: 1}; foo(obj); console.log(obj, b); // { a: 2 } 2
with可以形成一个新的作用域,其词法标识符就是这个对象的属性,所以可以看到的是a = 2,本质上改变的是obj的属性,而b = 2由于这个作用域下没有找到该变量,所以会沿着作用域链向上查找,由于在foo的作用域内也没有找到,一直到全局作用域都没有找到,所以会给全局添加一个属性,这就将b变为了全局变量,所以我们在全局作用域中可以访问到b。但是我们在with中用var定义变量时,会将该变量定义到其外层作用域中。
var obj = {a: 1}; with (obj) { var b = 2; var a = 2; } console.log(obj, b, a); // { a: 2 } 2 undefined
所以我们可以看到的with块内的变量也有着提前声明,但是其提前声明的位置是其上层作用域中。这种行为实在是十分让人理解,将对象放入一个新的作用域,但是同时可以给其上层作用域定义变量。当然在严格模式中,完全不用担心,因为with在严格模式中被禁用。
动态作用域动态作用域取决于其调用方式以及在哪里调用,这个听着有点像this啊,没错,在js中唯一的动态作用域就是this,当然也可以叫其延迟绑定。在谈起this之前,首先要知道的是,执行上下文是什么?
每一种代码的执行都依赖于自身的上下文,函数的每一次调用都会进入函数执行中的一个上下文,并且在函数每次调用时都会产生一个变量对象(虚拟出来的,真实代码中是访问不出来的,会被视作undefined),函数中的每一个变量都可以视为这个变量对象的一个属性,在进入上下文时,首先会对函数的形参进行操作,将形参添加到变量对象中,然后会对函数声明进行操作,假若变量对象中存在同名属性时,同名属性将被覆盖,最后对变量声明进行操作,假若变量对象中存在同名属性时,该变量则会被忽略声明,下面的代码就可以验证我们的这个过程:
function foo (fn) { function fn () {} var fn; console.log(fn); // [Function: fn] } foo();
我们的this和执行上下文没有关系,但是和我们的变量对象有着很大的关系。
在非严格模式下,this指向null或者undefined时会指向全局对象。
以这个准则来看我们《JavaScript语言精粹》中提到的函数的四种调用方式与this取值的关系:
1.方法调用模式
var obj = { a: 2, foo: function () { console.log(this.a); } }; obj.foo();
这种情况下,this指向的就是该对象。
2.函数调用模式
var a = 2; function foo () { console.log(this.a); } foo(); // 浏览器环境中:2 // 严格模式下 var a = 2; function foo () { "use strict"; console.log(this.a); // TypeError } foo();
这种情况下this应该指向的那个我们上文所提到的那个虚拟出来的变量对象,由于其本来并不存在,所以this指向的undefined,在非严格模式下指向的是全局window,但是在严格模式下,禁止了这种隐式的转换。函数调用模式下,this其实可以理解为指向我们虚拟出来的那个变量对象。
3.构造器调用模式
也就是该函数被当做构造函数来调用,这是首先会在函数内部创建一个空对象,然后在将this指向这个空对象,最后将这个对象返回出去。
4.call与apply调用
这时this指向的是其第一个参数。
但是自动有了箭头函数后,箭头函数中的this并不是动态作用域,而是属于词法作用域,再其定义时就已经确定好了,相当于function () {}.bind(this)。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/79871.html
摘要:也毫不例外,但在中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核心知识点。主要使用的是函数作用域。 关于作用域:About Scope 作用域是程序设计里的基础特性,是作用域使得程序运行时可以使用变量存储值、记录和改变程序的状态。JavaScript 也毫不例外,但在 JavaScript 中作用域的特性与其他高级语言稍有不同,这是很多学习者久久难以理清的一个核...
摘要:作用域分类作用域共有两种主要的工作模型。换句话说,作用域链是基于调用栈的,而不是代码中的作用域嵌套。词法作用域词法作用域中,又可分为全局作用域,函数作用域和块级作用域。 一篇巩固基础的文章,也可能是一系列的文章,梳理知识的遗漏点,同时也探究很多理所当然的事情背后的原理。 为什么探究基础?因为你不去面试你就不知道基础有多重要,或者是说当你的工作经历没有亮点的时候,基础就是检验你好坏的一项...
摘要:图片中的作用域链,是全局执行环境中的作用域链。然后此活动对象被推入作用域链的最前端。在最后调用的时候,创建先构建作用域链,再创建执行环境,再创建执行环境的时候发现了一个变量标识符。 从图书馆翻过各种JS的书之后,对作用域/执行环境/闭包这些概念有了一个比较清晰的认识。 栗子说明一切 第一个栗子 来看一个来自ECMA-262的栗子: var x = 10; (function foo(...
摘要:在中的应用采用词法作用域,也就是静态作用域。那什么又是词法作用域或者静态作用域呢请继续往下看静态作用域与动态作用域因为采用的是词法作用域函数的作用域在函数定义的时候就决定了。 开篇 当我们在开始学习任何一门语言的时候,都会接触到变量的概念,变量的出现其实是为了解决一个问题,为的是存储某些值,进而,存储某些值的目的是为了在之后对这个值进行访问或者修改,正是这种存储和访问变量的能力将状态给...
摘要:作用域作用域是指程序源代码中定义变量的区域。采用词法作用域,也就是静态作用域。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。前面我们已经说了,采用的是静态作用域,所以这个例子的结果是。 JavaScript深入系列的第二篇,JavaScript采用词法作用域,什么语言采用了动态作用域?两者的区别又是什么?还有一个略难的思考题,快来看看吧。 作用域 作用域是指...
阅读 3916·2021-09-09 09:33
阅读 1772·2021-09-06 15:14
阅读 1918·2019-08-30 15:44
阅读 3070·2019-08-29 18:36
阅读 3764·2019-08-29 16:22
阅读 2094·2019-08-29 16:21
阅读 2529·2019-08-29 15:42
阅读 1644·2019-08-29 11:00