把leader安排的任务干完了,继续写一篇笔记,这一章是昨晚读的。第二章的title是词法作用域,之前读的时候对这个词是很陌生的,到这次读的话,已经很熟悉了。
1.词法作用域
文中指出作用域有两种工作模型,一种是动态作用域(不做讨论),一种是词法作用域,也是JS所采用的,说白了就是作用域是由我们写代码的位置决定的。
比如:
function a(){
function b(){
// ...
}
}
那由b创建的作用域完完全全被包含在a里面,为啥啊?
因为咱们写的时候就把函数b写在了函数a里面。这就是词法作用域。
2.查找
当引擎需要查找一个变量的时候,那么会在本作用域中查询,如果没有的话便会逐层网上,直到全局作用域。作用域查找会在找到第一个匹配的标识符时停止,在多层的嵌套作用域中可以定义同名的标识符,这叫做“遮蔽效应”。说白了就是,一旦找到了匹配的标识符,就算是外层还有同样匹配的,也不找了。
let a = 'window.trio'
function foo(){
let a = 'foo.trio'
console.log(a) // foo.trio
}
foo()
但是!我们可以这样子绕开遮蔽效应。
var a = 'window.trio'
function foo(){
let a = 'foo.trio'
console.log(window.a) // window.trio
}
foo()
3.欺骗作用域
在JS中,由两种机制,在运行的时候,可以修改(欺骗)作用域。
3.1 eval
eval(...) eval函数可以接受一个字符串,并将其中的内容视为好像在书写时就存在于程序中的代码,并且就在这个位置。
function trio(str){
try{
console.log(a)
}catch(error){
console.log(error) // ReferenceError: a is not defined
}
eval(str)
console.log(a) // ② 不会报错 打印123
}
trio('var a = 123')
我们从try...catch...的结果可以看出,eval()函数所传入的参数并不会被编译器所编译(其实我知道根本就不可能编译,那可是一个函数的参数啊,只有当函数执行的时候才会有预编译。但是我还是忍不住想确定一下。)。
而在eval之后,再次打印a并不会报错。因为引擎认为②这行代码本身就在那里呢。
从编译的结果来看,②这里必报错。但是真正执行的时候,却没有报错,原因就是eval修改了词法作用域。
如果eval()通常传入的是一个动态创建的代码。
3.2 with
如果是eval函数可以修改作用域,那么with更像是创建了一个作用域。
with()函数可以接受一个对象,然后根据对象去创建一个新的作用域
function foo(obj){
with(obj){
a = 2;
b = 3; // ①
var c = 4; // ②
}
}
var obj1 = {
a : 1
}
foo(obj1)
console.log(obj1.a) // 2
console.log(obj1.b) //undefined
console.log(window.b) //3
console.log(obj1.c,window.c) // undefined undefined
with可以将一个没有或有多个属性的对象处理为一个完全隔离的词法作用域,因此这个对象的属性也会被处理为定义在这个作用域中的词法标识符。
!!!但是有个注意点!如果这个对象上没有的属性,单纯的去赋值,如①,会泄露到全局作用域上,但是如果是var一个玩意儿并赋值,则不会泄露,也不会更改当前对象,如②。这两点在后面的控制台打印内容可以体现。
3.3性能方面
使用这其中任何一个机制都会导致代码运行的很慢,不要使用。虽然不用,但了解新的知识,总是好的,不是吗?