美文网首页
2018-12-20

2018-12-20

作者: 废废_siri | 来源:发表于2018-12-20 17:05 被阅读0次

    闭包前言--异步编程

    进程:进程是操作系统分配资源(时间片)的最小单位
    线程:线程是进程中一个概念 (线程是程序执行的最小单元)
    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>
    

    相关文章

      网友评论

          本文标题:2018-12-20

          本文链接:https://www.haomeiwen.com/subject/wkpykqtx.html