摘要:本文为你不知道的上卷中关于作用域相关的知识点的总结。第一层代表当前作用域,大楼的顶层代表全局作用域。如果一定要找一个点与动态词法作用域扯上关系的话,那就是值了。
作用域 赋值操作本文为《你不知道的JavaScript(上卷)》中关于作用域相关的知识点的总结。
LHS以及RHS变量的赋值操作实际上有两个动作,首先编译器会在当前作用域中声明一个变量(如果之前没有声明过),然后在运行时引擎会在作用域中查找该变量,如果能够找到就对它进行赋值。
在运行时引擎会在作用域中查找该变量
引擎对变量所做的查找分为LHS查询以及RHS查询,L和R分别代表一个赋值操作的左侧以及右侧。
讲的稍微精确一点:RHS查询与简单地查找某个变量的值别无二致,而LHS则是试图查找到变量的容器本身,从而对其进行赋值。
RHS可以理解成retrieve his source value(取到其源值),这意味着“得到某某的源值”。
深入一点:
console.log(a);
上诉代码对于a的引用就是一个RHS引用,即查找然后取到a的值。
相比之下,
a = 2;
这里的a就是一个RHS引用。
我们可以简单的记忆:
当变量出现在赋值操作的左侧时进行LHS查询,出现在赋值操作的右侧时进行RHS查询.
注意:作用域查找会在找到第一个匹配的标识符时停止
作用域嵌套作用域是根据名称查找变量的一套规则
作用域嵌套的定义如下:
当一个块或者函数嵌套在另一个块或函数中时,就发生了作用域的嵌套。
理解作用域嵌套这一机制,我们就可以理解变量查找的顺序:
在当前作用域查找变量。如果没有,则进行下一步
判断是否是全局作用域。如果是,则停止查找过程;如果不是,则进行下一步
进入当前作用域的外层作用域,并进行第一步
形象一点,我们可以把作用域查找想象成在大楼中找人。
第一层代表当前作用域,大楼的顶层代表全局作用域。
首先在当前楼层查找,如果没有找到,则上一楼进行查找,一直到找到这个人或者找完整个大楼依然没有找到为止。
异常报错的种类如果能将LHS以及RHS进行很好的区分,那我们就能够很好的理解浏览器所抛出的各种异常。
下举几种特别常见的报错:
ReferenceError:
RHS查询变量未找到值
严格模式LHS查询失败
TypeError:
RHS找到该变量值,但尝试对这个变量的值进行不合理的操作(例如,引用null或者undefined类型的值中的属性)
词法作用域词法作用域完全由写代码期间函数所声明的位置来定义
欺骗词法作用域注意:欺骗词法作用域会导致性能下降
evalwitheval() 是一个危险的函数, 他执行的代码拥有着执行者的权利。如果你运行eval()伴随着字符串,那么你的代码可能被恶意方(不怀好意的人)影响, 通过在使用方的机器上使用恶意代码,可能让你失去在网页或者扩展程序上的权限。更重要的是,第三方代码可以看到作用域在某一个eval()被调用的时候,这有可能导致一些不同方式的攻击。相似的Function就是不容易被攻击的。
根据你所传递给它的对象凭空创建了一个全新的词法作用域
性能问题欺骗词法作用域会导致性能下降,其原因在于编译阶段的性能优化不起作用。
JavaScript引擎会在即时编译阶段(during the compilation phase)进行数项的性能优化。其中的某些优化依赖于能够根据代码的词法进行静态分析,并预先确定所有变量和函数的定义位置,才能在执行的过程中快速找到标识符。
但是,编译到含有eval和with的代码时,编译器无法知道eval或者with会接受什么代码,自然无法做代码优化。
函数作用域以及块作用域隐藏组件内部实现函数作用域:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
开发者最主要是利用函数作用域实现隐藏组件或者API的内部实现,最小限度的暴露必要内容。
比如对于一些组件的开发,大家习惯于利用立即执行函数(function() {})()进行内部实现的封装。
规避冲突利用函数作用域将变量保持在私有、无冲突的作用域中,这样可以有效规避掉所有的冲突。
举个例子,underscore这个库里面有跟原生js一样的方法map,那怎么区分这两个方法呢?通过将map当做一个属性挂载在underscore上面,这样可以避免两者的冲突。
立即执行函数表达式形式如下:
(function() {...})()
(function() {...})()
上面两种形式没有区别,可依个人兴趣随意使用。
立即执行函数表达式的一种进阶用法就是把它们当做函数调用并传递参数进去。
各种类库常见的用法是:
(function(global) { ... })(window)块作用域
块作用域目前在ES6中有如下体现:
let
const
with:用with从对象创建出的作用域仅在with声明而非外部作用域中有效。
try/catch:catch分句会创建一个块作用域,其中声明的变量仅在catch内部有效。
例如:
for (let i; i < 4; i ++) { ... } console.log(i) // Uncaught ReferenceError: i is not defined
try { undefined(); } catch (err) { console.log(err); } console.log(err); // Uncaught ReferenceError: err is not defined作用域闭包
知乎上面有关于闭包的问题:什么是闭包?
其中寸志老师的解释我认为是比较好的。
对于闭包,《你不知道的JavaScript(上卷)》这本书的解释是:
当函数可以记住并访问所在的词法作用域时,就产生了闭包。
我们实际上来理解闭包时,需要特别注意是两个点:函数和作用域。
简单的来说,就是函数以及作用域的结合,注意,作用域必须是封闭的,其主要的表现形式就是函数中返回一个函数。
闭包在类库、组件封装中有太多的示例了,本文就不拓展了。
块作用域与闭包的结合首先看一个单纯的闭包的代码:
for (var i = 0; i <= 5; i++) { (function() { var j = i; setTimeout(function timer(){ console.log(j); }, j * 1000) })() }
这段代码就是在每次循环的时候创建一个新的封闭作用域,保存当次循环的i值。
再看一下下面的代码:
for (let i = 0; i <= 5; i++) { setTimeout(function timer(){ console.log(i); }, i*1000) }
利用let创建块作用域,当块作用域与闭包结合之后,我们可以减少创建新的封闭作用域这一操作(var j = i);
that"s cool!
动态词法作用域动态作用域链是基于调用栈的,而不是代码中的作用域嵌套。
对于JavaScript,不存在动态作用域。如果一定要找一个点与动态词法作用域扯上关系的话,那就是this值了。this值打算在下一篇文章中详解。
变量提升举个最简单的例子:
alert(a); // undefined var a = 12;
有同样作用的是函数声明function,例如:
alert(func); // function func(){} function func() {};
但是函数表达式不会提升:
foo(); // TypeError var foo = function bar() { ... }
注意:仅有var和函数声明function才可以变量提升。
函数声明与函数表达式的区别:
区别函数声明和函数表达式最简单的方法是看function关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
ES6中新增的let以及const关键字不可以进行变量提升,我们可以尝试一下:
// 1. let alert(a); // Uncaught ReferenceError: a is not defined let a = "abc"; // 2. const alert(b); // Uncaught ReferenceError: b is not defined const b = 123;函数优先
先来看下面的代码:
foo(); // 1 var foo; function foo() { console.log(1); } foo = function() { console.log(2); }
上面的例子说明:
函数会被首先提升,然后才是变量
上面的代码实际等于:
function foo() { console.log(1); } foo(); // 1 var foo; foo = function() { console.log(2); }模块
模块这一利器,在以前封装插件用的非常多,示例如下:
var foo = (function CoolModule() { var something = "cool"; var another = [1, 2, 3]; function doSomething() { console.log(something) } function doAnother() { console.log(another.join("!")); } return { doSomething: doSomething, doAnother: doAnother } })()
模块模式必备条件如下:
必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的莫模块实例)。
封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中形成闭包,并且可以访问或者修改私有的状态。
当然,说到模块,我们不得不提到CMD、AMD、ES6 module等模块机制了。
知乎上有提到AMD 和 CMD 的区别有哪些?
我这里简单提一下两者的区别:
AMD:
early executing(提前执行)
推荐依赖前置
示例:requireJs
CMD:
as lazy as possible(延迟执行)
推荐依赖就近
示例:seaJs
继续聊一下ES6的模块机制(import、export)。
import可以将一个模块中的一个或多个API导入到当前的作用域中,并分别绑定在一个变量上。
export会将当前模块的一个标识符(变量、函数)导出为公共API。
Github有很多基于es6实现的代码功能,请自行查阅。
结语好了,作用域相关的点整理完了,我将其中主要分成三部分:
作用域
提升
模块
如果有遗漏,欢迎指正~
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/81796.html
摘要:关于本书,我会写好几篇读书笔记用以记录那些让我恍然大悟的瞬间,本文是第一篇弄懂的作用域和闭包。作用域也可以看做是一套依据名称查找变量的规则。声明实际上是根据你传递给它的对象凭空创建了一个全新的词法作用域。 《你不知道的JavaScript》真的是一本好书,阅读这本书,我有多次哦,原来是这样的感觉,以前自以为理解了(其实并非真的理解)的概念,这一次真的理解得更加透彻了。关于本书,我会写好...
摘要:假设有两个域名域名域名域名有分级的概念,也就是说域名与域名都是的子域名,又是的子域名在域名所使用的服务中,可以设置域名在服务端设置的时候,设置为或没有区别,注意前面的点,即只要是为显式的声明,前面带不带点没有区别。 1 Cookie简介 Cookie是由W3C组织提出,最早由NetScape社区发展的一种机制。Cookie是存储于访问者的计算机中的变量。每当同一台计算机通过浏览器请求某...
摘要:假设有两个域名域名域名域名有分级的概念,也就是说域名与域名都是的子域名,又是的子域名在域名所使用的服务中,可以设置域名在服务端设置的时候,设置为或没有区别,注意前面的点,即只要是为显式的声明,前面带不带点没有区别。 1 Cookie简介 Cookie是由W3C组织提出,最早由NetScape社区发展的一种机制。Cookie是存储于访问者的计算机中的变量。每当同一台计算机通过浏览器请求某...
摘要:原文链接原文作者你想知道的关于作用域的一切译中有许多章节是关于的但是对于初学者来说甚至是一些有经验的开发者这些有关作用域的章节既不直接也不容易理解这篇文章的目的就是为了帮助那些想更深一步学习了解作用域的开发者尤其是当他们听到一些关于作用域的 原文链接: Everything you wanted to know about JavaScript scope原文作者: Todd Mott...
摘要:嵌套对象成员会造成重大性能影响尽量少用。一般来说你可以通过这种方法提高代码的性能将经常使用的对象成员数组项和域外变量存入局部变量中。在反复访问的地方使用局部变量存放引用小心地处理集合因为他们表现出存在性总是对底层文档重新查询。 前言 本期我来给大家推荐的书是《高性能JavaScript》,在这本书中我们能够了解 javascript 开发过程中的性能瓶颈,如何提升各方面的性能,包括代码...
阅读 3480·2021-11-24 09:39
阅读 760·2019-08-30 14:22
阅读 3018·2019-08-30 13:13
阅读 2290·2019-08-29 17:06
阅读 2879·2019-08-29 16:22
阅读 1241·2019-08-29 10:58
阅读 2384·2019-08-26 13:47
阅读 1602·2019-08-26 11:39