JavaScript-变量,作用域,闭包,this,内存问题

386 阅读6分钟

ECMAScript 定义变量包含两种不同的数据类型的值

基本类型值 简单的数据段

  • Null, Undefined,String, Boolean, Number

  • 按值访问

  • 复制

    • 基本类型的复制后,值是独立的

      var n=5//独立
      var m =n//独立
      m=4//不影响n
      

引用类型的值,可能由多个值构成的对象

  • 值是内存中的对象,按引用访问

  • Javascript不允许直接访问内存中的位置,也就是说不能直接操作内存空间,操作的是对象的引用,不是实际对象

  • 可以添加,改变,删除属性,方法

    var p = new Object();
    p.name='haha';
    console.log(p.name)//'haha';
    
  • 复制

    • 实际是一个指针,指向内存中堆的一个对象,两个变量实际上引用同一个对象

      var oj1=new Object();
      var oj2=oj1;
      oj1.name='d'
      console.log(oj2.name)//d
      

传递参数

ECMAScript中所有函数的参数都是按值传递的

就是函数的外部的值,复制给函数内部的参数

  • 基本类型,如复制值,函数内外独立
  • 引用类型,会把内存中的地址复制给函数内部的一个局部变量,局部变化可以反应到外部
function add(num){
    num +=10;
    return num;
}
var c = 20;
var r=add(c);
console.log(c)//20
​
function setName(obj){
    obj.name='e';
}
var person = new Object();
setName(person);
console.log(person.name);//e

为何说参数是按照值传递的

function setName(){
    obj = arguments[0]//复习函数章节,arguments对象
    obj.name='N';//反应到a对象,添加了name属性,值为N
    obj = new Object();
    obj.name='G'
}
const a = new Object();
setName(a);
console.log(a.name)//N

实际函数内部修改了obj对象,但是原始的a对象的引用没有发生改变, obj变成了局部变量,函数执行完毕后obj被销毁。

执行环境及作用域

  • 每个执行环境都有一个与之关联的变量对象

  • 环境中所有的变量和函数都保存在这个对象中

  • Web浏览器,全局执行环境是window对象

  • 环境中所有代码执行完毕后,环境会被销毁,其中的变量,函数也被销毁

  • 环境栈

    • 每个函数都有自己的执行环境, 全局或局部
    • 执行一个函数会被推入一个环境栈中,执行后,环境弹出栈,控制权交给之前的执行环境(ECMAScript 程序中的执行流)
  • 当代码在一个环境中执行,会创建变量对象的一个作用域链

    • 作用域链的用途是保证所有变量和函数的有序访问

    • 作用域链的前端始终是当前执行的代码所在环境的变量对象

    • 如果环境是函数,将其活动对象作为变量对象,arguments作为第一个变量(全局环境不存在arguments)

    • 全局执行环境的变量对象始终是作用域链最后的一个对象

      const color = 'blue';
      ​
      function changeColor() {
          const anotherColor = 'red';
      ​
          function swapColors() {
              const tempColor = anotherColor;//red
              anotherColor = color;//blue
              color = tempColor;//red
          }
      ​
          swapColors();
      }
      ​
      changeColor();
      

作用域.png

  • 内部环境可以通过作用域访问所有外部环境

  • 外部环境不能访问内部环境是变量和函数

  • 环境可以向上搜索作用域链,查询变量,和函数, 不可以向下收索进入内部环境

  • 延长作用域链,临时,添加一个变量对象到作用域链的前端

    • try-catch 中的catch
    • with语句

没有块级作用域

  • var声明的变量,会添加到最接近的环境中,函数内部,最接近的环境就是函数的局部环境
if(true){
    var c='blue'
}
console.log(c)//blue
for(var i=0;i<10;i++){
    doSomething(i)
}
console.log(i);//10

闭包

闭包是指有权访问另一个函数作用域中的变量的函数

创建方式

function a() {
    const a = 'ddd';
    return function () {
        console.log(this.a);//作用域链包含了外部作用域
    }
}
var aF= a();
aF();//ddd

