JS:闭包,面向对象,封装,继承

  1. 闭包
    *****面向对象*****
  2. 什么是面向对象
  3. 封装
  4. 继承

一. 闭包

  1. 问题: 全局变量和局部变量都有不可兼得的优缺点
    (1). 全局变量:
    a. 缺点: 极易被篡改!全局污染
    b. 优点: 可反复使用
    (2). 局部变量:
    a. 优点: 不会被外部篡改
    b. 缺点: 不可反复使用!
  2. 解决: 今后,只要希望给一个函数保存一个专属的变量,既可重用,又不会被随意篡改,都要用闭包解决!
  3. 如何: 3步:
    (1). 用外层函数,包裹要保护的变量和内层函数——相当于给内层函数找一妈
    (2). 外层函数要把内层函数返回到外层函数外部
    (3). 想使用内层函数的人,必须调用外层函数,用变量保存住返回出来的内层函数对象,才能反复调用。
  4. 示例: 定义函数帮小孩儿保管压岁钱
    1_closure.html
<script>
  //定义一个函数,帮小孩儿管理压岁钱
  // // var total=1000;//全局,极易被篡改
  // function pay(money){
  //   // var total=1000;//局部,不可重用
  //   //每花一笔钱,都从总钱数里扣掉这笔钱
  //   total-=money;
  //   console.log(`花了${money},还剩${total}`);
  // }

  //1. 用外层函数包裹要保护的变量和内层函数
  //为内层函数找一妈
  function mother(){
    var total=1000;//要保护的变量
    //问题: pay成了局部函数,外部无法直接使用
    //2.外层函数将内层函数返回到外部去!
    return function(money){
      total-=money;
      console.log(`花了${money},还剩${total}`);
    }
  }
  //3. 想使用内层函数的人,必须调用外层函数,用变量保存住返回出来的内层函数对象,才能反复调用。
  var pay=mother();
  //pay: function pay(money){//函数对象
  //      total-=money;
  //      console.log(`花了${money},还剩${total}`);
  //     }
  //问题: mother()的局部变量1000,在mother调用后,应该释放了?下边pay()为什么可以持续-100?怎么重用的呢?
  pay(100);//闭包中的total=900
  total=0;//别人的代码,强行在全局创建total,但是不会影响闭包中的total。
  pay(100);//闭包中的total=800
  pay(100);//闭包中的total=700
</script>
</body>
</html>

运行结果:
花了100,还剩900
花了100,还剩800
花了100,还剩700

5. 原理:
图片1.png

图片2.png

图片3.png

图片4.png

6. 一句话概括闭包是如何形成的 / 什么是闭包?

答: 外层函数调用后,外层函数的函数作用域对象,被内层函数的作用域链引用着,无法释放!形成了闭包对象!

  1. 闭包的缺点: 比一般的函数多占用一块存储空间——外层函数的函数作用域对象。
  2. 解决: 只要一个闭包结构不再被使用时,都要将保存内层函数的外部变量赋值为null,比如pay=null;

*****面向对象*****

一. 什么是面向对象:

  1. 问题: 程序中通常都会管理大量数据!如果毫无组织的存放,极容易出错!极其不便于使用
  2. 解决: 今后,几乎所有程序,都用面向对象思想保存数据。
  3. 什么是: 将现实中一个事物的属性和功能集中保存在程序中一个对象结构中,再起一个名字。
  4. 为什么: 极其便于大量数据的管理和维护
  5. 如何: 3步/3大特点: 封装,继承,多态

