一、作用域
定义:变量可访问的范围
划分:全局作用域和私有作用域
私有作用域:在私有作用域中的变量只能在私有作用域中访问,全局无法访问,但是私有可访问全局;
变量又分为全局变量和私有变量
全局变量:在全局定义的变量
私有变量:在函数内声明的变量,形参也是私有变量
二、作用域链
规则:
1、先看私有的有没有,再看形参,找到就停止;
2、私有没有,再看上级作用域,直到找到为止,没有就报错;
function(){
console.log(a)
}
//报错
function(){
a=100;
console.log(a)//100
}
//相当于在全局对象(GO)身上添加了一个a属性,值是100
//全局上下文中,使用一个变量,首先看是否是VO(G)中的,如果不是,则继续看GO中是否有,如果也没有,则报未定义的错误
var a = 13;
let b = 14;
const c = 15;
d = 16; //window.d=16
console.log(a, window.a); //13 13
console.log(b, window.b); //14 undefined
console.log(x); //Uncaught ReferenceError: x is not defined
console.log(window.x); //undefined
图片解析:
1、为了让js代码运行,浏览器打开页面的时候会开辟堆内存
2、浏览器自身内置的方法和属性,会存在一个堆内存里[(GO:Global Object):全局对象]
3、为了区分代码所在的不同作用域,又分为全局执行上下文(ECg)和私有执行上下文(EC fn)
4、在全局执行上下文中有一个存储全局变量的对象:VO,在私有执行上下文中也有一个存储变量的地方:AO
5、全局Vo中提供了一个window对象,让他的值指向GO,window可省略不写
6、带var和function相当于省去了window,直接在GO添加,不带关键字也是在GO添加(a=10),
let和const是在VO添加
三、变量提升
规则:
带var和带function都找到;
带var只声明,不定义;
带function是既声明又定义;
变量提升的特殊情况概括
1、判断条件下,无论if内条件是否成立,都会进行变量提升;
2、判断条件下,在老版浏览器中,判断时function声明并定义;在新版浏览器中,判断时function只声明不定义;
3、在判断条件下,一旦条件成立;以函数为分割线,形成两个作用域,function上面是全局,下面是私有,又做了一次变量提升;
4、自调用函数不会变量提升;
5、只对等号左边变量提升;
6、return后面的不变量提升,return 下面的代码不执行但是变量提升
变量提升中,声明只有一次,但是赋值可以多次。所以当变量名重复时,第二次不再创建变量
题目:
//------------------------------
console.log(a);//und
if (1 == 2) {
var a = 12;
};
console.log(a)//und
//------------------------------
console.log(fn);//und
if (1 == 2) {
function fn () {
console.log("1")
}
};
console.log(fn);//und
//------------------------------
console.log(a);//und
if (1 == "1") {
console.log(a);//und
var a = 2;
console.log(a);//2
}
console.log(a);//2
//------------------------------
console.log(fn);//und
if(1=="1"){
console.log(fn);//函数体
function(){};
fn=3;
console.log(fn);//3
}
console.log(fn);//函数体
四、带var和不带var的区别
1、带var的能进行变量提升,不带var不提升;
2、删除 delete window.a 带var删不掉;
共同点:都是往GO中添加属性,可通过window.a拿到;
五、es6中的let
1、es6不存在变量提升(let const)
2、同一个作用域下不能重复声明(有词法检测)
3、es5、es6混用,该提升提升,不提升就不提升
六、es6中暂时性死区:
typeof a;//报错
let a=100;//--------以上空间是暂时性死区,不可访问在声明之前
上级作用域和在哪调用没关系和在哪定义有关系
七、内存释放的问题
我们每次给变量存值或者执行函数的时候都会占用内存空间,如果一直这样下去,日积月累,电脑总会装不下的,所以内存是需要释放的。
1)堆内存的释放:
常见的浏览器释放方式主要有以下两种:
谷歌浏览器是标记方式,每隔一段时间就会检测以下当前作用域中的内存,是否被占用,如果没有被占用,就会释放掉。
ie和火狐等浏览器是采用计数方法,当前作用域中如果一个空间地址被占用一次,就会累加一,
如果减少一次占用就会减1,直到0的时候,说明已经没有被占用了,就释放了。
堆内存释放:让所有引用这个堆内存的变量赋值为null,堆内存地址不在被占用,浏览器在空闲的时候就会把堆内存释放
2)栈内存释放:
-
销毁:全局栈内存,只有当页面关闭的时候才会被释放
-
销毁:函数执行完成,一般情况下都会被销毁掉
-
不销毁:
- 当前作用域中如果有一个引用数据类型的值被外面的变量占用就不销毁
-
不立即销毁,(当函数执行完毕之后销毁)
八、闭包
定义:函数执行时,内部有一个引用数据类型被外面的变量所占用,形成不销毁的作用域就叫闭包
作用:
1、不污染全局变量
2、形成不销毁的作用域,保存私有变量
题目:
console.log(a);
var a=12;
function fn(){
console.log(a);
var a=13;
}
fn();
console.log(a);
// A、undefined 12、13
// B、undefined undefined、12
// C、undefined undefined、13
// D、有程序报错
console.log(a);
var a=12;
function fn(){
console.log(a);
a=13;
}
fn();
console.log(a);
// A、undefined 12、13
// B、undefined undefined、12
// C、undefined undefined、13
// D、有程序报错
console.log(a);
a=12;
function fn(){
console.log(a);
a=13;
}
fn();
console.log(a);
// A、undefined 12、13
// B、undefined undefined、12
// C、undefined undefined、13
// D、有程序报错
var foo=1;
function bar(){
if(!foo){
var foo=10;
}
console.log(foo);
}
bar();
A 1
B 10
C undefined
D 报错
var n=0;
function a(){
var n=10;
function b(){
n++;
console.log(n);
}
b();
return b;
}
var c=a();
c();
console.log(n);
var a=10,b=11,c=12;
function text(a){
a=1;
var b=2;
c=3;
}
text(10);
console.log(a);
console.log(b);
console.log(c);
A 1 11 3
B 10 11 12
C 1 2 3
D 10 11 3
if(!("a" in window)){
var a=1;
}
console.log(a);
A undefined
B 1
C 报错
D 以上答案都不对
var a=4;
function b(x,y,a){
console.log(a);
arguments[2]=10;
console.log(a);
}
a=b(1,2,3);
console.log(a)
``js var foo="hello"; (function(foo){ console.log(foo); var foo=foo||"word"; console.log(foo); })(foo); console.log(foo);
A hello hello hello B undefined world hello c hello world world D 以上答案都不正确
```js
var ary=[1,2,3,4];
function fn(ary){
ary[0]=0;
ary=[0];
ary[0]=100;
return ary;
}
var res=fn(ary);
console.log(ary);
console.log(res);
return function (n){
console.log(n+(++i));
}
}
var f=fn(10);
f(20);
fn(20)(40);
fn(30)(50);
f(30);
function fn(){
return function(n){
console.log(n+(++i));
}
}
var f=fn();
f(20);
fn()(20);
fn()(30);
f(30);
九、this关键字
函数执行的主体,意思是谁把函数执行的,那么执行主体就是谁。(不是上下文) this非常不好理解,但是我们会对此总结几条规律,让你能迅速而准确的找到this是谁。
1):在全局作用域下,this就是window
2):函数执行的时候,看它前面有没有点,如果有,点前面是谁,this就是谁,如果没有,就是window(在非严格模式下),在严格模式下就是undefined
3):当给元素绑定事件的时候,在函数执行体中的this就是绑定的元素
4):自执行函数里面的this是在非严格模式下是window,严格模式下是undefined
5):回调函数中的this是window
6):构造函数中的this,是当前类的实例
7):通过bind、call、apply可以改变this的指向
题目:
var name="xiaohui";
function fn(){
console.log(this.name)
}
var obj={
name:"你好世界",
fn:fn
}
obj.fn();
fn();
(function(){
this.fn();
})();
let obj={
name:"li",
fn:(function(n){
// 这里的this
console.log(this);
return function(){
// 这里的this
console.log(this);
}
})(10),
}
obj.fn();
var num=10;
var obj={num:20};
obj.fn=(function(num){
this.num=num*3;
num++;
return function(n){
this.num+=n;
num++;
console.log(num);
}
})(obj.num);
var fn=obj.fn;
fn(5);
obj.fn(10);
console.log(num,obj.num)