浅谈js执行与内存

本文来自于我的github转载时请注明出处

0.前言

主要结合了内存的概念讲了js的一些的很简单、但是又不小心就犯错的地方。
结论:js执行顺序,先定义,后执行,从上到下,就近原则

1.先说类型

在ECMAscript数据类型有基本类型和引用类型,基本类型有Undefined、Null、Boolean、Number、String,引用类型有Object,所有的的值将会是6种的其中之一(数据类型具有动态性,没有定义其他数据类型的必要了)
引用类型的值,也就是对象,一个对象是某个引用类型的一个实例,用new操作符创建也可以用字面量的方式(对象字面量创建var obj ={ })。ECMA里面有很多原生的引用类型,就是查文档的时候看见的那些:Function、Number (是对于原始类型Number的引用类型)、String(是对于原始类型String的引用类型)、Date、Array、Boolean(…)、Math、RegExp等等。
在程序运行的时候,整块内存可以划分为常量池(存放基本类型的值)、栈(存放变量)、很大的堆(存放对象)、运行时环境(函数运行时)
1

var a = 1;
var b = 'hello';
var c = a;
var obj1 = new Object();
obj1.name = 'obj1'
var obj2 = obj1

基本数据类型的值是直接在常量池里面可以拿到,而引用类型是拿到的是对象的引用
c = a,这种基本数据类型的复制,只是重新复制一份独立的副本,在变量的对象上创建一个新的值,再把值复制到新变量分配的位置上,a、c他们自己的操作不会影响到对方。

a++;console.log(a);console.log(c)

显然是输出2、1
obj1和obj2,拿到的是新创建的对象的引用(也就是家里的钥匙,每个人带一把),当操作对象的时候,对象发生改变,另一个obj访问的时候,发现对象也会改。就像,家里有一个人回去搞卫生了,另一个回家发现家里很干净了。

console.log(obj2) //’obj1’

2

函数也是同理

var a = function(){console.log(1)}
var b = a;
a = null;
b();a()
//b输出1,a报错:Uncaught TypeError: a is not a function

把a变成null,只是切断了a和函数之间的引用关系,对b没有影响

2.再说顺序

大家常听说的先定义后执行,其实就是在栈中先开辟一块内存空间,然后在拿到他所对应的值,基本类型去常量池,引用类型去堆拿到他的引用。大家常说的原始类型值在栈,其实就是这种效果。
3

2.1 为什么引用类型值要放在堆中,而原始类型值要放在栈

栈比堆的运算速度快,Object是一个复杂的结构且可以扩展:数组可扩充,对象可添加属性,都可以增删改查。将他们放在堆中是为了不影响栈的效率。而是通过引用的方式查找到堆中的实际对象再进行操作。
因此又引出另一个话题,查找值的时候先去栈查找再去堆查找。

2.2 为什么先去栈查找再去堆查找

简单来说,宁愿大海捞针呢还是碗里捞针呢?

3.然后到了函数

先抛出一个问题

function a(){console.log(2)};
var a  = function(){console.log(1)};
a()

覆盖?那么交换的结果又是什么呢?

var a  = function(){console.log(1)};
function a(){console.log(2)};
a()

都是1,然后有的人就说了,var优先。好的,那为什么var优先?
4

先定义后执行,先去栈查找

变量提升,其实也是如此。先定义(开辟一块内存空间,此时值可以说是undefined)后执行(从上到下,该赋值的就赋值,该执行操作的就去操作),就近原则
函数声明和函数表达式,有时候不注意,就不小心出错了

 a(); function a(){console.log(666)}//666

另一种情况:

a(); var a = function (){console.log(666)}//a  is not a function

虽然第一种方法有变量提升,不会出错,正常来说,还是按顺序写,定义语句放前面。如果想严格要求自己,就手动来个严格模式‘use strict’吧。对于框架的开发,需要严谨遵守规则,所以一般会用严格模式。

4.接着是临时空间

函数执行的时候,会临时开辟一块内存空间,这块内存空间长得和外面这个一样,也有自己的栈堆,当函数运行完就销毁。

4.1 eg1:

