理解JavaScript的作用域
栏目: JavaScript · 发布时间: 5年前
内容简介:当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。作用域嵌套的查询规则:这样由多个执行上下文的变量对象构成的链表就叫做作用域链。而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
- 作用域
- 作用域嵌套与作用域链
- 查询异常
- 词法作用域(静态作用域)
- 函数作用域
- 全局作用域
- 块级作用域
- 变量提升机制
- 函数优先
作用域
- 是什么? 作用域是指程序源代码中定义变量的区域。
- 有什么用? 作用域规定了如何查找变量,也就是确定当前执行代码对变量的访问权限。
作用域嵌套与作用域链
当一个块或函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。作用域嵌套的查询规则:
- 首先,JS引擎从当前的执行作用域开始查找变量。
- 然后,如果找不到,引擎会在外层嵌套的作用域中继续查找。
- 最后,直到找到该变量,或抵达最外层的全局作用域为止。
这样由多个执行上下文的变量对象构成的链表就叫做作用域链。
function foo(b) { // 变量a在全局作用域下查询得到的 console.log(a + b) // 4 } var a = 2 foo(2) 复制代码
查询异常
- 如果获取未声明的变量的值会导致 ReferenceError 异常。
-
如果对未声明过的变量进行赋值:
- 在非严格模式下,JS引擎会为其自动创建一个全局变量且进行赋值。
- 如在严格模式下,会导致 ReferenceError 异常。
var a = 10 function sum() { // b 是没有显式声明的,但被隐式创建为全局变量 b = 20 return a + b } console.log(sum()) // 30 console.log(c) // ReferenceError: c is not defined // 因为c是未定义的变量,无法使用 复制代码
词法作用域(静态作用域)
- 词法作用域就是定义在词法阶段的作用域,简单说就是函数的作用域在函数定义的时候就决定了。
- 词法作用域查找规则是:作用域查找是从内到外进行查找的,直到找到第一个匹配的标识符时停止。
而与词法作用域相对的是动态作用域,函数的作用域是在函数调用的时候才决定的。
词法作用域的经典例子
请思考以下2个例子输出的结果
// 例子1 var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f(); } checkscope(); // 例子2 var scope = "global scope"; function checkscope() { var scope = "local scope"; function f() { return scope; } return f; } checkscope()(); 复制代码
上面两段代码的结果都输出: local scope
,因为 JS 采用的是词法作用域,函数的作用域基于函数创建的位置。
欺骗词法作用域
- 欺骗词法作用域意思是(无意地)修改了所在的作用域。
-
欺骗词法作用域的两个方法:
eval()
和with()
。 - 缺点:欺骗词法作用域会导致性能下降。
eval()
函数可以接受一个字符串,并执行其中的的 JS 代码。
function foo(str, a) { eval(str) // 欺骗作用域,引擎在此声明了:var b = 2,所以能计算以下结果 console.log(a + b) // 3 } var b = 1 foo('var b = 2', 1) 复制代码
with()
语句通常被当作重复引用同一个对象中多个属性的快捷方式,可以不需要重复引用对象本身。
function foo(obj) { with (obj) { b = 4 a = 2 } } var obj = { b: 3 } foo(obj) console.log(obj.a) // undefined console.log(obj.b) // 4 console.log(a) // 2,a被当前全局变量泄露到全局作用域上了 复制代码
为什么最后能输出a的值为2?
原因是把 obj
对象传入函数内, obj
对象没有 a
属性,所以 obj.a
的值是 undefined
,却在 with()
语句中的 a
被当作全局变量隐式声明了,而且进行了赋值为2。
函数作用域
函数作用域内的变量或者内部函数,对外都是封闭的,从外层的作用域无法直接访问函数内部的作用域,否则会报引用错误异常。解决方法: 闭包
。
function f1() { var a = 1; var b = 2; var c = 3; } console.log(a, b, c) // ReferenceError: a, b, c is not defined // 原因变量a,b,c是定义在函数内部的变量,外部作用域是无法访问的。 复制代码
全局作用域
最外层的全局作用域,任何地方都可以访问得到。在最外层作用域下使用 var
关键字会定义全局变量,也就是说会挂载在 window
对象上,或者不使用关键字 var、let、const
直接对变量名字进行赋值,JS也会自动为其创建为全局变量。
var a = 10; function f1() { b = 20 function f2() { c = 30 console.log(a) // 10 } f2() } f1() // b和c变量被隐式声明到全局变量了,所以能访问到,这也叫变量提升机制 console.log(b) // 10 console.log(c) // 20 // 但a,b,c也被挂载在window对象(全局作用域)上面了 console.log(window.a) // 10 console.log(window.b) // 20 console.log(window.c) // 30 复制代码
块级作用域
块级作用域指在代码块 {}
里面定义的变量,只会在当前代码块有效,如果外层作用域下想访问该变量,会报引用错误异常。
使用关键字 let
或 const
定义块级作用域的变量。
for (let i = 0; i < 10; i++) { } console.log(i) // ReferenceError: i is not defined // 因为i只能在for循环内部有效,外部作用域是访问不到的。 复制代码
变量提升机制
先声明,后赋值
JS变量的声明和赋值是2个不同的步骤,比如:
a = 10 var a console.log(a) // 10 复制代码
JS引擎会将 var a
和 a = 10
当作两个单独的声明,第一个是编译阶段的任务,而第二个则是执行阶段的任务:
// 编译阶段的任务 var a // 执行阶段的任务 a = 10 console.log(a) // 10 复制代码
变量提升
function getValue(condition) { console.log(value) // 不会报错 if (condition) { var value = "blue"; return value; } else { return null; } } getValue(undefined == null) 复制代码
实际上以上代码的 value 已经被变量提升了:
function getValue(condition) { // 编译阶段的任务 var value; console.log(value) // undefined if (condition) { // 执行阶段的任务 value = "blue"; return value; } else { return null; } } getValue(undefined == null) 复制代码
函数优先
- 函数声明和变量声明都会被提升,但是出现在有多个“重复”声明的代码中,函数会首先被提升,然后才是变量。
- 函数没有方法重载,存在两个相同的函数名,后面的函数会覆盖前面的函数。
foo() // 3 function foo() { console.log(1) } var foo = function () { console.log(2) } function foo() { console.log(3) } 复制代码
为什么呢?原因:
- 函数声明和变量声明,函数优先。
- 相同函数名字,后面函数覆盖前面的函数。
实际上以上代码可以看成这样:
function foo() { console.log(3) } var foo // 被忽略了 foo() console.log(foo) // [Function: foo] 复制代码
参考链接
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 深入理解Javacript, 从作用域与作用域链开始
- 深入理解JavaScript作用域
- 重读《深入理解ES6》 —— 块级作用域
- 理解 C++ 中的头文件和源文件的作用
- 深入理解es6读后总结--块级作用域绑定
- 深入理解CSS background-blend-mode的作用机制
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
JavaScript设计模式
Ross Harmes、Dustin Diaz / 谢廷晟 / 人民邮电出版社 / 2008 / 45.00元
本书共有两部分。第一部分给出了实现具体设计模式所需要的面向对象特性的基础知识,主要包括接口、封装和信息隐藏、继承、单体模式等内容。第二部分则专注于各种具体的设计模式及其在JavaScript语言中的应用,主要介绍了工厂模式、桥接模式、组合模式、门面模式等几种常见的模式。为了让每一章中的示例都尽可能地贴近实际应用,书中同时列举了一些JavaScript 程序员最常见的任务,然后运用设计模式使其解决方......一起来看看 《JavaScript设计模式》 这本书的介绍吧!