有关作用域的知识

Posted 阿冰介

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了有关作用域的知识相关的知识,希望对你有一定的参考价值。

内容摘自:

你不知道的JavaScirpt上册

并作了一些总结

什么是作用域?

三大角色:引擎,编译器,作用域

  • 引擎:负责JS的编译和执行——大佬
  • 编译器:负责语法分析和代码生成——二哥
  • 作用域:对声明变量查询,确定标识符的访问权限—三哥

示例:

var a =2;
///
var a;
a=2;

可以看成是两步的操作:
1. 变量的赋值会执行两个动作,首先编译器会在当前作用域中声明一个变量()
2. 然后在运行时引擎会在作用域中查找该变量,如果找到会对它赋值

LHS(left)和RHS(right)怎么理解?

赋值操作的目标是谁LHS。
谁是赋值操作的源头RHS。
被赋值的为LHS,主动调用的为RHS
对变量进行赋值,用LHS查询;目的是获取变量的值,用RHS查询

作用域嵌套?

当一个块或者函数嵌套另一个块或者函数中时。
如果在当前作用域中找不到会一直往上层寻找

词法作用域:

立即执行函数表达式:

var a=2;
(function foo(){
   var a=3;
   console.log(a);   //3
})();
console.log(a); //2

倒置代码的顺序

var a=2;
(function IFIE(def){
   def(window)
})(function def(global){
    var a=3;
    console.log(a);  //3
    console.log(global.a);  //获得window的a的值
});

块作用域

with—
try catch—
let 声明局部作用域,限定到作用域内才能执行
函数变量不会声明提前
const 创建局部作用域变量,值为固定的值无法修改

作用域提升:

1.变量作用域提升

先有鸡还是先有蛋?

结论:先有蛋(声明)后有鸡(赋值)

console.log(a);  //undefined
var a=2;     
=>等同于
var a;
console.log(a); //undefined
a=2;

2.函数作用域提升

foo();
function foo(){      ///
   console.log("hello world");
}
=>正常执行并输出hello world
foo();
var foo=function(){    ///
    console.log(‘hello world‘);
}
=>输出错误,等同于↓
var foo;
foo();
foo=function(){
  console.log(‘hello world‘);
}

只有以function声明的变量才能提升,以变量声明的函数不能提升。

3.当声明多个相同函数的时候。

foo();        //2
var foo;
function foo(){
   console.log(1);
}
function foo(){
   console.log(2);
}
=>等同于下面
foo=function(){
   console.log(2);
}
function foo(){
   console.log(1);
}
foo();

//谁最后加载,谁就最后执行

如果存在用变量声明的函数呢?

foo();  //3
function foo(){
   console.log(1);
}
foo=function(){
   console.log(2);
}
function foo(){
   console.log(3);
}
=====>等同于下面
function foo(){
   console.log(1);
}
function foo(){
   console.log(3);
}
foo();  //3

foo=function(){   //变量声明无法提前
   console.log(2);
}

4.函数在块级里面的声明都不会提前

foo();  //error
var a=true;
if(a){
    function foo(){console.log(‘a‘)};
}else{
    function foo(){console.log(‘b‘)};
}
==>

总结:

1.我们总是习惯吧var a=2,看作是一个声明
然而JS引擎却是吧var a 和 a=2 两个单独声明,
第一个是编译阶段的任务,第二个是执行阶段的任务

2.什么是提升?
- 作用域声明出现在什么地方,都将在代码本身被执行前首先进行处理。
- 官方理解:所有的声明(变量和函数)都会被移动到各自作用于的最顶端
- 个人理解:称之为编译的声明(变量和函数)都将在执行之前提升至最顶端。

———————————- 华丽的分割线 ————————————–

作用域的闭包?

1.闭包无处不在….
闭包是发生在定义时的,但并不明显。

2.循环和比闭包:
(1)

for(var i=1;i<5;i++){
    setTimeout(function timer(){
        console.log(i);   //这个i是不能访问i的
    },i*1000);  //这个i是可以访问循环的,因为它暴露在for循环里面
}
  • 延迟函数的回调会在循环结束时才执行。
  • 根据作用域的工作原理,循环中的函数是各个迭代中分别定义的,都封闭在一个共享的全局作用域中。
  • 个人理解:
    其实就是定义了6个函数,然后在执行的时候,它的函数作用域里面的i会自动指向全局的i,也就是最后一个循环产生的6,至于为什么计算时间的i不是全局的,只能说明他是for循环里面产生的,并存储在里面。

(2)解决方案1。通过IIFE(即时执行)解决

for(var i=1;i<=5;i++){          
   (function(){
    setTimeout(function timer(){
     console.log(i);
    },i*1000);
    })();
}   ///这样写还是全部输出6

