变量及作用域
JavaScript的变量是松散型(一个变量可以改变类型)。
-
基本类型和引用类型的值
ECMA变量可能包含了两种不同类型的值:基本类型(栈内存),这种值完全保存在内存中的一个位置。引用类型(堆内存),这种值保存实际上是一个指针,也就是一个指针指向内存中的另一个位置,该位置保存对象。
将一个值赋给变量的时候,解析器必须确定这个值的类型是基本类型,还是引用类型。基本类型有以下集中:Undefined、Boolean、Null、Number和String。这些类型再内存中分别占有固定大小的空间,他们的值保存在栈空间,我们通过按值访问。
一些语言中字符串用对象表示,但是ECMAscript的字符串是基本类型。
如果赋值是引用类型的值,则必须在堆内存中分配空间。由于这种值大小不确定,所以不能将其保存在栈内存中。但内存地址大小是固定的,因此可以将内存地址保存在栈内存中。当查询引用类型的变量时,先从栈中读取内存地址,然后通过地址找到堆内存的值。这种叫做按引用访问。
-
动态属性
引用类型可以添加属性方法,但是基本类型不可以。
let obj = { name: 'xiaoming' }; console.log(obj.name); // xiaoming let str = 'xiaoming'; str.age = 18; console.log(str.age); // undefined
-
复制变量值
再变量赋值方面,基本类型和引用类型不同。基本类型复制的是值,引用类型复制的是地址。可就是说,复制的都是栈内存中的数据。
基本类型
不想画图,于是想到这样一个栗子来说他们不一样。
let str1 = 'haha'; let str2 = str1; console.log(str1 === str2); // true 基本类型 复制的是值。 let str3 = 'haha'; console.log(str1 === str3); //true 基本类型比较的是值
let str1 = '2019'; let str2 = str1; str1 = 'nihao'; console.log(str1, str2); // nihao 2019
值类型复制结束后,改变一个变量,不会影响另外一个变量。
引用类型
let obj1 = {a: 1, b: 2}; let obj2 = obj1; console.log(obj1 === obj2); // true // 引用类型地址不同,即便数据相同也不全等 let obj3 = {a: 1, b: 2}; console.log(obj1 === false); // true
引用类型复制:
let obj1 = {a: 1, b: 2}; let obj2 = obj1; obj2.name = '2019'; console.log(obj1); // {a: 1, b: 2, name: "2019"} console.log(obj2); // {a: 1, b: 2, name: "2019"}
引用类型复制的是地址,他们其实都是一个变量,所以给一个变量增加属性,会影响另外一方。注意:但是重新赋值给这个变量,会指向新的地址,不会影响。
-
传递参数
ECMAscritp中所有的函数的参数都是按值传递的,也就是说参数不会有按引用类型传递,虽然变量有基本类型和引用类型之分。
function foo(num) { // 按值传递 num += 10; return num; } let num = 50; console.log(foo(num)); // 60 console.log(num); // 50 如果是按引用传递,那么函数里的num会替换全局的num 也就是说上面应 // 该是60
以上代码传递的是基本类型,而函数num是一个局部变量,和外面的num没有任何关系。
下面来一个参数作为引用类型的栗子:
function foo(obj) { obj.name = 'xiaoming'; // 传递的是引用类型的参数 但是是按值传递的地址 } let obj = {}; foo(obj); console.log(obj.name);
如果按引用传递的话,函数里的变量应该是全局变量,如果PHP参数加上&符号表示引用传递。而js函数内的变量都是独立作用域,都是局部变量。以上两段代码涉及到作用域的问题下面再说。可以大致理解为,js传参,都是在按值传递,所谓的按引用类型传递也是在传递保存有引用类型数据地址的值,所以按引用类型传递和按引用传递不是一回事,也没有按引用传递。
-
检测类型
typeof方法 这里不再重复。
通常我们并不想知道它是不是Object,因为Array、null、RegExp都是Object。那么我们如何检测类型呢?
-
instanceof
基本类型用instanceof,会返回false。除非用
new String()
这种方法产生的基本类型,可以正常判断。console.log([1, 2, 3] instanceof Array); console.log(/^\w+/ instanceof RegExp); console.log({} instanceof Object); //以上都是true
let str1 = 'hello'; console.log(str1 instanceof String); //false //字符串类型比较特别,它与基本类型相似,但是又是不可变引用类型。 let str2 = new String('hello'); console.log(str2 instanceof String); //true
-
-
执行环境及作用域
执行环境实JavaScript中的一个重要概念。执行环境定义了变量或函数有权访问的其他数据,决定了它们各自的行为。
全局执行环境是最外围的执行环境。在web浏览器中,全局环境被认为是window对象。因此所有的全局变量和函数都是作为window对象的属性和方法创建。
var num = 10; function foo() { console.log(num); //全局变量即window的属性 console.log(window.num); // 函数可以访问全局作用域 } window.foo(); //全局函数即window方法
如果用const或者let定义变量,window.num为undefined。因为他们定义的变量不再window中,这点以后再看。
当执行环境所有的代码执行完毕后,该环境被销毁,保存在其中的所有变量及函数也随之销毁。如果是全局环境下,需要程序执行完毕,或者网页被关闭才会销毁。
每个执行环境都有一个与之关联的变量对象,就好比全局的window可以调用变量和属性一样。局部环境也有一个类似window的变量对象,环境中定义的所有变量和函数都保存在这个对象中。(我们无法访问这个变量对象,但是解析器会处理数据时后台使用它)。
函数的局部作用域里的变量替换全局变量,但作用域仅限在函数体内这个局部环境。(函数参数也是局部变量)
var num = 10; function foo() { var num = 20; console.log(num); // 20局部 console.log(window.num); // 10全局 } foo();
var num = 10; function foo(num) { console.log(num); // 20 } foo(20);
-
没有块级作用域
if语句没有块级作用域
if(true) { var num = 10; } console.log(num); //10
for语句没有块级作用域
for(var i = 0; i < 10; i++) { var num = 20; } console.log(i); //10 console.log(num); //20
函数中声明变量如果省略var是全局变量。(这种写法不会使用到)
-
变量会沿作用链向上查找
-
内存问题
JavaScript具有自动垃圾回收机制,也就是说,执行环境会负责管理代码执行过程中使用的内存。开发人员不用关心内存使用问题,所需内存的分配以及无用内存的回收完全实现了自动管理。
这种垃圾回收机制原理很简单:找出那些不再继续使用的变量,然后释放其占用的内存。为此,垃圾收集器会按照固定的时间间隔(或代码执行中预定的收集时间),周期性地执行这一操作。
局部变量的正常生命周期
局部变量只在函数执行的过程中存在。而在这个过程中,会为局部变量在栈(或堆)内存上分配相应的空间,以便存储它们的值。函数执行结束,局部变量就没有必要存在了,因此可以释放它们的内存。
var obj = { name: 'Lee' }; obj = null; //删除对象引用,等待垃圾收集器回收
网友评论