《你不知道的JavaScript》-- 精读(二)

583 阅读4分钟

知识点

1.词法阶段

词法作用域就是定义在词法阶段的作用域。简单说,词法作用域是由你在写代码时将变量和块作用域写在哪里来决定的,因此当词法分析器处理代码时会保持作用域不变(大部分情况下)。

// 全局作用域,只有一个标识符:foo
function foo(a){
// foo所创建的作用域,三个标识符:b,a,bar
    var b = a * 2;
    function bar(c){
    // bar所创建的作用域,只有一个标识符:c
        console.log(a,b,c);
    }
    bar(b * 3);
}
foo(2);  // 2,4,12

作用域气泡由其对应的作用域代码写在哪里决定,它们是逐级包含的。 没有任何函数可以部分地同时出现在两个父级函数中。

2.查找

作用域气泡的结构和相互之间的位置关系给引擎提供了足够的位置信息,引擎用这些信息来查找标识符的位置。

作用域查找会在找到第一个匹配的标识符时停止。在多层的嵌套作用域中可以定义同名的标识符,产生“遮蔽效应”。

全局变量会自动成为全局对象(比如浏览器中的window对象)的属性,因此可以通过window.a的方式进行访问那些被同名变量所遮蔽的全局变量。但非全局变量被遮蔽的时候是没办法访问到的

无论函数在哪里被调用,也无论它如何被调用,它的词法作用域都只由函数被声明时所处的位置决定。

3.欺骗词法

欺骗词法作用域会导致性能下降。

1.eval

JavaScript中的eval()函数可以接受一个字符串为参数,可以在你写的代码中用程序生成代码并运行,就好像代码是写在那个位置的一样。

eval()进行欺骗词法的原理就是:在运行期修改书写期的词法作用域

function foo(str,a){
    eval(str); // 欺骗
    console.log(a,b); 
}
var b = 2;
foo("var b = 3",1); // 1,3
在严格模式下,eval()在运行时有自己的词法作用域
function foo(str){
    "use strict";
    eval(str);
    console.log(a); // ReferenceError:a is not defined
}
foo("var a = 2");

setTimeout()和setInterval()也可以实现跟eval()一样的效果。

2.with

with通常被当作重复引用同一个对象中的多个属性的快捷方式,可以不需要重复引用对象,例如:

var obj = {
    a: 1,
    b: 2,
    c: 3
}
// 改值
obj.a = 2;
obj.b = 3;
obj.c = 4;
// with改值
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); // 2

foo(o2);
console.log(o2.a); // undefined
console.log(a); // 2 -- a 被泄露到全局作用域

with块可以将一个对象处理为词法作用域,但是这个块内部正常的var声明并不会被限制在这个块作用域中,而是被添加到with所处的函数作用域中。

总结

1.词法作用域意味着作用域是由书写代码时函数声明的位置决定的。编译的词法分析阶段基本能够知道全部标识符在哪里以及是如何声明的,从而能够预测在执行的过程中如何对它们进行查找。

2.JavaScript“欺骗”词法作用域的两个机制:eval()和with,副作用是引擎无法在编译时对作用域查找进行优化,导致代码运行变慢,不建议使用。

巴拉巴拉

工作是什么?

以一个俗人的角度可能是为了生存,衣食住行,以一个超脱的人的角度可能是实现自我的价值,得到某种荣耀或者升华,对目前的我来说,工作是什么,只是工作,这样说有点偷换概念,明明没有回答什么,工作现在是我的生活的一部分,有时候我会因为它开心,有时候会因为它难过,希望自己能做到一个平常心,就像经常被灌输的只是进城打工,看淡,认真,不较真,与人为善,技术上多学习,多思考,多总结。