js运行环境
- 浏览器 -> webkit内核(V8)、Trident、Gecko、Blink......
- Node -> webkit内核
- webview ->webkit内核
var a = 12;
var b = a;
b = 13;
cosnole.log(a); // 12
堆内存Heap和栈内存Stack都存在于运行内存中 (CPU)
ECStack (Execution Context Stack) 执行环境栈(栈内存)
- 供代码执行
- 存储原始值和变量
EC (Execution Context) 执行上下文 | ECG 全局执行上下文
-
区分代码执行的环境
-
全局代码都会在全局上下文中执行
-
VO (Varlable Object)变量对象 | VOG全局变量对象
- 存储当前上下文声明的变量
- 栈内存
-
进栈执行 / 出栈释放
创建与赋值
等号赋值操作
- 创建一个值
- 原始值类型直接在栈内存中存储起来
- 对象类型单独开辟一个堆内存空间,用来存储对象中的成员等信息
- 声明变量 Declare (var、function、let、const...)把声明的变量存储到当前上下文的
”变量对象 VO/AO“中,声明的变量存在栈内存中,存在变量提升 - 让变量和创建的值关联到一起
Defined定义
计算机中所有的赋值操作都是指针指向操作
对象类型(非函数)创建
- 在堆内存中开辟一块单独的空间,会产生一个供访问的16进制的地址
- 把对象中的键值对依次存储到空间当中
- 把空间地址放到栈中存储,以此来供变量引用
GO 全局对象(堆内存)
- 存放浏览器内置的API
var a = {n: 1};
var b = a;
a.x = a = {n: 2};
console.log(a.x); // undefined
console.log(b); // {n: 1, x: {n: 2}} 成员访问优先级比=赋值高
创建函数
- 开辟一个堆内存空间,有一个16进制的地址
- 存储的内容:函数体中的代码当做字符串先存储起来;当做普通对象也会存一些键值对。
- 创建函数时,声明了其作用域[[scope]] (创建函数所在的上下文)
- 堆内存地址放在栈中,供函数名(变量)调用
函数执行
- 形成一个私有的执行上下午
EC(fn),创建AO(Active Object)函数中的变量对象 - 初始化作用域链SCOPE-CHAIN:<EC(fn), EC(G)> 左侧是自己的私有上下文,右侧是创建函数的作用域
- 初始化this指向
- 初始化arguments(实参集合)
- 形参赋值(形参是私有变量)
- 变量提升
- 代码执行
- 根据情况,决定当前形成的私有上下文是否会出栈释放
- 函数再次执行,所有的操作重新走一遍,函数每一次执行没有直接关系
var x = [12, 23];
function fn(y) {
y[0] = 100;
y = [100];
y[1] = 200;
console.log(y); // [100, 200]
}
fn(x);
console.log(x); // [100, 23]
/**
EC(G)
VO(G)
i = 0;
A = 0X000 [A函数 [[scope]]: EC(G)] // SCOPE-CHAIN
y = 0x001
B = 0x003 [B函数 [[scope]]:EC(G)]
*/
var i = 0;
function A() {
/**
EC(A)
AO(A)
i = 10
x = 0x001 [x函数 [[scope]]:EC(A)]
作用域链:<EC(A), EC(G)>
*/
var i = 10;
function x() {
/**
EC(x)
AO(x)
作用域链:<EC(x), EC(A)> 函数执行的上级上下文是创建它的作用域【只和在哪创建有关系,和在哪执行没有关系】
*/
console.log(i); // 获取其上级上下文 EC(A) 中的 i
}
return x; // return 0x001;
}
var y = A();
y();
function B() {
/**
EC(B)
AO(B)
i = 20
作用域链:<EC(B), EC(G)>
*/
var i = 20;
y();
}
B();
let x = 5;
function fn(x) {
return function (y) {
console.log(y + (++x));
}
}
let f = fn(6);
f(7);
fn(8)(9);
f(10);
console.log(x);
// 14 18 18 5
函数执行
- 产生一个私有的上下文,然后进栈
- 当函数执行完,一般情况下,当前形成的上下文都会被栈释放掉(优化栈内存):上下文被释放,之前存储的私有变量等也会被释放;
- 但是如果当前上下文中的某些东西(一般都是堆内存),被当前上下文以外的事物所占用,则当前上下文不能出栈释放,函数中声明的所有变量也都被存储起来
闭包是一种函数执行的机制,函数执行产生的私有上下文,一方面可以保护内部的私有变量不被污染,另一方面如果不被释放,私有的变量及相关信息也都会保存起来。我们把这种保护 + 保存的机制,称之为闭包。(不被释放的上下文称为闭包)
堆内存的释放
如果当前的堆被引用,则不能释放,如果不被引用,浏览器会在空闲的时候释放它
垃圾回收机制(GC)
- 引用计数:被占用一次计数累加1,当取消运用再减去1,当减到零的时候,会把其释放掉
- 标记清除:被占用后做一个标记,当移除引用时,取消标记,在浏览器空闲的时候,会把所有未被标记的内存回收
let a = 0, b = 0;
function A(a) {
A = function (b) {
console.log(a + (b++));
};
console.log(a++);
}
A(1); // 1
A(2); // 4
变量提升
预解析:在当前上下文 代码自上而下执行之前,浏览器会把所有带 var 和 function 关键字的进行提前的声明或定义
- 带var的只是提前声明
- 带function的是提前声明+赋值(定义)
/**
EC(G)
变量提升:
var a;
fn1 = 0x000; [[scope]]:EC(G)
var fn2;
a = 10;
fn2 = 0x001; [[scope]]:EC(G)
*/
console.log(a); // undefined
fn1(); // 'fn1'
var a = 10;
function fn1() { // 函数声明
/**
EC(FN1)
作用域链: <EC(FN1), EC(G)>
形参赋值:--
变量提升:
var a;
a = 20;
*/
console.log('fn1'); // 'fn1'
console.log(a); // undefined
var a = 20;
console.log(a); // 20;
}
fn2(); // fn2 is not a function
var fn2 = function () { // 函数表达式
console.log('fn2');
}
fn2();
// 项目中推荐使用函数表达式的方式创建函数,可以规范函数执行的顺序
var fn = function sum() {
console.log('sum');
console.log(sum);
}
// 匿名函数具名化,设置的函数名不能在函数以外使用(并没有在当前上下文中声明这个变量)
// 具名化的名字可以在函数内部的上下文中使用,代表函数本身;默认情况下,其值是不能被修改的;但是可重新声明同名变量,当做私有变量处理
// 老版本浏览器会不管判断直接对函数提升赋值
// 新版本浏览器对于判断体中的函数只声明 不定义
if (1 === 1) {
function foo() {}
}
// arguments.callee 表示一个函数本身,但是在严格模式下会报错。
// 匿名函数具名化是为了可以在自执行函数内调用其本身
let/const/var 的区别
let和const都是声明变量,只不过const不允许重定向变量的指针,不能重新赋值
let x = 10;
x = 20;
const y = 10;
y = 20; // Uncaught TypeError: Assignment to constant variable. (variable =》变量)
var VS let
- let不存在变量提升
console.log(x); // Uncaught ReferenceError: Cannot access 'x' before initialization
// 无法在初始化之前访问'x'
let x = 10;
词法解析(AST) :基于HTTP从服务器拉取回来的JS代码是一段字符串,浏览器首先会按照ECMAScript规则,把字符串变为C++可以识别和解析的一套树结构对象,所以let一个变量前访问这个变量,词法解析已经知道这个变量会被声明,所以会提示Cannot access 'x' before initialization 而不是 x is not defined
-
let不允许重复声明(不论基于什么方式)
词法解析阶段报的错误,所有代码不会被执行
console.log(1); // 不执行 var x = 20; console.log(x); // Uncaught SyntaxError: Identifier 'x' has already been declared let x = 10; -
全局上下文中基于var声明的变量,新版浏览器中不是存放到VO(G)中的,而是直接放到了GO(window)中,基于let声明的变量是存放到VO(G)中的。
- 全局上下文查找一个变量:
- 1.先去VO(G)中是否存在,如果存在就用这个全局变量;
- 2.如果VO(G)中没有,则再次尝试去GO中找,因为js中的某些操作是可以省略window的,如果有就是获取某个属性值;
- 3.如果再没有,则直接保存:xxx is not defined
-
暂时性死区问题
-
块级作用域(排除函数和对象的大括号)中出现let/const/function,则当前的{}会成为一个块级私有的上下文
/** EC(G) GO -> x: 10 VO(G) -> y = 20 变量提升: var x; x = 10; */ var x = 20; let y = 20; if (1 === 1) { /** EC(BLOCK) VO(BLOCK) y = 200 作用域链:<EC(BLOCK), EC(G)> this: 使用其上级上下文的this 变量提升:var不受块级上下文影响 */ var x = 100; // 操作全局的x,var中没有块级上下文 window.x = 100; let y = 200; console.log(x, y); // 100 200 } console.log(x, y); // 100 20for (var i = 0;i < 5;i++) { setTimeout(() => { console.log(i); // 5 5 5 5 5 }) } for (let j = 0;j < 5;j++) { // 产生6个块级上下文 控制循环的父级块上下文,五次循环产生的五个子级块上下文 setTimeout(() => { console.log(j); // 0 1 2 3 4 }) } let arr = [10, 20, 30]; let i = 0, len = arr.length; for (; i < len; i++) { // 不会产生块级上下文 性能更好 console.log(i); }
变量提升练习题
console.log(foo) // undefined
{
console.log(foo); // foo
function foo() {}
foo = 1;
console.log(foo); // 1
}
console.log(foo); // foo
/**
在新版本浏览器中
1、如果function出现在除函数、对象的大括号中,则在变量提升阶段,只声明不定义。(即在全局声明变量foo)
2、如果除了函数、对象的大括号中,只要出现let/const/function 关键词,都会产生块级私有上下文,对var无效。
3、function foo() {} 如果变量赋值时在全局和块级上下文中都出现过,那么就会导致一个特殊性
+ 把这段代码及以前的对foo的操作,都映射给全局一份
+ 但是之后的代码都只认为是操作块级上下文中的,和全局上下文没有关系
*/
/**
两次function foo() {}
每次都把上面对foo的操作映射给全局上下文一份
*/
console.log(foo) // undefined
{
console.log(foo); // foo(2)
function foo() {1}
foo = 1;
console.log(foo); // 1
function foo() {2}
console.log(foo); // 1
}
console.log(foo); // 1
console.log(foo) // undefined
{
console.log(foo); // foo(2)
function foo() {1}
foo = 1;
console.log(foo); // 1
function foo() {2}
console.log(foo); // 1
foo = 2;
console.log(foo); // 2
}
console.log(foo); // 1
浏览器某机制:如果当前函数使用了ES6中的形参赋值默认值(不论是否生效),并且函数体内有基于let/const/var 声明的变量,则函数在执行的时候,除了形成一个私有的函数上下文,而且还会把函数体{} 当做一个私有的块级上下文,并且块级上下文的上级上下文是私有函数上下文。
如果函数体中声明的变量和形参变量一直,最开始的时候,会把形参变量的值,同步给同名的私有变量一份。
var x = 1;
function func(x, y = function foo() { x = 2 }) {
x = 3;
y();
console.log(x); // 2
}
func(5);
console.log(x); // 1
var x = 1;
function func(x, y = function foo() { x = 2 }) {
var x = 3;
y();
console.log(x); // 3
}
func(5);
console.log(x); // 1
var x = 1;
function func(x, y = function foo() { x = 2 }) {
var x;
y();
console.log(x); // 5
}
func(5);
console.log(x); // 1