一、预解释
1、当浏览器加载HTML页面的时候,首先会提供一个供全局JS代码执行的环境->全局作用域(global/window)
2、预解释:(变量提升)
在当前的作用域中,JS代码执行之前,浏览器首先会默认的把所有的带var和function的进行提前的声明或者定义
var num = 12;
1)、理解声明和定义
声明(declare): var num ; 告诉浏览器在全局作用域中有一个num的变量了,如果一个变量只是声明了没有赋值,默认值为undefined
定义(defined):num = 12;给我们的变量进行赋值
2)、对于带var 和function关键字的在预解释的时候操作还是不一样的
var 在预解释的时候只是提前的声明
function 在预解释的时候提前的声明+定义都完成了
3)、预解释在发生在当前的作用域下,例如:开始只对window下的进行预解释,只有函数执行的时候才会对函数中的进行预解释。
3、JS中的内存分类
堆内存:用来存储引用数据类型的值 -> 对象存储的是属性名和属性值,函数存储的是代码字符串...
栈内存:用来提供一个供JS代码执行的环境 -> 作用域(全局作用域/似有的作用域)
console.log(num);//undefined var num = 12; console.log(num)//12 var obj = {name:'lisi',age:21}; fn(100,200)//可以在上面执行,因为预解释的时候声明+定义已经完成了 function fn(num1,num2){ var total = num1 + num2 console.log(total) }
二、作用域链
1、如何区分私有变量和全局变量
在全局作用域下声明(预解释的时候)的变量是全局变量
在"私有作用域中声明的变量"和"函数的形参"都是私有的变量
在私有作用域中,我们代码执行的时候遇到了一个变量,首先我们需要确定它是否为私有的变量,如果是私有的变量,那么和外面的没有任何的关系;如果不是私有的,则往当前作用域的上级作用域进行查找,如果上级作用域也没有则继续查找,一直找到window为止...(作用域链)
2、当函数执行的时候(直接目的:让函数体中的代码执行),首先会形成一个私有的作用域,按照如下的步骤执行
1)、如果有形参,先给形参赋值
2)、进行私有作用域中的预解释
3)、私有作用域中的代码从上到下执行
函数形成一个新的私有的作用域保护了里面的私有变量不受外界的干扰(外面修改不了私有的,私有的也修改不了外面的) -->"闭包"
注意:在全局作用域中,带var和不带var的关系?
区别:带var的可以进行预解释,所以在赋值的前面执行不会报错;不带var的是不能进行预解释的,在前面执行会报错
num = 12 ->相当于给window增加了一个叫做num的属性名,属性值是12
var num = 12 ->肯定他相当于给全局增加了一个全局变量num,但是不仅如此,它也相当于给window增加一个属性名num,属性值为12
私有作用域中出现的一个变量不是私有的,则往上级作用域进行查找,上级没有则继续向上查找,一直找到window为止,如果window下也没有呢?
如果我们是获取值 console.log(total) ->报错
如果是设置值 total = 100 ->相当于给window增加了一个属性名total,属性值是100
JS中如果不进行任何特殊处理的情况下,上面的代码报错,下面的代码都不再执行了。
三、预解释是一种毫无节操的机制
if(!("num" in window)){//不进循环 var num = 12; } console.log(num);//undefined
1、预解释的时候,不管条件是否成立,都要把带var的进行提前的声明
2、预解释的时候只预解释"="左边的,右边的是值。不参与预解释
匿名函数之函数表达式:把函数定义的部分当作一个值赋值给我们的变量/元素的某一个事件
window下的预解释:var fn fn() //undefined() 报错 fn is not a function var fn = function(){ console.log("ok"); }
3、执行函数定义的那个function在全局作用域下不进行预解释,当代码执行到这个位置的时候定义和执行一起完成了
自执行函数:定义和执行一起完成了
4、函数体中的return下面的代码虽然不在执行了,但是需要进行预解释;return后面跟着的都是我们返回的值,所以不进行预解释
5、在预解释的时候,如果名字已经声明过了,不需要从新的声明,但是需要重新的赋值
在js中如果变量的名字和函数的名字重复了,也算冲突
var fn = 13; function fn(){ console.log('ok'); }
fn();//2 function fn(){console.log(1)}; fn();//2 var fn = 10; fn();// fn is not a function function fn(){console.log(2)}; fn();
四、如何查找上级作用域
看当前函数是在哪个作用域下定义的,那么它的上级作用域就是谁,和函数在哪执行没有任何的关系
五、关于内存释放和作用域销毁的研究
堆内存
对象数据类型或者函数数据类型在定义的时候首先都会开辟一个堆内存,堆内存有一个引用的地址,如果外面有变量等知道了这个地址,我们就说这个内存被占用了,就不能销毁。
我们想要让堆内存释放/销毁,只需要把所有引用它的变量值赋值为null即可,如果当前的堆内存没有任何东西被占用了,那么浏览器会在空闲的时候把它销毁...
栈内存
全局作用域
只有当页面关闭的时候全局作用域才会销毁。
私有作用域:(只有函数执行会产生私有的作用域)
一般情况下,函数执行会形成一个私有的作用域,当私有作用域中的代码执行完成后,我们当前的作用域都会主动的进行销毁
但是还是存在特殊情况的:
当前私有作用域中的部分内存被作用域以外的东西占用了,那么当前的这个作用域就不能销毁了
a、函数执行返回了一个引用数据类型的值,并且在函数的外面被一个其他的东西给接收了,这种情况下一般形成的私有作用域都不会销毁。
function fn(){ var num = 100; return function(){ }}var f = fn(); // fn执行形成的这个私有的作用域就不能销毁了
b、在一个私有的作用域中给DOM元素的事件绑定方法,一般情况下,我们的私有作用域不能销毁
c、不立即销毁
作用域练习题:
function fn(){ var i = 10; return function(n){ console.log(n+(++i)); } } var f = fn() f(10);//21 f(20);//32 //上面两个i 没有进行销毁 fn()(10);//21 fn()(20);//31 上面两个属于不立即销毁的情况
function fn(i){ return function(n){ console.log(n+i++); } } var f = fn(13) f(12);//25 f(14);//28 fn(15)(12);//27 fn(16)(13);//29
五、this关键字
我们在js中主要研究的是函数中的this
js中的this代表的是当前行为执行的主体:js中的context代表的是当前行为执行的环境(区域)
例如:我在沙县小吃 吃蛋炒饼,this->我 context->沙县小吃
this是谁和函数在哪定义的和在哪执行的都没有任何关系;如何的区分this呢
1、函数执行,首先看函数名前面是否有".",有的话“.”前面是谁 this就是谁,否则就是window
function fn(){ console.log(this) } var obj = {fn : fn} fn()//window obj.fn()//obj
2、自执行函数中的this永远是window
3、给元素的某一个事件绑定方法,当事件触发的时候,执行对应的方法,这个方法中的this就是当前的元素
综合练习题:
360面试题:
var num = 20; var obj = { num:30, fn:(function(num){ this.num*=3; num+=15; var num = 45; return function(){ this.num*=4; num+=20; console.log(num) } })(num)//把全局的num变量的值20赋值给了自执行函数的形参,而不是obj下的30,如果想是obj下的30,我们需要写obj.num } var fn = obj.fn; fn();//65 obj.fn();//85 console.log(window.num,obj.num) // 240 120
执行过程见下图
综合实战题(投票)
投票 请点击如下,进行投票
我赞同上述观点 0