本文已参与『新人创作礼』活动,一起开启掘金创作之路
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第2天,点击查看活动详情
1. 作用域
1.1 作用域
在JS中,作用域是用来规定变量访问范围的规则
作用域分为三种
- 全局作用域
- 私有作用域
es6块级作用域
全局作用域:
当打开页面的时候,会提供一个供js代码执行的环境全局作用域,会默认提供一个window对象
全局变量:在全局作用域中声明的变量
全局变量和window的关系
function one() {
var a = 1;
}
console.log(a);
1.2 作用域链
- 作用域链是由当前执行环境与上层执行环境的一系列变量对象组成的,它保证了当前执行环境对符合访问权限的变量和函数的有序访问
1.2.1 作用域链
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
console.log(a, b, c);
}
three();
}
two();
}
one();
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}` }
var globalExecuteContext = {
VO: globalExecuteContextVO,
scopeChain: [globalExecuteContextVO]
}
var executeContextStack = [globalExecuteContext];
//2.执行one,创建one执行上下文
var oneExecuteContextVO = {
a: 1,
two: `()=>{var b = 2 ;}`
}
var oneExecuteContext = {
VO: oneExecuteContextVO,
scopeChain: [oneExecuteContextVO, globalExecuteContext.VO]
}
//2.执行two,创建two执行上下文
var twoExecuteContextVO = {
b: 2,
three: `()=>{var c = 3 ;}`
}
var twoExecuteContext = {
VO: twoExecuteContextVO,
scopeChain: [twoExecuteContextVO, oneExecuteContext.VO, globalExecuteContext.VO]
}
//3.执行three,创建three执行上下文
var threeExecuteContextVO = {
c: 3
}
var threeExecuteContext = {
VO: threeExecuteContextVO,
scopeChain: [threeExecuteContextVO, twoExecuteContext.VO, oneExecuteContext.VO, globalExecuteContext.VO]
}
function getValue(varName) {
for (let i = 0; i < threeExecuteContext.scopeChain.length; i++) {
if (varName in threeExecuteContext.scopeChain[i]) {
return threeExecuteContext.scopeChain[i][varName];
}
}
}
//console.log(a, b, c);
console.log(
getValue('a'),
getValue('b'),
getValue('c'),
);
1.2.2 作用域链
scopeChain其实是在创建函数的时候确定的
function one() {
var a = 1;
function two() {
console.log(a);
}
return two;
}
var a = 2;
var two = one();
two();
// 1.创建全局上下文
var globalExecuteContextVO = { one: `()=>{var a = 1;}`, a: undefined, two: undefined }
var globalExecuteContext = {
VO: globalExecuteContextVO,
scopeChain: [globalExecuteContextVO]
}
//2.开始执行
globalExecuteContextVO.a = 2;
//3.开始执行one
var oneExecuteContextVO = { a: undefined, two: `()=>{console.log(a)}` }
var oneExecuteContext = {
VO: oneExecuteContextVO,
scopeChain: [oneExecuteContextVO, globalExecuteContextVO]
}
oneExecuteContextVO.a = 1;
//4.给two赋值
globalExecuteContextVO.two = oneExecuteContextVO.two;
//5.执行two
var twoExecuteContextVO = {}
var twoExecuteContext = {
VO: twoExecuteContextVO,
//scopeChain是在创建此函数据的时候就决定了,跟在哪里执行无关
scopeChain: [twoExecuteContextVO, oneExecuteContextVO, globalExecuteContextVO]
}
2. 闭包
- 闭包有两部分组成,一个是当前的执行上下文A,一个是在该执行上下文中创建的函数
B - 当B执行的时候引用了当前执行上下文
A中的变量就会产出闭包 - 当一个值失去引用的时候就会会标记,被垃圾收集回收机回收并释放空间
- 闭包的本质就是在函数外部保持内部变量的引用,从而阻止垃圾回收
- 调用栈的并不会影响作用域链,函数调用栈是在执行时才确定,而作用域规则是在代码编译阶段就已经确定了
MDN定义:闭包是指这样的作用域foo,它包含了一个函数fn,这个函数fn1可以调用被这个作用域所封闭的变量a、函数等内容
2.1 闭包
Call Stack为当前的函数调用栈Scope为当前正在被执行函数的作用域链Local为当前的活动对象
function one() {
var a = 1;
var b = 2;
function two() {
var c = 3;
debugger;
console.log(a,c);
}
return two;
}
let two = one();
two();
function one() {
var a = 1;
var b = 2;
function two() {
debugger;
console.log(a);
}
two();
}
one();
2.2 闭包优化
- 中间没用到的变量闭包会被忽略
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
debugger;
console.log(a, b, c);
}
three();
}
two();
}
one();
function one() {
var a = 1;
function two() {
var b = 2;
function three() {
var c = 3;
debugger;
console.log(a, c);
}
three();
}
two();
}
one();
2.3 arguments
function log(a, b) {
debugger;
console.log(a, b);
}
console.log(1, 2);
3. var和let
- JS中作用域有:全局作用域、函数作用域。没有块作用域的概念。
ECMAScript 6(简称ES6)中新增了块级作用域 - 块作用域由
{ }包括,if语句和for语句里面的{ }也属于块作用域
3.1 ES5问题
3.1.1 全局变量
-
在if或者for循环中声明的变量会变成全局变量
for(var i=0;i<=5;i++){ console.log("hello"); } console.log(i); //53.1.2 内层变量可能会覆盖外层变量
var a = 1; function fn() { console.log(a); if (false) { var a = 2; } } fn(); //undefined
3.2 let
- 允许块级作用域任意嵌套
- 外层作用域无法读取内层作用域的变量
- 内层作用域可以定义外层作用域的同名变量
- 函数本身的作用域在其所在的块级作用域之内
'use strict'
function fn() {
console.log("out");
}
(function () {
if (false) {
function fn() {
console.log("in");
}
}
fn();
}());
3.3 var&let&const
var定义的变量没有块的概念,可以跨块访问,不能跨函数访问,有变量提升,可重复声明let定义的变量,只能在块作用域里访问,不能跨块访问,也不能跨函数访问,无变量提升,不可以重复声明let声明的变量只在块级作用域内有效,不存在变量提升,而是绑定在暂时性死区- 或者说
let变量提升了,但是在let声明变量前不能使用该变量,这特性叫暂时性死区(temporal dead zone) - 如果有重复变量
let会在编译阶段报错
3.3.1 暂时性死区
// 不存在变量提升
'use strict';
function func(){
console.log(i);
let i;
};
func(); // 报错
3.3.2 全局变量
- ES5声明变量只有两种方式:var和function
- ES6有
let、const、import、class再加上ES5的var、Function共有六种声明变量的方式 - 浏览器环境中顶层对象是
window,Node中是global对象 - ES5中 顶层对象的属性等价于全局变量
- ES6中
var、Function声明的全局变量,依然是顶层对象的属性;let、const、class声明的全局变量不属于顶层对象的属性
4 变量提升
在全局变量中var定义和不带var定义没有变量提升
相同点:var定义 和不用var 在全局作用于都是给window添加了一个属性和属性值
不同点:
-
var定义 有变量提升, 不用var定义的不会产生变量提升 -
var定义 的通过deletewindow.属性名 删除不掉, 不用var定义的可以删除
4.1 var和function区别
代码执行的时候首先会形成一个供JS执行的环境栈,接下来在代码自上而下之前有一步操作:“变量提升”
会把带var 和用Function定义的的变量都给找出来;
- var :只声明(默认赋值
undefined) - function:声明和定义(赋值)一起完成
// 代码自上而下执行之前,先进行变量提升; 也就是先定义 var a;
console.log(a) // undefined 并没有给a赋值
var a = 8; //给 8 赋值给 a
console.log(a) // 然后再打印就是8
console.log(fn(1,2)); // 依然是3
// 代码自上而下执行之前,先进行变量提升 function 会开辟出一个内存然后把里面的私有变量用字
// 符串方式存储起来。在提供一个十六进制地址和 fn(1,2) 连接
function fn(n,m){
return n+m;
}
console.log(fn(1,2));
4.2变量提升的特殊性
无论条件是否成立,都会进行变量提升;var 还是之前理解;只声明不定义
-
Function在老版本浏览器中:声明+定义 -
在新版本浏览器中:只声明不定义
特殊记忆:在判断语句中,如果出现Function,在Function 后面在对此变量改值,是改变私有的
特殊记忆:变量提升只对等号左边的进行
console.log(fn); //undefined
console.log(fn(1,2)); // 报错
var fn=function (n,m){
return n+m;
}
console.log(fn(3,4));
特殊记忆:函数里面的return,return下面的代码本身是不执行的,但是可以进行变量提升 (f2进行变量提升),return 后面的代码不进行变量提升(f1不进行变量提升)
小知识
自执行函数执行在当前作用域中不进行变量提升, 但是他里面执行的时候也有变量或者
Function会有照常变量提升
题目
'use strict'
var a = 1;
console.log(a);
{
console.log(a);
function a() {
console.log(1);
}
}
console.log(a);
知道答案或者文章有遗漏地方的小伙伴可以在评论区评论哦~~