欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

JS 高级 - ES6 中的类和继承、递归、ES6 新语法、闭包

最编程 2024-06-02 20:12:00
...

1.instanceof 和 constructor

<script>
      // instanceof:判断某个对象是否是某个构造函数创建的
      function Student(name, age) {
        this.username = name
        this.userage = age
      }

      function Teacher(name, age) {
        this.username = name
        this.userage = age
      }

      let stu = new Student()
      let tea = new Teacher()
      console.log(stu instanceof Student, tea instanceof Student)
      // console.log(typeof stu, typeof tea)
    </script>

2. es6 class

JavaScript 语言中,生成实例对象的传统方法是通过构造函数,es6的class 的出现 基本上可以替代了es5的构造函数和原型,使之代码结构上更加简洁。
1.构造函数的写法跟传统的面向对象语言(比如 C++ 和 Java)差异很大,很容易让新学习js的程序员感到困惑。
2.ES6 提供了更接近传统语言的写法,引入了 Class(类)这个概念,作为对象的模板。通过class关键字,可以定义类。
3.新的class写法只是让对象原型的写法更加清晰、更像面向对象编程的语法而已。

ES5 创建对象

function Person(name, age) {
  this.name = name;
  this.age = age;
}
​
Person.prototype.sayHi = function () {
  console.log("hello, 我是" + this.name);
};
​
var p = new Person("lw", 36);
p.sayHi();

ES6 创建对象

<script>
  // 同样实现两种继承:继承原型方法,继承构造函数
  // 1.使用class定义类型,功能和构造函数类似
  class Person {
    // -添加构造器:为对象进行成员的初始化
    constructor(name, age) {
      this.username = name
      this.userage = age
    }
    // 在原型中添加成员:直接写函数
    say() {
      console.log(this.username)
    }
  }

  let per = new Person('jack', 20)
  per.say()
</script>

回顾构造函数方式的继承

<script>
  // 代码演示构造函数方式的原型继承及构造函数的继承
  function Person(name, age) {
    this.username = name
    this.userage = age
  }
  Person.prototype.say = function() {
    console.log(this.username)
  }

  function Student(name, age, gender) {
    // this:调用当前Student构造函数所创建的对象
    // this.username = name
    // this.userage = age
    // 调用父类Person的构造方法构建Student子类的成员
    // 愿望:希望Person构造函数中的this指向student对象
    Person.call(this, name, age)
    this.usergender = gender
  }
  // 1.实现原型方法的继承
  Student.prototype.say = Person.prototype.say

  let stu = new Student('jack', 20, '男')
  console.log(stu)
</script>

ES6-class实现默认继承

Class 可以通过extends关键字实现继承,这比 ES5 的通过修改原型链实现继承,要清晰和方便很多。

<script>
  // 同样实现两种继承:继承原型方法,继承构造函数
  // 1.使用class定义类型,功能和构造函数类似
  class Person {
    // -添加构造器:为对象进行成员的初始化
    constructor(name, age) {
      this.username = name
      this.userage = age
    }
    // 在原型中添加成员:直接写函数
    say() {
      console.log(this.username)
    }
  }

  // 定义一个子类Student,要实现继承Person类
  // 通过 extends 实现继承
  // 语法: class 子类  extends  父类
  // 1.有了extends关键字,子类默认就会继承父类的所有原型中的成员
  // 2.同时,默认也会继承父类的构造器
  class Student extends Person {}
  // 实例化对象的时候,会调用class的构造器(函数)
  let stu = new Student('rose', 18)

  stu.say()
</script>

ES6-class方式实现继承-添加子类构造器

<script>
  // 同样实现两种继承:继承原型方法,继承构造函数
  // 1.使用class定义类型,功能和构造函数类似
  class Person {
    // -添加构造器:为对象进行成员的初始化
    constructor(name, age) {
      this.username = name
      this.userage = age
    }
    // 在原型中添加成员:直接写函数
    say() {
      console.log(this.username)
    }
  }

  // 定义一个子类Student,要实现继承Person类
  // 通过 extends 实现继承
  // 语法: class 子类  extends  父类
  // 1.有了extends关键字,子类默认就会继承父类的所有原型中的成员
  // 2.同时,默认也会继承父类的构造器
  class Student extends Person {
    constructor(name, age, gender) {
      // 使用this之前必须调用父类构造器
      // 同时让父类构造器中的this为当前 子类对象
      // 所以我们可以调用父类构造器来构建子类对象的成员
      super(name, age)
      // this.username = name
      // this.userage = age
      this.usergender = gender
    }
  }
  // 实例化对象的时候,会调用class的构造器(函数)
  let stu = new Student('rose', 18, '男')
  console.log(stu)

  stu.say()
