你不知道的JS之作用域和闭包 附录
原文:你不知道的js系列
A 动态作用域
动态作用域 是和 JavaScript中的词法作用域 对立的概念。
动态作用域和 JavaScript 中的另外一个机制 (this)很相似。
词法作用域是在代码编写时就定义好了的(假设没有使用 eval() 或者 with 欺骗词法作用域)
动态作用域也就意味着在运行时才能动态确定。
function foo() {
console.log( a ); //
}
function bar() {
var a = 3;
foo();
}
var a = 2;
bar();
词法作用域会对 foo() 中的 a 进行 RHS 引用查询,最后会得到全局变量 a 的值。
而动态作用域相反,它不关心函数和代码块声明的位置和方式,只关心他们在哪儿被调用。换句话说,作用域链是基于调用栈,而不是代码的嵌套。
所以,如果 JavaScript 有动态作用域,那么当 foo() 执行的时候,理论上输出的结果会是 3。
因为这时候,foo() 会从调用它的那个栈向上去查找 a ,就找到 bar() 内部的值为 3 的那个变量 a 了。
JavaScript 事实上不存在动态作用域,虽然 this 机制看起来想动态作用域。
词法作用域是代码编写时确定的,动态作用域(和 this)是在运行时确定的。
词法作用域关心的函数声明的位置,动态作用域关心的是函数被调用的位置。
最后:this 关心是一个函数调用的方式,关于这部分要去看 “this 和 对象原型” 的内容
B 块作用域的 Polyfill
在第(三)节中介绍了块作用域。在 ES3 的时候就已经存在以 with 和 catch 语句创建的块作用域了。
但是 ES6 引入了 let 之后才是对块作用域的完整支持,但是如果想在 ES6 之前的环境中使用块作用域应该怎么做?
比如下面的代码:
{
let a = 2;
console.log( a ); //
}
console.log( a ); // ReferenceError
在 ES6 之前的环境中可以这么做:
try{throw 2}catch(a){
console.log( a ); //
}
console.log( a ); // ReferenceError
但是这样的代码既难看又奇怪。
其实,你可以使用一些工具将 ES6+ 的代码转译成可以在老版本的环境中运行的代码。
Tracer
Google 有一个叫做 Tracuer 的项目,它就是普遍用来转译 ES6 代码的工具。TC39 (ECMA 的 Technical Committee 39)依赖这个工具测试 JavaScript 的新特性。
它可以把上面的代码转成如下的代码:
{
try {
throw undefined;
} catch (a) {
a = 2;
console.log( a );
}
}
console.log( a );
隐式 vs. 显示的代码块
下面是另外一个 let 的形式,叫做 let 语句或者 let 块(相对于 let 声明):
let (a = 2) {
console.log( a ); //
}
console.log( a ); // ReferenceError
这个 let 语句显式地创建了一个作用域绑定。这不仅是让块看起来更明显,在代码重构方面鲁棒性也更强,它把所以声明强制放在块的顶部,更容易分清块作用域包含了哪些内容。
与它对应的模式是,很多开发者在函数作用域的时候,也会将他们的 var 声明提前到函数的顶部。
这种 let 语句刻意将声明提前,如果你没有在代码中滥用 let 声明的话,你的块作用域声明会更容易识别和维护。
但是,ES6 中还没有支持 let 语句。官方的 Traceur 也不接受这种方式。
所以我们可以在代码中加一点注释:
/*let*/ { let a = 2;
console.log( a );
}
console.log( a ); // ReferenceError
性能
为什么不使用 IIFE 创建作用域呢?
首先,try/catch 的性能是比较低的,但它没有原因必须这么低。TC39 官方支持的 ES6 转译工具也使用 try/catch,所以 Traceur 团队已经让 Chrome 提升了 try/catch 的性能。
第二,IIFE 和 try/catch 没有可比性。将任何一段代码包装在一个函数中都可能会改变这段代码里面 return,this,break 以及 continue的含义。所以 IIFE 只适用于特定场景。
问题变了:你是否真的想要块作用域,如果需要,这些工具可以帮你实现。如果不需要,继续使用 var 写代码也没事的!
C 词法-this
ES6 添加了一个特殊的语法——箭头函数:
var foo = a => {
console.log( a );
};
foo( 2 ); //
这个箭头经常被当作关键字 function 的简写形式。
但箭头函数不只是用来减少敲击键盘的次数的
下面的代码就有一个问题
var obj = {
id: "awesome",
cool: function coolFn() {
console.log( this.id );
}
};
var id = "not awesome";
obj.cool(); // awesome
setTimeout( obj.cool, 100 ); // not awesome
在 setTimeout 中的 cool() 回调的 this 绑定丢了,通常的解决办法是在内部声明 var self = this;,如下:
var obj = {
count: 0,
cool: function coolFn() {
var self = this;
if (self.count < 1) {
setTimeout( function timer(){
self.count++;
console.log( "awesome?" );
}, 100 );
}
}
};
obj.cool(); // awesome?
这只是把整个问题从 理解和正确使用 this 绑定 回退到我们更使用起来更舒服的词法作用域上,self 成了一个标识符,不用关心 this 绑定发生了什么。
大家都不喜欢写冗余的东西,尤其是一遍又一遍重复这些东西。所以 ES6 出现了一个解决办法,箭头函数引入了一个现象叫做 “词法 this”
var obj = {
count: 0,
cool: function coolFn() {
if (this.count < 1) {
setTimeout( () => { // arrow-function ftw?
this.count++;
console.log( "awesome?" );
}, 100 );
}
}
};
obj.cool(); // awesome?
简单的解释就是箭头函数不像普通函数的 this 绑定一样,他们会将 this 的值绑定在当前的词法作用域中。
在这段代码中,this 的绑定 “继承” 了函数 coo() 绑定的 this。
箭头函数实际上只是将开发者普遍存在的错误写进了语法,因为他们将 this 绑定规则 和词法作用域混为一谈。
注:另一个箭头函数非议是他们都是匿名的,第(三)节介绍了为什么匿名函数不如命名的函数。
解决这个问题的另一个办法就是,正确使用 this绑定:
var obj = {
count: 0,
cool: function coolFn() {
if (this.count < 1) {
setTimeout( function timer(){
this.count++; // `this` is safe because of `bind(..)`
console.log( "more awesome" );
}.bind( this ), 100 ); // look, `bind()`!
}
}
};
obj.cool(); // more awesome
如果你完全理解了词法作用域和闭包,那么理解 词法this 的用法是轻而易举!小菜一碟
你不知道的JS之作用域和闭包 附录的更多相关文章
- 你不知道的JS之作用域和闭包(五)作用域闭包
原文:你不知道的js系列 一个简单粗暴的定义 闭包就是即使一个函数在它所在的词法作用域外部被执行,这个函数依然可以访问这个作用域. 比如: function foo() { var a = 2; fu ...
- 你不知道的JS之作用域和闭包(三)函数 vs. 块级作用域
原文:你不知道的js系列 在第(二)节中提到的,标识符在作用域中声明,这些作用域就像是一个容器,一个嵌套一个,这个嵌套关系是在代码编写时定义的. 那么到底是什么产生了一个新的作用域,只有函数能做到 ...
- 你不知道的JS之作用域和闭包(四)(声明)提升
原文:你不知道的js系列 先有鸡还是先有蛋? 如下代码: a = 2; var a; console.log( a ); 很多开发者可能会认为结果会输出 undefined,因为 var a 在 a ...
- 你不知道的JS之作用域和闭包(二)词法作用域
原文:你不知道的js系列 词法作用域(Lexical Scope) Lex time 一个标准的编译器的第一个阶段就是分词(token化) 词法作用域就是在词法分析时定义的作用域.换句话说,词法作用域 ...
- 你不知道的JS之作用域和闭包(一)什么是作用域?
原文:你不知道的js系列 什么是作用域(Scope)? 作用域 是这样一组规则——它定义了如何存放变量,以及程序如何找到之前定义的变量. 编译器原理 JavaScript 通常被归类为动态语言或者解释 ...
- JS之作用域与闭包
JS之作用域与闭包 作用域在JS中同样也是一个重要的概念.它不复杂,因为ES5中只有全局作用域和函数作用域,我们都知道他没有块级作用域.但在ES6中多了一个let,他可以保证外层块不受内层块的影响 ...
- 解析js中作用域、闭包——从一道经典的面试题开始
如何理解js中的作用域,闭包,私有变量,this对象概念呢? 就从一道经典的面试题开始吧! 题目:创建10个<a>标签,点击时候弹出相应的序号 先思考一下,再打开看看 //先思考一下你会怎 ...
- 你不知道的JavaScript(作用域和闭包)
作用域和闭包 ・作用域 引擎:从头到尾负责整个JavaScript的编译及执行过程. 编译器:负责语法分析及代码生成等. 作用域:负责收集并维护由所有声明的标识符(变量)组成的一系列查询,并实施一套非 ...
- JS的作用域和闭包
1.作用域 作用域是根据名称找变量的一套规则. 变量的赋值操作会执行两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就会对它 ...
随机推荐
- Java基础 -- String,StringBuilder,StringBuffer三者的区别
结论 1-String,StringBuilder,StringBuffer 之间的区别主要是在两个方面,即运行速度和线程安全这两方面: 首先说运行速度,或者说是执行速度,在这方面运行速度快慢为:St ...
- javascript闭包学习
(function(){})()===>>>>函数会被立即执行function(){}是一个函数用括号包起来表示是函数表达式再加()表示函数自执行 如何理解闭包?1.定义和用 ...
- java请求URL带参之防XSS攻击
1.web.xml新增filter配置 <!-- URL请求参数字符过滤或合法性校验 --> <filter> <filter-name>XssFilter< ...
- linux线程 join/detach
linux中创建线程时,可以通过 __attr 指定线程的属性 extern int pthread_create (pthread_t *__thread, __const pthread_attr ...
- 主席树+树链剖分——南昌邀请赛Distance on the tree
学了差不多一星期的主席树+树链剖分,再来看这题发现其实是个板子题 一开始想复杂了,以为要用类似求树上第k大的树上差分思想来解决这道题,但其实树链上<=k的元素个数其实直接可以用树链剖分来求 具体 ...
- 网络流24题——魔术球问题 luogu 2765
题目描述:这里 这道题是网络流问题中第一个难点,也是一个很重要的问题 如果直接建图感觉无从下手,因为如果不知道放几个球我就无法得知该如何建图(这是很显然的,比如我知道 $1+48=49=7^2$ ,可 ...
- 学习笔记_J2EE_Spring(一)_入门
3. Spring概述 3.1. Spring是什么 Spring是一个优秀的高可用的JavaEE轻量级开发框架.提供一站式开发解决方案. 3.2. Spring框架出现的背景 在世 ...
- Dubbo序列化多个CopyOnWriteArrayList对象变成同一对象的一个大坑!!
环境: win10 + jdk 1.8 + dubbo 2.5.10 问题描述: 当一个对象(此对象内包含多个CopyOnWriteArrayList对象) 作为参数调用RPC接口后, 服务提供者拿到 ...
- docker 从入门到精通
转载请注明出处!!!! 1.Docker 基本指令 下载镜像 docker pull 镜像名称:版本 查看已有镜像 docker images 查看已有容器 docker ps 启动docker do ...
- Anki 2.0 用户手册
Anki 2.0 用户手册 目录 Anki 2.0 用户手册 视频介绍 卡片和模板 Javascript Anki 2.0 用户手册 视频介绍 共享牌组和基础复习技巧 调换卡片正反面 卡片样式设计 亲 ...