JS 作用域
作用域(栈内存、执行上下文)
全局作用域(全局栈内存)
浏览器打开一个页面,开始运行时率先形成一个栈内存,这个栈内存又叫全局作用域,为代码提供执行环境,在全局作用域下会生成一个全局的大对象叫window。
浏览器打开,生成的全局作用域一般不销毁,直到页面关闭。
console.log(window.Number)`
全局变量
在全局作用域下声明的变量就是全局变量
全局变量的区别
用var和function 声明的变量会在全局作用域下率先创建,而且也会给window增加属性,属性名是变量名,属性值是变量名储存的值(let不可以)
var s = 12;
function fn(){}
console.log(window.s)
console.log(window.fn)
let a = 12;
console.log(window.a) // undefined`
var跟function可以重复创建同一个变量名(let不可以)
var a = 12;
var a = 13;
console.log(a) // 13
let a = 12; // 报错 SyntaxError(语法错误)
let a = 13;`
var和function有变量提升(let没有)
b = 12 // 等价于window.b = 12因为window.可以省略
var b = 12(有变量提升)
var a,b,c = 12; //undefined undefined 12
//创建变量a,b,c,但是只给c赋值
var a = 12,b = 13, c = 14; // 创建三个变量,给每一个变量都进行赋值
var a = b = c = 12; // 等价于 var a = 12; b = 12; c = 12;
私有作用域(私有栈内存)
全局作用域生成之后才会有私有作用域,私有作用域属于全局作用域
函数执行时会形成一个私有作用域(私有栈内存)为函数内的代码执行提供环境。
创建函数时
首先开辟一个堆内存生成一个16进制的空间地址
把函数体里的代码以字符串的格式存储进去
把16进制的地址赋值给函数名
执行函数时
首先开辟出一个私有栈内存(私有作用域)
形参赋值
变量提升
代码从上往下执行
作用域是否被销毁
私有变量
在私有作用域中定义的变量就是私有变量(var、function、let、const····)
形参也是私有变量
在私有作用域里使用一个变量,如果自己私有作用域里有,直接用自己的,如果没有,就取上一级作用域的
函数外边不能拿到函数里边的变量
var a = 12;
function fn(a) {
console.log(a) //13
}
fn(13)
var name = 'erYa';
function fn(name){
function name(){
}
name = 7;
console.log(name)
}
fn(name)
let y = 12;
function fn(){
function y(){}
y =100;
console.log(y) // 100
}
fn();
console.log(y) // 12
var num = 12;
function fn(){
var num = 0
num++
}
fn();
console.log(num)
函数的作用域查找
函数的上一级作用域是谁,在函数定义的时候就已经确定了,函数在哪创建的,他的上一级作用域就是谁,跟函数在哪执行没有关系。
作用域链查找机制
在私有作用域中,函数执行,如果要使用一个变量,自己作用域要是有,就使用自己的,要是没有,就向上一级作用域查找,上一级还没有,在向上一级查找,直到找到全局作用域,如果还没有就报错--->这种一级一级向上查找的机制就是【作用域链查找机制】
变量提声(升)
浏览器解析代码的一个过程
变量提升的含义
在当前作用域,代码执行之前。浏览器会对当前作用域里的带var和function进行提前声明和定义。
带var的会只声明(创建变量),不定义(赋值)
带function的既声明(创建变量),又定义(赋值)
变量提升机制
当浏览器开辟出供代码执行的栈内存之后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var/function关键字的进行提前的声明和定义
console.log(a) // undefind
var a =12; // a =12
console.log(a) // 12
/* console.log(num);
var num = 100;
console.log(num); */
console.log(window.a); // undefined
console.log(b); // 报错 引用错误 在当前代码之后的代码不在运行
var a = 10;
b = 12;
/*
console.log(b; // 报错 语法错误,他会使整个页面不运行
*/
console.log(a); // undefined
console.log(b); // 引用错误
var a = b = c = 100;
console.log(a); // undefined
console.log(b); // undefined
console.log(c); // undefined
var a,b,c = 100;
console.log(a);// undefined
console.log(b);// undefined
console.log(c);// 100
function fn(){
console.log(num);
var num = 12;
}
fn();
console.log(num)
/*
变量提升
var name
fn (){}
*/
var name = 'jinYu';
function fn(name){
/*
name // f
*/
console.log(name); //f
function name(){}
console.log(name); // f
}
fn(name)
/*
变量提升的特殊情况
变量提升发生在等号左边
var a = function () {}//此处,浏览器变量提升时,只识别var,不识别function
不管if条件是否成立,if里的代码都要进行变量提升
console.log(num)//undefined
if(false){
var num = 12;
}
console.log(num)//undefined
console.log(fn); // undefined
// 在老版本浏览器里,if条件里的function既声明又定义,
// 在新版本浏览器里,if条件里的函数只声明不定义
if(false){
// 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
fn()
function fn(){}
}
console.log(fn) // undefined
console.log(fn); // undefined
// 在老版本浏览器里,if条件里的function既声明又定义,
// 在新版本浏览器里,if条件里的函数只声明不定义
if(true){
// 条件一旦成立,第一件事就是给函数名赋值,然后在执行代码
fn()
function fn(){}
}
console.log(fn) // fn(){}
在函数里,虽然return下面的代码不执行,但是要进行变量提升
function fn() {
console.log(ss); // undefined
return;//中断后边代码执行
var ss = 34;//此处的ss仍然要变量提升。永远是undefined
}
fn()
匿名函数不进行变量提升
(function(){
})()
let和var区别:
1.let和const不存在变量提升机制,var和function有变量提升
创建变量六种方式中:var和function有变量提升,而let,const,class,import不存在这个机制
2.var允许重复声明,而let不允许
在相同作用域中或执行上下文中,使用var和function声明变量并且重复声明,是不会有影响的(声明第一次后,在遇到就不再重复声明)
但是let和const不行,浏览器会校验当前作用域中是否存在这个变量,已存在,则再次基于let等重新声明就会报错
3.let能解决typeof检测出现的暂时性死区问题(let比var更严谨)
console.log(a);//报错 a is not defined
console.log(typeof a);//undefined 本该报错 ,bug浏览器死区
解决方法;
console.log(typeof a);
let a; 作用域与变量提升
JS中报错
SyntaxError(语法错误) 会使整个页面不运行
引用错误ReferenceError 在当前代码之后的代码不运行
引用错误
他会使,报错的代码 之后的代码不运行
语法错误
他会使页面不运行
变量提升例子
// 1.
var i = 5;
function fn(i) {
return function (n) {
console.log(n + (++i));//4,8,12,10,5
}
}
var f = fn(1);
f(2);
fn(3)(4);
fn(5)(6);
f(7);
console.log(i);
// let f = function(n){
// console.log(n+(++1))
// }
// let f(2) = function(2){
// console.log(2+(++1))//4
// }
// function (n){
// console.log(n+(++3))
// }(4)
// function(4){
// console.log(4(++3))//8
// }
// function (n){
// console.log(n+(++5))
// }(6)
// function (6){
// console.log(6+(++5))//12
// }
// let f(7) = function(7){
// console.log(7+(++2))//10 //上面为2 所以 ++2
// }
// 2.
var ary = [12, 23];
function fn(ary) {
console.log(ary);//[12,23]
ary[0] = 100; //[100,23]
ary = [100]; // [100] //新创建私有作用域
ary[0] = 0; //[0]
console.log(ary);//[0]
}
fn(ary);
console.log(ary);//[100,23]连接全局作用域下面
// 3.
console.log(a) //undefined
if(!('a' in window)){ //false 取反为false
var a = 13
}
console.log(a) //undefined
//4.
var s = 5;
function fn() {
var n = 0;
function f() {
n++;
console.log(n); // 1 //2
}
f();
return f;
}
var r = fn();
r();
console.log(s); //5
//5.
var a = 4;
var f = 3;
function b(x,y,a) { // x,y,a = 1,2,f a = f =3
alert(a); //a=3
// 形参和arguments存在映射关系;
arguments[2]=10; //1,2,10
alert(a); //a=10
}
a=b(1,2,f);
alert(a); //undefined //a = b(1,2,10) //b这个函数没有return 所以undefined
console.log(f);//3
//6.
var foo='hello';
(function(foo){
console.log(foo); //hello
var foo=foo||'world'; //如果前面转布尔为false,取后面的值 ,反之取前面的值 'hello'字符串转布尔为true
console.log(foo); //hello
})(foo);
console.log(foo); //hello
//7.
f = function(){return true}//true
g = function(){return false}//false
~function(){
if(g() && [] == ![]){
f = function(){return false}
function g(){return true}
}
}();
console.log(f()) //true
console.log(g()) //false