Skip to content

Latest commit

 

History

History
147 lines (100 loc) · 6.33 KB

4.JS--变量、作用域、与内存.md

File metadata and controls

147 lines (100 loc) · 6.33 KB

一、原始值与引用值

原始值就是简单的数据;引用值则是由多个值构成的对象。

原始值: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引擎不再使用这种算法。
  • 引用计数在代码中存在循环引用时会出现问题。
  • 解除变量的引用不仅可以消除循环引用,而且对垃圾回收也有帮助。为促进内存回收,全局对象、全局对象的属性和循环引用都应该在不需要时解除引用。