1.JavaScript闭包总结:
含义:
一个函数和对其周围状态(lexical environment,词法环境)的引用捆绑在一起(或者说函数被引用包围),这样的组合就是闭包(closure)。也就是说,闭包让你可以在一个内层函数中访问到其外层函数的作用域。在 JavaScript 中,每当创建一个函数,闭包就会在函数创建的同时被创建出来
在JS中,通俗来讲,闭包就是能够读取外层函数内部变量的函数。
变量作用域
变量的作用域为两种:全局作用域和局部作用域
1)函数内部可以读取全局变量
let code = 200; function f1() { console.log(code); } f1(); // 200
2)函数外部无法读取函数内部的局部变量
function f1() { let code = 200; } console.log(code); // Uncaught ReferenceError: code is not defined
读取函数内部的局部变量
1)在函数内部再定义一个函数
function f1() { let code = 200; function f2() { console.log(code); } }
函数f1内部的函数f2可以读取f1中所有的局部变量。因此,若想在外部访问函数f1中的局部变量code
,可通过函数f2间接访问。
2)为外部程序提供访问函数局部变量的入口
function f1() { let code = 200; function f2() { console.log(code); } return f2; } f1()(); // 200
闭包概念
目录读取函数内部的局部变量中的函数f2,就是闭包,其作用就是将函数内部与函数外部进行连接。
-
闭包访问的变量,是每次运行上层函数时重新创建的,是相互独立的。
function f1() { let obj = {}; function f2() { return obj; } return f2; } let result1 = f1(); let result2 = f1(); console.log(result1() === result2()); // false
-
不同的闭包,可以共享上层函数中的局部变量
function f() { let num = 0; function f1() { console.log(++num); } function f2() { console.log(++num); } return {f1, f2}; } let result = f(); result.f1(); // 1 result.f2(); // 2 旅行者走路的问题 function factory() { var start = 0 function walk(step) { var new_total = start + step start = new_total return start } return walk } var res = factory() res(1) res(2) res(3)
从结果可以看出,闭包f1和闭包f2共享上层函数中的局部变量num。
2.this总结
关键点:
-
this始终指向调用该函数的对象;
-
若没有指明调用的对象,则顺着作用域链向上查找,最顶层为global(window)对象;
-
箭头函数中的this是定义函数时绑定的,与执行上下文有关
-
简单对象(非函数、非类)没有执行上下文;
-
类中的this,始终指向该实例对象;
-
箭头函数体内的this对象,就是定义该函数时所在的作用域指向的对象,而不是使用时所在的作用域指向的对象。
四类调用方式
1)作为对象方法的调用
function f() { console.log( this.code ); } let obj = { code: 200, f: f }; obj.f(); // 200
2)纯粹的函数调用
function f() { console.log( this.code ); } // 此处,通过var(函数作用域)声明的变量code会绑定到window上;如果使用let(块作用域)声明变量code,则不会绑定到window上,因此下面的2次函数调用f(),会输出undefined // let code = 200; var code = 200; f(); // 200 code = 404; f(); // 404
复杂一点:
function doF(fn) { this.code = 404; fn(); } function f() { console.log(this.code); } let obj = { code: 200, f: f }; var code = 500; doF(obj.f); // 404
该列子中,为分析出this的指向,应找到关键点,哪个对象调用了函数f()。obj.f作为doF()的入参,将函数f传给了doF,而doF是由window对象调用的,所以函数doF中的this指向window,继而函数f中的this也指向window。
但最终执行韩式是doF所以this指向doF,结果404
function doF(fn) { this.code = 404; fn(); } function f() { this.code = 401; console.log(this.code); } let obj = { code: 200, f: f }; var code = 500; doF(obj.f); // 401
3)作为[构造函数]调用
code = 404 function A() { this.code = 200 this.callA = function() { console.log(this.code) } } var a = new A() a.callA() // 200, callA在new A返回的对象里
4)使用apply、call、bind调用
var code = 404; let obj = { code: 200, f: function() { console.log(this.code); } } obj.f(); // 200, 实际上是作为对象的方法调用 obj.f.apply(); // 404,参数为空时,默认使用全局对象global,在此处为对象window obj.f.apply(obj); //200,this指向参数中设置的对象
call
function f() { console.log( this.code ); } var obj = { code: 200 }; f.call( obj ); // 200
bind
// bind返回一个新的函数 function f(b) { console.log(this.a, b); return this.a + b; } var obj = { a: 2 }; var newF = f.bind(obj); var result = newF(3); // 2 3 console.log(result); // 5
箭头函数中的this
箭头函数中的this是定义函数时绑定的,而不是在执行函数时绑定。若箭头函数在简单对象中,由于简单对象没有执行上下文,所以this指向上层的执行上下文;若箭头函数在函数、类等有执行上下文的环境中,则this指向当前函数、类。
1)箭头函数在普通对象中
var code = 404; let obj = { code: 200, getCode: () => { console.log(this.code); } } obj.getCode(); // 404 在箭头函数中,this 的值是在定义函数时确定的,而不是在运行时确定的。在这个例子中,箭头函数 getCode 是在对象 obj 定义时创建的,而不是在调用 obj.getCode() 的时候。 箭头函数中的 this 指向的是外层的词法作用域的 this 值,而不是指向调用它的对象。在全局作用域中,this 指向的是全局对象(在浏览器环境中通常是 window 对象)。所以,当箭头函数中使用 this.code 时,它实际上是引用全局作用域中的 code 变量,其值为 404。
2)箭头函数在函数中
var code = 404; function F() { this.code = 200; let getCode = () => { console.log(this.code); }; getCode(); } var f = new F() //200 var f = F() //构造函数没有new调用,就成为了一个普通函数 console.log(f) console.log(code)
3)箭头函数在类中
var code = 404; class Status { constructor(code) { this.code = code; } getCode = () => { console.log(this.code); }; } let status = new Status(200); status.getCode(); // 200
不管是箭头函数还是普通函数,只要是类中,this就指向实例对象。
3.微任务宏任务
什么是微任务和宏任务
首先,我们要先了解下 Js 。js 是一种单线程语言,简单的说就是:只有一条通道,那么在任务多的情况下,就会出现拥挤的情况,这种情况下就产生了 ‘多线程’ ,但是这种“多线程”是通过单线程模仿的,也就是假的。那么就产生了同步任务和异步任务。
JS为什么要区分微任务和宏任务
(1)js是单线程的,但是分同步异步
(2)微任务和宏任务皆为异步任务,它们都属于一个队列 (3)宏任务一般是:script、setTimeout、setInterval、postMessage (4)微任务:Promise.then ES6
先执行同步 再执行异步
(5)先执行同步再执行异步,异步遇到微任务,先执行微任务,执行完后如果没有微任务,就执行下一个宏任务,如果有微任务,就按顺序一个一个执行微任务
微任务和宏任务有哪些
(1)宏任务一般是:script、setTimeout、setInterval、postMessage
(2)微任务:Promise.then
微任务和宏任务是怎么执行的?
执行顺序:先执行同步代码,遇到异步宏任务则将异步宏任务放入宏任务队列中,遇到异步微任务则将异步微任务放入微任务队列中,当所有同步代码执行完毕后,再将异步微任务从队列中调入主线程执行,微任务执行完毕后再将异步宏任务从队列中调入主线程执行,一直循环直至所有任务执行完毕。
这里容易产生一个错误的认识:就是微任务先于宏任务执行。实际上是先执行同步任务然后在执行异步任务,异步任务是分宏任务和微任务两种的。