</script>

案例:Tab栏切换

HTML结构

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
    <link rel="stylesheet" href="./css/tab.css" />
  </head>
  <body>
    <div class="box" id="box">
      <div class="hd">
        <span class="current">体育</span>
        <span>娱乐</span>
        <span>新闻</span>
        <span>综合</span>
      </div>
      <div class="bd">
        <ul id="list">
          <li class="current">我是体育模块</li>
          <li>我的娱乐模块</li>
          <li id="li3">我是新闻模块</li>
          <li>我是综合模块</li>
        </ul>
      </div>
    </div>
    <script src="./js/tab.js"></script>
    <script>
      // 模拟一个tab栏插件
      // 1.引入插件资源:js css
      // 2.添加必须的结构和样式
      // 3.初始化,实现功能

      new MyTab('#box')
    </script>
  </body>
</html>

CSS代码

* {
  margin: 0;
  padding: 0;
}

ul {
  list-style: none;
}

.box {
  width: 400px;
  height: 300px;
  border: 1px solid #ccc;
  margin: 100px auto;
}

.hd {
  width: 100%;
  height: 45px;
  display: flex;
}

.hd span {
  display: inline-block;
  /*将行内元素转换成行内块元素,宽高才起作用*/
  flex: 1;
  background-color: pink;
  line-height: 45px;
  text-align: center;
  cursor: pointer;
  border-right: 1px solid #fff;
}
.hd span:last-child {
  border-right: none;
}

.hd span.current {
  /*交集选择器,标签指定式选择器*/
  background-color: rgb(240, 175, 35);
  /*紫色*/
}

.bd li {
  height: 255px;
  background-color: rgb(189, 189, 189);
  display: none;
  /*设置隐藏*/
}

.bd li.current {
  display: block;
  /*显示*/
}

JS代码

class MyTab {
  // 添加一个构造器,接收用户传入的参数
  // 构造器的功能是为对象添加成员
  constructor(selector) {
    // 获取Tab栏整个结构窗口
    let box = document.querySelector(selector)
    this.spans = box.querySelectorAll('.hd > span')
    this.lis = box.querySelectorAll('.bd li')
    this.box = box
    // this:是指当前所创建的对象
    // this调用方法,那么方法中的this就是当前的对象
    this.tabClick()
  }
  // 实现功能
  tabClick() {
    console.log(this)
    // 将this存储到一个变量中,以便后期在函数中使用的时候还是我们所需要的this
    // 命名一般有两个常见命名方式: _this  that
    let _this = this

    // // 循环遍历所有spans,绑定事件
    this.spans.forEach(function(ele, index) {
      ele.addEventListener('click', function() {
        console.log(_this)

        // 在事件处理函数中,实现再次排他
        // 1.获取当前有acitve的span元素,清除它的active样式
        _this.box.querySelector('.hd span.current').classList.remove('current')
        // 2.为当前被单击的span添加active样式
        ele.classList.add('current')

        _this.box.querySelector('.bd li.current').classList.remove('current')
        // 2.为当前被单击的span添加active样式
        _this.lis[index].classList.add('current')
      })
    })
  }
}

递归函数

递归函数:函数内部直接或者间接的调用自己

递归的要求:

  1. 自己调用自己(直接或者间接)
  2. 要有结束条件(出口)

递归函数主要是化归思想 ,将一个复杂的问题简单化,主要用于解决数学中的一些问题居多。

  • 把要解决的问题,归结为已经解决的问题上。
  • 一定要考虑什么时候结束让函数结束,也就是停止递归(一定要有已知条件)

什么是递归

在程序中,所谓的递归,就是函数自己直接或间接的调用自己。调用自己分两种:

  1. 直接调用自己
  2. 间接调用自己

就递归而言最重要的就是跳出结构,因为跳出了才可以有结果.

化归思想

化归思想: 将一个问题由难化易,由繁化简,由复杂化简单的过程称为化归,它是转化和归结的简称。

