zoukankan      html  css  js  c++  java
  • 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
  • 相关阅读:
    一般索引
    微信小程序扫码
    微信小程序
    PHPStudy设置局域网访问
    phpstudy
    爱番番
    织梦栏目url的seo处理
    织梦dedecms网站迁移搬家图文教程
    打开存储过程中的代码目录(转)
    正在载入
  • 原文地址:https://www.cnblogs.com/holoyong/p/8972312.html
Copyright © 2011-2022 走看看