二. 封装:

  1. 什么是: 创建一个对象结构,保存现实中一个事物的属性和功能。
  2. 为什么: 极其便于大量数据的管理和维护
  3. 何时: 今后,只要使用面向对象思想开发,都要先创建对象。
  4. 如何: 3种:
    (1). 用{}:
    a. var 对象名={
    属性名: 属性值,
    ... : ... ,
    方法名: function(){
    ... ...
    }
    }
    b. 说明:
    1). 事物的属性值,就会成为对象中的属性值
    2). 事物的功能,就会成为对象中的方法。
    科普: 方法和函数:
  5. 相同点: 本质都是function(){ ... }
  6. 不同点: 保存的位置不同:
    1). 不属于任何对象的独立在全局的function,称为函数
    2). 保存在对象中的function,称为方法
    c. 如何访问对象中的成员: (成员=属性+方法)
    1). 想访问对象中的属性:先找到对象,再进入对象,找属性名
    语法: 对象名.属性名
    2). 想访问对象中的方法: 先找到对象,再进入对象,找方法名
    语法: 对象名.方法名() //调用
    d. 示例: 定义对象lilei,描述lilei的属性和方法:
    2_{}.html
<script>
    //定义一个学生对象,保存lilei同学的个人信息
    //姓名: Li Lei, 年龄: 11,
    //lilei会自我介绍
    var lilei={
      sname:"Li Lei",
      sage:11,
      intrSelf:function(){
        console.log(`I'm ${this.sname}, I'm ${this.sage}`)
      }
    };
    //想输出lilei的年龄
    console.log(lilei.sage);
    //请lilei做自我介绍
    lilei.intrSelf();
    //过了一年, 李磊长了一岁
    lilei.sage++;
    //再输出lilei的年龄
    console.log(lilei.sage);
    //再请lilei做自我介绍
    lilei.intrSelf();
    //输出lilei的存储结构: 
    console.log(lilei);

    //不用写,只是一个教训。
    //把lilei对象中的intrSelf函数赋值给全局变量a
    //a就得到了函数
    var a=lilei.intrSelf;
    //调用a,相当于调用lilei中的intrSelf
    a();//I'm undefined, I'm undefined;
    //前边没有点,所以this默认->window。
    //this.sname=>window.sname=>undefined。
  </script>
</body>
</html>

运行结果:
I'm Li Lei, I'm 11
12
I'm Li Lei, I'm 12
{sname: "Li Lei", sage: 12, intrSelf: ƒ}
I'm undefined, I'm undefined

e. this:
1). 问题: 明明同一个对象中的方法和属性,方法中想用自己对象中的属性,竟然报错:xxx属性名 未定义
2). 原因: 对象不是作用域,对象无权进入方法的作用域链中。所以,在方法内,不加任何前缀,就想像普通变量一样直接使用对象中的属性,根本找不到的!

图片5.png

3). 解决: 2种:
i. 不好: 在方法中用"对象名.属性名",先找到对象,再找到对象内的属性。
缺点: 对象名其实就是一个普通的变量,所以有对象名很可能被改变。一旦对象名被修改,而忘记修改方法中写死的对象名,立刻就会出现不一致,程序立刻就错误!
ii. 好: 用关键字this代替方法中写死的对象名: "this.属性名"
什么是this:
每个函数中都自带的——不用创建就可直接使用。
专门在调用函数时——只有调用函数时讨论this才有意义。
自动获得当前正在调用函数的.前的对象——内容
的关键字。
总结: 今后只要对象自己的方法中,想使用对象自己的属性,都必须加this!
图片6.png

4). 强调: 判断this指向,一定不要看定义在哪里,必须看在哪里,如何被调用的,前边有没有点。
图片7.png

