2018-12-20

2018-12-20  本文已影响0人  废废_siri

闭包前言--异步编程

进程:进程是操作系统分配资源(时间片)的最小单位
线程:线程是进程中一个概念 (线程是程序执行的最小单元)
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.这题的注意点:每次调用外部函数时,都会产生一个闭包,每个闭包都是不同的;每次调用内部函数时,都会产生一个执行上下文环境,每个执行上下文环境是不同的。
解析图:

image.png
第一个输出:
全部是对象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>
上一篇下一篇

猜你喜欢

热点阅读