JavaScript中闭包的使用和各种继承介绍

  • 2019 年 10 月 13 日
  • 笔记

一、什么是闭包?

    (1)闭包的概念:a、闭包就是函数嵌套时,让局部变量变成自由变量的环境,是一种让局部变量进化的方式。
 
                   b、定义在一个函数内部的函数。
    
    (2)垃圾回收机制:用过一次的东西,先放在一个内存中,不立即删掉,可以随时进行还原或再次使用,直到没有任何作用的时候再清除。
      tip: 如家用电器,电脑回收站。
 
二、闭包的应用场景:
        //1、for循环之中:              // for循环之中的i变量会因为for的循环次数被覆盖,所以在for循环内部存在函数时,而且这个函数内会调用i变量,这种情况下就需要用到闭包。                for (var i = 0; i < 10; i++) {                  console.log(i);        //可以访问到每次的i              }                // 必须满足两个条件:              //     1.在for循环内存在函数              //     2.函数内会调用这个变量                var ali = document.getElementsByTagName("li");              for(var i=0;i<10;i++){                  ali[i].onclick = function(){                      console.log(i);        //在函数内部就无法访问到外部变量                  }              }                // 如何形成闭包              var ali = document.getElementsByTagName("li");              for(var i=0;i<10;i++){                  (function(a){                      ali[a].onclick = function(){                          console.log(a);                      }                  })(i)              }          //     一旦内部函数调用外部函数的局部变量,那么这个时候,这个局部变量就会变成内部函数的私有变量              // 2、当需要给setTimeout的回调函数传参时:              setTimeout(function (a){                  console.log(a); //两秒后,undefined              }, 2000);              // 或者              setTimeout(fn(10), 2000);              function fn(a){                  console.log(a); //没有延迟,直接打印10              }                // 怎么使用闭包:                function fn(a){                  return function(){                      console.log(a); //两秒后,10                  }              }              var f = fn(10);              setTimeout(f, 2000);  

三、闭包的特点:

  (1)闭包是将函数内部和函数外部连接起来的桥梁.

  (2)可以读取函数内部的变量。

  (3)让这些变量的值,始终保存在内存中,不会在调用结束后被系统回收。

  (4)避免全局变量命名空间的污染。

  (5)内存消耗很大,不能滥用。

  (6)闭包会在父函数外部,改变父函数内部变量的值。

四、构造函数继承:

        // 在构造函数中,同样属于两个新创建的函数,也是不相等的              function Fn(name){                  this.name = name;                  this.show = function(){                      alert(this.name);                  }              }              var obj1 = new Fn("AAA");              var obj2 = new Fn("BBB");              obj1.show()              obj2.show()          // 此时,任何一个new出来的实例上都有了show方法,可以视为最基础的继承。

