全面解析JS作用域原型链各类笔试题

预解析:浏览器在解析js代码时,会提前将变量的声明和函数的声明解析到当前作用域的最前面去,当浏览器开辟出供代码执行的栈内存之后,代码并没有自上而下立即执行,而是继续做了一些事情:把当前作用域中所有带var/function关键字的进行提前的声明和定义

  1. 把变量的声明提升到当前作用域的最前面,只会提升声明,不会提升赋值。
  2. 把函数的声明提升到当前作用域的最前面,只会提升声明,不会提升调用。
  3. 先提升var,再提升function
    又叫变量提升
    Js代码执行前,浏览器会给一个全局作用域window
    Window分两个模块一个是存储模块一个是执行模块
    存储模块找到所有的var和function 关键字给这些变量添加内存地址
    执行模块,代码从上到下执行,遇到变量就会去存储模块查找,有和没有
    有就看你赋值没有,赋值了就是后面的值没有赋值就是undefined。
    没有结果就是xxx is not defined
    浏览器解析代码的一个过程
    变量提升的含义
    在当前作用域,代码执行之前。浏览器会对当前作用域里的带var和function进行提前声明和定义。
    带var的会只声明(创建变量),不定义(赋值)
    带function的既声明(创建变量),又定义(赋值)
    变量提升的特殊情况
    变量提升发生在等号左边
    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和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<n;i++){
    arr[i+2] = arr[i] + arr[i+1];  //1,?1,?2,?3,?5,?8,?13,?21,?34
    }
    return arr[n-1];arr[7-1]
    }
    console.log(func1(7));//13
    
function testFunc(data) {
    data = data + "200";
    }
    var data = 100;
    console.log(data);//100
    testFunc(data);
    console.log(data);//100

https://www.cnblogs.com/jinfeixiang/p/10055113.html

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dev _

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值