JS深入学习笔记 - 第三章.变量作用域与内存
1.原始值和引用值
ECMScript变量包含两种不同类型是数据:原始值和引用值。
原始值:最简单的数据。有6中原始值:Undefined、Null、Boolean、Number、String和Symbol。原始值是按值访问。
引用值:由多个值构成的对象。三大引用类型:1.object 2.Array类型 3.Function类型。引用值是保存在内存中的对象,由于js不允许直接访问内存位置,所以对引用值的访问是访问该对象的引用而非对象本身。
这里可以类比java中的基本数据类型和引用数据类型。
2.复制
通过变量把一个原始值复制到另一个另一个变量时,原始值会在内存中增加一个值,赋值为新的变量,如num2上
let num1 = 5
let num2 = num1
此时修改num1 的值,猜一猜会不会对num2有影响?
let num1 = 5; let num2 = num1; num1 = 545454; console.log(num2);//5
答案是不会的,因为在上面提到了,num1与num2对应的是两个独立的值。
但是如果是引用值呢?
let arr1 = [1,2] let arr2 = arr1 arr1.push(6) console.log(arr2)//(3) [1, 2, 6]
欸?arr2也改变了呢,是什么原因呢?
原因是这里的复制的值实际上是一个指针,它指向存储在堆内存中的对象。他们指向的是同一个值。
那如果传递是对象呢?
//对象 function setName(obj) { obj.name = "zooey"; } const person = new Object(); setName(person); console.log(person.name); //zooey
此时对象类型person复制给obj,看起来很像是引用类型的改变,
但是事实上,对象仍然是按值传进函数的,但是obj是按照引用访问的person,怎么?你不信?
//对象 function setName(obj) { obj.name = "zooey"; obj = { name: "anan", }; console.log(obj.name);//anan } const person = new Object(); setName(person); console.log(person.name); //zooey
看吧,的确是没有改掉吧,确实是按值引用。
令人觉得很复杂,对吧。
object对象本身是按值访问的,而对象的属性则是按照引用访问的
3.确定类型
1.typeof
最适合用来判断变量是否是原始类型。更确切的说它是判断一个变量是否是字符串、数值、布尔值或者undefined的最好的方式。
et str = "absce"; let num = 12; let boo = true; let und = undefined; let obj = { name: "aaa" }; console.log(typeof num); //number console.log(typeof str); //string console.log(typeof boo); //boolean console.log(typeof und); //undefined console.log(typeof obj); //object
但是,typeof对引用值的作用不大
2.instanceof
按照定义,所有引用值都是Object的实例,因此通过instanceof操作符检测任何引用值和Object构造函数都会返回true。
类似的,如果用instanceof检测原始值,则始终会返回false,因为原始值不是对象。
let arr = [1, 23, 4]; let fun = () => { console.log("i am function"); }; class Myclass { constructor() { name; } } function Cons() { return "sss"; } console.log(typeof arr); //object console.log(typeof fun); //function console.log(typeof Myclass); //function console.log(typeof Cons); //function console.log(arr instanceof Array); //true console.log(fun instanceof Function); //true console.log(Myclass instanceof Function); //true console.log(Cons instanceof Function); //true
4.执行上下文和作用域
1.什么是执行上下文(以下简称上下文)?
变量和函数的上下文决定了它们可以访问哪些数据,以及他们的行为。
每个上下文都有一个关联 变量对象(variable object),而这个上下文中定义的所有变量和函数都存在于这个对象上。虽然没办法通过代码真正的访问它。
2.什么是全局上下文?
最外层的上下文。在浏览器中,全局上下文就是我们常说的window对象,因此,所有通过var定义的全局变量和函数都会成为window对象的属性和方法
3.上下文什么时候销毁?
上下文在其所有的代码都执行完毕后会被销毁,包括定义在它上面的所有变量和函数。
全局上下文在应用程序退出前才会被销毁,比如关闭页面或者退出浏览器。
4.上下文的执行流程
每个函数调用都有自己的上下文。当代码执行流进入到函数时,函数的上下文被推到一个上下文栈上,在函数执行文之后,上下文栈会弹出该函数上下文,将控制器泛黄之前的执行上下文。ECMAScript程序的执行流就是通过这个上下文栈进行控制的。
上下文中代码在执行的时候,会创建变量对象的一个作用域链(scope chain),这个作用域链决定看各级上下文中代码在访问变量和函数时的顺序。代码正在执行的上下文的变量对象始终位于作用域的最前端。
5.标识符解析在上下文中的寻找规则
代码执行时的标识符解析时通过沿着作用域链逐级搜索标识符名称完成的。搜索过程始终从作用域链的最前端开始,然后逐级往后,直到找到标识符。(如果没有找到则会报错)
let name = "zooey"; function getName() { let myname = "anan"; if (name === "zooey") { name = myname; } else { name = "zooey"; } } getName(); console.log(name); //anan
在这个例子中,函数getName的作用域链包含两个对象“一个是他自己的变量对象myname,一个是全局上下文的变量对象name,之所有能够访问color,就是因为可以在作用域链中找到它。
此外,局部作用域中定义的变量可以用于在局部上下文中替换全局变量。
总结一下:
类似于java的局部变量和全局变量,
局部上下文中的变量只能在本作用域中使用
全局上下文中的变量可以在全局使用
如果包含嵌套关系,那么父作用域中的变量,子作用域也可以使用 。
即,内部上下文可以通过作用域链访问外部上下文中的一切,但外部上下文无法访问内部上下文中的任何东西。
每个上下文都可以到上一级上下文中去搜索变量和函数,但任何上下文都不能去下一级上下文中去搜索。
(此处举一个不恰当的例子,儿子可以继承父亲的房子,换掉父亲的凳子椅子,但是父亲不能继承儿子的房子,换掉儿子的家具,儿子会生气的~)
注意:函数参数(实参)被认为是当前上下文中的变量,因此也跟上下文中的其他变量遵循相同的访问规则。