带你精读你不知道的Javasript(上)(一)
斌果在这几天看了下你不知道的js这本书,这本书讲的东西还是挺不错的,其中有很多平时我压根没接触到的概念和方法。借此也可以丰富一下我对js的了解。
第一部分
第一章 作用域是什么?
1.程序中一点源代码在执行之前会经历以下三个步骤,统称为“编译”。
分词/词法分析:
这个过程将由字符组成的字符串分解成有意思的代码块(词法单元)。例如 var a = 2;会被分解为var、a、=、2、;。空格是否被分解取决于其是否有意义。
解析/语法分析:
这个过程会讲单元流(数组)转换成“抽象语法书”。就如将一条语句拆分成词法单元放分别放到一课分层树上。
代码生成:
将AST转换为课执行的代码。
2.编译器中的术语(LHS.RHS)
其中最主要介绍的两个查询 LHS和RHS查询
LHS:赋值操作的目标是谁;(可以理解为要找到目标进行操作)
RHS:谁是赋值操作的源头;(可以理解为查找是否有这个目标)
//下面看一个例子
function foo(a){
console.log(a); //
} foo(2);
在这个例子中foo进行RHS引用,看下作用域中是否定义了foo函数;然后再为a进行LHS引用,将a赋值为2;接着对console进行RHS引用,看下作用域中是否有console这个内置
对象是否有log方法;最后再对a进行RHS引用。LHS和RHS引用会在作用域中一层一层向上找,如何抵达顶层(全局作用域)还没找到,就会停止。
//考虑一下这段代码 function foo(a){
console.log(a + b);
b = a;
} foo(2);
在这段代码中,当对b进行RHS引用时,在作用域中无法找到该变量,引擎会抛出ReferenceError异常。相比之下LHS在非严格模式下是不会抛出异常而是隐式地创建一个全局变量。
而在严格模式下他也会抛出和ReferencError异常。
第二章 词法作用域
1.词法作用域
简单来说,词法作用域就是定义在词法阶段的作用域。换句话说,词法作用域是由你在写代码时将变量和作用域写在哪里来决定的,一般情况下词法作用域在词法分析器处理代码时
会保持作用域不变。
//分析一下下面这段代码 function foo(a){
var b = 1 * 2;
function bar(c){
console.log(a,b,c);
}
bar(b*3);
} foo(2); //2,4,12
在这个例子中一共有三层嵌套作用域。(灰色、浅蓝色、浅黄色),其中又外到内是逐层包含关系。没有一个作用域能够同时出现在两个外部作用域中!就如没有任何函数可以部分地同
时出现在两个父级函数中一样。作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,这叫做“遮蔽效应”;
2.欺骗词法
javascript中有两种机制可以实现这个目的,但是欺骗词法作用域是会付出相应的代价,那就是会导致性能的下降。
1.eval
eval()函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序这个位置的代码。
在执行eval()之后的代码是,引擎并不“知道”或“在意”前面的代码时以动态形式插入进来,并对词法作用域的环境是进行修改的。
//让我们来看一下这段代码 function foo(str,a){
eval(str);
console.log(a,b);
}
var b = 2; foo("var b = 3;",1); //1,3
这段代码中foo会在内部找到变量a和b,而遮蔽了外部的全局变量b,所以会输出1,3.
无论在何种情况下,eval()都可以在运行期修改书写期的词法作用域。
2.with
with通常被当做重复引用同一个对象中的多个属性的快捷方式,可以不需要重负应用对象本身。
//例如下面这个例子 var obj = {
a: 1,
b: 2,
c: 3
}; //单调乏味的重复“obj”
obj.a = 2;obj.b = 3;obj.c = 4; //简单的快捷方式
with(obj){
a = 3;
b = 4;
c = 5;
}
然而其作用不单单这样,再来看下下面的代码。
function foo(obj) {
with(obj){
a = 2;
}
} var o1 = {
a:3
}; var o2 = {
b:3
}; foo(o1);
console.log(o1.a); // foo(o2);
console.log(o2.a); //undefind
console.log(a); //2--------不好,a被泄漏到全局作用域上了!
在with块内部,看起来只是对变量a进行了简单的词法引用,实际上就是一个LHS引用,由于o2中没有a属性,其会隐式创建一个全局变量a。
总结:
eval()函数如果接受了函数一个或多个声明的代码,就会修改其所处的词法作用域,而with声明实际上是根据你传递给它的对象凭空创建一个全新的词法作用域。
然而,最悲观的是如果代码中出现eval()或with,引擎只能简单假设关于标识符位置的判断是无效的,因为无法预测其会接受到什么。从而可能导致引擎中所有的优化都是无意义的,因此最简单的做法就是其完全不做任何优化。所以我们不要使用它们。
第3章 函数作用域和块作用域
1.函数作用域
函数作用域的含义:属于这个函数的全部变量都可以在整个函数的范围内使用及复用(事实上在嵌套的作用域中也可以使用)。
function foo(a) {
var b = 2;
function bar() {
//.....
}
var c = 3;
} bar(); //失败
console.log(a, b, c); //访问失败
其中(a、b、c、foo和bar)在foo()中是可以访问的,而这些标识符无法从全局作用域中进行访问。
2. 隐藏内部实现
“隐藏”作用域中的变量和函数能将具体内容私有化,并且能避免同名标识符之间的冲突。
//下面看一下同名冲突例子 function foo(){
function bar(a){
i = 3; //修改for循环所属作用域中的i
console.log(a + i);
}
for(var i=0;i<10;i++){
bar(i * 2); //糟糕,无限循环了!
}
}
虽然我们可以修改bar中i的名字就可以解决这个问题,但软件设计在某种情况下可能自然而然地要求使用同样的标识符名称,因此,使用
“隐藏”内部声明是唯一的最佳途径。
var a = 2;
function foo(){ //添加这一行
var a = 3;
console.log(a); //
} //以及这一行
foo(); //和这一行 console.log(a); //
这样虽然能“隐藏”内部的变量和函数定义,但是,其又多了一个foo的全局函数污染了作用域,所以,我们可以采用下面这种方案:
var a = 2;
(function () { //添加该行
var a = 3;
console.log(a);
})(); //还有该行 console.log(a); //
这样,函数会被当做函数表达式而不是一个标准的函数声明来处理。
3.匿名和具名函数
函数表达式可以是匿名,而函数声明则不可以省函数名---在js中这是非法的。、
在很多场合我们都喜欢用匿名函数,这样显得简便而高大上,但还要考虑到一下的缺点
1.匿名函数在栈追踪中不会显示出有意义的函数名,使得调试很困难。
2.如果没有函数名,当函数需要引用自身是只能使用已经过期的arguments.callee引用,
比如在递归中。另一个函数需要引用自身的例子,是在事件触发后事件监听器需要解绑自身。
3.匿名函数省略了对代码的可读性/可理解性很重要的函数名。一个描述性的名称可以让代码不言自明。
所以,我们应该尽量使用具名函数而少或不使用匿名函数
4.立即执行函数表达式
IIFE:代表立即执行表达式;
立即执行表达式非常常见,而且它还能“隐藏”函数的定义和声明。
var a = 2;
(function IIFE(s) {
var a = 3;
console.log(a+s); //3oh
})("oh"); console.log(a); //
因为函数被一对()包裹,因此成为了一个表达式,而末尾加上一对()可以
立即执行这个函数。IIFE的一个非常普遍的进阶用法是把它们当做函数调用并传递参数。
5.块作用域
相信大家对块作用域一点也不陌生,在c/java都支持块作用域。
for(var i=0;i<10;i++){
console.log(i);
};
在for循环头部定义了变量i,通常只想在for循环中上下文使用,然而在js中,并没有块作用域!这以为着,在外部也可以调用到变量i。
但是我们可以通过下属方法创建块作用域:
with: 虽然with会严重影响程序的性能,但其确实可以创建一个块作用域。
try/catch:在ES3规范中catch分局会创建一个块作用域,其中声明的变量仅在catch内有效。
let:这是ES6的语言,其可以为声明的变量隐式地劫持了所在的块作用域。
const:同样可以用来创建块作用域变量,但其值是固定的。
块作用域非常有用的原因和闭包及回收内存垃圾的回收机制相关。
第四章 提升
//首先先上一段代码 a = 2;
var a;
console.log(a); //
在javascript中所有声明本身会被提升,而复制或者其他运行逻辑会留在原地。就像下面这段代码
foo(); function foo(){
console.log(a); //undefined
var a = 2;
}
代码中foo函数声明会被提升(这个例子还包括实际函数的隐含值),函数体中的a声明也会被提升看,但a中的LHS引用却会留在原地。
foo(); //TypeError
bar(); //ReferenceError
var foo = function bar(){
//...........
};
可以看到当函数是表达式时候,函数名虽然被提升了,但表达式仍然留在原地,这会使得foo报类型错误,而bar直接是表达式的内容从而直接报错!
需要注意的是函数会首先被提升,然后才是变量。
第5章 作用域闭包
1.对闭包的理解
当函数可以记住并访问所在的词法作用域时,就产生了闭包。
这个理解起来比较难讲,还是引用百度百科上的定义吧!既:闭包就是能够读取其他函数内部变量的函数。
闭包其实无处不在,举个简单的例子:
function foo(){
var a = 2;
function bar(){
console.log(a);
}
return bar;
} var baz = foo();
baz(); //2---这就是闭包的效果。
函数bar()的词法作用域能够访问foo()的内部作用域,这就是闭包!当然,无论使用何种方式对函数类型的值进行传递,当函
数在别处被调用时都可以观察到闭包。
function foo(){
var a = 2;
function baz(){
console.log(a); //
}
bar(baz);
} function bar(fn){
fn(); //老弟快看,这就是闭包!
} foo();
要说明闭包,for循环是最常见的例子。
for(var i=1;i<=5;i++){
setTimeout(function timer() {
console.log(i);
},i*1000);
}
按照我们的预期,是分别输出1到5,每秒一次,每次一个,但实际上,这段代码在运行时以每秒一个的频率输出5个6。
这是因为延时函数的回调函数会在循环结束时才执行。事实上,当定时器运行时即使每个迭代中执行的是setTimeout(...,0),
所有的回调依然在循环结束后才会被执行,因此会输出6。尽管循环中五个函数是在各个迭代中分别定义的,但是他们都被
封闭在一个共享的全局作用域中,因此实际上只有一个i。
因此我们可以通过传参的IIFE来迭代自己的变量。
for(var i =1;i<=5;i++){
(function (j) {
setTimeout(function timer() {
console.log(j);
},j*1000)
})(i)
}
这样每个迭代都生成一个新的作用域,使得延迟函数的回调可以将新的作用域封闭在每个迭代内部。
2.模块
模块就是利用了闭包的强大威力,但从表面上看,它们似乎与回调无关。
function foo() {
var something = "cool";
var another = [1,2,3]; function doSomething() {
console.log(something);
}
function doAnother() {
console.log(another.join("!"));
}
return{
doSomething:doSomething,
doAnother:doAnother
}
}
var bar = foo();
bar.doSomething(); //cool
bar.doAnother(); //1!2!3
这个模式在javascript中被称为模块。最常见的实现模块模式的方法通常被称为模块暴露。
简单的描述,模块模式需要具备两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次(每次调用都会创建一个新的模块实例)
2.封闭函数必须返回至少一个内部函数,这样内部函数才能在私有作用域中星城闭包,并且可以访问或者修改私有又的状态。
只有数据属性而没有闭包函数的对象并不是真正的模块。
而ES6的模块没有“行内”格式,必须被定义在一个独立的文件中。通过module.export将文件抛出,主文件通过import将一个模块中的
一个或多个API导入到当前作用域中,并分别绑定在一个变量上。module会讲整个模块的API导入并绑定到一个变量上。
---------------------由于篇幅有限,不想写太长,不方便看。第二部分后续会更新,因为近期时间有点紧所以可能更新会有点迟。
带你精读你不知道的Javasript(上)(一)的更多相关文章
- Ajax实现带进度条的文件上传
Ajax实现带进度条的文件上传 文件上传页面运行效果 上传文件并显示进度条运行效果 代码如下; DiskFileItemFactory factory = new DiskFileItemFactor ...
- 读书笔记-你不知道的JavaScript(上)
本文首发在我的个人博客:http://muyunyun.cn/ <你不知道的JavaScript>系列丛书给出了很多颠覆以往对JavaScript认知的点, 读完上卷,受益匪浅,于是对其精 ...
- Android带进度条的文件上传,使用AsyncTask异步任务
最近项目中要做一个带进度条的上传文件的功能,学习了AsyncTask,使用起来比较方便,将几个方法实现就行,另外做了一个很简单的demo,希望能对大家有帮助,在程序中设好文件路径和服务器IP即可. A ...
- 详细介绍redis的集群功能,带你了解真正意义上的分布式
Redis 集群是一个分布式(distributed).容错(fault-tolerant)的 Redis 实现, 集群可以使用的功能是普通单机 Redis 所能使用的功能的一个子集(subset). ...
- 将 java 项目打包成可运行的 jar 包(main 函数带参数),并上传到 linux 服务器上运行
一.概述 java项目有两种架构,一种是 B/S 架构的,一种是 C/S 架构的. 对于 B/S 架构来说,我们常见的 java ee 即是 B/S 架构,通常,开发人员会在本地进行开发,然后将项目打 ...
- 利用ThinkPHP自带的七牛云驱动上传文件到七牛云以及删除七牛云文件方法
一.准备工作 1.注册七牛云账号 2.选择对象储存->创建空间->设置为公开 3.在config配置文件中添加以下代码 'UPLOAD_FILE_QINIU' => array ( ...
- 采用formdata做跨域的、无刷新、带进度条的文件上传
以前做无刷新上传,都要用iframe,如果想有进度条,就千难万难,不得不用flash等插件来实现. 现在HTML5终于普及了,筒子们不用再那么痛苦了. 所有这一切都变得异常简单!! 不信?且看如下代码 ...
- Ajax技术——带进度条的文件上传
1.概述 在实际的Web应该开发或网站开发过程中,经常需要实现文件上传的功能.在文件上传过程中,经常需要用户进行长时间的等待,为了让用户及时了解上传进度,可以在上传文件的同时,显示文件的上传进度条.运 ...
- 读书笔记-你不知道的JS上-混入与原型
继承 mixin混合继承 function mixin(obj1, obj2) { for (var key in obj2) { //重复不复制 if (!(key in obj1)) { obj1 ...
随机推荐
- fasthttp 的 goroutine pool 实现探究
引言 fasthttp是一个非常优秀的web server框架,号称比官方的net/http快10倍以上.fasthttp用了很多黑魔法.俗话说,源码面前,了无秘密,我们今天通过源码来看一看她的gor ...
- hibernate MTM 联合主键
//适用于表里没有其他列,只有主键 //Course.java实体类 package com.tao.pojo; import java.util.HashSet; import java.util. ...
- Java基础系列--基础排序算法
原创作品,可以转载,但是请标注出处地址:https://www.cnblogs.com/V1haoge/p/9082138.html 一.概述 基础排序算法包括:桶排序.冒泡排序.选择排序.插入排序等 ...
- csc.exe的环境变量设置
csc.exe使用来编译*.cs文件的,但必须要在安装目录下使用.所以需要设置一下环境变量. C#的环境变量设置 1.“win+R” 打开运行窗口,并输入 “cmd”: 2.运行“set path=% ...
- Python中的turtle初探
turtle Python自带了一个turtle库,就像名字turtle说的那样,你可以创建一个turtle,然后这个turtle可以前进,后退,左转,这个turtle有一条尾巴,能够放下和抬起,当尾 ...
- YAML基础教程
一.YAML介绍YAML参考了其他多种语言,包括:XML.C语言.Python.Perl以及电子邮件格式RFC2822.Clark Evans在2001年5月在首次发表了这种语言,另外Ingy döt ...
- FreeSql 新的八大骚功能,.NETCore 你必须晓得的 ORM
前言 FreeSql 目前版本号 0.5.5,预计明年元旦发布 1.0.0,切莫小看了版本号,目前单元测试方法1350+,并且每个方法内的涵盖面又比较广(不信的话见下图),每一次版本发布都作了较多的测 ...
- netty 之 telnet HelloWorld 详解
前言 Netty是 一个异步事件驱动的网络应用程序框架, 用于快速开发可维护的高性能协议服务器和客户端. etty是一个NIO客户端服务器框架,可以快速轻松地开发协议服务器和客户端等网络应用程序.它极 ...
- Mybatis之旅第六篇-关联查询
一.引言 通过动态SQL我们可以进行复杂SQL的编写,但之前的例子都是单表查询,在实际开发中,当然不可能都是单表,很多时候我们需要进行关联多表查询(有些公司为了性能还是尽量的使用单表查询),表与表之间 ...
- 『Lucas定理以及拓展Lucas』
Lucas定理 在『组合数学基础』中,我们已经提出了\(Lucas\)定理,并给出了\(Lucas\)定理的证明,本文仅将简单回顾,并给出代码. \(Lucas\)定理:当\(p\)为质数时,\(C_ ...