【高性能JavaScript】读书笔记 - 数据存取(一) - 04


【简介】第一部分将js中涉及到的数据进行了分类;第二部分我们详细叙述了作用域和作用域链的概念,以及相关的应用。

1. 数据存取(Data Access)

数据存储的位置关系到代码执行过程中数据的检索速度,通过改变数据的存储位置来实现最佳的读写性能。

字面量:

只代表自身,不存储在特定位置,JavaScript中的字面量有:字符串、数字、布尔值、对象、数组、函数、正则表达式,以及特殊的null和undefined。

本地变量:

通过关键字var创建的数据存储单元。

数组元素:

存储在数组对象内部,以数字作为索引。

对象成员:

存储在对象内部,以字符串作为索引。

每一种数据存储的位置都有不同的读写消耗。大多数情况下,从一个字面量存取数据的性能差异是微不足道的,而访问数组元素和对象成员的代价则要高一些。
所以,如果在乎运行速度,那么尽量使用字面量和局部变量,减少数组项和对象成员的使用。

2. 管理作用域(Managing Scope)

先来梳理一下作用域的相关概念:

  • 作用域(Scope):控制着变量和函数的可访问范围。作用域按这个可访问范围可分为全局作用域和局部作用域。
  • 全局作用域(Global Scope):分三种情况。
    a,最外层函数和最外层变量。
<script type="text/javascript">
// 在最外层的变量在全局作用域中
var name = "globe";

// 在最外层的函数在全局作用域中
function sum(num1, num2){
    return num1+num2;
}
</script>

b,没有通过var声明的变量。

<script type="text/javascript">
function sum(num1, num2){
    // 没有通过var定义的变量,即使在函数体内,也是属于全局作用域
    sum = 0;

    return num1+num2;
}
</script>  

c,window对象的属性。比如window.location等,实际上,所有的全局变量和全局属性都是window对象的属性。

全局作用域内的变量和函数内被

  • 局部作用域(Local Scope):函数体内的函数变量。
<script type="text/javascript">
function fun(){
    // 函数体内的变量在局部作用域内
    var name = "局部变量";

    // 定义在函数内的函数在局部作用域内
    function innerFun() {
        console.log("局部函数");
    }
}
</script> 

注意1:局部作用域内的函数和变量不能被在全局作用域内调用,全局作用域中的函数和变量能在局部作用域内调用。

<script type="text/javascript">
var name = "全局变量";

function fun() {    
    console.log("全局函数");

    another = "未通过var定义的全局变量";

    var local = "局部变量";

    function innerFun() {
        console.log("内部函数");
    }
// 全局作用域中的函数和变量能在局部作用域内调用
console.log(name);

}

// 全局变量name和全局函数fun()能正常在全局中调用
console.log(name);
fun();

// 未通过var定义的变量another能被调用,说明这是个全局变量
console.log(another);

// 局部变量local和局部函数innerFun()不能在全局中调用
console.log(local);
innerFun();
</script>  

注意2:只有在函数体内定义的变量和函数才算是局部变量和局部函数,在局部作用域内;在流程控制(if-else,while,for等)中的变量和函数不属于局部变量和局部函数。

<script type="text/javascript">
var name="原来的名称";  
if(true){  
    var name="新的名称";  
    console.log(name);  
} 
// if语句中的定义的name不是局部变量,而是全局变量
// 相当于对name重新赋值了,所以输出"新的名称"
console.log(name);

var i =1;
for (var i = 0; i < 3; i++) {} 
// for循环中的i变量也不是局部变量,而是全局变量,所以输出的i=3
console.log(i); 
</script> 
  • 作用域链(Scope Chains):决定了哪些函数和变量能被访问以及访问的优先级。

要理解这句话,我们仍然来看这个例子,不过我们稍微做一下修改,将name重新在函数体内重新定义一下:

<script type="text/javascript">
var name = "全局变量";

function fun() {    
    console.log(name);
    var name = "局部变量";
}

fun();
</script>  

那么在这种情况下 console log 打印 name 会是什么信息呢?如果你觉得是「全局变量」(正确的答案应该是「undefined」),那么你就应该好好往下看,因为你没有建立起作用域链的概念。