var a = 10;
function() {
console.log(a);//undefined
var a = 1;
console.log(a)//1
}

宏观来说,只有2步一和二,当执行第二步,就跳到函数内部执行②-⑧
5
函数外部的a=10完全就没有关系,这里面造成undefined主要因为变量提升,其实准确的顺序是:

var a
console.log(a);//undefined
a = 1;
console.log(a)//1

为什么不出去找全局的a?
就近原则。为什么就近原则?都确定函数内部有定义了,就不会再去外面白费力气。其实是,函数在自己的作用域内找到就不会再再继续找,类似原型链一样,在构造函数里面找到某个属性就不会去原型找,找不到才去,再找不到就再往上。函数也是,沿着作用域链查找。类似的一个例子,我们用函数声明定义一个函数f,再用一个变量g拿到这个函数的引用,然后在外面用f是访问不了这个函数的,但是在函数内部是能找到f这个名字的:

var g = function f(){
   console.log(f)
    }
g()//打印整个函数
f()//报错

4.2 eg2

function f(){
return function f1(){
       console.log(1)
   }
};
var res = f();
res();
f1()

res(),返回的是里面的函数,如果直接f1()就报错,因为这是window.f1()
6

  • 函数声明后,可以通过引用名称查找或者内存地址查找
  • 局部作用域用function声明,声明不等于创建,只有调用函数的时候才创建
  • 函数f有内存地址的话,通过栈找f的内存空间,如果找不到栈中f这个变量,就去堆中找

5.IIFE和闭包

5.1 IIFE立即执行函数,内部就是一个闭包,形成一个沙盒环境,防止变量污染内部,是做各种框架的好方法

先手写一段假的jQuery

(function(root){
 var $ = function(){
//代码
}
root.$ = $
})(this)

这样子在内部函数里面写相关的表达式,我们就可以用美元符号使用jQuery(实际上jQuery第一个括号是全局环境判断,真正的函数体放在第二个括号里面,号称世界上最强的选择器sizzle也里面)
7

5.2闭包

闭包的概念各有各的说法,平时人家问闭包是什么,大概多数人都是说函数中的函数、函数外面能访问到里面的变量,这些显而易见的现象,或者把一些长篇大论搬出来。简单来说,就是外部访问内部变量,而且内部临时开辟的内存空间不会被垃圾回收。查找值的时候沿着作用域链查找,找到则停止。
对于js各种库,是一个庞大的IIFE包裹着,如果他被垃圾回收了,我们肯定不能利用了。而我们实际上就是能利用他,就是因为他暴露了接口,使得全局环境保持对IIFE内部的函数和变量的引用,我们才得以利用。
各种书对于闭包的解释:
《权威指南》:函数对象通过作用域链相互关联起来,函数内部变量都可以保持在函数的作用域中,有权访问另一个函数作用域中的变量
《忍者秘籍》:一个函数创建时允许自身访问并操作该自身函数以外的变量所创建的作用域
《你不知道的js》:是基于词法的作用域书写代码时所产生的结果,当函数记住并访问所在的词法作用域,闭包就产生了
其实这是闭包的现象,闭包的产生,会导致内存泄漏。
js具有垃圾回收机制,如果发现变量被不使用将会被回收,而闭包相互引用,让他不会被回收,一直占据着一块内存,长期持有一块内存的引用,所以导致内存泄漏。

var b = 10
function a(){
    var b = 1
    return function c(){//暴露内部函数的接口
        console.log(b)
    }
}
a()()//1,外部拿到内部的引用,临时开辟的内存空间不会被回收

//改写成IIFE形式
var b = 10
var a = (function(){
    var b = 1
    return function c(){
        console.log(b)
    }
})()
a()//1

//改成window对象的一个引用
var b = 10
(function(){
    var b = 1
    window.c =  function(){
        console.log(b)
    }
})()
c()//1

//多个闭包
function a(){
    var s = 1
    return function count(){
        s++
        console.log(s)
    }
}
var b = a()//相当于赋值
var c = a()
b()//2
b()//3
c()//2,各自保持各自的”赋值结果”,互相不干扰
  • 9
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值