递归思想就是将一个问题转换为一个已解决的问题来实现

假如有一个函数f, 如果它是递归函数的话, 那么也就是说函数体内的问题还是转换为 f的形式.

function f() {
    ... f( ... ) ...
}
​

常规应用

1, 2, 3, 4, 5, ..., 100 求和

  1. 首先假定递归函数已经写好, 假设是foo. 即foo(100)就是求1100的和
  2. 寻找递推关系. 就是nn-1, 或n-2之间的关系:foo( n ) == n + foo( n - 1 )
var res = foo(100);
var res = foo(99) + 100;
var res = foo(98) + 99 + 100
........................
var res = foo(2) + 3 + 4 + 5 .....+ 98 + 99 + 100
var res = foo(1) + 2 + 3 + 4 + 5 .....+ 98 + 99 + 100
var res = 1 + 2 + 3 + 4 + 5 .....+ 98 + 99 + 100

将递推结构转换为递归体

function foo(n){
    return n + foo( n - 1 );
}

上面就是利用了化归思想:

- 将 求 100 转换为 求 99
- 将 求 99 转换为 求 98
- ......
- ......
- 将求 2 转换为 求 1
- 求 1 结果就是 1
- 即: foo( 1 ) 是 1

练习:

  • 计算1-100之间所有数的和

    // 1, 2, 3, 4, 5, ..., 100 求和
    // 递归思想就是将一个问题转换为一个已解决的问题来实现
    // 1. 100 + (1到99的和)
    // 2. 100 + (99 + (1到98的和))
    // 3. 100 + (99 + (98 + (1到97的和)))
    // .....
    // 99. 100 + (99 + ( 98 + ( 97 + (96 +(95.....(2 + 1))))))
    function getSum(n){ // 5
        if(n == 1){
            return 1
        }
        return n + getSum(n-1)
    }
    console.log(getSum(100))
    

image.png

  • 斐波那契数列

    <script>
        /*1 1 2 3 5 8 13 21 ...*/
        // 1.前面两位固定:结束递归的条件,如果第一位或第二们,直接返回1
        // 2.从第三位开始,每一位是前面两位数字的和
        // 独白:
        // 1.如果要求第100位的数字,那么先得到第99位和第98位数字
        // 2.如果要求第99位的数字,那么先得到第98位和第97位数字
        // 3.如果要求第98位的数字,那么先得到第97位和第96位数字
        // ......
        // 97:如果要求第4位的数字,那么先得到第3位和第2位数字
        // 98:如果要求第3位的数字,那么先得到第2位和第1位数字
        // 99.如果要求第2位的数字,直接返回1
        // 100.如果要求第1位的数字,直接返回1
    ​
    function fibonaci(n){
        if(n == 2 || n == 1){
            return 1 
        }
        return fibonaci(n-1) + fibonaci(n-2)
    }
    console.log(fibonaci(5))
    </script>
    

image.png

案例-生成目录结构

