变量,作用域和内存问题
ES变量包含两种不同数据类型的值,基本类型和引用类型。
基本类型有五种:Undefined, Null, Boolean, Number, String. 引用类型是保存在内存中的对象。
我们在访问基本类型和引用类型的时候会有一些差别。
1.1.1 复制变量值
var num1 = 5;
var num2 = num1;
此时内存中 num1 和 num2 是完全独立的,修改任一个变量的值对另一个变量都不会有影响。因为 5 是一个基本类型值,从一个变量向另一个变量复制基本类型的值的时候,会直接在变量对象上创建一个新值,然后把值分配到为新变量分配的位置上。
但是当你从另一个变量向另一个变量复制引用类型的值的时候,复制结束时,实际上创建的是一个新的指向该对象的指针,所以两个变量实际上会引用同一个对象。
var obj1 = new Object();
var obj2 = obj1;
obj1.name = "Nich";
console.log(obj2.name); // "Nich"
1.1.2 传递参数
ES中所有函数都是按值传递。
即会把函数的参数先传给函数内部的变量,再操作变量。
可以这样理解,假如我们先声明了一个函数,需要一个参数,然后对这个变量进行一些处理:
function add1(num) {
num++;
}
var a = 5;
add1(5); // a = 5;
这个函数执行完之后,a 的值仍然是 5
在把 a 当做参数传进函数的时候,可以把内部发生的看做 :
function add1(num) {
num = a;
num++;
}
由于 a 是个基本类型,在执行 num = a 的时候,相当于直接创立了一个新值,后面的操作对 a 是不会有影响的。
那就同样可以理解给函数传入对象的时候发生的事情了。
function addName(obj) {
obj.name = "Nich";
}
var myObj = {};
addName(myObj); // myObj.name = "Nich";
貌似函数影响到了外部的值,函数内部仍然发生了类似上面的过程,传入 myObj 的时候,内部相当于这么处理
function addName(obj) {
obj = myObj;
obj.name = "Nich";
}
但是记住在试图通过 obj = myObj 来复制一个对象的时候,实际上是取得了一个对象引用。通过任一变量对对象进行的操作都会立即在另一个变量上体现出来。因此这并不能说明ES中函数是按引用传递的。
1.1.3 检测类型
这里介绍一下 typeof 和 instanceof 操作符
前面说过 typeof 有六种返回值,分别是 'undefined', 'boolean', 'string', 'number', 'object', 'function'
typeof 在确定一个变量是字符串,数值,布尔值还是 undefined 时很简单,但是对象是一个对象或者 null 的话,都会返回 object
当我们想知道某个值是什么类型的对象的时候,可以使用 instanceof 操作符,他会判断一个变量是否是给定引用类型的实例,返回true 或者 false。
实际上 Object.prototype.toString.call() 可以很好的判断 某个变量是哪种数据类型。
var number = 5;
var string = "hello";
var nul = null;
var notDefined;
var obj = {};
var boo = true;
console.log(Object.prototype.toString.call(number)
, Object.prototype.toString.call(string)
,Object.prototype.toString.call(nul)
,Object.prototype.toString.call(notDefined)
,Object.prototype.toString.call(obj)
,Object.prototype.toString.call(boo));
// [object Number] [object String] [object Null] [object Undefined] [object Object] [object Boolean]
执行环境及作用域
执行环境:执行环境定义了变量或函数有权访问的其他数据。每个环境都有一个与之关联的变量对象,环境中定义的所有变量和函数都保存在这个对象中。
当代码在一个环境中执行的时候,会创建对象变量的一个作用域链,以保证对执行环境有权访问的所有变量和函数的有序访问。
作用域链的前端始终都是当前执行的代码所在的环境的变量对象,下一个变量对象来自包含环境(外部环境),再下一个变量则来自下一个包含环境,一直延续到全局执行环境。
标识符即是沿着作用域链一级一级地搜索标识符的过程。
每个环境都可以向上搜索作用域链,查询变量和函数,但是任何环境都不能通过向下搜索所用域链进入另一个执行环境。
2.1.1 没有块级作用域
块级作用域:某些语言中,由花括号封闭的代码块都有自己的作用域(即自己的执行环境),出了该作用域就无法访问里面定义的变量了。
但在ES中不同,if 语句块里面定义的变量出了花括号之后依然可以访问,这是由于 var 声明符的函数作用域特性导致的
if (true) {
var color = "red";
}
console.log(color); // red
function name() {
var a = "nich";
}
console.log(a); // ReferenceError
在ES6中新增了 const 和 let 声明符,可以提供块级作用域,可以避免很多由于 var 导致的 bug。最佳实践:任何情况下都使用 const 声明变量,除非一定要对变量做出修改,则使用 let 操作符声明变量。
const 和 let 在上面第一种情况下都会导致错误。
查询标识符:
当某个环境为了读取或写入而引用一个标识符的时候,必须通过搜索来确定该标识符实际代表什么。
搜索的过程从作用域链前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部变量中找到了该标识符,搜索过程停止。如果在局部变量中没找到该变量名,就会沿作用域链向上搜索,一直追溯到全局环境的变量对象。如果全局变量都没找到这个标识符,则意味着该变量未声明(或者不在能够访问的范围内)。