js 高级程序设计 第四章总结

时间:2023-01-21 00:11:18

变量,作用域和内存问题

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 在上面第一种情况下都会导致错误。

 

查询标识符:

当某个环境为了读取或写入而引用一个标识符的时候,必须通过搜索来确定该标识符实际代表什么。

搜索的过程从作用域链前端开始,向上逐级查询与给定名字匹配的标识符。如果在局部变量中找到了该标识符,搜索过程停止。如果在局部变量中没找到该变量名,就会沿作用域链向上搜索,一直追溯到全局环境的变量对象。如果全局变量都没找到这个标识符,则意味着该变量未声明(或者不在能够访问的范围内)。