因为没有存储外部作用域i的东西

for(var i=1;i<=5;i++){
    (function(){
        var j=i;   //因为计时器里面的作用域只能访问闭包里面函数的作用域。
        //如果将i赋给j,相当于拓展了作用域的范围。
        setTimeout(function timer(){
            console.log(j);
        },i*1000);
    })();
}

这样就可以正常输出了,但是还有更好的方案。

for(var i=1;i<=5;i++){
    (function(i){
        setTimeout(function timer(){
            console.log(i);
        },i*1000);
    })(i);
}

其实就是把所有的作用域都串联在一起,共享了作用域的i的动态值

(3) 重返块作用域:let

for(var i=1;i<=5;i++){
    let j=i
    setTimeout(function(){
        console.log(j);
    },j*1000);
}

相当于把循环分割成5个独立的块,单独执行5个不同的函数
什么是let?
let 允许把变量的作用域限制在块级域中。

{
    let j=1;
    var i=1;
    {
    var i=2;
    let j=2;
    }
    console.log(i);  //2
    console.log(j);  //1
}

原理:将一个块转换成一个可以被关闭的作用域
个人理解:实际上就是单独声明每个变量的时候,把变量绑定为一个人单独的作用域,跟绑定在单独的函数中原理类似。

(4)模块化:

function coolModule(){
    var something="cool";
    var another=[1,2,3];
    function doSomething(){
        console.log(something);
    }
    function doAnother(){
        console.log(another);
    }
    return {
        doSomething:doSomething,
        doAnother:doAnother
    }
}
var foo=coolModule();
foo.doSomething();
foo.doAnother();

API的由来:
返回一个对象字面量语法{key:value}来表示对象。
这个用来返回的对象中含有对内部函数而不是内部数据变量的引用。
保持你妹不数据变量是隐藏且私有的状态。
可以将这个对象类型返回值本质上是模块公共的API。

使用模块模式的两个必要条件:
1.必须有外部的封闭函数,该函数必须至少被调用一次
2.封闭函数必须返回至少一个内部函数u,并且可以访问或者修改私有的状态

——————————– 华丽的分割线 ———————-
API模块化

var foo=(function coolModule(id){
    function change(){
        API.ides=id2;
    }
    function ide1(){
        console.log(id);
    }
    function id2(){
        console.log(id.toUpperCase());
    }
    var API={
        change:change,
        ides:ide1
    };
    return API;
})("for module");

foo.ides();
foo.change();
foo.ides();

小结

1.作用域:词法作用域和动态作用域
词法作用域:寻找变量以及在何处找到变量的规则
动态作用域:不关心函数和作用域如何声明和在何处声明的,从何处调用。类似于this

区别:词法作用域是在定义书写的时候确定的,而动态作用域是在运行的时候才确定。

ES6前块作用域的替代方案

// ES6
{
    let a=2;
    console.log(a);    //2
}
console.log(a); //ReferenceError

///ES6前-----利用catch分句有块作用域的特点
try{throw 2;}catch(a){
    console.log(a);  //2
}
console.log(a); //ReferenceError

this的简单词法:
- this动态作用域无绑定,导致函数调用错误问题?

var obj={
    id:"帅",
    cool:function coolFn(){
        console.log(this.id);
    }
}
var id=‘不帅‘;
obj.cool();   //帅
setTimeout(obj.cool,100);

等同于

setTimeout(function coolFn(){
    console.log(this.id);
},100);

访问的居然是全局的id

解决方案:
(1)绑定this,var self=this;

var obj={
    count:0,
    cool:function coolFn(){
        var self=this;
        if(self.count<1){
            setTimeout(function timer(){
                self.count++;
                console.log("帅?");
            },100);
        }
    }
}
obj.cool();

(2)ES6箭头绑定作用域

var obj={
    count:0,
    cool:function coolFn(){
    if(this.count<1){
       setTimeout(()=>{
        this.count++;
        console.log("很帅?");
    },100);
    }
}
obj.cool();  //很帅

(3)用bind绑定this作用域

var obj={
    count:0,
    cool:function coolFn(){
        if(this.count<2){
            setTimeout(function timer(){
                this.count++;
                console.log("更酷了");
                console.log(this.count);
            }.bind(this),100);
        }
    }
}
obj.count=1;
obj.cool();

以上是关于有关作用域的知识的主要内容,如果未能解决你的问题,请参考以下文章

js作用域的相关知识

js有关变量作用域的问题

Python分享:命名空间和作用域的基础知识整合

片段 getActivity 不起作用

Recyclerview 滚动在嵌套滚动视图中的片段中不起作用

作用域知识总结