1、RHS和LHS查询
当变量出现在赋值操作的左侧时进行LHS查询,出现在右侧时进行RHS查询。
讲得更准确一点,RHS查询与简单地查找某个变量的值别无二致,而LHS查询则是试图找到变量的容器本身,从而可以对其赋值。从这个角度说,RHS并不是真正意义上的“赋值操作的右侧”,更准确地说是“非左侧”。
你可以将RHS理解成retrieve his source value(取到它的源值),这意味着“得到某某的值”。作用域是一套规则,用于确定在何处以及如何查找变量(标识符)。如果查找的目的是对变量进行赋值,那么就会使用LHS查询;如果目的是获取变量的值,就会使用RHS查询。
2、eval()
JavaScript中的eval(..)
函数可以接受一个字符串为参数,并将其中的内容视为好像在书写时就存在于程序中这个位置的代码。换句话说,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。
但是在严格模式的程序中,eval(..)
在运行时有其自己的词法作用域,意味着其中的声明无法修改所在的作用域。
3、函数表达式
var a = 2;
(function foo(){ // <-- 添加这一行
var a = 3;
console.log( a ); // 3
})(); // <-- 以及这一行
console.log( a ); // 2
接下来我们分别介绍这里发生的事情。
首先,包装函数的声明以(function...
而不仅是以function...
开始。尽管看上去这并不是一个很显眼的细节,但实际上却是非常重要的区别。函数会被当作函数表达式而不是一个标准的函数声明来处理。
区分函数声明和表达式最简单的方法是看
function
关键字出现在声明中的位置(不仅仅是一行代码,而是整个声明中的位置)。如果function
是声明中的第一个词,那么就是一个函数声明,否则就是一个函数表达式。
函数声明和函数表达式之间最重要的区别是它们的名称标识符将会绑定在何处。
比较一下前面两个代码片段。第一个片段中foo
被绑定在所在作用域中,可以直接通过foo()
来调用它。第二个片段中foo
被绑定在函数表达式自身的函数中而不是所在作用域中。
换句话说,(function foo(){ .. })
作为函数表达式意味着foo
只能在..
所代表的位置中被访问,外部作用域则不行。foo
变量名被隐藏在自身中意味着不会非必要地污染外部作用域。
对于函数表达式你最熟悉的场景可能就是回调参数了,比如:
setTimeout( function() {
console.log("I waited 1 second!");
}, 1000 );
这叫作匿名函数表达式,因为function()..
没有名称标识符。函数表达式可以是匿名的,而函数声明则不可以省略函数名——在JavaScript的语法中这是非法的。
可以创建块作用域变量
let
关键字可以将变量绑定到所在的任意作用域中(通常是{ .. }内部)。换句话说,let
为其声明的变量隐式地劫持了所在的块作用域。
5、闭包
闭包就是一个函数引用另外一个函数的变量,因为变量被引用着所以不会被回收,因此可以用来封装一个私有变量。
当函数可以记住并访问所在的词法作用域时,就产生了闭包,即使函数是在当前词法作用域之外执行
function f1(){
var n=999;
function f2(){
alert(n);
}
return f2;
}
var result=f1();
result(); // 999
闭包可以用在许多地方。它的最大用处有两个,一个是前面提到的可以读取函数内部的变量,另一个就是让这些变量的值始终保持在内存中。为什么会这样呢?原因就在于f1是f2的父函数,而f2被赋给了一个全局变量,这导致f2始终在内存中,而f2的存在依赖于f1,因此f1也始终在内存中,不会在调用结束后,被垃圾回收机制(garbage collection)回收。
使用闭包的注意点
1)由于闭包会使得函数中的变量都被保存在内存中,内存消耗很大,所以不能滥用闭包,否则会造成网页的性能问题,在IE中可能导致内存泄露。解决方法是,在退出函数之前,将不使用的局部变量全部删除。
2)闭包会在父函数外部,改变父函数内部变量的值。所以,如果你把父函数当作对象(object)使用,把闭包当作它的公用方法(Public Method),把内部变量当作它的私有属性(private value),这时一定要小心,不要随便改变父函数内部变量的值。
6、this
this
既不指向函数自身也不指向函数的词法作用域。
this
实际上是在函数被调用时发生的绑定,它指向什么完全取决于函数在哪里被调用。
隐式丢失
一个最常见的this
绑定问题就是被隐式绑定的函数会丢失绑定对象,也就是说它会应用默认绑定,从而把this
绑定到全局对象或者undefined
上,取决于是否是严格模式。
new
操作符时被调用的函数。它们并不会属于某个类,也不会实例化一个类。实际上,它们甚至都不能说是一种特殊的函数类型,它们只是被
new
操作符调用的普通函数而已。
使用new
来调用函数,或者说发生构造函数调用时,会自动执行下面的操作。
1. 创建(或者说构造)一个全新的对象。
2. 这个新对象会被执行[[原型]]连接。
3. 这个新对象会绑定到函数调用的this
。
4. 如果函数没有返回其他对象,那么new
表达式中的函数调用会自动返回这个新对象。