(2). 用new:
a. 如何: 2步:
1). 先创建一个空对象等待:
var 对象名=new Object();
2). 再强行向对象中添加新属性:
对象名.属性名=属性值;
对象名.方法名=function(){
... this.属性名 ...
}
b. 揭示了本质: 其实js内存中,一切都是关联数组!
c. 对象与关联数组:
1). 都是名值对儿的集合
2). 访问对象成员:
i. 标准: 对象名["属性名"]
ii. 简写: 对象名.属性名
iii. 特例: 如果属性名不是写死的,来自于其他变量,则既不能用点,又不能加"",只能: 对象名[变量名]
3). 都可随时向不存在的位置添加新属性
所以,将来想向对象中添加一个新属性,没有专门的函数或优雅的办法。只有唯一一种野蛮的办法!强行赋值!
对象名.新属性=新值;
4). 都可随时访问对象中不存在下标位置或属性,都不报错,都返回undefined。
所以,将来想判断一个数组或对象中是否包含某个成员,都可以用:
if(对象.属性名!==undefined){ ... 包含该成员 ... }
5). 都可用for in循环遍历
d. 示例: 定义一个clone函数,可以克隆任何一个对象

 <script>
    var lilei={sname:"Li Lei",sage:11};
    //错误: 只是将原对象地址复制一份给lilei2
    //并没有创建新对象
    //结果: 两个变量用相同的地址值,指向同一个变量
    // var lilei2=lilei;
    //正确: (暂时用中文起变量名)
    function clone(原对象){
      //1. 先创建新的空对象
      var 新对象={};//new Object();新对象,新地址
      //2. 变量原对象中每个属性
      //in,自动依次取出原对象中每个属性名,保存到in前的自定义变量中
      //        自定义变量
      for(var 原对象中的属性名 in 原对象){
        //3. 将原对象中当前属性名和属性值,强行添加到新对象中
        //3.1 先取出原对象中的属性值
        //                  自定义变量,变量都不加""
        var 原属性值=原对象[原对象中的属性名]
        //3.2 强行给新对象添加相同属性名和属性值的新属性
        新对象[原对象中的属性名]=原属性值;
      }
      //4. 返回新对象
      return 新对象;
    }

    //英文版:
    // function clone(oldObj){
    //   var newObj={};
    //   for(var key in oldObj){
    //     var value=oldObj[key];
    //     newObj[key]=value;
    //   }
    //   return newObj;
    // }

    //想把lilei克隆出一个新对象lilei2
    //        ←        ↓
    var lilei2=clone(lilei);
    console.log(lilei2);
    console.log(lilei==lilei2);//false
    //          地址    地址     不相同 2个对象
  </script>
</body>
</html>
运行结果: 
{sname: "Li Lei", sage: 11}
false

(3). 用构造函数:

a. 问题: {}一次只能创建一个对象。如果想创建多个相同结构,不同属性值的对象,用{},代码就会很繁琐,且重复,极其不便于今后的维护和修改。
b. 解决: 今后,只要想创建多个相同结构,不同属性值的对象时,都用构造函数来创建。
c. 什么是: 专门描述同一类型所有对象的统一结构的函数。
d. 如何: 2步:
1). 定义构造函数:
function 类型名(形参1, 形参2,...){
this.属性名1=形参1;
this.属性名2=形参2;
this.方法名=function(){
... this.属性名 ...
}
}
强调:
i. 将要添加到新对象中的属性,必须用this.前缀才行!不加this.前缀,则该属性无法加入将来的新对象中。
ii. 属性值不能写死,应该将来在创建某一个新对象时,动态传入属性值。传入什么,对象中就保存什么。所以,构造函数中应该用形参变量,暂时为将来的属性值占位。
2). 调用构造函数:
i. var 对象名=new 类型名(属性值1, 属性值2, ... )
ii. 强调: 只要调用构造函数,必须用new。
e. 优点: 重用对象的结果。
f. 示例: 定义学生类型,反复创建两个学生对象
5_constructor.html

 <script>
    //想定义一个学生类型,描述所有学生统一结构
    //       学生类型
    function Student(sname, sage){
      this.sname=sname;
      this.sage=sage;
      this.intrSelf=function(){
        console.log(`I'm ${this.sname}, I'm ${this.sage}`);
      }
    }
    //创建lilei对象,应该传入lilei的属性值
    var lilei=new Student("Li Lei",11);
    //创建hmm对象,应该传入hmm的属性值
    var hmm=new Student("Han Meimei",12);
    console.log(lilei);
    console.log(lilei.sage);
    lilei.intrSelf();
    console.log(hmm);
    console.log(hmm.sage);
    hmm.intrSelf();
  </script>
