JS简记-作用域1

var a = 1;
console.log(a);

第一行代码var a=1,会被引擎看成两句话:var a和a=1。

首先,会由编译器询问当前作用域内(这里就是全局作用域)是否存在已经声明的变量a,如果存在则忽略var a,否则在当前作用域内声明变量a(这时,作用域内就有了变量a。测试时可以发现,即便两行代码倒置顺序,并不会抛ReferenceError,而是打印undefined,说明引擎首先会提前通过编译器执行代码中所有的声明操作)。

其次,编译器声明变量a(即在作用域内生成了变量a)后,就会编译a=1语句,编译后的语句由执行引擎执行。

最后,在执行引擎执行时,也就是在执行被编译后的a=1时,首先会进行LHS(左查找,目的是找到变量所在的容器,顺着作用域链查找,如果最终在全局作用域中仍未找到,则会默认在全局作用域内声明该变量,但如果是“use strict”,则与RHS未找到一样,抛ReferenceError),然后在赋值1。

var o = {};
o.b = 1;
console.log(o.b);

引擎在执行o.b=1时(假设已被编译器编译),首先会LHS查找变量o,然后使用对象访问规则访问o的b属性,这里就是对b赋值1。

 

eval和with

js中变量的作用域通常情况下,在书写代码时就已经确定了,但也有在运行时动态修改作用域的语法。

eval函数可以传入一个字符串,该字符串在运行时被解析为相应语句来执行。

function foo(str){
    eval(str);//改变a的作用域
    console.log(a);//2
}
var a = 1;
foo("var a = 2;");

with可以将一个对象视作为一个作用域,在with块内,可以直接引用对象中的属性,而无需再强调对象本身。在with块中仍然会使用LHS和RHS查找原则,由于o2没有a属性,所以with(o2)块中使用LHS对a赋值时,最终赋在了全局作用域上。

var o1 = {
    a: 1
};
var o2 = {};

with(o1){
    a = 2;
}
with(o2){
    a = 2;
}
console.log(o1.a);//2
console.log(o2.a);//undefined

 

上面我们见到了两种作用域:函数和with块

我们通常会将一些执行逻辑放在函数内,但在js中函数还有一个重要的用途,那就是封装变量与函数,将变量与函数放在一个函数内可以很好将其私有化,可以避免将所有变量和函数暴露在全局作用域中。

我们可以这样做:

function doSomething(a) {
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}
function doSomethingElse(a) {
    return a - 1;
}
var b;
doSomething( 2 ); // 15

但更好的做法是:

function doSomething(a) {
    function doSomethingElse(a) {
        return a - 1;
    }
    var b;
    b = a + doSomethingElse( a * 2 );
    console.log( b * 3 );
}
doSomething( 2 ); // 15    

由于将函数和变量封装起来,使得外部更加“干净”,但这样写需要先声明函数,再进行函数调用,很罗嗦,好在js提供了相应的解决策略。

function foo(a) { 
    //...
}
foo(a); 

相当于:

(function foo(a) { 
    //...
})(a);
//or
(function foo(a) { 
    //...
}(a)); 

这种模式叫做IIFE,即立即执行函数表达式。

catch作用域:

try{
    var a = 1;
    console.log(b);//ReferenceError
}catch(e){
    var b = 2;
    console.log(e);
}
console.log(a);//1
console.log(b);//ReferenceError

let(es6)可以将变量绑定到任意的作用域中,通常是{...}中,且其声明不会被提升,使用let可以将变量限定在局部逻辑中,更加利于垃圾收集,下面的代码没有使用let声明someReallyBigData,而且someReallyBigData只会被process临时使用,而由于click形成了一个覆盖整个作用域的闭包,所以js引擎可能在process执行完后,还会一直保留someReallyBigData。

function process(data) {
    // 在这里做点有趣的事情
}
var someReallyBigData = { .. };
process( someReallyBigData );
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt) {
    console.log("button clicked");
}, /*capturingPhase=*/false );

为了提高垃圾收集效率,可以将someReallyBigData使用let声明,并放在代码块中:

function process(data) {
    // 在这里做点有趣的事情
}
    // 在这个块中定义的内容可以销毁了!
{
    let someReallyBigData = { .. };
    process( someReallyBigData );
}
var btn = document.getElementById( "my_button" );
btn.addEventListener( "click", function click(evt){
    console.log("button clicked");
}, /*capturingPhase=*/false );

let在循环的使用中也是非常重要的:

for (let i=0; i<10; i++) {
    console.log( i );
}
console.log( i ); // ReferenceError

上面这段代码相当于下面代码,每次迭代时进行重新绑定(这对于闭包是非常重要的)

{
    let j;
    for (j=0; j<10; j++) {
        let i = j; // 每个迭代重新绑定!
        console.log( i );
    }
}    

 const(es6)与let类似,不同的是其值不可变:

{
    const a = 1;//必须在声明时初始化
    var b = 2;
    console.log(a);//1
    try{
        a = 2;
    }catch(e){
        console.log(e);//TypeError
    }
    
}
console.log(b);//2
console.log(a);//ReferenceError

 

posted @ 2018-04-29 23:13  holoyong  阅读(134)  评论(0编辑  收藏  举报