理解

  1. 当某个函数被调用时,会创建一个执行环境,及相应的作用域链
  2. 使用arguments和其它命名参数的值来初始化函数的活动对象
  3. 作用域链中,外部函数的活动对象处于第二位,外部函数的外部函数的活动对象处于第三位,直至作为作用域终点的全局执行环境
  4. 作用域链本质上是一个指向变量对象的指针列表
  5. 一般来讲,当函数执行玩后,局部活动对象会被销毁,内存中仅保存全局作用域(全局执行环境的变量对象)
1.创建compare()函数时,会创建一个预先包含全局变量对象的作用域链,这个作用域链保存在内部的[[scope]]属性中
2.调用compare()函数时,会为函数创建一个执行环境,通过复制函数的[[scope]]属性中的对象构建执行环境的作用域链。
3.然后,有一个活动对象被创建推入执行环境作用域链的前端。
function compare(value1,value2){
    if(value1<value2){
        return -1;
    }
}
var result = compare(5,10);

闭包有所不同

function a() {
    const a = 'ddd';
    return function () {
        console.log(this.a);//作用域链包含了外部作用域
    }
}
var aF= a();
aF();//ddd
  • 当闭包的匿名函数的作用域链在外部函数a执行完后,a的活动对象没有被销毁,因为匿名函数还引用着这个活动对象
  • a函数返回后,其执行环境的作用域链会被销毁,但它的活动对象仍然留在内存中,被内部匿名函数引用着。

闭包与变量

  • 闭包只能包含函数中任何变量的最后一个值(现在执行,不存在这个问题,有待考究)
function createFunctions() {
    var result = new Array();
    for (let i = 0; i < 10; i++) {
        result[i] = function () {
              return i;
        };
    }
    return result;
}
​
const result = createFunctions();
console.log(result[7]());//7


function createFunctions1 () {
    var result = new Array();
    
    for (var i = 0; i < 10; i++) {
        result[i] = function () {
            return i;
        }
    }
    
    return result;
}

var funcArray = createFunctions1();
for (let func of funcArray) {
    console.log(func())//这样输出都是10
}


function createFunctions1() {
    var result = new Array();
    for (let i = 0; i < 10; i++) {
        result[i] = function (num) {
          return function () {
              return num;
          }
        }(i);
    }
    return result;
}
const result = createFunctions1();
console.log(result[7]());//7

关于this对象

  • this对象是在运行时基于函数的执行环境绑定的
  • 全局函数中,this等于window,当函数被作为某个对象的方法调用时,this等于那个对象
  • 匿名函数的执行环境具有全局性,因此this对象通常指向window
var name = 'the window'
var object = {
    name:"my object",
    getName:function(){
        return function(){
            return this.name;
        }
    }
};
console.log(object.getName()())//"the window"
function TestA(){
    console.log(this)
   this.b='ddd';
   function b(){
       console.log(this)
   }
   b()
}
const t = new TestA();
VM225:2 TestA {}
VM225:5 Window {0: global, window: Window, self: Window, document: document, name: "", location: Location, …}

匿名函数的执行环境具有全局性,因此this对象通常指向window

理解

  • 每个函数调用时都会自动取得两个特殊变量:this和arguments,内部函数在搜索这两个变量时,只会搜到其活动对象为止
  • 不可能直接访问外部函数中的this和arguments

把外部作用域的中的this对象保存在一个闭包能够访问的变量里,就可以让闭包访问该对象

var name = 'a';
var object = {
    name:'b',
    getNameFunc:function(){
        var that = this;
        return function(){
            return that.name;
        }
    }
}

不能维持的this的写法

var name = "The Window";
var object = {
    name:'my',
    getName:function(){
        return this.name;
    }
}
object.getName();//"my"
(object.getName)()//'my'
(object.getName=object.getName)()//"The Window"

JavaScript自动回收垃圾

垃圾收集方式

  • mark-and-sweep(标记清除)

    • 进入环境进行标记
    • 离开环境进行标记
    • 存储在内存中的所有变量加上标记
    • 先清除标记,之后再加标记就是准备删除,最后垃圾收集器完成内存清除工作
  • 引用计数

    • 引用一次,次数加1
    • 当引用为0,进行内存回收