</body>
</html>
运行结果: 
Student {sname: "Li Lei", sage: 11, intrSelf: ƒ}
11
I'm Li Lei, I'm 11
Student {sname: "Han Meimei", sage: 12, intrSelf: ƒ}
12
I'm Han Meimei, I'm 12

g. 原理: new做了4件事儿:
1). 新建一个空对象等待
2). (未完待续...)
3). 自动调动构造函数,为新对象添加规定的新属性:
i. new关键字可以先将构造函数中的this,强行指向正在创建的新对象。
ii. 构造函数中的this.属性名=属性值,都变成新对象.属性名=属性值,都变成给新对象强行赋值新属性
4). 返回新创建的对象的地址。

图片8.png

简写:

  1. [] 是new Array()
  2. function 是new Function()
  3. // 是new RegExp()
  4. {} 是new Object()
    {}和function(){}
总结:

this共有几种情况:3种
一定不要看定义在哪儿,只看在哪里如何调用

  1. obj.fun() this->.前的obj对象
  2. 普通函数调用fun(),既没有点,也没有new,this->window(默认)
  3. new Fun() this->new正在创建的新对象
    (5). 闭包:
    a. 只要希望给一个函数保护一个可反复使用的专属变量,又防止这个变量被外界篡改时,都用闭包。
    b. 闭包三步:
    1). 用外层函数妈妈包裹要保护的变量和内层函数
    2). 外层函数妈妈用return把内层函数孩子返回到外部
    3). 外部想使用内层函数的人,必须调用外层函数,才能获得return出来的内层函数对象。并将内层函数保存在一个变量中反复使用。
    c. 闭包形成的原因: 外层函数调用后,外层函数的作用域对象被内层函数引用着无法释放,形成了闭包对象
    d. 闭包的缺点: 闭包比一般的函数占用多一块内存——外层函数的函数作用域对象。
    所以,用完闭包后,应该尽快释放:
    保存内层函数的变量=null
    4. 面向对象: 封装 继承 多态
    自己跟着视频,一步一步画图,自己标顺序,知识才能变成自己的
    (1). 封装: 3种:
    a. 用{}创建一个对象:
    var 对象名={
    属性名:属性值,
    ... : ... ,
    方法名: function(){
    ... this.属性名 ...
    }
    }
    b. 用new Object():
    1). 2步:
    i. var 对象名=new Object()
    ii. 对象名.属性名=属性值;
    对象名.方法名=function(){ ... }
    2). 对象底层也是关联数组:
    i. 都是名值对儿的集合
    ii. 都可用[""]和.方式访问成员。
    如果属性名来自于变量,就只能用[],不要加""
    iii. 访问不存在的属性,都不报错,返回undefined
    判断是否包含某个属性:
    对象.属性名!==undefined
    iv. 强行给不存在的属性赋值,都不报错,而是自动添加该属性
    给对象添加新属性,唯一办法,强行赋值:
    对象名.新属性名=新值
    v. 都可用for in遍历
    c. 只要反复创建多个相同结构的对象都用构造函数:
    1). 2步:
    i. 定义构造函数:
    function 类型名(形参1,形参2, ...){
    this.属性名1=形参1;
    this.属性名2=形参2;
    //构造函数中不要再包含方法定义定义!
    }
    ii. 用new 调用构造函数:
    var 对象名=new 类型名(属性值1, 属性值2,...)
    2). new做了4件事:
    i. 创建一个新的空对象
    ii.
    iii. 调用构造函数,传入实参,并自动替换构造函数中的this为new正在创建的新对象。构造函数中,通过强行赋值的方式为新对象添加规定的属性,并保存属性值。
    iv. 返回新对象的地址,保存到=左边的变量中。
    3). 优点: 重用对象结构代码
    4). 缺点: 如果构造函数中包含方法定义,则每次创建新对象都会重复创建相同方法的副本。——浪费内存!
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容