Js中的作用域/this/闭包


作用域

全局作用域、函数作用域、块作用域(ES6)
函数作用域在函数定义的时候就确定了,this是在函数调用的时候确定的

三种变量

1
2
3
4
5
6
7
8
//实例变量:(this)类的实例才能访问到的变量
//静态变量:(属性)直接类型对象能访问到的变量
//私有变量:(局部变量)当前作用域内有效的变量
function ClassA(){
var a = 1; //私有变量,只有函数内部可以访问
this.b = 2; //实例变量,只有实例可以访问
}
ClassA.c = 3; // 静态变量,也就是属性,类型访问

this

this指向

this永远指向函数运行时所在的对象。
解析器在调用函数每次都会向函数内部传递进一个隐含的参数,这个隐含的参数就是this。
this的指向在函数定义时无法确认,只有函数执行时才能确定。

根据函数的调用方式的不同,this会指向不同的对象:
1、以函数的形式调用时,this永远都是window。比如fun();相当于window.fun(),不管在哪个函数里调用,都是这样;
2、以方法的形式调用时,this是调用方法的那个对象
3、以构造函数的形式调用时,this是新创建的那个对象
4、使用bind,call和apply调用时,this是指定的那个对象
5、箭头函数this:完全修复了this的指向,它没有自己的this,箭头函数的this不是调用的时候决定的,而是在定义的时候处在的对象就是它的this。箭头函数的this看外层的是否有函数,如果有,外层函数的this就是内部箭头函数的this,如果没有,则this是window。

各类this
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 全局下的this
console.log(this); //window


// 对象中的this
let obj = {
name: 'Ethan',
say(){console.log(this.name)}
}
obj.say() // 'Ethan'


// 绑定事件的this
oDiv.onclick=function(){
//this->oDiv
};

document.addEventListener('click', function(e){
console.log(this); //document
}, false);

oDiv.attachEvent("click",function(){
//this->window,在IE6~8下使用attachEvent,默认的this就是指的window对象
});

document.getElementById("div1").onclick=function(){
console.log(this);//this->#div1
fn();//this->window
};


// setTimeout(),setInterval()的this特殊性
document.addEventListener('click', function(e){
console.log(this); //document
setTimeout(function(){
console.log(this); //window
}, 200);
}, false);


// 构造函数的this
function Fn(){
this.x=100;//this->f1
this.getX=function(){
console.log(this.x);//this->需要看getX执行的时候才知道
}
}
var f1=new Fn;
f1.getX();//->方法中的this是f1,所以f1.x=100
var ss=f1.getX;
ss();//->方法中的this是window ->undefined


// 箭头函数中的this
obj = {fn(){
console.log(1,this) // obj
let a = function() {
console.log(2,this) // window
}
a()
}}
obj()

obj = {fn(){
console.log(1,this) // obj
let a = () => {
console.log(2,this) // obj
}
a()
}}
obj()
易错题
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var name = '我是window'
// 对象中的函数
let obj = {
say(){console.log(this.name)}
}
obj.say() // undefined 此时的this是对象
obj.say.bind(null)() // '我是window'

// 全局函数
let fn = function() {
return function () {
console.log(this.name)
}
}
fn()() // '我是window' 此时的this是window
转换this方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
//Function.prototype.bind();替换this,但是不会执行函数
var xw={
name: "小王",
say: function(x, y){
if (x && y) {
console.log(this.name, x, y);
} else {
console.log(this.name)
}
}
}
var xh={
name: "小红"
}
xw.say() // '小王'
// 怎么让xh也有say函数功能
let fn = xw.say.bind(xh, 1)
fn(2) // '小红 1 2'
应用场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// bind(arg1, arg2...) 第一个参数为this,第二个参数当函数的第一个实参,执行的时候还可以再传
//call(arg1,arg2,arg3...);第一个参数为替换this,第二个开始是传给函数的参数,并执行函数
//apply(arg1,[arg2,arg3...]);第一个参数为替换this,第二个开始是数组,数组里的值传给函数的参数,并执行函数

// 获取数组中最大值
var arr=[1,3,9,7,4];
//Math.max(1,3,9,7,4);
console.log(Math.max.apply(null,arr)); //利用apply函数把数组里的值取出并传递给Math.max(),使得函数可以执行

// 函数中的arguments
let fn = function () {
Array.prototype.forEach.call(arguments, item => {
console.log(item)
})
}
fn(1,2,3,4,5)

// document.querySelectorAll('div')

闭包

什么是闭包

一个函数里的一个局部变量和一个内部函数的总和就是闭包

闭包的作用

闭包常常用来间接访问一个变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 最简单的闭包
let fn = function () {
var local = 'aaa' // 局部变量
function foo() { // 一个函数
console.log(local)
}
}

// 常规闭包
let fn1 = function () {
var local = 'aaa'
function foo() {
local++
return local
}
return foo
}
let fn2 = fn1()
fn2() // 能拿到local的局部变量,并且不能直接拿到(通过window.local),但是可以操作local
函数执行完后,内部声明的局部变量还存在吗

一般是不存在的,存在于闭包中的变量会存在

闭包的生命周期

产生: 嵌套内部函数fn2被声明时就产生了(不是在调用)
死亡: 嵌套的内部函数成为垃圾对象时。(比如f = null,就可以让f成为垃圾对象。意思是,此时f不再引用闭包这个对象了)

闭包的缺点

缺点:函数执行完后, 函数内的局部变量没有释放,占用内存时间会变长,容易造成内存泄露。
解决:能不用闭包就不用,及时释放。比如:

1
f = null;  // 让内部函数成为垃圾对象 -->回收闭包

内存溢出和内存泄露

内存溢出

一种程序运行出现的错误。当程序运行需要的内存超过了剩余的内存时, 就出抛出内存溢出的错误。
比如死循环,数据过大。

内存泄漏

占用的内存没有及时释放。
常见的内存泄漏:1.意外的全局变量 2.没有及时清理的计时器或回调函数 3.闭包