一、js的运行机制及原理:
1. js运行的环境(栈内存):作用域
作用域是 js 运行的环境,另外一个功能是保存基本数据类型。在 js 中作用域分为:
- 全局作用域:当页面打开时,首先形成一个全局作用域,执行全局中的代码,全局作用域是 window;
- 私有作用域(函数作用域)当函数执行时,会形成一个函数作用域,这个作用域用来保存函数中的基本数据类型同时执行函数代码;
- 块级作用域(类似私有作用域 ES6)
2. js运行过程
- 在 js 代码执行前,浏览器会开辟一个全局作用域,然后执行变量提升,完成变量提升操作后,代码开始从上到下开始执行;
- 当执行时,如果遇到基本数据类型,就在作用域中存储该基本数据类型;
- 如果遇到引用数据类型,则浏览器会再次分配一个堆内存,然后把引用数据类型的内容存储到堆内存中,接下来再把这个堆内存的地址赋值给变量(此时这个地址是存储在作用域内存中的);
- 遇到函数执行时,会经历以下几步:
- 浏览器开辟一个私有作用域;
- 形参赋值,把执行时的实参赋值给函数形参变量;
- 私有作用域中变量提升
- 函数代码从上到下执行
3. 私有变量和全局变量
- 全局变量:在全局作用域中声明的变量
- 私有变量:函数的形参以及在函数私有作用域中声明的变量
4. 预解释只发生在当前作用域,如果函数不执行,函数中的变量不会进行变量提升。
二、作用域链
// 作用域(scope):js代码执行的执行的环境,另外还可以存储基本数据类型的值。
// 全局作用域:打开一个页面的时候,浏览器就会创建一个全局作用域(window 对象)
// 私有作用域(函数作用域):函数执行的时候浏览器开辟的,只要函数执行就会新开辟一个私有作用域;
// 块级作用域:把代码块(if / for 循环...的代码块变成作用域),功能和私有作用域类似,是 ES6 新增的;
var num = 100;
function sum(a, b) {
return a + b;
}
sum();
sum();
function fn(a) {
var x = 6;
// var num = 15;
return num + a; // ?? fn 执行的时候为什么拿到外面的变量 num 的值呢?
}
var r = fn(3);
console.log(r); // 103 ?
// console.log(x); // ?? 为啥我拿不到私有变量呢?
作用域链:变量的查找机制;当我们引用一个变量时,浏览器会首先在当前作用域中查找,看当前作用域中是否有这样一个变量,如果有直接使用。如果没有,就向上一级作用域查找,如果找到就使用,如果没找到,就继续向上查找,一直找到 window,如果还没有,如果是引用就会报错,如果是赋值就是给 window 增加一个同名属性;
- 形参也是私有属性
三、栈内存不销毁
/*
* 目标:
* 1. 理解函数栈内存不销毁的情形
* 2. 理解函数栈内存不销毁时,保存在栈内存中的数据也不销毁
* */
// 栈内存不销毁的特殊情况:一般情况下,函数执行完,栈内存就会销毁。但是函数执行时,函数作用域中有一些值被其他地方占用,此时作用域不能销毁(栈内存不销毁);
function add() {
var n = 1; // 2 3
return {
name: '珠峰',
n: 2,
addNum: function () {
n++; // 这个 n 是 add 函数作用域中的私有变量 n;对象的花括号不是作用域;
console.log(n);
}
}
}
var obj = add(); // add() 执行后返回的对象被 obj 所接收,所以 add 执行的作用域不能被销毁,所以栈内存不能释放,保存在作用域中的变量n的值也不会被销毁。
obj.addNum(); // 2
obj.addNum(); // 3
add(); // 这样执行 add 函数,没有人接收它的返回值,执行完后,add 的作用域也就销毁了。
function foo() {
var n = 1;
n++;
console.log(n);
}
foo(); // 2
foo(); // 2
// 1. 当函数执行完,如果函数返回了一个引用数据类型的值,并且被外面的变量或者属性接收,此时函数执行的作用域不能销毁,在这个作用域中储存的值也不会销毁;
function f1() {
console.log('1');
}
f1();
f1();
// 2. 函数内部的引用数据类型值,被函数外的变量或者属性占用,此时函数作用域不能销毁;
var x = null;
function happyPass() {
x = {
name: 'zf'
}
}
happyPass(); // 因为全局变量 x 占用着 happyPass 里面的一个对象,所以 happyPass 的作用域不能销毁
function foo1() {
var n = 15;
return function (x) {
// 形参赋值:x = 2 // 3 18
// 变量提升:无
x++;
x += n;
console.log(x); // 18
console.log(n); // 15
}
}
var f2 = foo1();
f2(2);
function f4() {
return function () {
console.log('赵四');
}
}
var obj2 = {
getF4: f4()
};
// obj2 的 getF4 这个属性占用着 f4 的返回值,所以 f4 执行时,作用域不销毁。