词法作用域
词法作用域
作用域共有两种主要的工作模式,第一种最为普遍,被大多数编语言所采用的词法作用域,另一种叫做动态作用域,仍有一些编程语言在使用(Bash
脚本、Perl
中的一些模式等)
大多数标准语言编译器的第一个工作阶段叫作词法化(也叫单词化),词法化的过程会对源代码中的字符进行检查。词法作用域就是定义在词法阶段的作用域,由 变量和作用域的位置 来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况是这样)
// 三层嵌套的作用域
// 作用域气泡由其对应的作用域代码块写在哪里决定的,是逐级包含的
function foo(a) {
var b = a * 2;
function bar(c) {
console.log(a, b, c);
}
bar(b * 3)
}
foo(2)
查找
作用域气泡的结构和互相之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置。比如bar
函数里做了三个RHS
查询,首先会在 bar 作用域气泡里去查找,如果没有会向上级作用域foo
作用域气泡去查找,当查找到b
在foo
的作用域里,则会停止查找。多层嵌套作用域可以定义同名的标识符,这叫作“遮蔽效应”(内部的标识符遮蔽了外部的标识符)
全局变量
全局变量会自动成为全局对象window
的属性,因此,可以不直接通过全局对象的词法名称,而是间接的通过对全局对象属性的引用来间接访问。
通过这种技术可以访问那些被同名变量所遮蔽的全局变量,但非全局的变量如果被遮蔽了,无论如何都无法被访问到了。
无论函数在哪里被调用,且无论它如何被调用,它的词法作用域都只有函数被声明所处的位置决定。
注:词法作用域只会查找一级标识符,比如a
。如果代码中引用了foo.bar.baz
,作用域只会试图查找到 foo的作用域,然后通过属性访问规则,去对bar
和baz
进行属性访问。
欺骗词法
如果说词法作用域完全由写代码期间函数所声明的位置来定义,怎样才能在运行时来“修改”词法作用域?
JavaScript
提供了两种机制,一种时eval
还有一种是with
社区普遍认为在代码中使用这两种机制不是什么好主意,最容易被人们忽略掉的点是:欺骗词法作用域会导致性能下降
在详细解释性能问题之前,首先先看一下这两种机制是什么原理。
例外
eval
JavaScript
中的eval(…)
可以接受一个字符串作为参数,并将其中的内容视为好像就在书写时就存在于这个程序中这个位置的代码。
function foo(str, a) {
eval(str);
console.log(a, b); // 1, 3
}
var b = 2;
foo("var b = 3;", 1);
eval()
调用的var b = 3
,这段代码会被当做本来就在那里,由于这段代码声明了一个新的变量b
,因此它对已经存在的 foo
的词法作用域进行了修改,遮蔽了外部全局作用域中的同名变量
但是在 严格模式 中,eval()
在运行时有自己的词法作用域,意味着其中的声明无法修改所在的作用域
with
with 通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象本身
var obj = {
a: 1,
b: 2,
c: 3
}
obj.a = 2;
obj.b = 3;
obj.c = 4;
with (obj) {
a = 2,
b = 3,
c = 4
}
但实际上这不仅仅是为了 方便的访问代码
function foo(obj) {
with(obj) {
a: 2
}
}
var obj1 = {
a: 3
}
var obj2 = {
b: 3
}
foo(obj1);
console.log(obj1.a); // 2
foo(obj2);
console.log(obj2.a); // undefined
console.log(a); // 2
性能
不推荐使用with
和eval
的原因是因为会被严格模式所影响。
其次,JavaScript
引擎在编译阶段会进行数项性能优化,其中有些优化依赖于能够依据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行过程中快速找到标识符。但,如果引擎在代码中发现eval
和with
,那就毫无性能可言了
- 排序含有数字的字符串:一个巧妙地方法
- wpf 控件大小随窗体大小改变而改变
- 零基础学编程036:快速编写一个GUI程序
- WPF TreeView 选择事件执行两次,获取TreeView的父节点的解决方法
- 零基础学编程041:欧拉公式的几何意义
- 零基础学编程040:在Windows上安装Python库的正确姿势
- c++/c 获取cpp文件行号跟文件名
- 零基础学编程042:画函数图像
- C-SATS工程副总裁教你如何用TensorFlow分类图像 part2
- C++11 Lambda表达汇总总结
- TensorFlow开发环境搭建(Ubuntu16.04+GPU+TensorFlow源码编译)
- C++虚析构函数解析
- C-SATS工程副总裁教你如何用TensorFlow分类图像 part1
- 帝国cms文章页调用当前文章URL如何操作?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- DiDi Kafka-Manager安装和简单使用
- Docker数据管理与挂载管理
- Docker Dockerfile 指令详解与实战案例
- flume kafka和sparkstreaming整合
- Docker如何搭建私有registry镜像仓库
- Harbor介绍与企业级私有Docker镜像仓库搭建
- 如何查看docker run启动参数命令
- YAML 语言教程与使用案例
- 计算等压面要素场的基本检验指标
- Kubernetes K8S之SSL证书有效期修改
- Kubernetes K8S之通过yaml文件创建Pod与Pod常用字段详解
- Kubernetes K8S之kubectl命令详解及常用示例
- Kubernetes K8S之Pod 生命周期与init container初始化容器详解
- Kubernetes K8S之Pod生命周期与探针检测
- Kubernetes K8S之Pod 生命周期与postStart、preStop事件