五、js中的call和apply继承:

        function Father(skill){                  this.skill = skill;                  this.show = function(){                      alert("我会"+this.skill);                  }              }              function Son(abc){                  //这里的this指向函数Son的实例化对象                  //将Father里面的this改变成指向Son的实例化对象,当相遇将father里面所有的属性和方法都复制到了son身上                  //Father.call(this,abc);//继承结束,call适合固定参数的继承                  //Father.apply(this,arguments);//继承结束,apply适合不定参数的继承              }              var f = new Father("绝世木匠”);              var s = new Son("一般木匠");              f.show()              s.show();              // 优点:              //     创建子类实例时,可以向父类的构造器传参;              // 缺点:              //     只能继承构造器中定义的属性和方法,不能继承原型上定义的属性和方法

六、js中prototype的概念:

  (1)prototype是原型对象

  (2)指针  constructor  表示当前函数属于谁,用来指向当前原型所属的函数

  (3)原型指针  [[prototype]]  __proto__  相当于一根原型指针,指向当前对象的“父级”

  (4)可以在当前函数的原型身上添加各种属性和方法,以供使用

  (5)JS中万物皆对象,所有内容的指针终点都指向Object

七、原型链继承:

        // 方法一:              Son.prototype = new Father();              //创建一个函数Father,用来做原始对象              function Father(){                  this.skill = "铁匠"              };              Father.prototype.show = function(){                  console.log(this.skill)              }              //创建一个函数Son,准备继承Father的原型              function Son(){};              //将Son点原型,赋值为一个指针,指向Father的原型              Son.prototype = new Father();                //此时就可以通过执行new Son()获取到从构造函数Father上继承过来属性和方法              var s = new Son()              s.show();        //铁匠                // 优点:              //     1.可以访问父类原型上的方法和属性              //     2.简单方便,易于实现                // 缺点:              //     1.创建子类实例时,无法向父类的构造器传参            // 方法二:              //创建一个函数Father,用来做原始对象              function Father(){                  this.skill = "铁匠";              };              Father.prototype.show = function(){                  console.log("hello world")              }              //创建一个函数Son,准备继承Father的原型              function Son(){};              //将Son的原型,设置为Father的原型              //Son.prototype = Father.prototype;              //但是这种拷贝方式为对象的浅拷贝,一旦后期修改Son原型上的方法,会影响到Father的原型              //需采用对象的深拷贝方法              for(var i in Father.prototype){                  Son.prototype[i] = Father.prototype[i];              }              Son.prototype.show = function(){                  console.log("这是子类修改之后的show方法")              }              var f = new Father()              f.show()              var s = new Son()              s.show();                // 优点:              //     完全将父类原型上的方法和属性拷贝到子类原型                // 缺点:              //     只能继承原型上的方法和属性,构造函数无法传参和继承

八、混合继承:

        // 特点:          //         使用call或apply继承父类的构造器中的内容,使用原型继承,继承父类的原型              function Father(skill,id){                  this.skill = skill;                  this.id = id;              }              Father.prototype.show = function(){                  alert("我是father,这是我的技能"+this.skill);              }                function Son(){                  Father.apply(this,arguments);              }              //如果不做Son的原型继承Father的原型,此时会报错:son.show is not a function              for(var i in Father.prototype){                  Son.prototype[i] = Father.prototype[i];              }              //因为,如果不让Son的原型等于Father的原型,Son使用apply是继承不到原型上的方法              Son.prototype.show = function(){                  alert("我是son,这是我的技能"+this.skill);              }              var f = new Father("专家级铁匠","father");              var s = new Son("熟练级铁匠","son");              f.show();              s.show();

九、ES6中class的继承:

        class Father{                  constructor(){                  }                  show(){                  }              }            class Son extends Father{              constructor(){                  super()              }              show(){              }          }

十、原型对象相关概念解析:

1、对象中的__proto__是什么:
  js中万物皆对象,每个数据都会有一个__proto__的属性,这个属性叫隐式原型,一个对象(obj)的隐式原型(__proto__)指向构造该对象(obj)的构造函数(object())的原型属性(object.prototype),这样做的原因是为了能够保证实例(obj)能够访问到在构造函数(object())的原型属性(object.prototype)中定义的属性和方法。
 
2、函数中的prototype是什么:
  (1)函数(Function)是一个特殊的对象,除了和其他对象一样有上述__proto__属性之外,还有自己特有的属性——原型(prototype),这个属性被描述成指针,他指向一个对象类型的数据,这个对象的用途就是包含所有将来使用该函数构造出来的可被共享的属性和方法(我们把这个对象叫做原型对象)。
  (2)原型对象内也有一个属性,叫做constructor,这个属性包含了一个指针,指回原函数(类似于arguments.callee。但是arguments只能在函数内部获得,而函数原型对象内的constructor属性,可以在任何能访问到这个函数的位置使用)。
 
3、构造函数,原型,实例之间的关系:
  (1)构造函数Fn身上有属性prototype为原型对象,原型对象内有constructor属性指向当前prototype所在的构造函数Fn。
  (2)在new执行构造函数Fn时,创造了一个实例对象f,实例对象f的__proto__指向构造函数Fn的原型prototype。
  (3)因为实例对象f的__proto__指向构造函数Fn的原型prototype,所以实例对象f可以间接访问到Fn原型prototype的方法。

                                       
4、查看实例对象f是否有指针指向构造函数Fn的原型:
  (1)isPrototypeOf()用于检测两个对象之间似乎否存在这种关系,使用方法如下:
    Fn.prototype.isPrototypeOf(f)    // 查看 Fn 的 prototype 对象,是否是 f 原型
  (2)类似的还有instanceof运算符,使用方法如下:
    console.log(f instanceof Fn)     // 查看 f 对象是否是构造函数 Fn 的实例
    console.log(f instanceof Object)
    tip:两种使用,如果是返回ture,如果不是返回false。
    tip:instanceof运算符右侧为构造函数,并且js中所有原型都来自Object构造函数。
5、javascript中解析器访问属性顺序:
  (1)当访问实例 f 的属性或方法时,会先在当前实例对象 f 中查找,如果没有,则沿着__proto__继续向上寻找,如果找到最顶头的Object还是找不到,则会抛出undefined。如果在实例中找到,或某层原型中找到,就会读取当前值并执行,同时停止向上找寻。
  (2)由此可见,解析器的解析顺序遵循就近原则,如果在最近的位置发现属性存在,便不会继续向上找寻。