传统方式生成目录

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script>
      var data = {
        parent: '递归案例',
        children: [
          {
            parent: '目录1',
            children: []
          },
          {
            parent: '目录2',
            children: []
          },
          {
            parent: '目录3',
            children: [
              {
                parent: '子目录1',
                children: [
                  {
                    parent: '第一层',
                    children: [
                      {
                        parent: '第二层',
                        children: [
                          {
                            parent: '第三层',
                            children: [
                              {
                                parent: '第四层',
                                children: [
                                  {
                                    parent: '第五层',
                                    children: [
                                      {
                                        parent: '第六层',
                                        children: []
                                      }
                                    ]
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                parent: '子目录2',
                children: []
              }
            ]
          },
          {
            parent: '目录4',
            children: [
              {
                parent: '第一层',
                children: []
              }
            ]
          }
        ]
      }

      let str = '递归案例<br>'
      // 读取第一层children
      data.children.forEach(function(one) {
        str += '&nbsp;&nbsp;' + one.parent + '<br>'
        // 判断是否有下一层,下面的条件说明有下一层
        if (one.children && one.children.length > 0) {
          one.children.forEach(function(two) {
            str += '&nbsp;&nbsp;&nbsp;&nbsp;' + two.parent + '<br>'
            // 判断是否还有下一层,如果有则继续遍历拼接
            if (two.children && two.children.length > 0) {
              two.children.forEach(function(three) {
                str +=
                  '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' + three.parent + '<br>'
                // 判断是否还有下一层,如果有则继续遍历拼接
                if (three.children && three.children.length > 0) {
                  three.children.forEach(function(forth) {
                    str +=
                      '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
                      forth.parent +
                      '<br>'

                    // 判断是否还有下一层,如果有则继续遍历拼接
                    if (forth.children && forth.children.length > 0) {
                      forth.children.forEach(function(fifth) {
                        str +=
                          '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
                          fifth.parent +
                          '<br>'
                        // 。。。。
                      })
                    }
                  })
                }
              })
            }
          })
        }
      })

      document.write(str)
    </script>
  </body>
</html>

递归方式生成目录

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <script>
      var data = {
        parent: '递归案例',
        children: [
          {
            parent: '目录1',
            children: []
          },
          {
            parent: '目录2',
            children: []
          },
          {
            parent: '目录3',
            children: [
              {
                parent: '子目录1',
                children: [
                  {
                    parent: '第一层',
                    children: [
                      {
                        parent: '第二层',
                        children: [
                          {
                            parent: '第三层',
                            children: [
                              {
                                parent: '第四层',
                                children: [
                                  {
                                    parent: '第五层',
                                    children: [
                                      {
                                        parent: '第六层',
                                        children: [
                                          {
                                            parent: '第100层',
                                            children: []
                                          }
                                        ]
                                      }
                                    ]
                                  }
                                ]
                              }
                            ]
                          }
                        ]
                      }
                    ]
                  }
                ]
              },
              {
                parent: '子目录2',
                children: []
              }
            ]
          },
          {
            parent: '目录4',
            children: [
              {
                parent: '第一层',
                children: []
              }
            ]
          }
        ]
      }

      let str = '递归案例<br>'
      let count = 0

      function printDir(data, count) {
        count++
        // 判断是否还有下一层,如果有则继续遍历拼接
        if (data.children && data.children.length > 0) {
          data.children.forEach(function(dir) {
            // repeat:将前面的字符串重复指定的次数
            str += '&nbsp;&nbsp;'.repeat(count) + dir.parent + '<br>'
            // 调用函数实现递归生成目录
            printDir(dir, count)
          })
        }
      }

      printDir(data, count)

      document.write(str)
    </script>
  </body>
</html>

函数参数默认值

<script>
  // 函数的形参是一个变量,可以直接赋值一个默认值
  // 1.如果调用函数时没有传递参数,则使用默认值
  // 2.如果调用函数时传递了参数,则使用传递的参数
  function show(msg = '大家一起快活呀') {
    // msg = msg || '大家一起快活呀'
    // if (!msg) {
    //   msg = '大家一起快活呀'
    // }
    console.log(msg)
  }

  show() // 打印 大家一起快活呀
  show('搞笑不') // 打印 搞笑不
</script>

对象简写

在定义对象的时候,如果属性名和变量名一致,那么可以实现简写

const name = "悟空";
const skill = "72变";
const say = function () { }
const obj = {
    name, skill, say
}
console.log(obj);// {name:"悟空",skill:"72变",say:function(){}}

对象的方法也可以简写

const obj = {
    say() {
        console.log(this);
    }
}

解构

提供更加方便获取数组中元素或者对象中属性的写法

获取数组中的元素

      const [a, b, c, d] = [1, 2, 3, 4];
      console.log(a, b, c, d);// 1,2,3,4

元素交互顺序

    let a = 1111;
    let b = 2222;
    [b, a] = [a, b];
    console.log(a, b);// 2222 1111 

获取对象中的属性(重点)

const obj = {
    name: "悟空",
    skill: "72变",
    say() { }
}
const { name, skill,say } = obj;
console.log(name, skill,say);// 悟空 72变 function(){}

解构-补充

<script>
  let name = 'rose'
  // 解构:就是从对象或者数组中提取元素
  // 数组解构
  // let arr = [12, 3, 245, 364]

  // let [a, b, ...c] = arr
  // Rest剩余的 element元素 must be必须 last最后 element元素
  // 不能解构出中间的元素
  // let [a, ...b, c] = arr
  // console.log(a, b, c)
  // let a = 10,
  //   b = 20
  // ;[b, a] = [a, b]
  // console.log(a, b)

  // 对象解构
  let obj = {
    name: 'jack',
    age: 20,
    gender: '男',
    computer: {
      brand: '搞不清',
      price: 2000
    }
  }
  // Identifier标识 'name' has already been已经被 declared声明
  // 对象解构的时候,只能解构出对象中已有属性,如果解构的名称在对象中不存在,则返回undefined
  // 我们在解构的时候可以为解构出的成员设置别名,后续使用应该使用别名
  // let { name: username, age } = obj
  // console.log(username, age)
  // 实现二次解构
  let { computer:{brand} } = obj
  console.log(brand)
</script>

拓展(展开)运算符 || 剩余运算符

通过 ...符号来获取剩下的参数

函数内获取

function show(a, ...all) {      // 只能放最后
    console.log(a);
    console.log(all);
}
​
​
show(1);// 1 []
show(1, 2, 3);// 1 [2,3]

数组内获取

const [a, ...rest] = [1, 2, 3, 4, 5];
console.log(a); // 1
console.log(rest);// [2, 3, 4, 5]

对象内获取

const obj={
    name:"悟空",
    skill:"72变",
    say(){}
}
​
const {name,...others}=obj;
console.log(name); // 悟空
console.log(others); // {skill: "72变", say: ƒ}

Set

永远不会有重复元素的对象

可以理解为不重复的数组

const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set);

Set对象转为数组

const set = new Set([1, 5, 3, 4]);
set.add(5);
set.add(5);
console.log(set);
​
const arr = [...set];// 将set对象转数组
console.log(arr);

闭包

闭包的概念

计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了*变量的函数

闭:封闭:函数内部声明的成员默认在函数外部不能使用

包:包裹:函数内部包裹函数,内部所声明的函数就可以访问内部的成员

在JavaScript中,在函数中可以(嵌套)定义另一个函数时,如果内部的函数引用了外部的函数的变量,则产生闭包。简单的来说闭包就是,一个具有封闭的对外不公开的, 包裹结构, 或空间.

在JavaScript中函数可以构成闭包. 一般函数是一个代码结构的封闭结构, 即包裹的特性, 同时根据作用域规则, 只允许函数访问外部的数据, 外部无法访问函数内部的数据, 即封闭的对外不公开的特性. 因此说函数可以构成闭包

产生闭包的条件

当内部函数访问了外部函数的变量的时候,就会形成闭包。
// 1.函数内部声明函数--嵌套函数--内部函数
// 2.内部声明的函数使用了外部声明的成员
// 3.返回了内部函数,外面进行接收,通过这个函数使用内部成员

闭包要解决什么问题?

  1. 闭包内的数据不允许外界访问
  2. 要解决的问题就是间接访问该数据

函数就可以构成闭包, 要解决的问题就是访问到函数内部的数据

我们观察下面的函数foo,在foo内部有一个变量num,能否在函数外部访问到这个变量num呢?

function foo () {
    var num = 123;
    return num;
}
​
var res = foo();
console.log( res ); // => 123

分析:

在上面的代码中,确实可以访问到num这个函数内部的变量。但是能不能多次访问呢?

不能,因为每次访问都得重新调用一次foo函数,每次调用都会重新创建一个num = 123,然后返回。

解决思路

函数内的数据不能直接在函数外被访问,是因为作用域的关系,上级作用域不能直接访问下级作用域中的数据。

但是如果反过来,下级作用域可以直接访问上级作用域中的数据。那么如果在函数foo内定义一个函数,那么在这个内部函数中是可以直接访问foo中的num的。

function foo() {
    var num = Math.random();    
    function func() {
        return num;    
    }
    return func;
}
​
​
var f = foo();
// f可以直接访问num,而且多次访问,访问的也是同一个,并不会返回新的num
var res1 = f();
var res2 = f();

闭包的作用

保护私有变量不被修改

如何获得超过一个数据

函数的返回值只能有一个,那按照上面的方法,我们只能对函数内部的一个数据进行操作。怎么操作函数内的多个数据呢?

可以使用对象,代码如下:

function foo () {
    var num1 = Math.random();
    var num2 = Math.random();
    //可以将多个函数包含在一个对象内进行返回,这样就能在函数外部操作当前函数内的多个变量
    return {
        num1: function () {
            return num1;
        },
        num2: 
																				
															

推荐阅读