JS

作用域

2021-05-23  本文已影响0人  樱桃小白菜

什么是作用域?

作用域,按字面的意思就是作用的区域。在 JS 代码中无非就是在特定区域(函数、对象)内是否可读(访问)和写(增改查)。
在W3C上 作用域的定义:指的是 有权访问的变量集合。

JS作用域

因为 JS 拥有函数作用域:每个函数创建一个新的作用域。

作用域决定了这些变量的可访问性(可见性)。

函数内部定义的变量从函数外部是不可访问的(不可见的)。

所以作用域分为

全局作用域
局部作用域

全局变量

变量在函数外定义,即为全局变量。

全局变量有 全局作用域: 网页中所有脚本和函数均可使用。

全局变量是全局对象的一个属性。

var carName = " Volvo";
 
// 此处可调用 carName 变量
function myFunction() {
    // 函数内可调用 carName 变量
}
var truevar = 1;            //  声明一个不可删除的全局变量
fakevar = 2;                 //  创建全局对象的一个可删除的属性
this.fakevar2 = 3;        //  同上
delete truevar             //  => false:变量没有被删除
delete fakevar            //  => true:变量被删除
delete this.fakevar2   //  => true:变量被删除

局部作用域

在 JavaScript 函数中声明的变量,会成为函数的局部变量。

局部变量的作用域是局部的:只能在函数内部访问它们。

// 此处的代码不能使用 carName 变量

function myFunction() {
    var carName = "porsche";

    // 此处的代码能使用 carName 变量

}

作用域链

一般情况下,变量取值到 创建 这个变量 的函数的作用域中取值。

但是如果在当前作用域中没有查到值,就会向上级作用域去查,直到查到全局作用域,这么一个查找过程形成的链条就叫做作用域链。

var a = 1;

function fn(){
  console.log(a)
}

function fn1(fn){
  var a = 2;
  (function(){
    fn()    // 1
  })()  
}

fn1(fn)

作用域的判定

在 script 块中所有的变量和函数都是全局变量、全局函数。

多个script之间:自上而下,将上一个变量读到仓库,执行完后

再进行到下一个script,上一个script中的变量值仍存在仓库中

由里到外(局部到全局找变量值)

{}

在浏览器中如何读取 JS 代码

浏览器读取 HTML 文件时,遇到 <script> 标签时会运行“JS解析器”。

第一步:进行预解析

在当前作用域下, JS 代码执行之前,浏览器会默认把带有 varfunction 声明的变量在内存中进行提前声明或者定义。然后在从上到下去执行代码 ,预解析会把变量和函数的声明在代码执行之前执行完成。

变量提升 就是把所有的变量声明提升到当前的作用域最前面 不提升赋值操作

console.log(a)      // undefined
var a = 1

相当于

//  所有的变量,在正式运行代码之前,都提前赋了一个值:未定义 
var a
console.log(a)    // undiefined
a = 1

函数提升 就是把所有的函数声明提升到当前作用域的最前面 不调用函数

fn1()
console.log(c)
console.log(b)
console.log(a)
function fn1(){
  var a = b = c = 1
  console.log(a)
  console.log(b)
  console.log(c)
}

相当于

//  所有的函数,在正式运行代码之前,都是整个函数块
function fn1(){
  var   a
  a = b = c = 1
  console.log(a)
  console.log(b)
  console.log(c)
}
fn1()                  // 1 1 1
console.log(c)  //  1
console.log(b)  //  1
console.log(a)  //  not defined

注意

第二步:读取代码
var a=[]
  // 声明全局变量 i=0 ,i++ 不断对 i 进行赋值 i=10 时不符合条件停止执行
for(var i = 0;i<10;i++){   
   var q = i;    //  声明全局变量 q = i
   a[i]=function(){console.log(q)}
    // i = 10 不执行,存储的内存引用为 q = 9
}
a[0]()  // i = 10 q = 9

不过,在 es6 中新增了 let 命令声明变量,用法和 var 类似,不过 let 所声明的变量,只在 let 命令所在的代码块有效果,for 循环的计数器中就很适合 let 命令

var a=[];
for(let i = 0;i<10;i++){
   let q = i;
   a[i]=function(){console.log(q)}
}
a[6]()  //  6
  //  这里的 i 只在本轮循环有效果,每次循环的 i 其实都是一个新的变量

console.log 被调用的时候,匿名函数保持对外部变量 i 的引用,此时for 循环已经结束, i 的值被修改成了 10。

for(var i = 0; i < 10; i++) {
  setTimeout(function() {
    console.log(i);  //  10次10
  }, 1000);
}

为了正确的获得循环序号,最好使用自执行匿名函数。

for(var i = 0; i < 10; i++) {
  (function(e) {
    setTimeout(function() {
      console.log(e);  
    }, 1000);
  })(i);
}
//外部的匿名函数会立即执行,并把 i 作为它的参数,此时函数内 e 变量就拥有了 i 的一个拷贝。

//当传递给 setTimeout 的匿名函数执行时,它就拥有了对 e 的引用,而这个值是不会被循环改变的。

注意

表达式:= + - * / % ++ -- ! 参数……

let 声明的变量仅在块级作用域有效

上一篇下一篇

猜你喜欢

热点阅读