最近一直在看红宝书,说实话看的有点费劲,一些深点的知识点就能让我思考许久,例如:参数传递都是按值传递的、环境执行栈、做用域链等。我想了想,仍是决定经过写博客作笔记的方式记录下本身的学习过程和经验,一方面有利于加深印象和回顾;另外一方面也是对本身的一种督促吧,期待半年后再看的时候,能有新的感悟和理解。固然,也但愿经过这种形式,你们能给我提一些建议和指导。前端
ECMAScript 变量可能包含两种不一样数据类型的值:基本类型值和引用类型值。基本类型值指的是简单的数据段,而引用类型值指那些可能由多个值构成的对象。web
这里先简单说一下两个概念:堆内存和栈内存。数组
堆内存:先进先出(相似单向的管道,排队往前走,先进的先出去);是动态分配的内存,大小不一且不会被系统自动释放。 浏览器
栈内存:后进先出(相似只有一个出口的管道,后面进入的先出去);是自动分配相对固定大小的内存,且会被系统自动释放。bash
Undefined、Null、Boolean、Number和String。他们都是按值存储在栈内存中,每种类型的数据占用内存的大小都是固定的,由系统自动分配和释放。函数
var a = 10;
var b = a;
b = 20;
console.log(a); // 10
console.log(b); // 20复制代码
除了上述五种基本类型外都是引用类型,例如Object,Array,Function等。js不容许直接访问堆内存中的位置,也就是说不能直接操做对象的内存空间。在操做对象时,实际是在操做对象的引用而不是实际的对象,是按地址访问的。( 即:引用类型数据在栈内存中保存的其实是对象在堆内存中的引用地址,经过这个引用地址能够快速查找到保存中堆内存中的对象。)学习
var obj1 = new Object();
var obj2 = obj1;
obj2.name = 'hexh';
console.log(obj1.name); // hexh复制代码
obj1和obj2指向了堆内存中的同一个对象。var obj2 = obj1;,其实是将这个存储在堆内存中的对象,在栈内存的引用地址复制了一份给obj2,可是实际上他们共同指向了同一个堆内存对象,因此修改obj2其实就是修改那个对象,因此经过obj1访问也能访问的到。ui
ECMAScript 中全部函数的参数都是按值传递。也就是说,把函数外部的值复制给函数内部的参数,就和把值从一个变量复制到另外一个变量同样。基本类型值的传递如何基本类型变量的复制同样,而引用类型值的传递,则如同引用类型变量的复制同样。spa
function sum (num) {
num += 10;
return num;
}
ver count = 20;
var res = sum(count);
console.log(count); // 20
console.log(res); // 30复制代码
函数 sum() 有一个参数 num,而参数 num 其实是该函数的局部变量。在调用 sum() 函数时,变量 count 做为参数传递给该函数时,值是20。因而 20 被复制给参数 num 以便在 sum() 中使用。在函数内部,参数 num 的值被加上了 10,但这个操做不会影响函数外部的 count 变量。参数 num 和 count 互不认识,它们仅仅是具备相同的值。3d
例子1
function setName (obj) {
obj.name = 'hexh'
}
var person = new Object();
setName(person);
console.log(person.name); // hexh复制代码
建立一个对象,并将其保存在变量person中。而后该变量传递到 setName() 函数中以后,被复制给了 obj。在这个函数内部,obj 和 person 引用的是同一个对象。(即时这个变量是按值传递的,obj 也会按引用来访问同一个对象。)
有不少开发人员错误地认为:在局部做用域中修改的对象在全局做用域中反映出来,就说明参数是按引用传递的。
例子2
function setName (obj) {
obj.name = 'hexh'
obj = new Object();
obj.name = 'test'
}
var person = new Object();
setName(person);
console.log(person.name); // hexh复制代码
经过例子2能够看出,若是 person 是按引用传递的,那么 person 会被指向 obj 新对象。但打印 person.name,显示的值仍然是 "hexh"。这说明即便在函数内部修改了参数的值,但原始的引用仍然保持不变。
总结:
当引用类型的数据 Object 传递给函数时,也是使用的值传递。我是这样理解的:
此时传递给函数的是 Object 在栈内存中的 “引用地址”(A)。
“A” 被传递给函数的过程,实际上是复制了 Object 在栈内存中的 “引用地址”(A) ,并将复制的结果赋值(值传递)给了函数的参数 “B” 。因为 “A” 和 “B”的引用地址都是指向堆内存的同一个对象,因此在函数中经过 “B” 修改对象属性的值时,打印 “A” 的结果显示和 “B” 一致。符合了例子1中的结果。而例子2的函数中,将 “B” 从新 new 了一个Object对象,至关于改变了 “B” 的引用地址,此时 “A” 和 “B” 是互不关联的。
执行环境(execution context, EC)是JavaScript中最为重要的一个概念。执行环境定义了变量或函数有权访问的其余数据,决定了它们各自的行为。每一个执行环境都有一个与之关联的变量对象(variable object,VO),环境中定义的全部变量和函数都保存在这个对象中。
每当程序的执行流进入到一个能够执行的代码中时,就进入到了一个执行环境中。
执行环境分为三种:全局执行环境、函数执行环境、Eval执行环境。
执行流依次进入的执行环境在逻辑上造成了一个栈,栈的底部永远是全局执行环境的变量对象,栈的顶部则是当前执行的代码所在环境的变量对象(浏览器老是执行处于栈顶的上下文)。
var a = "global";
function example () {
console.log(a);
}
function outer () {
var b = "outer";
console.log(b);
function inner () {
var c = "inner";
console.log(c);
example();
}
inner();
}
outer();复制代码
咱们能够经过数组的形式表示上面代码的执行环境栈。程序首先进入全局执行环境(GlobalContext),此时环境栈中已经存放了全局执行环境。而后程序依次调用了outer、inner和example函数,每次调函数时都会建立一个函数执行环境放入环境栈中。当前的环境栈存储状况以下:
ECStack = [
// 栈顶
example(),
inner(),
outer(),
GlobalContext
// 栈底
]复制代码
个人我的理解是:环境栈就是用来存储当前程序的执行内容和顺序。
当代码在一个环境中执行时,会建立变量对象的一个做用域链。做用域链的用途,是保证对执行环境有权访问的全部变量和函数的有序访问。
做用域链的前端始终是当前执行代码的环境的变量对象。若是这个环境是函数,则将函数的活动对象(activition object,AO)做为变量对象。活动对象在最开始时,只包含一个变量,即 arguments 对象(这个对象在全局环境中是不存在的)。
做用域链的末端始终是全局执行环境的变量对象。
因此做用域链中的内容都是变量对象。
那做用域链究竟是什么呢?咱们先看一个例子:
var color = "blue";
function getColor () {
return color;
}
console.log(getColor()); // blue复制代码
在这个例子中,做用域链从前到后的顺序是:getColor函数的活动对象、全局执行环境的变量对象。
当咱们调用 getColor() 时,首先搜索 getColor() 的变量对象,查找名为 color 的标识符,在没有找到的状况下,搜索下一个变量对象(这里就是全局执行环境的变量对象),而后找到了名为 color 的标识符。这个查找链路就是例子中的做用域链,而它的做用也正是 保证对执行环境有权访问的全部变量和函数的有序访问。
即:内部环境能够经过做用域链访问全部的外部环境,可是外部环境不能访问内部环境中的任何变量和函数。
虽然执行环节的类型只有:全局和局部(函数)两种,可是能够经过:
对做用域链进行延长。对 with 语句来讲,会将指定的对象添加到做用域链中。对 catch 语句来讲,会建立一个新的变量对象,其中包含的是被抛出的错误对象的声明。
红宝书中这章的内容我看了好久,到如今仍是处于一种似懂非懂的状态,总感受还差点什么。上面的一些我的的理解,可能还存在很多问题,你们要是对这块了解比较深刻,有什么好的理解和想法,还请和我分享一下,谢谢!
另外,各位小哥哥小姐姐,点个赞吧~