预解析:浏览器在解析js代码时,会提前将变量的声明和函数的声明解析到当前作用域的最前面去,当浏览器开辟出供代码执行的栈内存之后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var/function关键字的进行提前的声明和定义
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和const
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 在当前代码之后的代码不运行
引用错误
他会使,报错的代码 之后的代码不运行
语法错误
他会使页面不运行
变量的声明提前
使用var关键字声明的变量,会在所有的代码执行之前被声明(但是不会赋值),
但是如果声明变量时不适用var关键字,则变量不会被声明提前
函数的声明提前
使用函数声明形式创建的函数 function 函数(){}
它会在所有的代码执行之前就被创建,所以我们可以在函数声明前来调用函数
使用函数表达式创建的函数,不会被声明提前,所以不能在声明前调用
函数内变量提升
var a = 1;
function fn1(){
console.log(a);//undefined
var a = 2;
}
fn1();
console.log(a);//1
函数内没有申明变量,去上一级找,在函数中不使用var声明的变量都会成为全局变量(需调用该函数)
var a = 1;
function fn1(){
alert(a);//1
a = 2;//调用后给到全局
}
fn1();//调用后函数里面的a = 2;在全局输出
alert(a);//2
函数内不使用var声明的变量没有调用的情况下,就不是全局变量
var a = 1;
function fn1(){
console.log(a);//1
a = 2;
}
console.log(a);//1 全局作用域
形参声明,没有赋值,调用无实参
var a = 1;
function fn1(a){//定义形参就相当于在函数作用域中声明了变量
alert(a);//undefined
a = 2;
}
fn1();
alert(a);//1
函数调用传递全局下的实参
var a = 1;
function fn1(a){
alert(a);//1
a = 2;
}
fn1(a);
alert(a);//1
调用前面的值赋值给形参
var a ;//申明了
function fn1(a){
console.log(a);//1
}
a = 1;//赋值给申明
console.log(a);//1
fn1(a);//调用,a值
console.log(a);//1
调用无参数,去上级作用域找
var a ;
function fn1(){
console.log(a);//1
}
a = 1;
console.log(a);//1
fn1();
console.log(a);//1
不调用函数情况下
var a ;
function fn1(){
console.log(a);//ReferenceError: a is not defined
}
//function fn1(a){
//console.log(a);//undefined
//}
a = 1;
console.log(a);//1
console.log(a);//1
函数提升
var a ;
function fn1(){
console.log(a);//获取不到值
}
a = 1;
---------------------------------------------------------
var a ;
a = 1;
function fn1(){ //function声明的函数存在函数提升,到var的下面去
console.log(a); //获取不到值
}
var的前置获取 function的前置调用
//变量提升var a
console.log("a = "+a);
var a = 123;
fun();
//函数声明,会被提前创建
function fun(){
console.log("我是一个fun函数");
}
//函数表达式,不会被提前创建
var fun2 = function(){
console.log("我是fun2函数");
};
fun2();
作用域 栈内存, 执行上下文
作用域指一个变量的作用的范围
浏览器打开一个页面,开始运行时率先形成一个栈内存,这个栈内存又叫全局作用域,为代码提供执行环境,在全局作用域下会生成一个全局的大对象叫window。
浏览器打开,生成的全局作用域一般不销毁,直到页面关闭。
在JS中一共有两种作用域:
1.全局作用域 全局栈内存
直接编写在script标签中的JS代码,都在全局作用域
全局作用域在页面打开时创建,在页面关闭时销毁
在全局作用域中有一个全局对象window,
它代表的是一个浏览器的窗口,它由浏览器创建我们可以直接使用
在全局作用域中:
创建的变量都会作为window对象的属性保存
创建的函数都会作为window对象的方法保存
全局作用域中的变量都是全局变量,
在页面的任意的部分都可以访问的到
全局变量
用var和function 声明的变量会在全局作用域下率先创建,而且也会给window增加属性,属性名是变量名,属性值是变量名储存的值(let不可以)
全局作用域生成之后才会有私有作用域,私有作用域属于全局作用域
函数执行时会形成一个私有作用域(私有栈内存)为函数内的代码执行提供环境。
创建函数时
首先开辟一个堆内存生成一个16进制的空间地址
把函数体里的代码以字符串的格式存储进去
把16进制的地址赋值给函数名
执行函数时
首先开辟出一个私有栈内存(私有作用域)
形参赋值
变量提升
代码从上往下执行
作用域是否被销毁
在私有作用域中定义的变量就是私有变量(var、function、let、const····)
形参也是私有变量
在私有作用域里使用一个变量,如果自己私有作用域里有,直接用自己的,如果没有,就取上一级作用域的
函数外边不能拿到函数里边的变量
函数的上一级作用域是谁,在函数定义的时候就已经确定了,函数在哪创建的,他的上一级作用域就是谁,跟函数在哪执行没有关系。
在私有作用域中,函数执行,如果要使用一个变量,自己作用域要是有,就使用自己的,要是没有,就向上一级作用域查找,上一级还没有,在向上一级查找,直到找到全局作用域,如果还没有就报错—>这种一级一级向上查找的机制就是【作用域链查找机制】
2.函数作用域
调用函数时创建函数作用域,函数执行完毕以后,函数作用域销毁
每调用一次函数就会创建一个新的函数作用域,他们之间是互相独立的
在函数作用域中可以访问到全局作用域的变量
在全局作用域中无法访问到函数作用域的变量
当在函数作用域操作一个变量时,它会先在自身作用域中寻找,
如果有就直接使用
如果没有则向上一级作用域中寻找,直到找到全局作用域
如果全局作用域中依然没有找到,则会报错ReferenceError
在函数中要访问全局变量可以使用window对象
//创建一个变量
var a = 10;
function fun(){
var a = "我是fun函数中的变量a";
var b = 20;
return;
}
fun();
console.log("a = "+a);//a=10
在函数作用域也有声明提前的特性,
* 使用var关键字声明的变量,会在函数中所有的代码执行之前被声明
* 函数声明也会在函数中所有的代码执行之前执行
* 在函数中不适用var声明的变量都会成为全局变量
* 定义形参就相当于在函数作用域中声明了变量
原型链:函数内部的变量被使用时,首先会在自己的私有作用域下查找是否有这个变量,有就直接使用,没有就向他的上一级查找,父级有就使用父级的,父级没有就以此继续向上查找直到查找带window有就使用没有就是is not defined。这种查找机制我们叫原型链/作用域链
链式调用机制
作用域链的定义:函数在调用参数时会从函数内部到函数外部逐个”搜索“参数,一直找到参数为止,如果没有声明就返回null,声明了没有赋值就返回undefined,就像沿着一条链子一样去搜索,这就是作用域的链式调用。
javascrip的变量作用域跟python或者其他后端语言不同,变量一经声明,它的作用域就是全局的。在函数内部如果调用一个变量,就会发生上述的作用域链式调用的过程。
例子:
window.onload = function(){
var foo = true;
if(1==1){
var bar = foo*2;
console.log(bar);//2
}
console.log(foo);//true
}
if大括号后面的表达式属于else部分(一个表达式可以不用写括号)该表达式在if作用域下
这段代码的实现过程大体是这样的:
首先在if语句外部声明一个foo变量并给它定义赋值,在if语句内部找不到foo,就会到全局作用域去寻找foo变量。而if语句内部并没有改变foo的值,所以在外部打印foo时,它的值还是true。
如果像下面一样稍微修改一下代码
window.onload = function(){
var foo = true;
if(1==1){
var foo = 2;
bar = foo*2;
console.log(foo);//2
console.log(bar);//4
}
console.log(foo);//2
}
if语句内部声明的变量foo把在if语句外面声明的变量foo覆盖了。
所以在声明和引用变量的时候需要格外谨慎,一不小心,变量的值就改变了。
在ES6之前,要防止变量被污染,要使用闭包这个概念。
ES6为了解决这个问题,提出了两种声明变量的方法
let关键字和const关键字
1)let关键字
可以将变量绑定到所在的作用域中,通常是{ … }内部。
例如:
window.onload = function(){
var foo = true;
if (1==1){
let foo = 2;
var bar = foo*2;
console.log(bar);//4
}
console.log(foo);//true
}
显然,if语句内部声明的foo并没有影响到外部的foo,在if语句外部调用foo,还是原来的值true。
2)const关键字
const关键字同样是用来创建块作用域变量的,但其值时固定不变的。
(2)js中的特殊情况:作用域链的改变
以下语句或方法都会产生作用域链的改变
1)with(实参){ } 语句
2)try{ } catch(err){ } 语句
2)eval()方法
函数调用参数时都不会先执行函数内部的参数,而是调用此前已经定义过的参数,及函数被传递进来的实参。如果没用实参的相关属性值没有定义过,再调用函数内部的参数属性,即所谓的临时改变。(catch内部的err比较特殊,有优先调用的权力)
(3)块作用域的理解
块作用域的定义:函数内部的参数只能在函数/语句内部使用,函数/语句块外部不能使用,很多情况下块作用域是隐式的,即表面上看不出来。
跟全局变量不同,块作用域内的变量不会链式调用。
块作用域举例:
1)for(var i)循环内部定义的参数i。在for循环结束后就会被销毁
2)try{ } catch(err){ }语句内部的err对象。err只在catch内部调用,一旦函数执行完毕,马上销毁,即使函数外部想调用或者重新定义err也是无法调用到catch内部的err的
3)with(var i)内部新定义的参数i
函数归属谁,跟她在哪调用没有关系,而是在哪定义有关。
函数外的变量叫全局变量,函数内的变量叫私有变量。
看变量归谁,看他在哪个作用域下声明。
console.log(num);//undefined
num = 10;
console.log(num);//10
f1();//调用f1
f2();//报错:预解析将var f2提升到作用域的最前面去,此时并没有给f2赋值为函数
f2 = function () {
console.log("匿名函数");
};
var a;
function abc() {
var a;
alert(a);//undefined
a = 10;
}
a = 25;
abc();
// 如果变量和函数同名的话,函数优先
var a;
function a() {
console.log('aaaaa');
}
console.log(a);// 函数体----如果变量和函数同名的话,函数优先
a = 1;
console.log(a);
var num;
function fun() {
var num;
console.log(num);//undefined
num = 20;
}
num = 10;
fun();
var a;
function f1() {
var b;
var a;
b = 9;
console.log(a);//undefined
console.log(b);//9
a = '123';
}
a = 18;
f1();//调用的时候才会执行函数体内的代码
// 3、-----------------------------------
function f1() {
var a;//局部变量
c=9;//隐式全局变量
b=c;//隐式全局变量
a=b;
var a = b = c = 9;
console.log(a);//9
console.log(b);//9
console.log(c);//9
}
f1();
console.log(c);//9
console.log(b);//9
console.log(a);// 报错
函数定义的方式
function fn(){return 1;}
var fn=function(){return 1;}
var fun=new function()
局部变量。全局变量
var x=10;
function fun1(x){//局部变量:函数体内定义var 形参
x++;
console.log(x);//11
}
fun1(x)//11--》值传递 将数值copy一个副本传递给形参(局部变量)
console.log(x);//10
var y=15;
function fun2(){
y++;
console.log(y);//16
}
fun2();//16
console.log(y);//16
var z=5;
function fun3(){
var z=6;
z++;
console.log(z);//
}
fun3();//7
console.log(z)//5
**申明提前** 申明关键字var提前 赋值留在原地
function f1( ){
console.log(x);//undefined
x=x+1;
console.log(x);//NaN
var x = 10;
console.log(x);//10
}
f1();
function申明提前 鄙视题
-----------------------------------------
function fn(){return 1;}
console.log(fn());//2
function fn(){return 2;}
console.log(fn());//2
var fn=100;
console.log(fn());//err
function func1(n){
var arr = [1,1];
for(var i=0;i
https://www.cnblogs.com/jinfeixiang/p/10055113.html