原始值就是简单的数据;引用值则是由多个值构成的对象。
原始值:Undefined、Null、Number、String、Boolean、Symbol,保存原始值的变量是按值访问的,实际上操作的就是存储在变量中的实际值。
引用值:是保存在内存中的对象,保存引用值的变量是按引用访问的,实际上操作的是对该对象的引用而非实际的对象本身。
-
动态属性
对于引用值而言,可以随时添加、修改、和删除某属性和方法。
-
复制值
let num1 = 5 let num2 = num1 console.log(num1, num2); // 5 5 num2 = 6; console.log(num1, num2); // 5 6
这里,num1包含数值5;当把num2初始化为num1时,num2也会得到数值5。这个值跟储存在num1的5是完全独立的,因为他是那个值的副本。
把引用值从一个变量赋给另一个变量时,存储在变量中的值也会被复制到新变量所在的位置。区别在于,这里复制的值实际上是一个指针,它指向存储在堆内存中的对象。操作完后,两个变量实际上指向同一个对象,因此一个对象上面的变化会在另一个对象上反映出来。
let obj1 = { name: 'jack' } let obj2 = obj1; console.log(obj1.name, obj2.name); // 'jack' 'jack' obj2.name = 'lucy' console.log(obj1.name, obj2.name); // 'lucy' 'lucy'
这里,变量obj1保存了一个对象,然后被赋值到obj2,这时两个变量都指向了同一个对象,所以obj1,obj2的那么都是'jack';然后对这同一个对象改变name值。因此obj1和obj2的name值都变成了'lucy'。
-
传递参数
ECMAScript中所有函数的参数都是按值传递的。这意味着函数外的值会被复制到函数内部的参数中,就像从一个变量复制到另一个变量一样。
function addTen(num) { num += 10 return num } let count = 20 let result = addTen(count) console.log(count); // 20 console.log(result); // 30
在函数内部,参数num的值被加上了10,但这不会影响函数外部的原始变量count。
function setName(obj) { obj.name = 'lucy' } let objP = { name: 'jack' } setName(objP) console.log(objP.name); // 'lucy' function setName2(obj) { obj.name = 'lucy' obj = {} obj.name = 'greg' } let objP2 = { name: 'jack' } setName2(objP2) console.log(objP2.name); // 'lucy'
这两个例子可以看到,函数参数是按值传递的。只是第一个例子中,即使对象按值传入函数,obj也会通过引用访问对象。当函数内部的obj设置了name属性时,函数外部的对象也会反映这个变化,因为obj指向的对象保存在全局作用域的堆内存上。
-
确定类型
typeof操作符最适合用来判断一个变量是否为原型类型。更确切的说,是判断字符串、数值、布尔值、undefined的最好方式。对原始值很有用,对引用值用处不大。
instanceof操作符是检测任何引用值和Object构造函数都会返回true。
变量与函数的上下文决定了他们可以访问哪些数据,以及他们的行为。
全局上下文是最外层的上下文(window对象)。全局上下文在应用程序退出前才会被销毁,比如关闭网页或退出浏览器。
每个函数都有自己上下文。当代码执行流进入函数时,函数的上下文就被推到一个上下文栈上,在函数之心完之后,上下文栈会弹出该函数上下文,把控制权还给之前的执行上下文。
上下文在执行的时候,会创建一个作用域链。
标记清理、引用计数
性能:垃圾回收程序会周期性运行,如果内存中分配了很多变量,则可能造成性能损失,因此垃圾回收的时间调度很重要
内存管理:如果数据不必要,那么把它设置为null,从而释放其引用。这个叫做解除引用。
1.通过const、let声明提升性能
不仅有助于改善代码风格优化,而且同样有助于改进垃圾回收的过程。因为const和let都以块(非函数)作为作用域。所以相比于使用var,使用这两个新关键字可能会更早的让垃圾回收程序介入。
2.隐藏类和删除操作
最佳实践:是把不想要的属性设置为null,这样可以保持隐藏类不变和继续共享,同时也能达到删除引用值供垃圾回收程序回收的效果。
3.内存泄漏
意外声明全局变量是最常见也是最容易修复的内存泄漏问题。
定时器也会悄悄导致内存泄漏。
js闭包。
4.静态分配与对象池
原始值和引用值的特点:
- 原始值大小固定,因此保存在栈内存上
- 从一个变量到另一个变量复制原始值会创建该值的第二个副本
- 引用值是对象,存储在堆内存上
- 包含引用值的变量实际上只包含指向相应对象的一个指针,而不是对象本身。
- typeof操作符可以确定值的原始类型,而instanceof操作符用于确保值的引用类型。
执行上下文:
- 执行上下文分全局上下文,函数上下文和块级上下文。
- 代码执行流每进入一个新的上下文,都会创建一个作用域链,用于搜索变量和函数。
- 函数与块的局部上下文不仅可以访问自己作用域内的变量,而且也可以访问任何包含上下文乃至全局上下文中的变量。
- 全局上下文只能访问全局上下文中的变量和函数,不能直接访问局部上下文中的任何数据。
- 变量的执行上下文用于确定什么时候释放内存。
js垃圾回收程序
- 离开作用域的值会被自动标记为可回收,然后在垃圾回收期间被删除。
- 主流的垃圾回收算法是标记清理,即先给当前不使用的值加上标记,再回来回收他们的内存。
- 引用计数是另一种垃圾回收策略,需要记录值被引用了多少次,js引擎不再使用这种算法。
- 引用计数在代码中存在循环引用时会出现问题。
- 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。