本篇作为自己已经看了一半的你不知道的javascript上卷的一些小节,重新认识js,希望我的一些理解能够给大家重新认识js。前半部分的内容主要有以下几点
作用域和闭包
- 作用域
- 词法作用域
- 函数作用域和块作用域
- 作用域闭包
首先对于一段js源代码,js引擎执行分为了三个步骤
- 词法分析
- 语法分析
- 代码生成
虽然引擎编译的步骤只有三步,但是千万不要以为js编译非常简单,实际实现的过程是非常复杂的,只是这里我们简化了编译器的具体实现。我们来看一段简单的代码在浏览器中的实现
var a = 2;
- 编译器首先会询问作用域,是否含有a的变量名称,如果有的话,编译器会忽略这个声明,继续执行下面的代码,如果没有的话,编译器会创建一个a变量的声明
- 编译器生成引擎执行所需的代码以后,引擎接下来会处理a = 2的代码,引擎会首先询问作用域是否含有a的变量,如果有,引擎会使用a的变量进行赋值操作,如果没有的话,引擎会去上一作用域继续查找变量a
- 如果引擎找到了变量a,那么就会将2赋值给变量a,如果最终没有找到变量a,那么引擎就会抛出一个异常
在引擎进行查找变量的时候,会发生两种类型的查询,书中对两种查询也做了比较准确容易理解的介绍
- LHS查询:赋值操作的目标是谁
- RHS查询:谁是赋值操作的源头
这样我们其实就很容易理解,例如
function foo(a) {
console.log(a);
}
foo(2);
执行这段代码的时候,先执行foo函数,那么引擎在查找foo的时候,其实就是RHS的引用,然后将2传递给参数a,这个就是一个LHS引用,将2的值赋给变量a,还有执行log方法的时候,也是引擎查找console对象,进行的也是RHS的查询。
最后总结出来作用域其实就是一套规则,用于确定在何处以及何时查找变量,理解了作用域,我们再来看看什么叫做词法作用域
词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的。
书中写的非常专业,其实简单来说,就是你代码写在了什么位置,在js中一般就是函数作用域,以及ES6新增的块作用域,这些作用层层嵌套,每个作用域能访问的变量和函数都是有相应的一套规则,也是自内向外去访问变量和函数方法,但是不能自外向内访问。
注意在这之中有一些方法可能会改变词法作用域,比如
- eval
- with
- setInterval
这些修改词法作用域的方法是不被提倡和使用的,尽量不要使用。在刚刚我们提到了函数作用域,相信小伙伴以及不陌生了,但是我们为什么需要函数作用域,为什么我们不直接写在外面呢,除了所说的污染全局变量,以及命名冲突之外,还涉及到了一个软件设计原则,最小授权或最小暴露原则
在软件设计中,应该最小限度的暴露必要内容,而将其他内容都隐藏起来
其实我们的JS的模块就是运用了这样的一个原则。另外说说新的ES6的块级作用域,除了我们熟知的let,const等关键字外,在ES5的时候try/catch中的catch分句也会创建块级作用域,里面的变量仅能在catch块级作用域中访问。
此外相信大家也比较熟悉的变量提升,函数和变量都会被提升到各自作用域的最顶端,当然需要注意的是let,const是不会提升变量的,不要弄错了。
最后也是提到了JS经典的闭包概念
当函数可以记住并访问所在的词法作用域时,就产生了闭包,及时函数是在当前词法作用域外执行的
大家可以细细琢磨这句话,所谓闭包,其实就是函数被包含函数返回出去,这样再外面拿变量接受这个函数并且执行,因为返回的函数还包含这对之前的包含函数的作用域的引用,本应该销毁的作用域,现在依然被返回的函数引用,因此返回函数依然是可以执行的,对于闭包的概念,学习JS一定要弄清楚,建议比较模糊的同学可以去腾讯课堂,看看渡一教育成哥的视频,讲的非常易懂,博主就是从那开始学习JS的。
上面提到的模块的功能,主要就是用了JS的闭包来实现的,我们来看一段代码
var MyModules = (function Manager(){
var modules = {};
function define (name, deps, impl) {
for (var i = 0;i < deps.length;i++) {
deps[i] = modules[deps[i]];
}
modules[name] = impl.apply(impl, deps);
}
function get (name) {
return modules[name]
}
return {
define: define,
get: get
};
}());
MyModules.define('bar', [], function(){
function hello (who) {
return 'let me introduce: ' + who;
}
return {
hello: hello
}
});
MyModules.define('foo', ['bar'], function(bar){
var hungry = 'heihe';
function awesome () {
console.log(bar.hello(hungry).toUpperCase());
}
return {
awesome: awesome
}
});
var bar = MyModules.get('bar');
var foo = MyModules.get('foo');
console.log(bar.hello('hippo'));
foo.awesome();
具体运行,博主就不多说了,大家可以在浏览器中断点调试,看看代码的运行步骤,原理其实就是利用闭包保存了立即执行函数中的变量,然后在另外一个函数中去访问这个变量。
至此上卷的前半部分内容就是这些,有些部分博主也是慢慢消化,不过确实给自己带来了很大的收货,换种方式去看待JS,又会发现有太多值得我们去研究,JS也并不是大家想象中那么简单或者是‘玩具语言’这样的称呼,JS学习好,框架什么的也都会非常容易。