2018-12-20
闭包前言--异步编程
进程:进程是操作系统分配资源(时间片)的最小单位
线程:线程是进程中一个概念 (线程是程序执行的最小单元)
CPU:单核的CPU,同一时间只能运行一个线程,平常我们的电脑上多个软件看似并发的运行(但其实不是,只是各个线程的时间切换很快,我们无法察觉而已)。
浏览器是多进程多线程的,js引擎是单线程的(同一时间只能做一件事情),js引擎是浏览器的一个线程。
回调函数:
一般回调函数都是一个匿名函数
我们只想要定义回调函数 不需要调用
最终函数会被js引擎自动调用(带条件)
条件: 时间到了 用户触发了一些行为
同步回调函数
立马执行 只有等同步回调函数执行完毕 后续代码才可以继续执行
arr.map(function(item,index,arr){})
异步回调函数
setTimeout(function(){},time)
生命周期
作用域的生命周期
---函数定义时被创建
---浏览器关闭时被销毁
执行上下的生命周期
---函数调用时被创建
---函数调用完毕闭包被释放,下一轮垃圾回收机制运行时被销毁
闭包的生命周期
---闭包被创建的前提条件:函数嵌套,内部函数使用了外部函数的变量
---外部函数被调用时,闭包被创建
---当内部函数设置为null时,闭包被释放,下一轮垃圾回收机制运行时被销毁
setTimeout(function(){
},time)
1.当js引擎遇到定时器时,会通知异步线程来管理定时器,js继续向下解析定时器以外的代码。
2.通知异步线程后,异步线程等待定时器设置的time时间后,将定时器中的函数放进异步队列中。
3.js引擎(主线程)执行完所有的同步操作,主线程空闲后就去异步队列中轮询,并将异步队列中的回调函数取出来执行。
异步编程面试题
<script>
for(var i=0;i<5;i++){
setTimeout(function () {
console.log(i)
},1000)
}
</script>
解析:
1.js引擎第一次解析到setTimeout处,通知异步线程来管理当i=0时的function(回调函数),暂且称作f0,异步线程需等待1s钟将回调函数放到异步队列中
2.js引擎继续解析,发现又是一个setTimeout,于是执行第一步一样的步骤(f1~f4)
3.js执行代码的速度远远小于1s,当异步线程还没有将回调函数放入异步队列中时,js引擎已经将代码执行完毕,那么它就是队列中轮询,没有回调函数就空轮询,直到1s后,异步线程将回调函数放入队列中,js引擎就将回调函数取出来执行。(注意:队列是先进先出,每个回调函数进队列的时间间隔可忽略不计,因为时间间隔很短,差不多同时)。
4.还值得注意的是,回调函数中并没有对i进行声明,所以根据作用域链的规则,i的值使用的是全局的变量i(也就是for中定义的i)),当回调函数还没有进队列时,for中变量i已经变为了5
5.最终1s后输出5个5.
--
改造一:
<script>
for(var i=0;i<5;i++){
setTimeout(function () {
console.log(i)
},1000*i)
}
</script>
解析:解析方法与前面一致,当这里的time变为了1000*i,于是每次的时间间隔不同了(0s,1s,2s,3s,4s),时间间隔为1s,第一个因为是等待0s,所以可以很快的输出,剩下的隔1s输出一个5.
--
IIFE(立即可执行函数表达式)
IIFE的组成部分:
1.第一个()中包含的是匿名函数function(){}
2.第二个()包含的是外部变量来传参(实参),传给匿名函数的形参,当js引擎遇到最后一个()时,那么IIFE就立即被执行。
--
IIFE的特点:
当函数变为立即可执行函数表达式(IIFE)时,外部是不能访问IIFE的,这样避免了外界对IIFE中变量的访问,也保证了IIFE中的变量不会污染全局。
--
改造二:
<script>
for(var i=0;i<5;i++){
(function(j){ //相当于 var j = i;
setTimeout(function () {
console.log(j) //1s后输出0,1,2,3,4
},1000)
})(i) //到最后的()处,就开始执行立即可执行函数的代码
}
解析:
1.js引擎首先解析到for循环,i=0,将i的值传递给IIFE的形参j,j的值为0,遇到setTimeout后,异步线程拿着回调函数等待1s,1s后将函数放到异步队列中。
2.因为IIFE的特性,IIFE中的变量是不会污染全局的,所以j的值每次都是i传给它的值,在异步队列中会产生5个回调函数,各个回调函数中的j值都不相同。
3.1s后,输出0,1,2,3,4
闭包
闭包的创建时间:
当外部函数的执行上下文被创建时(外部函数被调用时)
--
创建闭包的必要条件:
1.函数嵌套
2.内部函数有使用外部函数的变量
( //下面两句是装逼时使用的:)
1.函数产生了嵌套,内部函数使用了外部函数的变量 就会产生闭包
2.当函数可以记住并访问自己的作用域链时就会产生闭包
)
--
闭包存放的位置:
闭包放在内部函数的作用域中(在执行上下文还没有被创建时)
--
闭包的作用:
延长变量的生命周期
--
闭包的缺点:
闭包一般不会主动销毁,那么它所带来的问题:
1.内存泄露(被占用 无法释放)
2.内存溢出(使用的内存比分配的内存多)
销毁闭包的方法:
将内部函数置为null.
红色警报:下面这句话非常重要:
闭包和内部函数是一一对应的关系(外部函数每被调用一次就产生一个闭包,切记!!切记!!)
闭包面试题:
<script type="text/javascript">
/*
说说它们的输出情况
*/
function fun(n, o) {
console.log(o)
return {
fun: function (m) {
return fun(m, n)
}
}
}
var a = fun(0) //产生c0闭包,n=0
a.fun(1)
a.fun(2)
a.fun(3) //undefined,0,0,0
var b = fun(0).fun(1).fun(2).fun(3) //undefined,0,1,2
var c = fun(0).fun(1)
c.fun(2)
c.fun(3) //undefined,0,1,1
</script>
解析:
1.fun(0)调用完后,返回一个对象:
{
fun: function (m) {
return fun(m, n)
}
}
并且将这个对象的引用地址赋值给a
2.这道题满足闭包的必要条件--函数嵌套,内部函数使用了外部函数的变量,所以当外部函数时(fun(0))被调用时,闭包n已经产生,保存在内部函数的作用域中。
3.这题的注意点:每次调用外部函数时,都会产生一个闭包,每个闭包都是不同的;每次调用内部函数时,都会产生一个执行上下文环境,每个执行上下文环境是不同的。
解析图:
第一个输出:
全部是对象a对内部函数fun的调用,所以永远都是使用的A0闭包,那么n的值永远都是0,所以答案是undefied,0,0,0
第二个输出:
相当于以下形式
/*
对象a = fun(0)
对象b = 对象a.fun(1)
对象c = 对象b.fun(2)
对象d = 对象c.fun(3)
/
所以答案是:undefuned,0,1,2
第三个输出:
相当于以下形式
/
对象a = fun(0)
对象b = 对象a.fun(1)
对象b = 对象b.fun(2)
对象b = 对象b.fun(3)
*/
所以答案是:undefined,0,1,1
鸡肋闭包
变量得到作用域链与闭包同时存在
<script>
function wrap() {
var a="a-val";
function inner() {
console.log(a); //变量a通过作用域链与闭包都能找到
}
inner(); //return inner;
}
wrap();
</script>
原型&原型链
原型.png 经典的原型链图谱.jpg规则:
---显示原型:所有的函数都有一个显示原型
---隐式原型:所有的对象都有一个隐式原型
---所有对象的隐式原型指向其构造函数的显示原型
---大写的Function构造函数的proto(隐式原型)指向本身的prototype(显示原型)
---所有原型对象看成一个{},所有的原型对象的proto指向object.prototype
---object.prototype__proto__(object.prototype的隐式原型)
恒等于(===)null,null(原型链的头)
---所有的原型对象都有一个constructor属性指向原函数
原型链(原型)的作用
原型 服务于属性的查找与设置
--
属性查找:
---先在属性的直接对象中找,如果找到直接返回
如果找不到,上原型链上面找(只有隐式原型链,没有显示原型链),如果找到就返回
如果在隐式原型链上没有找到对应的显示原型,就返回undefined
image.png
--
属性设置
---永远只影响对象的直接属性,跟原型链没关系
如果对象中有此属性则直接将此属性的属性值改掉
如果对象中没有此属性则将此属性添加至对象中
<script>
Object.prototype.name="siri"; //所有的对象都可以继承Object.prototype的属性和方法
var obj = {
}
//obj对象中没有name属性,所以obj.name操作是将name属性及属性值添加至obj对象中,此过程并没有影响原型链
obj.name = "tom";
console.log(obj);
console.log(Object.prototype);
</script>
image.png
方法重写
默认的数组的原型链:
arr.proto ——>Array.prototype
Array.prototype__proto__ ——>Object.prototype
Object.prototype__proto__ ——>null
Array.prototype ——>Object.prototype——>null就是原型链
<script>
var arr = [1,2,3]
//方法的重写,原本的toString方法被改写掉了
console.log(arr.toString());
console.log(Object.prototype.toString.call(arr));
</script>
image.png
怎么判断一个对象是不是数组?(面试题)
--
使用isArray()API来判断
//isArray()API兼容移动端不太友好
var arr = [1,2,3];
console.log(Array.isArray(arr));
--
使用Object.prototype.toString()方法来判断
//使用Object.prototype.toString()方法
function isArray(obj){
//调用toString()方法的返回值是[object type],type视传入的参数而定
return Object.prototype.toString.call(obj).indexOf("Number") === 8;
}
console.log(isArray(12) );
--
使用instanceof来判断
/*
使用instanceof来判断
a(对象) instanceof b(构造函数)
*/
console.log([] instanceof Array); //true
instanceof的深度解析
a(对象) instanceof b(构造函数):查找构造函数的显示原型(b.prototype)是否出现在a的隐式原型链上(a.proto)
注意点:instanceof的对象(也就是a)一般不写基本数据类型来判断
--
改写instanceof规则
<script>
function Person(){
}
var p = new Person()
p.__proto__ = Array.prototype; //将p的隐式原型(b.__proto__)指向Array的显示原型(Array.prototype)
console.log(p instanceof Array); //true
console.log(p instanceof Person); //false
</script>