作用域链简单地说,就是函数在执行的时候,首先在离他最近的作用域中找相关变量和函数,在最近的作用域中找不到的情况下,才会一层一层往外找。
所以 fun() 函数首先在它本身的局部作用域中找name变量,注意 JS 有一个特性,在运行脚本之前,有一个预加载的过程,它会对变量、函数等进行注册,但不进行赋值操作。所以,此时局部作用域中的 name 值为「defined」,console log 在离它最近的局部作用域中找到了 name ,就输出了这个 name 的值「undefined」。

我们用一个流程来表示它的作用域链:fun() -> window;多层嵌套情况下也是一样的,比如下面这个 innerFun()中的作用域链为:innerFun() -> fun() -> window.

<script type="text/javascript">
var name = "全局变量";

function fun() {    
    console.log(name);
    var name = "局部变量";

    function innerFun() {
        console.log(name);
        var name = "inner局部变量";
    }
}

fun();
</script> 

实际上这个作用域链是一个函数对象的一个内部属性[[Scope]]。
每一个JavaScript函数都可以表示为Function对象的的一个实例。这个Function对象有一个仅供JavaScript引擎存储的内部属性[[Scope]],其中包含了一个函数被创建的作用域中对象的集合–作用域链。这个作用域链以「键值对」的方式存储对象,实际上,这些存储的对象就是作用域。

我们回过头来看这个函数:

// 函数创建
function fun() {    
    var name = "局部变量";
    console.log(name);
}

fun() 函数在创建的时候,这个函数作用域链 0 角标位置就插入了一个对象(全局对象),这个全局对象就包含了我们所熟悉的window、document等元素,因此我们能够直接使用这些元素。

作用域链1

fun(); // 函数执行

当我们执行这个函数的时候,会创建一个内部对象,我们称之为执行环境(execution context)。这个函数的作用域链就会变成这样:
这里写图片描述

在这个执行环境的最前端会被推入一个活动对象(activation object),这个活动对象会随着函数的执行,记录相应的局部变量、命名参数以及this。这个活动对象随着函数的执行(执行环境的创建)而创建,随着函数的结束(执行环境的销毁)而销毁。这就解释了为什么局部变量和局部函数不能被直接调用,因为他们只随着函数的执行而存在。

而在函数执行过程中,每次遇到变量需要解析,就会从作用域链的 0 角标位置开始查找,找到了这个变量,就调用这个变量,找不到再从 1 角标位置中找。这就是作用域链决定的访问的优先级。

优化作用域链(Optimization Scope Chains)

在执行环境的作用域链,一个变量所在的位置越深,它的读写速度就越慢。因此,作用域链的优化原则就是要将这些深位置的变量存储到浅位置来,尤其是一段脚本中使用频率比较高的变量。

<script type="text/javascript">
function initUI() {
    var bd = document.body,
        links = document.getElementsByTagName('a'),
        i = 0,
        len = links.length;

    while(i < len) {
        update(links[i++]);
    }

    document.getElementById("go-btn").onclick = function() {
        start();
    }
    bd.className = "active";
}
</script>  

对于这样一段代码,document对象被调用了多次,而document对象又是一个全局对象,搜索这个变量的过程必须遍历整个作用域链,知道最后在全局变量对象中找到。所以我们将document对象单独存到一个局部变量中,缩短它的调用深度。

<script type="text/javascript">
function initUI() {
    var doc = document,
        bd = doc.body,
        links = doc.getElementsByTagName('a'),
        i = 0,
        len = links.length;

    while(i < len) {
        update(links[i++]);
    }

    doc.getElementById("go-btn").onclick = function() {
        start();
    }
    bd.className = "active";
}
</script> 

尤其是碰到更多次的调用的时候,性能的改善将会是显著的。

参考博客
[1] JavaScript 开发进阶:理解 JavaScript 作用域和作用域链
[2] Js作用域与作用域链详解
[3] JavaScript作用域链

附: 欢迎大家关注我的新浪微博 - 一点编程,了解最新动态 。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值